Compare commits
38 Commits
v14
...
word-clock
Author | SHA1 | Date | |
---|---|---|---|
388023c69b | |||
a486fa202c | |||
|
6bbdb581cc | ||
|
16998a8ca8 | ||
|
270d15a8ea | ||
|
bf8c4dfef8 | ||
|
ecbcce41d7 | ||
|
3d75a7dc9f | ||
|
9d749434b5 | ||
|
8ff6bd9cae | ||
|
b5f500e96d | ||
|
1fd2496ee2 | ||
|
8a40eaa9c5 | ||
|
c87a297a2f | ||
15e525a5ba | |||
65976a2b23 | |||
|
86695db883 | ||
|
c8de75ccd9 | ||
|
163eaf5ab4 | ||
|
21e86c4220 | ||
09f7306e31 | |||
a2da74d93a | |||
e3c7295729 | |||
|
990ad0c87e | ||
|
c74ae787ae | ||
9258331e9c | |||
62e89c1bd3 | |||
1fbd00b43d | |||
4449a48de7 | |||
4e6b1c421f | |||
c04bd3bd7d | |||
|
cbabd8914c | ||
|
5fc831542f | ||
|
b0019da9dd | ||
|
3bb0ac29e2 | ||
|
329e42c5d4 | ||
|
ecb91712fd | ||
e334735697 |
@ -1,32 +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
|
||||
{
|
||||
"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"
|
||||
}
|
65
.devcontainer/Dockerfile
Normal file
65
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,65 @@
|
||||
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 \
|
||||
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
|
87
.devcontainer/build.sh
Normal file
87
.devcontainer/build.sh
Normal file
@ -0,0 +1,87 @@
|
||||
#!/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
|
2
.devcontainer/build_app.sh
Normal file
2
.devcontainer/build_app.sh
Normal file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
cmake --build /workspaces/Pinetime/build --config Release -- -j6 pinetime-app
|
3
.devcontainer/create_build_openocd.sh
Normal file
3
.devcontainer/create_build_openocd.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/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
|
38
.devcontainer/devcontainer.json
Normal file
38
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,38 @@
|
||||
// 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"
|
||||
}
|
2
.devcontainer/make_build_dir.sh
Normal file
2
.devcontainer/make_build_dir.sh
Normal file
@ -0,0 +1,2 @@
|
||||
#!/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}
|
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -31,10 +31,6 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install resource build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends python3-pil
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: /opt/build.sh all
|
||||
|
22
.vscode/c_cpp_properties.json
vendored
22
.vscode/c_cpp_properties.json
vendored
@ -1,9 +1,4 @@
|
||||
{
|
||||
"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",
|
||||
@ -15,22 +10,7 @@
|
||||
"defines": [],
|
||||
"compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++20",
|
||||
"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",
|
||||
"cppStandard": "c++14",
|
||||
"intelliSenseMode": "linux-gcc-arm",
|
||||
"configurationProvider": "ms-vscode.cpp-tools",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||
|
6
.vscode/cmake-kits.json
vendored
6
.vscode/cmake-kits.json
vendored
@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "InfiniTime Compiler",
|
||||
"environmentSetupScript": "${workspaceFolder}/docker/build.sh"
|
||||
}
|
||||
]
|
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@ -4,15 +4,17 @@
|
||||
{
|
||||
"name": "Debug - Openocd docker Remote",
|
||||
"type":"cortex-debug",
|
||||
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
|
||||
"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",
|
||||
// Connect to an already running OpenOCD instance
|
||||
"gdbTarget": "host.docker.internal:3333",
|
||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||
"runToEntryPoint": "main",
|
||||
"runToMain": true,
|
||||
// Work around for stopping at main on restart
|
||||
"postRestartCommands": [
|
||||
"break main",
|
||||
@ -22,15 +24,17 @@
|
||||
{
|
||||
"name": "Debug - Openocd Local",
|
||||
"type":"cortex-debug",
|
||||
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
|
||||
"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",
|
||||
// Connect to an already running OpenOCD instance
|
||||
"gdbTarget": "localhost:3333",
|
||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||
"runToEntryPoint": "main",
|
||||
"runToMain": true,
|
||||
// Work around for stopping at main on restart
|
||||
"postRestartCommands": [
|
||||
"break main",
|
||||
@ -47,11 +51,6 @@
|
||||
"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",
|
||||
@ -59,25 +58,7 @@
|
||||
"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
15
.vscode/settings.json
vendored
@ -1,20 +1,9 @@
|
||||
{
|
||||
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||
"cmake.configureArgs": [
|
||||
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}",
|
||||
"-DNRF5_SDK_PATH=${env:TOOLS_DIR}/${env:NRF_SDK_VER}",
|
||||
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}",
|
||||
"-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}",
|
||||
],
|
||||
"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
22
.vscode/tasks.json
vendored
@ -1,6 +1,20 @@
|
||||
{
|
||||
"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",
|
||||
@ -17,6 +31,14 @@
|
||||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "BuildInit",
|
||||
"dependsOn": [
|
||||
"update submodules",
|
||||
"create openocd build"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
|
||||
|
||||
project(pinetime VERSION 1.14.0 LANGUAGES C CXX ASM)
|
||||
project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
# set(CMAKE_GENERATOR "Unix Makefiles")
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
@ -13,6 +13,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(NRF_TARGET "nrf52")
|
||||
|
||||
if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
|
||||
@ -32,7 +33,7 @@ if(BUILD_RESOURCES)
|
||||
endif()
|
||||
|
||||
set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device")
|
||||
set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY_TFK5 MOY_TIN5 MOY_TON5 MOY_UNK)
|
||||
set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY-TFK5 MOY-TIN5 MOY-TON5 MOY-UNK)
|
||||
|
||||
set(PROJECT_GIT_COMMIT_HASH "")
|
||||
|
||||
@ -69,4 +70,5 @@ set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generate
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/Version.h)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docker/post_build.sh.in ${CMAKE_CURRENT_BINARY_DIR}/post_build.sh)
|
||||
|
||||
|
||||
add_subdirectory(src)
|
||||
|
28
README.md
28
README.md
@ -2,29 +2,33 @@
|
||||
## Completed
|
||||
- [x] Make $s in terminal watch face green
|
||||
- [x] Swipe left to access music control
|
||||
- [x] Flashlight starts on
|
||||
- [x] Calculator
|
||||
- [x] Redo music control gestures
|
||||
- [x] Swipe up toggles control mode and swipe down exits
|
||||
- [x] Swipe left/right goes to watchface/another app
|
||||
- [x] Rearrange quick acces and apps
|
||||
- [x] Exponent button on calculator
|
||||
|
||||
## Pending merge
|
||||
- [x] Flashlight starts on
|
||||
|
||||
## In progress
|
||||
Elijah
|
||||
### Elijah
|
||||
- [ ] Countdown timer presistant buzz
|
||||
|
||||
Josh
|
||||
### Josh
|
||||
- [ ] Seconds on digital watchface
|
||||
- [ ] Battery on digital watchface
|
||||
- [ ] Remap button double click (to music app)
|
||||
|
||||
### Moses
|
||||
|
||||
|
||||
- [ ] Add quick ring settings
|
||||
|
||||
Moses
|
||||
- [ ] Redo music control gestures
|
||||
- [ ] Swipe up toggles control mode and swipe down exits
|
||||
- [ ] Swipe left/right goes to watchface/another app
|
||||
## Medium priority
|
||||
- [ ] Temp sensor w/ arduino
|
||||
- [ ] Homeassistant control
|
||||
- [ ] Redo music control gestures
|
||||
- [ ] Swipe up toggles control mode and swipe down exits
|
||||
- [ ] Swipe left/right goes to watchface/another app
|
||||
- [ ] Periodic heart rate measurement
|
||||
|
||||
## Lower priority
|
||||
- [ ] Rearrange quick acces and apps
|
||||
- [ ] Maybe some prank watchfaces
|
||||
|
@ -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/wasp-os/wasp-os) as a way to
|
||||
[wasp-os](https://github.com/daniel-thompson/wasp-os) as a way to
|
||||
deliver OTA updates to nRF52-based smart watches, especially the
|
||||
[Pine64 PineTime](https://pine64.org/devices/pinetime/).
|
||||
[Pine64 PineTime](https://www.pine64.org/pinetime/).
|
||||
|
||||
## What does it do?
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
# Flashing your watch with an ESP
|
||||
|
||||
Here is a link about how to access and wire an SWD to the pinetime.
|
||||
- https://pine64.org/documentation/PineTime/Further_information/Devkit_wiring
|
||||
|
||||
|
||||
Here is a link that tells you how to setup the esp32 as a SWD.
|
||||
- https://hackaday.com/2021/07/01/esp3F2-turned-handy-swd-flasher-for-nrf52-chips/
|
||||
- When setting up WiFi got to http://192.168.4.1/0wifi to set the WiFi password and not crash the esp. If you go to the default http://192.168.4.1/wifi then the esp will often crash due to a watchdog timeout.
|
||||
|
||||
Here is the link to the information on what files to flash to your pinetime.
|
||||
- https://gitea.locker98.com/josh/InfiniTime/src/branch/v14/doc/SWD.md
|
@ -21,5 +21,3 @@ 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.
|
||||
|
@ -1,69 +0,0 @@
|
||||
# Simple Weather Service
|
||||
|
||||
## Introduction
|
||||
|
||||
The Simple Weather Service provides a simple and straightforward API to specify the current weather and the forecast for the next 5 days.
|
||||
It effectively replaces the original Weather Service (from InfiniTime 1.8) since InfiniTime 1.14.
|
||||
|
||||
## Service
|
||||
|
||||
The service UUID is `00050000-78fc-48fe-8e23-433b3a1942d0`.
|
||||
|
||||
## Characteristics
|
||||
|
||||
## Weather data (UUID 00050001-78fc-48fe-8e23-433b3a1942d0)
|
||||
|
||||
The host uses this characteristic to update the current weather information and the forecast for the next 5 days.
|
||||
|
||||
This characteristics accepts a byte array with the following 2-Bytes header:
|
||||
|
||||
- [0] Message Type :
|
||||
- `0` : Current weather
|
||||
- `1` : Forecast
|
||||
- [1] Message Version : Version `0` is currently supported. Other versions might be added in future releases
|
||||
|
||||
### Current Weather
|
||||
|
||||
The byte array must contain the following data:
|
||||
|
||||
- [0] : Message type = `0`
|
||||
- [1] : Message version = `0`
|
||||
- [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970) in local time (the same timezone as the one used to set the time)
|
||||
- [10, 11] : Current temperature (°C * 100)
|
||||
- [12, 13] : Minimum temperature (°C * 100)
|
||||
- [14, 15] : Maximum temperature (°C * 100)
|
||||
- [16]..[47] : location (string, unused characters should be set to `0`)
|
||||
- [48] : icon ID
|
||||
- 0 = Sun, clear sky
|
||||
- 1 = Few clouds
|
||||
- 2 = Clouds
|
||||
- 3 = Heavy clouds
|
||||
- 4 = Clouds & rain
|
||||
- 5 = Rain
|
||||
- 6 = Thunderstorm
|
||||
- 7 = Snow
|
||||
- 8 = Mist, smog
|
||||
|
||||
### Forecast
|
||||
|
||||
The byte array must contain the following data:
|
||||
|
||||
- [0] : Message type = `1`
|
||||
- [1] : Message version = `0`
|
||||
- [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970) in local time (the same timezone as the one used to set the time)
|
||||
- [10] Number of days (Max 5, fields for unused days should be set to `0`)
|
||||
- [11,12] Day 0 Minimum temperature (°C * 100)
|
||||
- [13,14] Day 0 Maximum temperature (°C * 100)
|
||||
- [15] Day 0 Icon ID
|
||||
- [16,17] Day 1 Minimum temperature (°C * 100)
|
||||
- [18,19] Day 1 Maximum temperature (°C * 100)
|
||||
- [20] Day 1 Icon ID
|
||||
- [21,22] Day 2 Minimum temperature (°C * 100)
|
||||
- [23,24] Day 2 Maximum temperature (°C * 100)
|
||||
- [25] Day 2 Icon ID
|
||||
- [26,27] Day 3 Minimum temperature (°C * 100)
|
||||
- [28,29] Day 3 Maximum temperature (°C * 100)
|
||||
- [30] Day 3 Icon ID
|
||||
- [31,32] Day 4 Minimum temperature (°C * 100)
|
||||
- [33,34] Day 4 Maximum temperature (°C * 100)
|
||||
- [35] Day 4 Icon ID
|
@ -92,10 +92,7 @@ The following custom services are implemented in InfiniTime:
|
||||
|
||||
- Since InfiniTime 1.8:
|
||||
|
||||
- ~~Weather Service: `00040000-78fc-48fe-8e23-433b3a1942d0`~~ (replaced by Simple Weather Service in InfiniTime 1.14)
|
||||
|
||||
- Since InfiniTime 1.14
|
||||
- [Simple Weather Service](SimpleWeatherService.md) : `00050000-78fc-48fe-8e23-433b3a1942d0`
|
||||
- [Weather Service](/src/components/ble/weather/WeatherService.h): `00040000-78fc-48fe-8e23-433b3a1942d0`
|
||||
|
||||
---
|
||||
|
||||
|
@ -42,8 +42,8 @@ CMake configures the project according to variables you specify the command line
|
||||
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
|
||||
**CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
|
||||
**BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1`
|
||||
**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [python3-pil/pillow](https://pillow.readthedocs.io) module). |`-DBUILD_RESOURCES=1`
|
||||
**TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY_TFK5, MOY_TIN5, MOY_TON5, MOY_UNK`|`-DTARGET_DEVICE=PINETIME` (Default)
|
||||
**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [lv_img_conv](https://github.com/lvgl/lv_img_conv). |`-DBUILD_RESOURCES=1`
|
||||
**TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY-TFK5, MOY-TIN5, MOY-TON5, MOY-UNK`|`-DTARGET_DEVICE=PINETIME` (Default)
|
||||
|
||||
#### (\*) Note about **CMAKE_BUILD_TYPE**
|
||||
By default, this variable is set to *Release*. It compiles the code with size and speed optimizations. We use this value for all the binaries we publish when we [release](https://github.com/InfiniTimeOrg/InfiniTime/releases) new versions of InfiniTime.
|
||||
@ -98,4 +98,4 @@ Binary files are generated into the folder `src`:
|
||||
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
|
||||
- **pinetime-mcuboot-app-dfu** : DFU file of the firmware
|
||||
|
||||
The same files are generated for **pinetime-recovery** and **pinetime-recovery-loader**
|
||||
The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader**
|
||||
|
@ -33,7 +33,7 @@ For cloning the repo, see [these instructions](../doc/buildAndProgram.md#clone-t
|
||||
|
||||
```bash
|
||||
cd <project_root> # e.g. cd ./work/Pinetime
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime-build
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime/infinitime-build
|
||||
```
|
||||
|
||||
By default, the container runs as `root`, which is not convenient as all the files generated by the build will also belong to `root`.
|
||||
@ -45,7 +45,7 @@ This means calling the script explicitly as it will override the `CMD`.
|
||||
Here's an example for `pinetime-app`:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime-build /opt/build.sh pinetime-app
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime/infinitime-build /opt/build.sh pinetime-app
|
||||
```
|
||||
|
||||
## Using the image from Docker Hub
|
||||
|
@ -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](usingDevcontainers.md)
|
||||
More documentation is available in the [readme in .devcontainer](../.devcontainer/README.md)
|
||||
|
||||
### DevContainer on Ubuntu
|
||||
|
||||
|
172
doc/code/Apps.md
172
doc/code/Apps.md
@ -9,144 +9,59 @@ This page will teach you:
|
||||
|
||||
The user interface of InfiniTime is made up of **screens**.
|
||||
Screens that are opened from the app launcher are considered **apps**.
|
||||
Every app in InfiniTime is its own class.
|
||||
Every app in InfiniTime is it's own class.
|
||||
An instance of the class is created when the app is launched, and destroyed when the user exits the app.
|
||||
Apps run inside the `DisplayApp` task (briefly discussed [here](./Intro.md)).
|
||||
Apps run inside the "displayapp" task (briefly discussed [here](./Intro.md)).
|
||||
Apps are responsible for everything drawn on the screen when they are running.
|
||||
Apps can be refreshed periodically and reacts to external events (touch or button).
|
||||
By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected.
|
||||
|
||||
## Interface
|
||||
|
||||
Every app class is declared inside the namespace `Pinetime::Applications::Screens`
|
||||
and inherits
|
||||
from [`Pinetime::Applications::Screens::Screen`](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Screen.h).
|
||||
Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`.
|
||||
The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen.
|
||||
Other parameters should be references to controllers that the app needs.
|
||||
A destructor is needed to clean up LVGL and restore any changes (for example re-enable sleeping).
|
||||
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
|
||||
If an app only needs to display some text and do something upon a touch screen button press,
|
||||
it does not need to override any of these functions, as LVGL can also handle touch events for you.
|
||||
If you have any doubts, you can always look at how the other apps function for reference.
|
||||
|
||||
Each app defines its own constructor.
|
||||
The constructors mostly take references to InfiniTime `Controllers` (ex: Alarm, DateTime, BLE services, Settings,...)
|
||||
the app needs for its operations. The constructor is responsible for initializing the UI of the app.
|
||||
### Continuous updating
|
||||
|
||||
The **destructor** cleans up LVGL and restores any changes (for example re-enable sleeping).
|
||||
If your app needs to be updated continuously, you can do so by overriding the `Refresh()` function in your class
|
||||
and calling `lv_task_create` inside the constructor.
|
||||
|
||||
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)`
|
||||
and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
|
||||
An example call could look like this:
|
||||
|
||||
Apps that need to be refreshed periodically create an `lv_task` (using `lv_task_create()`)
|
||||
that will call the method `Refresh()` periodically.
|
||||
|
||||
## App types
|
||||
|
||||
There are basically 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.
|
||||
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.
|
||||
|
||||
**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 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.
|
||||
|
||||
The constructor of **system** apps is called directly. If the application is a **user** app,
|
||||
the corresponding `AppDescription` is first retrieved from `userApps`
|
||||
and then the function `create` is called to create an instance of the app.
|
||||
|
||||
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()`
|
||||
in `UserApps.h`. This method takes the list of applications that must be built into the firmware image.
|
||||
This list of applications is defined as a list `Apps` enum values named `UserAppTypes` in `Apps.h`.
|
||||
For each application listed in `UserAppTypes`, an entry of type `AppDescription` is added to the array `userApps`.
|
||||
This entry is created by using the information provided by a template `AppTraits`
|
||||
that is customized for every user application.
|
||||
|
||||
Here is an example of an AppTraits customized for the Alarm application.
|
||||
It defines the type of application, its icon and a function that returns an instance of the application.
|
||||
|
||||
```c++
|
||||
template <>
|
||||
struct AppTraits<Apps::Alarm> {
|
||||
static constexpr Apps app = Apps::Alarm;
|
||||
static constexpr const char* icon = Screens::Symbols::clock;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Alarm(controllers.alarmController,
|
||||
controllers.settingsController.GetClockType(),
|
||||
*controllers.systemTask,
|
||||
controllers.motorController);
|
||||
};
|
||||
};
|
||||
```cpp
|
||||
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
||||
```
|
||||
|
||||
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
|
||||
to list all available applications.
|
||||
|
||||
## 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;
|
||||
}
|
||||
};
|
||||
```
|
||||
With `taskRefresh` being a member variable of your class and of type `lv_task_t*`.
|
||||
Remember to delete the task again using `lv_task_del`.
|
||||
The function `RefreshTaskCallback` is inherited from `Screen` and just calls your `Refresh` function.
|
||||
|
||||
## Creating your own app
|
||||
|
||||
A minimal user app could look like this:
|
||||
A minimal app could look like this:
|
||||
|
||||
MyApp.h:
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class MyApp : public Screen {
|
||||
public:
|
||||
MyApp();
|
||||
MyApp(DisplayApp* app);
|
||||
~MyApp() override;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::MyApp> {
|
||||
static constexpr Apps app = Apps::MyApp;
|
||||
static constexpr const char* icon = Screens::Symbols::myApp;
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::MyApp();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -155,10 +70,11 @@ MyApp.cpp:
|
||||
|
||||
```cpp
|
||||
#include "displayapp/screens/MyApp.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
MyApp::MyApp() {
|
||||
MyApp::MyApp(DisplayApp* app) : Screen(app) {
|
||||
lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(title, "My test application");
|
||||
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
||||
@ -170,42 +86,20 @@ MyApp::~MyApp() {
|
||||
}
|
||||
```
|
||||
|
||||
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/).
|
||||
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/)
|
||||
or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a setting app.
|
||||
|
||||
Now we have our very own app, but InfiniTime does not know about it yet.
|
||||
The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
|
||||
The first step is to include your MyApp.cpp (or any new cpp files for that matter)
|
||||
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
|
||||
The next step to making it launch-able is to give your app an id.
|
||||
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).
|
||||
|
||||
If your application is a **system** application, go to the function `DisplayApp::LoadScreen`
|
||||
and add another case to the switch statement.
|
||||
The next step to making it launchable is to give your app an id.
|
||||
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
|
||||
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
|
||||
Now, go to the function `DisplayApp::LoadScreen` and add another case to the switch statement.
|
||||
The case will be the id you gave your app earlier.
|
||||
If your app needs any additional arguments, this is the place to pass them.
|
||||
|
||||
If your application is a **user** application, you don't need to add anything in DisplayApp,
|
||||
everything will be automatically generated for you.
|
||||
The user application will also be automatically be added to the app launcher menu.
|
||||
|
||||
Since the list of **user** application is generated by CMake, you need to add the variable `ENABLE_USERAPPS` to the command line of CMake.
|
||||
This variable must be set with a string composed of an ordered list of the **user** applications that must be built into the firmware.
|
||||
The items of the list are fields from the enumeration `Apps`.
|
||||
Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the application will be listed in this specific order in the application menu).
|
||||
|
||||
```cmake
|
||||
$ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ...
|
||||
```
|
||||
|
||||
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" ...
|
||||
```
|
||||
If you want to add your app in the app launcher, add your app in [displayapp/screens/ApplicationList.h](/src/displayapp/screens/ApplicationList.h) to the array containing the applications and their corresponding symbol. If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.h](/src/displayapp/screens/settings/Settings.h).
|
||||
|
||||
You should now be able to [build](../buildAndProgram.md) the firmware
|
||||
and flash it to your PineTime. Yay!
|
||||
|
@ -1,54 +0,0 @@
|
||||
# Adapting old watchfaces:
|
||||
- When resolving conflicts, delete clock.h/.cpp, keep both in CMakeLists.cpp, accept current in SettingWatchFace.cpp, and keep both if there are any changes in fonts.
|
||||
- Add entry to WatchFace enum in displayapp/apps/Apps.h.in
|
||||
- Remove `DisplayApp* app` param from constructor in .h and .cpp and remove Screen(app) from initializer list on constructor in .cpp
|
||||
- Add const to batteryController and bleController is present in constructor params and in private members in .h
|
||||
- In .cpp, remove `#include <date/date.h>` and convert calls to get time to format like in working watch faces. For example:
|
||||
```cpp
|
||||
auto newDateTime = currentDateTime.Get();
|
||||
|
||||
auto dp = date::floor<date::days>(newDateTime);
|
||||
auto time = date::make_time(newDateTime - dp);
|
||||
|
||||
uint8_t hour = time.hours().count();
|
||||
uint8_t minute = time.minutes().count();
|
||||
```
|
||||
with
|
||||
```cpp
|
||||
uint8_t hour = dateTimeController.Hours();
|
||||
uint8_t minute = dateTimeController.Minutes();
|
||||
```
|
||||
, add
|
||||
```cpp
|
||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
|
||||
```
|
||||
to .h
|
||||
- Remove `motionSensorOk = motionController.IsSensorOk();` and reference to `motionSensorOk` in following if
|
||||
- In .h add `#include "utility/DirtyValue.h"` and replace all `DirtyValue<>` vars with `Utility::DirtyValue<>`
|
||||
- Add WatchFaceTraits:
|
||||
``` cpp
|
||||
template <>
|
||||
struct WatchFaceTraits<WatchFace::Digital> {
|
||||
static constexpr WatchFace watchFace = WatchFace::Digital;
|
||||
static constexpr const char* name = "Digital face";
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::WatchFaceDigital(controllers.dateTimeController,
|
||||
controllers.batteryController,
|
||||
controllers.bleController,
|
||||
controllers.notificationManager,
|
||||
controllers.settingsController,
|
||||
controllers.heartRateController,
|
||||
controllers.motionController, controllers.filesystem
|
||||
*controllers.weatherController);
|
||||
};
|
||||
|
||||
|
||||
static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
|
||||
return Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem);
|
||||
}
|
||||
};
|
||||
```
|
||||
modify as necesary.
|
||||
- Add include to UserApps.h: `#include "displayapp/screens/WatchFace---.h"`
|
||||
- Add to displayapp/apps/CMakeLists.txt
|
@ -1,13 +1,7 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG NODE_MAJOR=20
|
||||
RUN apt-get update -qq \
|
||||
&& apt-get install -y ca-certificates curl gnupg \
|
||||
&& mkdir -p /etc/apt/keyrings \
|
||||
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||
&& apt-get update -qq \
|
||||
&& apt-get install -y \
|
||||
# x86_64 / generic packages
|
||||
bash \
|
||||
@ -15,14 +9,13 @@ RUN apt-get update -qq \
|
||||
cmake \
|
||||
git \
|
||||
make \
|
||||
nodejs \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-pil \
|
||||
python-is-python3 \
|
||||
tar \
|
||||
unzip \
|
||||
wget \
|
||||
curl \
|
||||
# aarch64 packages
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
@ -35,25 +28,20 @@ RUN apt-get update -qq \
|
||||
libpango-1.0-0 \
|
||||
ibpango1.0-dev \
|
||||
libpangocairo-1.0-0 \
|
||||
libsdl2-dev \
|
||||
g++ \
|
||||
libpng-dev
|
||||
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs \
|
||||
&& 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
|
||||
RUN pip3 install -Iv cryptography==3.3
|
||||
RUN pip3 install cbor
|
||||
RUN npm i lv_font_conv@1.5.2 -g
|
||||
RUN npm install ts-node@10.9.1 @swc/core@1.3.82 lv_img_conv@0.3.0 -g
|
||||
|
||||
RUN npm i ts-node@10.9.1 -g
|
||||
RUN npm i @swc/core -g
|
||||
RUN npm i lv_img_conv@0.3.0 -g
|
||||
|
||||
# build.sh knows how to compile
|
||||
COPY build.sh /opt/
|
||||
@ -66,8 +54,5 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;"
|
||||
# McuBoot
|
||||
RUN bash -c "source /opt/build.sh; GetMcuBoot;"
|
||||
|
||||
# Add the infinitime user for connecting devcontainer
|
||||
RUN adduser infinitime
|
||||
|
||||
ENV SOURCES_DIR /sources
|
||||
CMD ["/opt/build.sh"]
|
||||
|
@ -83,7 +83,7 @@ And now run it if there were no errors!
|
||||
Now is the fun part, building your own version of InfiniTime and flashing it to your watch!
|
||||
First, cd into the directory into which InfiniTime cloned (`cd ~/InfiniTime` if you cloned from your home directory). Now run the docker container (for more info on this command, see https://github.com/InfiniTimeOrg/InfiniTime/blob/main/doc/buildWithDocker.md):
|
||||
```bash
|
||||
docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime/infinitime-build
|
||||
sudo docker run --rm -it -v ${PWD}:/sources --user $(id -u):$(id -g) infinitime/infinitime-build
|
||||
```
|
||||
|
||||
If that doesn't have any errors, it will save the files to build/output/. The file you will want to flash to your watch is `pinetime-mcuboot-app-dfu-x.x.x.zip`. I found a pretty nice program called WatchMate that you can use to flash it from linux, but I think you can also use Gadgetbridge to flash the file. In WatchMate, just scroll down to the "Firmware Version" dropdown and click on "Upload from file" >
|
||||
|
@ -1,33 +1,25 @@
|
||||
#!/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-//')"
|
||||
#!/bin/bash
|
||||
if clang-format --version | grep -q 'version 11\.'; then
|
||||
CLANG_FORMAT_EXECUTABLE="clang-format"
|
||||
else
|
||||
CLANG_FORMAT_EXECUTABLE="clang-format-11"
|
||||
fi
|
||||
|
||||
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"
|
||||
if ! command -v $CLANG_FORMAT_EXECUTABLE &> /dev/null
|
||||
then
|
||||
echo $CLANG_FORMAT_EXECUTABLE does not exist, make sure to install it
|
||||
exit 1
|
||||
fi
|
||||
|
||||
args="--binary $bin -q --extensions cpp,h --style file --staged -- :!src/FreeRTOS :!src/libs"
|
||||
|
||||
changedFiles="$(git "$name" --diffstat $args)"
|
||||
git "$name" $args
|
||||
|
||||
echo "$changedFiles" | head -n -1 | cut -d ' ' -f 2 | while read -r file; do
|
||||
git add -- "$file"
|
||||
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
|
||||
done
|
||||
|
@ -355,6 +355,14 @@ set(LVGL_SRC
|
||||
libs/lvgl/src/lv_widgets/lv_win.c
|
||||
)
|
||||
|
||||
set(QCBOR_SRC
|
||||
libs/QCBOR/src/ieee754.c
|
||||
libs/QCBOR/src/qcbor_decode.c
|
||||
libs/QCBOR/src/qcbor_encode.c
|
||||
libs/QCBOR/src/qcbor_err_to_str.c
|
||||
libs/QCBOR/src/UsefulBuf.c
|
||||
)
|
||||
|
||||
list(APPEND IMAGE_FILES
|
||||
displayapp/icons/battery/batteryicon.c
|
||||
)
|
||||
@ -365,8 +373,8 @@ list(APPEND SOURCE_FILES
|
||||
logging/NrfLogger.cpp
|
||||
displayapp/DisplayApp.cpp
|
||||
displayapp/screens/Screen.cpp
|
||||
displayapp/screens/Clock.cpp
|
||||
displayapp/screens/Tile.cpp
|
||||
displayapp/screens/Bird.cpp
|
||||
displayapp/screens/InfiniPaint.cpp
|
||||
displayapp/screens/Paddle.cpp
|
||||
displayapp/screens/StopWatch.cpp
|
||||
@ -377,44 +385,38 @@ list(APPEND SOURCE_FILES
|
||||
displayapp/screens/Label.cpp
|
||||
displayapp/screens/FirmwareUpdate.cpp
|
||||
displayapp/screens/Music.cpp
|
||||
displayapp/screens/Weather.cpp
|
||||
displayapp/screens/Navigation.cpp
|
||||
displayapp/screens/Calendar.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
|
||||
displayapp/screens/Twos.cpp
|
||||
displayapp/screens/Bird.cpp
|
||||
displayapp/screens/Calculator.cpp
|
||||
displayapp/screens/HeartRate.cpp
|
||||
displayapp/screens/Motion.cpp
|
||||
displayapp/screens/FlashLight.cpp
|
||||
displayapp/screens/List.cpp
|
||||
displayapp/screens/CheckboxList.cpp
|
||||
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
|
||||
displayapp/widgets/DotIndicator.cpp
|
||||
displayapp/widgets/StatusIcons.cpp
|
||||
|
||||
## Added
|
||||
displayapp/screens/Calculator.cpp
|
||||
|
||||
## Settings
|
||||
displayapp/screens/settings/QuickSettings.cpp
|
||||
displayapp/screens/settings/Settings.cpp
|
||||
displayapp/screens/settings/SettingWatchFace.cpp
|
||||
displayapp/screens/settings/SettingTimeFormat.cpp
|
||||
displayapp/screens/settings/SettingWeatherFormat.cpp
|
||||
displayapp/screens/settings/SettingWakeUp.cpp
|
||||
displayapp/screens/settings/SettingDisplay.cpp
|
||||
displayapp/screens/settings/SettingSteps.cpp
|
||||
@ -428,14 +430,11 @@ list(APPEND SOURCE_FILES
|
||||
## Watch faces
|
||||
displayapp/screens/WatchFaceAnalog.cpp
|
||||
displayapp/screens/WatchFaceDigital.cpp
|
||||
displayapp/screens/WatchFaceBinary.cpp
|
||||
displayapp/screens/WatchFaceInfineat.cpp
|
||||
displayapp/screens/WatchFaceTerminal.cpp
|
||||
displayapp/screens/WatchFacePineTimeStyle.cpp
|
||||
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
||||
displayapp/screens/WatchFaceHorizon.cpp
|
||||
displayapp/screens/WatchFaceAccurateWords.cpp
|
||||
displayapp/screens/WatchFaceGravel.cpp
|
||||
displayapp/screens/WatchFaceWordClock.cpp
|
||||
|
||||
##
|
||||
|
||||
@ -464,7 +463,7 @@ list(APPEND SOURCE_FILES
|
||||
components/ble/CurrentTimeService.cpp
|
||||
components/ble/AlertNotificationService.cpp
|
||||
components/ble/MusicService.cpp
|
||||
components/ble/SimpleWeatherService.cpp
|
||||
components/ble/weather/WeatherService.cpp
|
||||
components/ble/NavigationService.cpp
|
||||
components/ble/BatteryInformationService.cpp
|
||||
components/ble/FSService.cpp
|
||||
@ -533,7 +532,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||
components/ble/CurrentTimeService.cpp
|
||||
components/ble/AlertNotificationService.cpp
|
||||
components/ble/MusicService.cpp
|
||||
components/ble/SimpleWeatherService.cpp
|
||||
components/ble/weather/WeatherService.cpp
|
||||
components/ble/BatteryInformationService.cpp
|
||||
components/ble/FSService.cpp
|
||||
components/ble/ImmediateAlertService.cpp
|
||||
@ -553,6 +552,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||
systemtask/SystemTask.cpp
|
||||
systemtask/SystemMonitor.cpp
|
||||
drivers/TwiMaster.cpp
|
||||
components/gfx/Gfx.cpp
|
||||
components/rle/RleDecoder.cpp
|
||||
components/heartrate/HeartRateController.cpp
|
||||
heartratetask/HeartRateTask.cpp
|
||||
@ -582,6 +582,7 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
|
||||
|
||||
components/rle/RleDecoder.cpp
|
||||
|
||||
components/gfx/Gfx.cpp
|
||||
drivers/St7789.cpp
|
||||
components/brightness/BrightnessController.cpp
|
||||
|
||||
@ -599,6 +600,7 @@ set(INCLUDE_FILES
|
||||
displayapp/Messages.h
|
||||
displayapp/TouchEvents.h
|
||||
displayapp/screens/Screen.h
|
||||
displayapp/screens/Clock.h
|
||||
displayapp/screens/Tile.h
|
||||
displayapp/screens/InfiniPaint.h
|
||||
displayapp/screens/StopWatch.h
|
||||
@ -614,12 +616,12 @@ set(INCLUDE_FILES
|
||||
displayapp/screens/ApplicationList.h
|
||||
displayapp/screens/CheckboxList.h
|
||||
displayapp/Apps.h
|
||||
displayapp/WatchFaces.h
|
||||
displayapp/screens/Notifications.h
|
||||
displayapp/screens/HeartRate.h
|
||||
displayapp/screens/Metronome.h
|
||||
displayapp/screens/Motion.h
|
||||
displayapp/screens/Timer.h
|
||||
displayapp/screens/Dice.h
|
||||
displayapp/screens/Alarm.h
|
||||
displayapp/Colors.h
|
||||
displayapp/widgets/Counter.h
|
||||
@ -659,7 +661,7 @@ set(INCLUDE_FILES
|
||||
components/ble/BleClient.h
|
||||
components/ble/HeartRateService.h
|
||||
components/ble/MotionService.h
|
||||
components/ble/SimpleWeatherService.h
|
||||
components/ble/weather/WeatherService.h
|
||||
components/settings/Settings.h
|
||||
components/timer/Timer.h
|
||||
components/alarm/AlarmController.h
|
||||
@ -806,18 +808,18 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
|
||||
if(TARGET_DEVICE STREQUAL "PINETIME")
|
||||
add_definitions(-DDRIVER_PINMAP_PINETIME)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_TFK5") # P8a
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-TFK5") # P8a
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_TIN5") # P8a variant 2
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-TIN5") # P8a variant 2
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_TON5") # P8b
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-TON5") # P8b
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
||||
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_UNK") # P8b mirrored
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-UNK") # P8b mirrored
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
||||
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
||||
@ -858,8 +860,6 @@ target_compile_options(infinitime_fonts PUBLIC
|
||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
)
|
||||
|
||||
add_subdirectory(displayapp/apps)
|
||||
|
||||
# NRF SDK
|
||||
add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES})
|
||||
target_include_directories(nrf-sdk SYSTEM PUBLIC . ../)
|
||||
@ -897,6 +897,27 @@ target_compile_options(lvgl PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
)
|
||||
|
||||
# QCBOR
|
||||
add_library(QCBOR STATIC ${QCBOR_SRC})
|
||||
target_include_directories(QCBOR SYSTEM PUBLIC libs/QCBOR/inc)
|
||||
# This is required with the current configuration
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_FLOAT_HW_USE)
|
||||
# These are for space-saving
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_PREFERRED_FLOAT)
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_EXP_AND_MANTISSA)
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_STRINGS)
|
||||
#target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_ARRAYS)
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_UNCOMMON_TAGS)
|
||||
target_compile_definitions(QCBOR PUBLIC USEFULBUF_CONFIG_LITTLE_ENDIAN)
|
||||
set_target_properties(QCBOR PROPERTIES LINKER_LANGUAGE C)
|
||||
target_compile_options(QCBOR PRIVATE
|
||||
${COMMON_FLAGS}
|
||||
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
|
||||
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
-O3
|
||||
)
|
||||
|
||||
# LITTLEFS_SRC
|
||||
add_library(littlefs STATIC ${LITTLEFS_SRC})
|
||||
target_include_directories(littlefs SYSTEM PUBLIC . ../)
|
||||
@ -915,7 +936,7 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime
|
||||
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
|
||||
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
||||
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
|
||||
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts)
|
||||
target_compile_options(${EXECUTABLE_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
${WARNING_FLAGS}
|
||||
@ -949,7 +970,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERS
|
||||
set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
||||
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
|
||||
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
|
||||
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
@ -991,7 +1012,7 @@ endif()
|
||||
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
||||
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
||||
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
|
||||
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
|
||||
@ -1023,7 +1044,7 @@ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-$
|
||||
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
|
||||
set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
||||
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
|
||||
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
||||
@ -1063,7 +1084,7 @@ endif()
|
||||
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
|
||||
set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
||||
add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
|
||||
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
@ -1098,7 +1119,7 @@ set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_N
|
||||
set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME_HEX ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.hex)
|
||||
set(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
||||
add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
|
||||
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
|
@ -75,7 +75,6 @@
|
||||
#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
|
||||
|
@ -357,8 +357,6 @@ 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) {
|
||||
|
@ -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 {
|
||||
|
@ -120,7 +120,3 @@ void MotionService::UnsubscribeNotification(uint16_t attributeHandle) {
|
||||
else if (attributeHandle == motionValuesHandle)
|
||||
motionValuesNoficationEnabled = false;
|
||||
}
|
||||
|
||||
bool MotionService::IsMotionNotificationSubscribed() const {
|
||||
return motionValuesNoficationEnabled;
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ namespace Pinetime {
|
||||
|
||||
void SubscribeNotification(uint16_t attributeHandle);
|
||||
void UnsubscribeNotification(uint16_t attributeHandle);
|
||||
bool IsMotionNotificationSubscribed() const;
|
||||
|
||||
private:
|
||||
NimbleController& nimble;
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include "components/ble/NavigationService.h"
|
||||
#include "components/ble/ServiceDiscovery.h"
|
||||
#include "components/ble/MotionService.h"
|
||||
#include "components/ble/SimpleWeatherService.h"
|
||||
#include "components/ble/weather/WeatherService.h"
|
||||
#include "components/fs/FS.h"
|
||||
|
||||
namespace Pinetime {
|
||||
@ -67,7 +67,7 @@ namespace Pinetime {
|
||||
return anService;
|
||||
};
|
||||
|
||||
Pinetime::Controllers::SimpleWeatherService& weather() {
|
||||
Pinetime::Controllers::WeatherService& weather() {
|
||||
return weatherService;
|
||||
};
|
||||
|
||||
@ -99,7 +99,7 @@ namespace Pinetime {
|
||||
AlertNotificationClient alertNotificationClient;
|
||||
CurrentTimeService currentTimeService;
|
||||
MusicService musicService;
|
||||
SimpleWeatherService weatherService;
|
||||
WeatherService weatherService;
|
||||
NavigationService navService;
|
||||
BatteryInformationService batteryInformationService;
|
||||
ImmediateAlertService immediateAlertService;
|
||||
|
@ -1,173 +0,0 @@
|
||||
/* Copyright (C) 2023 Jean-François Milants
|
||||
|
||||
This file is part of InfiniTime.
|
||||
|
||||
InfiniTime is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
InfiniTime is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "components/ble/SimpleWeatherService.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <nrf_log.h>
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
namespace {
|
||||
enum class MessageType : uint8_t { CurrentWeather, Forecast, Unknown };
|
||||
|
||||
uint64_t ToUInt64(const uint8_t* data) {
|
||||
return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24) + (static_cast<uint64_t>(data[4]) << 32) +
|
||||
(static_cast<uint64_t>(data[5]) << 40) + (static_cast<uint64_t>(data[6]) << 48) + (static_cast<uint64_t>(data[7]) << 56);
|
||||
}
|
||||
|
||||
int16_t ToInt16(const uint8_t* data) {
|
||||
return data[0] + (data[1] << 8);
|
||||
}
|
||||
|
||||
SimpleWeatherService::CurrentWeather CreateCurrentWeather(const uint8_t* dataBuffer) {
|
||||
SimpleWeatherService::Location cityName;
|
||||
std::memcpy(cityName.data(), &dataBuffer[16], 32);
|
||||
cityName[32] = '\0';
|
||||
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
|
||||
ToInt16(&dataBuffer[10]),
|
||||
ToInt16(&dataBuffer[12]),
|
||||
ToInt16(&dataBuffer[14]),
|
||||
SimpleWeatherService::Icons {dataBuffer[16 + 32]},
|
||||
std::move(cityName));
|
||||
}
|
||||
|
||||
SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
|
||||
auto timestamp = static_cast<uint64_t>(ToUInt64(&dataBuffer[2]));
|
||||
|
||||
std::array<SimpleWeatherService::Forecast::Day, SimpleWeatherService::MaxNbForecastDays> days;
|
||||
const uint8_t nbDaysInBuffer = dataBuffer[10];
|
||||
const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer);
|
||||
for (int i = 0; i < nbDays; i++) {
|
||||
days[i] = SimpleWeatherService::Forecast::Day {ToInt16(&dataBuffer[11 + (i * 5)]),
|
||||
ToInt16(&dataBuffer[13 + (i * 5)]),
|
||||
SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}};
|
||||
}
|
||||
return SimpleWeatherService::Forecast {timestamp, nbDays, days};
|
||||
}
|
||||
|
||||
MessageType GetMessageType(const uint8_t* data) {
|
||||
auto messageType = static_cast<MessageType>(*data);
|
||||
if (messageType > MessageType::Unknown) {
|
||||
return MessageType::Unknown;
|
||||
}
|
||||
return messageType;
|
||||
}
|
||||
|
||||
uint8_t GetVersion(const uint8_t* dataBuffer) {
|
||||
return dataBuffer[1];
|
||||
}
|
||||
}
|
||||
|
||||
int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
return static_cast<Pinetime::Controllers::SimpleWeatherService*>(arg)->OnCommand(ctxt);
|
||||
}
|
||||
|
||||
SimpleWeatherService::SimpleWeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) {
|
||||
}
|
||||
|
||||
void SimpleWeatherService::Init() {
|
||||
ble_gatts_count_cfg(serviceDefinition);
|
||||
ble_gatts_add_svcs(serviceDefinition);
|
||||
}
|
||||
|
||||
int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
|
||||
const auto* buffer = ctxt->om;
|
||||
const auto* dataBuffer = buffer->om_data;
|
||||
|
||||
switch (GetMessageType(dataBuffer)) {
|
||||
case MessageType::CurrentWeather:
|
||||
if (GetVersion(dataBuffer) == 0) {
|
||||
currentWeather = CreateCurrentWeather(dataBuffer);
|
||||
NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s",
|
||||
currentWeather->timestamp,
|
||||
currentWeather->temperature,
|
||||
currentWeather->minTemperature,
|
||||
currentWeather->maxTemperature,
|
||||
currentWeather->iconId,
|
||||
currentWeather->location.data());
|
||||
}
|
||||
break;
|
||||
case MessageType::Forecast:
|
||||
if (GetVersion(dataBuffer) == 0) {
|
||||
forecast = CreateForecast(dataBuffer);
|
||||
NRF_LOG_INFO("Forecast : Timestamp : %d", forecast->timestamp);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
NRF_LOG_INFO("\t[%d] Min: %d - Max : %d - Icon : %d",
|
||||
i,
|
||||
forecast->days[i].minTemperature,
|
||||
forecast->days[i].maxTemperature,
|
||||
forecast->days[i].iconId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<SimpleWeatherService::CurrentWeather> SimpleWeatherService::Current() const {
|
||||
if (currentWeather) {
|
||||
auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch();
|
||||
auto weatherTpSecond = std::chrono::seconds {currentWeather->timestamp};
|
||||
auto weatherTp = std::chrono::duration_cast<std::chrono::seconds>(weatherTpSecond);
|
||||
auto delta = currentTime - weatherTp;
|
||||
|
||||
if (delta < std::chrono::hours {24}) {
|
||||
return currentWeather;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<SimpleWeatherService::Forecast> SimpleWeatherService::GetForecast() const {
|
||||
if (forecast) {
|
||||
auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch();
|
||||
auto weatherTpSecond = std::chrono::seconds {forecast->timestamp};
|
||||
auto weatherTp = std::chrono::duration_cast<std::chrono::seconds>(weatherTpSecond);
|
||||
auto delta = currentTime - weatherTp;
|
||||
|
||||
if (delta < std::chrono::hours {24}) {
|
||||
return this->forecast;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const {
|
||||
return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp &&
|
||||
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
|
||||
std::strcmp(this->location.data(), other.location.data()) == 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
/* Copyright (C) 2023 Jean-François Milants
|
||||
|
||||
This file is part of InfiniTime.
|
||||
|
||||
InfiniTime is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
InfiniTime is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||
#define max
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_uuid.h>
|
||||
#include <optional>
|
||||
#include <cstring>
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
|
||||
int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
||||
class SimpleWeatherService {
|
||||
public:
|
||||
explicit SimpleWeatherService(const DateTime& dateTimeController);
|
||||
|
||||
void Init();
|
||||
|
||||
int OnCommand(struct ble_gatt_access_ctxt* ctxt);
|
||||
|
||||
static constexpr uint8_t MaxNbForecastDays = 5;
|
||||
|
||||
enum class Icons : uint8_t {
|
||||
Sun = 0, // ClearSky
|
||||
CloudsSun = 1, // FewClouds
|
||||
Clouds = 2, // Scattered clouds
|
||||
BrokenClouds = 3,
|
||||
CloudShowerHeavy = 4, // shower rain
|
||||
CloudSunRain = 5, // rain
|
||||
Thunderstorm = 6,
|
||||
Snow = 7,
|
||||
Smog = 8, // Mist
|
||||
Unknown = 255
|
||||
};
|
||||
|
||||
using Location = std::array<char, 33>; // 32 char + \0 (end of string)
|
||||
|
||||
struct CurrentWeather {
|
||||
CurrentWeather(uint64_t timestamp,
|
||||
int16_t temperature,
|
||||
int16_t minTemperature,
|
||||
int16_t maxTemperature,
|
||||
Icons iconId,
|
||||
Location&& location)
|
||||
: timestamp {timestamp},
|
||||
temperature {temperature},
|
||||
minTemperature {minTemperature},
|
||||
maxTemperature {maxTemperature},
|
||||
iconId {iconId},
|
||||
location {std::move(location)} {
|
||||
}
|
||||
|
||||
uint64_t timestamp;
|
||||
int16_t temperature;
|
||||
int16_t minTemperature;
|
||||
int16_t maxTemperature;
|
||||
Icons iconId;
|
||||
Location location;
|
||||
|
||||
bool operator==(const CurrentWeather& other) const;
|
||||
};
|
||||
|
||||
struct Forecast {
|
||||
uint64_t timestamp;
|
||||
uint8_t nbDays;
|
||||
|
||||
struct Day {
|
||||
int16_t minTemperature;
|
||||
int16_t maxTemperature;
|
||||
Icons iconId;
|
||||
|
||||
bool operator==(const Day& other) const;
|
||||
};
|
||||
|
||||
std::array<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() {
|
||||
return CharUuid(0x00, 0x00);
|
||||
}
|
||||
|
||||
// 0005yyxx-78fc-48fe-8e23-433b3a1942d0
|
||||
static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
|
||||
return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
|
||||
.value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x05, 0x00}};
|
||||
}
|
||||
|
||||
ble_uuid128_t weatherUuid {BaseUuid()};
|
||||
|
||||
ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
|
||||
|
||||
const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = &weatherDataCharUuid.u,
|
||||
.access_cb = WeatherCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE,
|
||||
.val_handle = &eventHandle},
|
||||
{0}};
|
||||
const struct ble_gatt_svc_def serviceDefinition[2] = {
|
||||
{.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition},
|
||||
{0}};
|
||||
|
||||
uint16_t eventHandle {};
|
||||
|
||||
const Pinetime::Controllers::DateTime& dateTimeController;
|
||||
|
||||
std::optional<CurrentWeather> currentWeather;
|
||||
std::optional<Forecast> forecast;
|
||||
};
|
||||
}
|
||||
}
|
385
src/components/ble/weather/WeatherData.h
Normal file
385
src/components/ble/weather/WeatherData.h
Normal file
@ -0,0 +1,385 @@
|
||||
/* Copyright (C) 2021 Avamander
|
||||
|
||||
This file is part of InfiniTime.
|
||||
|
||||
InfiniTime is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
InfiniTime is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Different weather events, weather data structures used by {@link WeatherService.h}
|
||||
*
|
||||
* How to upload events to the timeline?
|
||||
*
|
||||
* All timeline write payloads are simply CBOR-encoded payloads of the structs described below.
|
||||
*
|
||||
* All payloads have a mandatory header part and the dynamic part that
|
||||
* depends on the event type specified in the header. If you don't,
|
||||
* you'll get an error returned. Data is relatively well-validated,
|
||||
* so keep in the bounds of the data types given.
|
||||
*
|
||||
* Write all struct members (CamelCase keys) into a single finite-sized map, and write it to the characteristic.
|
||||
* Mind the MTU.
|
||||
*
|
||||
* How to debug?
|
||||
*
|
||||
* There's a Screen that you can compile into your firmware that shows currently valid events.
|
||||
* You can adapt that to display something else. That part right now is very much work in progress
|
||||
* because the exact requirements are not yet known.
|
||||
*
|
||||
*
|
||||
* Implemented based on and other material:
|
||||
* https://en.wikipedia.org/wiki/METAR
|
||||
* https://www.weather.gov/jetstream/obscurationtypes
|
||||
* http://www.faraim.org/aim/aim-4-03-14-493.html
|
||||
*/
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class WeatherData {
|
||||
public:
|
||||
/**
|
||||
* Visibility obscuration types
|
||||
*/
|
||||
enum class obscurationtype {
|
||||
/** No obscuration */
|
||||
None = 0,
|
||||
/** Water particles suspended in the air; low visibility; does not fall */
|
||||
Fog = 1,
|
||||
/** Tiny, dry particles in the air; invisible to the eye; opalescent */
|
||||
Haze = 2,
|
||||
/** Small fire-created particles suspended in the air */
|
||||
Smoke = 3,
|
||||
/** Fine rock powder, from for example volcanoes */
|
||||
Ash = 4,
|
||||
/** Fine particles of earth suspended in the air by the wind */
|
||||
Dust = 5,
|
||||
/** Fine particles of sand suspended in the air by the wind */
|
||||
Sand = 6,
|
||||
/** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */
|
||||
Mist = 7,
|
||||
/** This is SPECIAL in the sense that the thing raining down is doing the obscuration */
|
||||
Precipitation = 8,
|
||||
Length
|
||||
};
|
||||
|
||||
/**
|
||||
* Types of precipitation
|
||||
*/
|
||||
enum class precipitationtype {
|
||||
/**
|
||||
* No precipitation
|
||||
*
|
||||
* Theoretically we could just _not_ send the event, but then
|
||||
* how do we differentiate between no precipitation and
|
||||
* no information about precipitation
|
||||
*/
|
||||
None = 0,
|
||||
/** Drops larger than a drizzle; also widely separated drizzle */
|
||||
Rain = 1,
|
||||
/** Fairly uniform rain consisting of fine drops */
|
||||
Drizzle = 2,
|
||||
/** Rain that freezes upon contact with objects and ground */
|
||||
FreezingRain = 3,
|
||||
/** Rain + hail; ice pellets; small translucent frozen raindrops */
|
||||
Sleet = 4,
|
||||
/** Larger ice pellets; falling separately or in irregular clumps */
|
||||
Hail = 5,
|
||||
/** Hail with smaller grains of ice; mini-snowballs */
|
||||
SmallHail = 6,
|
||||
/** Snow... */
|
||||
Snow = 7,
|
||||
/** Frozen drizzle; very small snow crystals */
|
||||
SnowGrains = 8,
|
||||
/** Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */
|
||||
IceCrystals = 9,
|
||||
/** It's raining down ash, e.g. from a volcano */
|
||||
Ash = 10,
|
||||
Length
|
||||
};
|
||||
|
||||
/**
|
||||
* These are special events that can "enhance" the "experience" of existing weather events
|
||||
*/
|
||||
enum class specialtype {
|
||||
/** Strong wind with a sudden onset that lasts at least a minute */
|
||||
Squall = 0,
|
||||
/** Series of waves in a water body caused by the displacement of a large volume of water */
|
||||
Tsunami = 1,
|
||||
/** Violent; rotating column of air */
|
||||
Tornado = 2,
|
||||
/** Unplanned; unwanted; uncontrolled fire in an area */
|
||||
Fire = 3,
|
||||
/** Thunder and/or lightning */
|
||||
Thunder = 4,
|
||||
Length
|
||||
};
|
||||
|
||||
/**
|
||||
* These are used for weather timeline manipulation
|
||||
* that isn't just adding to the stack of weather events
|
||||
*/
|
||||
enum class controlcodes {
|
||||
/** How much is stored already */
|
||||
GetLength = 0,
|
||||
/** This wipes the entire timeline */
|
||||
DelTimeline = 1,
|
||||
/** There's a currently valid timeline event with the given type */
|
||||
HasValidEvent = 3,
|
||||
Length
|
||||
};
|
||||
|
||||
/**
|
||||
* Events have types
|
||||
* then they're easier to parse after sending them over the air
|
||||
*/
|
||||
enum class eventtype : uint8_t {
|
||||
/** @see obscuration */
|
||||
Obscuration = 0,
|
||||
/** @see precipitation */
|
||||
Precipitation = 1,
|
||||
/** @see wind */
|
||||
Wind = 2,
|
||||
/** @see temperature */
|
||||
Temperature = 3,
|
||||
/** @see airquality */
|
||||
AirQuality = 4,
|
||||
/** @see special */
|
||||
Special = 5,
|
||||
/** @see pressure */
|
||||
Pressure = 6,
|
||||
/** @see location */
|
||||
Location = 7,
|
||||
/** @see cloud */
|
||||
Clouds = 8,
|
||||
/** @see humidity */
|
||||
Humidity = 9,
|
||||
Length
|
||||
};
|
||||
|
||||
/**
|
||||
* Valid event query
|
||||
*
|
||||
* NOTE: Not currently available, until needs are better known
|
||||
*/
|
||||
class ValidEventQuery {
|
||||
public:
|
||||
static constexpr controlcodes code = controlcodes::HasValidEvent;
|
||||
eventtype eventType;
|
||||
};
|
||||
|
||||
/** The header used for further parsing */
|
||||
class TimelineHeader {
|
||||
public:
|
||||
/**
|
||||
* UNIX timestamp
|
||||
* TODO: This is currently WITH A TIMEZONE OFFSET!
|
||||
* Please send events with the timestamp offset by the timezone.
|
||||
**/
|
||||
uint64_t timestamp;
|
||||
/**
|
||||
* Time in seconds until the event expires
|
||||
*
|
||||
* 32 bits ought to be enough for everyone
|
||||
*
|
||||
* If there's a newer event of the same type then it overrides this one, even if it hasn't expired
|
||||
*/
|
||||
uint32_t expires;
|
||||
/**
|
||||
* What type of weather-related event
|
||||
*/
|
||||
eventtype eventType;
|
||||
};
|
||||
|
||||
/** Specifies how cloudiness is stored */
|
||||
class Clouds : public TimelineHeader {
|
||||
public:
|
||||
/** Cloud coverage in percentage, 0-100% */
|
||||
uint8_t amount;
|
||||
};
|
||||
|
||||
/** Specifies how obscuration is stored */
|
||||
class Obscuration : public TimelineHeader {
|
||||
public:
|
||||
/** Type of precipitation */
|
||||
obscurationtype type;
|
||||
/**
|
||||
* Visibility distance in meters
|
||||
* 65535 is reserved for unspecified
|
||||
*/
|
||||
uint16_t amount;
|
||||
};
|
||||
|
||||
/** Specifies how precipitation is stored */
|
||||
class Precipitation : public TimelineHeader {
|
||||
public:
|
||||
/** Type of precipitation */
|
||||
precipitationtype type;
|
||||
/**
|
||||
* How much is it going to rain? In millimeters
|
||||
* 255 is reserved for unspecified
|
||||
**/
|
||||
uint8_t amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* How wind speed is stored
|
||||
*
|
||||
* In order to represent bursts of wind instead of constant wind,
|
||||
* you have minimum and maximum speeds.
|
||||
*
|
||||
* As direction can fluctuate wildly and some watch faces might wish to display it nicely,
|
||||
* we're following the aerospace industry weather report option of specifying a range.
|
||||
*/
|
||||
class Wind : public TimelineHeader {
|
||||
public:
|
||||
/** Meters per second */
|
||||
uint8_t speedMin;
|
||||
/** Meters per second */
|
||||
uint8_t speedMax;
|
||||
/** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
|
||||
uint8_t directionMin;
|
||||
/** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
|
||||
uint8_t directionMax;
|
||||
};
|
||||
|
||||
/**
|
||||
* How temperature is stored
|
||||
*
|
||||
* As it's annoying to figure out the dewpoint on the watch,
|
||||
* please send it from the companion
|
||||
*
|
||||
* We don't do floats, picodegrees are not useful. Make sure to multiply.
|
||||
*/
|
||||
class Temperature : public TimelineHeader {
|
||||
public:
|
||||
/**
|
||||
* Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
|
||||
* -32768 is reserved for "no data"
|
||||
*/
|
||||
int16_t temperature;
|
||||
/**
|
||||
* Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
|
||||
* -32768 is reserved for "no data"
|
||||
*/
|
||||
int16_t dewPoint;
|
||||
};
|
||||
|
||||
/**
|
||||
* How location info is stored
|
||||
*
|
||||
* This can be mostly static with long expiration,
|
||||
* as it usually is, but it could change during a trip for ex.
|
||||
* so we allow changing it dynamically.
|
||||
*
|
||||
* Location info can be for some kind of map watch face
|
||||
* or daylight calculations, should those be required.
|
||||
*
|
||||
*/
|
||||
class Location : public TimelineHeader {
|
||||
public:
|
||||
/** Location name */
|
||||
std::string location;
|
||||
/** Altitude relative to sea level in meters */
|
||||
int16_t altitude;
|
||||
/** Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
|
||||
int32_t latitude;
|
||||
/** Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
|
||||
int32_t longitude;
|
||||
};
|
||||
|
||||
/**
|
||||
* How humidity is stored
|
||||
*/
|
||||
class Humidity : public TimelineHeader {
|
||||
public:
|
||||
/** Relative humidity, 0-100% */
|
||||
uint8_t humidity;
|
||||
};
|
||||
|
||||
/**
|
||||
* How air pressure is stored
|
||||
*/
|
||||
class Pressure : public TimelineHeader {
|
||||
public:
|
||||
/** Air pressure in hectopascals (hPa) */
|
||||
int16_t pressure;
|
||||
};
|
||||
|
||||
/**
|
||||
* How special events are stored
|
||||
*/
|
||||
class Special : public TimelineHeader {
|
||||
public:
|
||||
/** Special event's type */
|
||||
specialtype type;
|
||||
};
|
||||
|
||||
/**
|
||||
* How air quality is stored
|
||||
*
|
||||
* These events are a bit more complex because the topic is not simple,
|
||||
* the intention is to heavy-lift the annoying preprocessing from the watch
|
||||
* this allows watch face or watchapp makers to generate accurate alerts and graphics
|
||||
*
|
||||
* If this needs further enforced standardization, pull requests are welcome
|
||||
*/
|
||||
class AirQuality : public TimelineHeader {
|
||||
public:
|
||||
/**
|
||||
* The name of the pollution
|
||||
*
|
||||
* for the sake of better compatibility with watchapps
|
||||
* that might want to use this data for say visuals
|
||||
* don't localize the name.
|
||||
*
|
||||
* Ideally watchapp itself localizes the name, if it's at all needed.
|
||||
*
|
||||
* E.g.
|
||||
* For generic ones use "PM0.1", "PM5", "PM10"
|
||||
* For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3"
|
||||
* For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores
|
||||
*/
|
||||
std::string polluter;
|
||||
/**
|
||||
* Amount of the pollution in SI units,
|
||||
* otherwise it's going to be difficult to create UI, alerts
|
||||
* and so on and for.
|
||||
*
|
||||
* See more:
|
||||
* https://ec.europa.eu/environment/air/quality/standards.htm
|
||||
* http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
|
||||
*
|
||||
* Example units:
|
||||
* count/m³ for pollen
|
||||
* µgC/m³ for micrograms of organic carbon
|
||||
* µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust
|
||||
* mg/m³ CO2, CO
|
||||
* ng/m³ for heavy metals
|
||||
*
|
||||
* List is not comprehensive, should be improved.
|
||||
* The current ones are what watchapps assume!
|
||||
*
|
||||
* Note: ppb and ppm to concentration should be calculated on the companion, using
|
||||
* the correct formula (taking into account temperature and air pressure)
|
||||
*
|
||||
* Note2: The amount is off by times 100, for two decimal places of precision.
|
||||
* E.g. 54.32µg/m³ is 5432
|
||||
*
|
||||
*/
|
||||
uint32_t amount;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
605
src/components/ble/weather/WeatherService.cpp
Normal file
605
src/components/ble/weather/WeatherService.cpp
Normal file
@ -0,0 +1,605 @@
|
||||
/* Copyright (C) 2021 Avamander
|
||||
|
||||
This file is part of InfiniTime.
|
||||
|
||||
InfiniTime is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
InfiniTime is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <algorithm>
|
||||
#include <qcbor/qcbor_spiffy_decode.h>
|
||||
#include "WeatherService.h"
|
||||
#include "libs/QCBOR/inc/qcbor/qcbor.h"
|
||||
|
||||
int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(ctxt);
|
||||
}
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
WeatherService::WeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) {
|
||||
nullHeader = &nullTimelineheader;
|
||||
nullTimelineheader->timestamp = 0;
|
||||
}
|
||||
|
||||
void WeatherService::Init() {
|
||||
uint8_t res = 0;
|
||||
res = ble_gatts_count_cfg(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
|
||||
res = ble_gatts_add_svcs(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
int WeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
const uint8_t packetLen = OS_MBUF_PKTLEN(ctxt->om); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
if (packetLen <= 0) {
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
// Decode
|
||||
QCBORDecodeContext decodeContext;
|
||||
UsefulBufC encodedCbor = {ctxt->om->om_data, OS_MBUF_PKTLEN(ctxt->om)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
|
||||
QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL);
|
||||
// KINDLY provide us a fixed-length map
|
||||
QCBORDecode_EnterMap(&decodeContext, nullptr);
|
||||
// Always encodes to the smallest number of bytes based on the value
|
||||
int64_t tmpTimestamp = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp);
|
||||
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
int64_t tmpExpires = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires);
|
||||
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpExpires < 0 || tmpExpires > 4294967295) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
int64_t tmpEventType = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType);
|
||||
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpEventType < 0 ||
|
||||
tmpEventType >= static_cast<int64_t>(WeatherData::eventtype::Length)) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
switch (static_cast<WeatherData::eventtype>(tmpEventType)) {
|
||||
case WeatherData::eventtype::AirQuality: {
|
||||
std::unique_ptr<WeatherData::AirQuality> airquality = std::make_unique<WeatherData::AirQuality>();
|
||||
airquality->timestamp = tmpTimestamp;
|
||||
airquality->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
airquality->expires = tmpExpires;
|
||||
|
||||
UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
|
||||
QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Polluter", &stringBuf);
|
||||
if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
airquality->polluter = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
|
||||
|
||||
int64_t tmpAmount = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
|
||||
if (tmpAmount < 0 || tmpAmount > 4294967295) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
if (!AddEventToTimeline(std::move(airquality))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Obscuration: {
|
||||
std::unique_ptr<WeatherData::Obscuration> obscuration = std::make_unique<WeatherData::Obscuration>();
|
||||
obscuration->timestamp = tmpTimestamp;
|
||||
obscuration->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
obscuration->expires = tmpExpires;
|
||||
|
||||
int64_t tmpType = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
|
||||
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::obscurationtype::Length)) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
obscuration->type = static_cast<WeatherData::obscurationtype>(tmpType);
|
||||
|
||||
int64_t tmpAmount = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
|
||||
if (tmpAmount < 0 || tmpAmount > 65535) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
if (!AddEventToTimeline(std::move(obscuration))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Precipitation: {
|
||||
std::unique_ptr<WeatherData::Precipitation> precipitation = std::make_unique<WeatherData::Precipitation>();
|
||||
precipitation->timestamp = tmpTimestamp;
|
||||
precipitation->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
precipitation->expires = tmpExpires;
|
||||
|
||||
int64_t tmpType = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
|
||||
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::precipitationtype::Length)) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
precipitation->type = static_cast<WeatherData::precipitationtype>(tmpType);
|
||||
|
||||
int64_t tmpAmount = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
|
||||
if (tmpAmount < 0 || tmpAmount > 255) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
if (!AddEventToTimeline(std::move(precipitation))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Wind: {
|
||||
std::unique_ptr<WeatherData::Wind> wind = std::make_unique<WeatherData::Wind>();
|
||||
wind->timestamp = tmpTimestamp;
|
||||
wind->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
wind->expires = tmpExpires;
|
||||
|
||||
int64_t tmpMin = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMin);
|
||||
if (tmpMin < 0 || tmpMin > 255) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
int64_t tmpMax = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax);
|
||||
if (tmpMax < 0 || tmpMax > 255) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
int64_t tmpDMin = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin);
|
||||
if (tmpDMin < 0 || tmpDMin > 255) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
int64_t tmpDMax = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax);
|
||||
if (tmpDMax < 0 || tmpDMax > 255) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
if (!AddEventToTimeline(std::move(wind))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Temperature: {
|
||||
std::unique_ptr<WeatherData::Temperature> temperature = std::make_unique<WeatherData::Temperature>();
|
||||
temperature->timestamp = tmpTimestamp;
|
||||
temperature->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
temperature->expires = tmpExpires;
|
||||
|
||||
int64_t tmpTemperature = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Temperature", &tmpTemperature);
|
||||
if (tmpTemperature < -32768 || tmpTemperature > 32767) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
temperature->temperature =
|
||||
static_cast<int16_t>(tmpTemperature); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
int64_t tmpDewPoint = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint);
|
||||
if (tmpDewPoint < -32768 || tmpDewPoint > 32767) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
temperature->dewPoint =
|
||||
static_cast<int16_t>(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
if (!AddEventToTimeline(std::move(temperature))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Special: {
|
||||
std::unique_ptr<WeatherData::Special> special = std::make_unique<WeatherData::Special>();
|
||||
special->timestamp = tmpTimestamp;
|
||||
special->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
special->expires = tmpExpires;
|
||||
|
||||
int64_t tmpType = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
|
||||
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::specialtype::Length)) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
special->type = static_cast<WeatherData::specialtype>(tmpType);
|
||||
|
||||
if (!AddEventToTimeline(std::move(special))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Pressure: {
|
||||
std::unique_ptr<WeatherData::Pressure> pressure = std::make_unique<WeatherData::Pressure>();
|
||||
pressure->timestamp = tmpTimestamp;
|
||||
pressure->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
pressure->expires = tmpExpires;
|
||||
|
||||
int64_t tmpPressure = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure);
|
||||
if (tmpPressure < 0 || tmpPressure >= 65535) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
|
||||
if (!AddEventToTimeline(std::move(pressure))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Location: {
|
||||
std::unique_ptr<WeatherData::Location> location = std::make_unique<WeatherData::Location>();
|
||||
location->timestamp = tmpTimestamp;
|
||||
location->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
location->expires = tmpExpires;
|
||||
|
||||
UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
|
||||
QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Location", &stringBuf);
|
||||
if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
location->location = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
|
||||
|
||||
int64_t tmpAltitude = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude);
|
||||
if (tmpAltitude < -32768 || tmpAltitude >= 32767) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
location->altitude = static_cast<int16_t>(tmpAltitude);
|
||||
|
||||
int64_t tmpLatitude = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude);
|
||||
if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
location->latitude = static_cast<int32_t>(tmpLatitude);
|
||||
|
||||
int64_t tmpLongitude = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude);
|
||||
if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
location->latitude = static_cast<int32_t>(tmpLongitude);
|
||||
|
||||
if (!AddEventToTimeline(std::move(location))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Clouds: {
|
||||
std::unique_ptr<WeatherData::Clouds> clouds = std::make_unique<WeatherData::Clouds>();
|
||||
clouds->timestamp = tmpTimestamp;
|
||||
clouds->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
clouds->expires = tmpExpires;
|
||||
|
||||
int64_t tmpAmount = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
|
||||
if (tmpAmount < 0 || tmpAmount > 255) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
clouds->amount = static_cast<uint8_t>(tmpAmount);
|
||||
|
||||
if (!AddEventToTimeline(std::move(clouds))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WeatherData::eventtype::Humidity: {
|
||||
std::unique_ptr<WeatherData::Humidity> humidity = std::make_unique<WeatherData::Humidity>();
|
||||
humidity->timestamp = tmpTimestamp;
|
||||
humidity->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
|
||||
humidity->expires = tmpExpires;
|
||||
|
||||
int64_t tmpType = 0;
|
||||
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType);
|
||||
if (tmpType < 0 || tmpType >= 255) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
humidity->humidity = static_cast<uint8_t>(tmpType);
|
||||
|
||||
if (!AddEventToTimeline(std::move(humidity))) {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
CleanUpQcbor(&decodeContext);
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
QCBORDecode_ExitMap(&decodeContext);
|
||||
GetTimelineLength();
|
||||
TidyTimeline();
|
||||
|
||||
if (QCBORDecode_Finish(&decodeContext) != QCBOR_SUCCESS) {
|
||||
return BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
} else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
|
||||
// Encode
|
||||
uint8_t buffer[64];
|
||||
QCBOREncodeContext encodeContext;
|
||||
/* TODO: This is very much still a test endpoint
|
||||
* it needs a characteristic UUID check
|
||||
* and actual implementations that show
|
||||
* what actually has to be read.
|
||||
* WARN: Consider commands not part of the API for now!
|
||||
*/
|
||||
QCBOREncode_Init(&encodeContext, UsefulBuf_FROM_BYTE_ARRAY(buffer));
|
||||
QCBOREncode_OpenMap(&encodeContext);
|
||||
QCBOREncode_AddTextToMap(&encodeContext, "test", UsefulBuf_FROM_SZ_LITERAL("test"));
|
||||
QCBOREncode_AddInt64ToMap(&encodeContext, "test", 1ul);
|
||||
QCBOREncode_CloseMap(&encodeContext);
|
||||
|
||||
UsefulBufC encodedEvent;
|
||||
auto uErr = QCBOREncode_Finish(&encodeContext, &encodedEvent);
|
||||
if (uErr != 0) {
|
||||
return BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
auto res = os_mbuf_append(ctxt->om, &buffer, sizeof(buffer));
|
||||
if (res == 0) {
|
||||
return BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) {
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(*this->nullHeader);
|
||||
}
|
||||
|
||||
size_t WeatherService::GetTimelineLength() const {
|
||||
return timeline.size();
|
||||
}
|
||||
|
||||
bool WeatherService::AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event) {
|
||||
if (timeline.size() == timeline.max_size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
timeline.push_back(std::move(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WeatherService::HasTimelineEventOfType(const WeatherData::eventtype type) const {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
for (auto&& header : timeline) {
|
||||
if (header->eventType == type && IsEventStillValid(header, currentTimestamp)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WeatherService::TidyTimeline() {
|
||||
uint64_t timeCurrent = GetCurrentUnixTimestamp();
|
||||
timeline.erase(std::remove_if(std::begin(timeline),
|
||||
std::end(timeline),
|
||||
[&](std::unique_ptr<WeatherData::TimelineHeader> const& header) {
|
||||
return !IsEventStillValid(header, timeCurrent);
|
||||
}),
|
||||
std::end(timeline));
|
||||
|
||||
std::sort(std::begin(timeline), std::end(timeline), CompareTimelineEvents);
|
||||
}
|
||||
|
||||
bool WeatherService::CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
|
||||
const std::unique_ptr<WeatherData::TimelineHeader>& second) {
|
||||
return first->timestamp > second->timestamp;
|
||||
}
|
||||
|
||||
bool WeatherService::IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp) {
|
||||
// Not getting timestamp in isEventStillValid for more speed
|
||||
return uniquePtr->timestamp + uniquePtr->expires >= timestamp;
|
||||
}
|
||||
|
||||
uint64_t WeatherService::GetCurrentUnixTimestamp() const {
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
int16_t WeatherService::GetTodayMinTemp() const {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) +
|
||||
((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds());
|
||||
uint64_t currentDayStart = currentDayEnd - 86400;
|
||||
int16_t result = -32768;
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart &&
|
||||
header->timestamp < currentDayEnd &&
|
||||
reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
|
||||
int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
|
||||
if (result == -32768) {
|
||||
result = temperature;
|
||||
} else if (result > temperature) {
|
||||
result = temperature;
|
||||
} else {
|
||||
// The temperature in this item is higher than the lowest we've found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int16_t WeatherService::GetTodayMaxTemp() const {
|
||||
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
|
||||
uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) +
|
||||
((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds());
|
||||
uint64_t currentDayStart = currentDayEnd - 86400;
|
||||
int16_t result = -32768;
|
||||
for (auto&& header : this->timeline) {
|
||||
if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart &&
|
||||
header->timestamp < currentDayEnd &&
|
||||
reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
|
||||
int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
|
||||
if (result == -32768) {
|
||||
result = temperature;
|
||||
} else if (result < temperature) {
|
||||
result = temperature;
|
||||
} else {
|
||||
// The temperature in this item is lower than the highest we've found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WeatherService::CleanUpQcbor(QCBORDecodeContext* decodeContext) {
|
||||
QCBORDecode_ExitMap(decodeContext);
|
||||
QCBORDecode_Finish(decodeContext);
|
||||
}
|
||||
}
|
||||
}
|
169
src/components/ble/weather/WeatherService.h
Normal file
169
src/components/ble/weather/WeatherService.h
Normal file
@ -0,0 +1,169 @@
|
||||
/* Copyright (C) 2021 Avamander
|
||||
|
||||
This file is part of InfiniTime.
|
||||
|
||||
InfiniTime is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
InfiniTime is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||
#define max
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_uuid.h>
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
#include "WeatherData.h"
|
||||
#include "libs/QCBOR/inc/qcbor/qcbor.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
|
||||
int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
||||
class WeatherService {
|
||||
public:
|
||||
explicit WeatherService(const DateTime& dateTimeController);
|
||||
|
||||
void Init();
|
||||
|
||||
int OnCommand(struct ble_gatt_access_ctxt* ctxt);
|
||||
|
||||
/*
|
||||
* Helper functions for quick access to currently valid data
|
||||
*/
|
||||
std::unique_ptr<WeatherData::Location>& GetCurrentLocation();
|
||||
std::unique_ptr<WeatherData::Clouds>& GetCurrentClouds();
|
||||
std::unique_ptr<WeatherData::Obscuration>& GetCurrentObscuration();
|
||||
std::unique_ptr<WeatherData::Precipitation>& GetCurrentPrecipitation();
|
||||
std::unique_ptr<WeatherData::Wind>& GetCurrentWind();
|
||||
std::unique_ptr<WeatherData::Temperature>& GetCurrentTemperature();
|
||||
std::unique_ptr<WeatherData::Humidity>& GetCurrentHumidity();
|
||||
std::unique_ptr<WeatherData::Pressure>& GetCurrentPressure();
|
||||
std::unique_ptr<WeatherData::AirQuality>& GetCurrentQuality();
|
||||
|
||||
/**
|
||||
* Searches for the current day's maximum temperature
|
||||
* @return -32768 if there's no data, degrees Celsius times 100 otherwise
|
||||
*/
|
||||
int16_t GetTodayMaxTemp() const;
|
||||
/**
|
||||
* Searches for the current day's minimum temperature
|
||||
* @return -32768 if there's no data, degrees Celsius times 100 otherwise
|
||||
*/
|
||||
int16_t GetTodayMinTemp() const;
|
||||
|
||||
/*
|
||||
* Management functions
|
||||
*/
|
||||
/**
|
||||
* Adds an event to the timeline
|
||||
* @return
|
||||
*/
|
||||
bool AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event);
|
||||
/**
|
||||
* Gets the current timeline length
|
||||
*/
|
||||
size_t GetTimelineLength() const;
|
||||
/**
|
||||
* Checks if an event of a certain type exists in the timeline
|
||||
*/
|
||||
bool HasTimelineEventOfType(WeatherData::eventtype type) const;
|
||||
|
||||
private:
|
||||
// 00040000-78fc-48fe-8e23-433b3a1942d0
|
||||
static constexpr ble_uuid128_t BaseUuid() {
|
||||
return CharUuid(0x00, 0x00);
|
||||
}
|
||||
|
||||
// 0004yyxx-78fc-48fe-8e23-433b3a1942d0
|
||||
static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
|
||||
return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
|
||||
.value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}};
|
||||
}
|
||||
|
||||
ble_uuid128_t weatherUuid {BaseUuid()};
|
||||
|
||||
/**
|
||||
* Just write timeline data here.
|
||||
*
|
||||
* See {@link WeatherData.h} for more information.
|
||||
*/
|
||||
ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
|
||||
/**
|
||||
* This doesn't take timeline data, provides some control over it.
|
||||
*
|
||||
* NOTE: Currently not supported. Companion app implementer feedback required.
|
||||
* There's very little point in solidifying an API before we know the needs.
|
||||
*/
|
||||
ble_uuid128_t weatherControlCharUuid {CharUuid(0x00, 0x02)};
|
||||
|
||||
const struct ble_gatt_chr_def characteristicDefinition[3] = {
|
||||
{.uuid = &weatherDataCharUuid.u,
|
||||
.access_cb = WeatherCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE,
|
||||
.val_handle = &eventHandle},
|
||||
{.uuid = &weatherControlCharUuid.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ},
|
||||
{nullptr}};
|
||||
const struct ble_gatt_svc_def serviceDefinition[2] = {
|
||||
{.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition},
|
||||
{0}};
|
||||
|
||||
uint16_t eventHandle {};
|
||||
|
||||
const Pinetime::Controllers::DateTime& dateTimeController;
|
||||
|
||||
std::vector<std::unique_ptr<WeatherData::TimelineHeader>> timeline;
|
||||
std::unique_ptr<WeatherData::TimelineHeader> nullTimelineheader = std::make_unique<WeatherData::TimelineHeader>();
|
||||
std::unique_ptr<WeatherData::TimelineHeader>* nullHeader;
|
||||
|
||||
/**
|
||||
* Cleans up the timeline of expired events
|
||||
*/
|
||||
void TidyTimeline();
|
||||
|
||||
/**
|
||||
* Compares two timeline events
|
||||
*/
|
||||
static bool CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
|
||||
const std::unique_ptr<WeatherData::TimelineHeader>& second);
|
||||
|
||||
/**
|
||||
* Returns current UNIX timestamp
|
||||
*/
|
||||
uint64_t GetCurrentUnixTimestamp() const;
|
||||
|
||||
/**
|
||||
* Checks if the event hasn't gone past and expired
|
||||
*
|
||||
* @param header timeline event to check
|
||||
* @param currentTimestamp what's the time right now
|
||||
* @return if the event is valid
|
||||
*/
|
||||
static bool IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp);
|
||||
|
||||
/**
|
||||
* This is a helper function that closes a QCBOR map and decoding context cleanly
|
||||
*/
|
||||
void CleanUpQcbor(QCBORDecodeContext* decodeContext);
|
||||
};
|
||||
}
|
||||
}
|
@ -115,8 +115,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
|
||||
return MonthsStringLow[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
const char* DateTime::DayOfWeekShortToStringLow(Days day) {
|
||||
return DaysStringShortLow[static_cast<uint8_t>(day)];
|
||||
const char* DateTime::DayOfWeekShortToStringLow() const {
|
||||
return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())];
|
||||
}
|
||||
|
||||
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
|
||||
@ -140,9 +140,9 @@ std::string DateTime::FormattedTime() {
|
||||
hour12 = (hour == 12) ? 12 : hour - 12;
|
||||
amPmStr = "PM";
|
||||
}
|
||||
snprintf(buff, sizeof(buff), "%i:%02i %s", hour12, minute, amPmStr);
|
||||
sprintf(buff, "%i:%02i %s", hour12, minute, amPmStr);
|
||||
} else {
|
||||
snprintf(buff, sizeof(buff), "%02i:%02i", hour, minute);
|
||||
sprintf(buff, "%02i:%02i", hour, minute);
|
||||
}
|
||||
return std::string(buff);
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ namespace Pinetime {
|
||||
const char* MonthShortToString() const;
|
||||
const char* DayOfWeekShortToString() const;
|
||||
static const char* MonthShortToStringLow(Months month);
|
||||
static const char* DayOfWeekShortToStringLow(Days day);
|
||||
const char* DayOfWeekShortToStringLow() const;
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
|
||||
return currentDateTime;
|
||||
|
196
src/components/gfx/Gfx.cpp
Normal file
196
src/components/gfx/Gfx.cpp
Normal file
@ -0,0 +1,196 @@
|
||||
#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--;
|
||||
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++;
|
||||
|
||||
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);
|
||||
}
|
62
src/components/gfx/Gfx.h
Normal file
62
src/components/gfx/Gfx.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include <FreeRTOS.h>
|
||||
#include <nrf_font.h>
|
||||
#include <task.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "drivers/BufferProvider.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Drivers {
|
||||
class St7789;
|
||||
}
|
||||
|
||||
namespace Components {
|
||||
class Gfx : public Pinetime::Drivers::BufferProvider {
|
||||
public:
|
||||
explicit Gfx(Drivers::St7789& lcd);
|
||||
void Init();
|
||||
void ClearScreen();
|
||||
void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap);
|
||||
void DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color);
|
||||
void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
|
||||
void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
|
||||
void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
|
||||
void SetScrollStartLine(uint16_t line);
|
||||
|
||||
void Sleep();
|
||||
void Wakeup();
|
||||
bool GetNextBuffer(uint8_t** buffer, size_t& size) override;
|
||||
void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
|
||||
|
||||
private:
|
||||
static constexpr uint8_t width = 240;
|
||||
static constexpr uint8_t height = 240;
|
||||
|
||||
enum class Action { None, FillRectangle, DrawChar };
|
||||
|
||||
struct State {
|
||||
State() : busy {false}, action {Action::None}, remainingIterations {0}, currentIteration {0} {
|
||||
}
|
||||
|
||||
volatile bool busy;
|
||||
volatile Action action;
|
||||
volatile uint16_t remainingIterations;
|
||||
volatile uint16_t currentIteration;
|
||||
volatile FONT_INFO* font;
|
||||
volatile uint16_t color;
|
||||
volatile uint8_t character;
|
||||
volatile TaskHandle_t taskToNotify = nullptr;
|
||||
};
|
||||
|
||||
volatile State state;
|
||||
|
||||
uint16_t buffer[width]; // 1 line buffer
|
||||
Drivers::St7789& lcd;
|
||||
|
||||
void SetBackgroundColor(uint16_t color);
|
||||
void WaitTransferFinished() const;
|
||||
void NotifyEndOfTransfer(TaskHandle_t task);
|
||||
};
|
||||
}
|
||||
}
|
@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
|
||||
service->OnNewStepCountValue(nbSteps);
|
||||
}
|
||||
|
||||
if (service != nullptr && (xHistory[0] != x || yHistory[0] != y || zHistory[0] != z)) {
|
||||
if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) {
|
||||
service->OnNewMotionValues(x, y, z);
|
||||
}
|
||||
|
||||
lastTime = time;
|
||||
time = xTaskGetTickCount();
|
||||
|
||||
xHistory++;
|
||||
xHistory[0] = x;
|
||||
lastX = this->x;
|
||||
this->x = x;
|
||||
yHistory++;
|
||||
yHistory[0] = y;
|
||||
zHistory++;
|
||||
@ -67,26 +67,20 @@ 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;
|
||||
|
||||
@ -99,7 +93,7 @@ bool MotionController::ShouldRaiseWake() const {
|
||||
constexpr int16_t yThresh = -64;
|
||||
constexpr int16_t rollDegreesThresh = -45;
|
||||
|
||||
if (std::abs(stats.xMean) > xThresh) {
|
||||
if (x < -xThresh || x > xThresh) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -113,9 +107,8 @@ 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 +
|
||||
(xHistory[0] - xHistory[histSize - 1]) / 4) *
|
||||
100 / (time - lastTime);
|
||||
int32_t speed =
|
||||
std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime);
|
||||
// (.2 * speed) + ((1 - .2) * accumulatedSpeed);
|
||||
accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
|
||||
|
||||
@ -123,11 +116,6 @@ 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 xHistory[0];
|
||||
return x;
|
||||
}
|
||||
|
||||
int16_t Y() const {
|
||||
@ -62,10 +62,6 @@ namespace Pinetime {
|
||||
this->service = service;
|
||||
}
|
||||
|
||||
Pinetime::Controllers::MotionService* GetService() const {
|
||||
return service;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t nbSteps = 0;
|
||||
uint32_t currentTripSteps = 0;
|
||||
@ -76,14 +72,11 @@ 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;
|
||||
};
|
||||
@ -92,8 +85,9 @@ 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;
|
||||
|
@ -24,7 +24,7 @@ namespace {
|
||||
* The pattern must end with a duration of vibration and a terminator.
|
||||
*/
|
||||
|
||||
static constexpr uint8_t vibrationPattern[] = {30, 150, 30, 150, 30, 0};
|
||||
static constexpr uint8_t vibrationPattern[] = {10, 100, 50, 200, 10, 0};
|
||||
|
||||
static size_t patternPosition = 0;
|
||||
if (vibrationPattern[patternPosition] != 0 && xTimerChangePeriod(vibTimer, vibrationPattern[patternPosition] << 1, 0) == pdPASS &&
|
||||
@ -87,10 +87,6 @@ void MotorController::StopRinging() {
|
||||
nrf_gpio_pin_set(PinMap::Motor);
|
||||
}
|
||||
|
||||
bool MotorController::IsRinging() {
|
||||
return (xTimerIsTimerActive(longVib) == pdTRUE);
|
||||
}
|
||||
|
||||
void MotorController::StopMotor(TimerHandle_t /*xTimer*/) {
|
||||
nrf_gpio_pin_set(PinMap::Motor);
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ namespace Pinetime {
|
||||
void StopRinging();
|
||||
void PatternFinished();
|
||||
bool StartPattern();
|
||||
bool IsRinging();
|
||||
|
||||
private:
|
||||
static void Ring(TimerHandle_t xTimer);
|
||||
|
@ -3,14 +3,13 @@
|
||||
#include <bitset>
|
||||
#include "components/brightness/BrightnessController.h"
|
||||
#include "components/fs/FS.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/WatchFaces.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class Settings {
|
||||
public:
|
||||
enum class ClockType : uint8_t { H24, H12 };
|
||||
enum class WeatherFormat : uint8_t { Metric, Imperial };
|
||||
enum class Notification : uint8_t { On, Off, Sleep };
|
||||
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
|
||||
enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 };
|
||||
@ -181,17 +180,6 @@ namespace Pinetime {
|
||||
return settings.clockType;
|
||||
};
|
||||
|
||||
void SetWeatherFormat(WeatherFormat weatherFormat) {
|
||||
if (weatherFormat != settings.weatherFormat) {
|
||||
settingsChanged = true;
|
||||
}
|
||||
settings.weatherFormat = weatherFormat;
|
||||
};
|
||||
|
||||
WeatherFormat GetWeatherFormat() const {
|
||||
return settings.weatherFormat;
|
||||
};
|
||||
|
||||
void SetNotificationStatus(Notification status) {
|
||||
if (status != settings.notificationStatus) {
|
||||
settingsChanged = true;
|
||||
@ -286,7 +274,7 @@ namespace Pinetime {
|
||||
private:
|
||||
Pinetime::Controllers::FS& fs;
|
||||
|
||||
static constexpr uint32_t settingsVersion = 0x0007;
|
||||
static constexpr uint32_t settingsVersion = 0x0006;
|
||||
|
||||
struct SettingsData {
|
||||
uint32_t version = settingsVersion;
|
||||
@ -294,7 +282,6 @@ namespace Pinetime {
|
||||
uint32_t screenTimeOut = 15000;
|
||||
|
||||
ClockType clockType = ClockType::H24;
|
||||
WeatherFormat weatherFormat = WeatherFormat::Metric;
|
||||
Notification notificationStatus = Notification::On;
|
||||
|
||||
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;
|
||||
|
@ -12,14 +12,12 @@ void Timer::StartTimer(std::chrono::milliseconds duration) {
|
||||
}
|
||||
|
||||
std::chrono::milliseconds Timer::GetTimeRemaining() {
|
||||
TickType_t remainingTime = 0;
|
||||
if (IsRunning()) {
|
||||
remainingTime = xTimerGetExpiryTime(timer) - xTaskGetTickCount();
|
||||
} else {
|
||||
remainingTime = xTaskGetTickCount() - xTimerGetExpiryTime(timer);
|
||||
}
|
||||
TickType_t remainingTime = xTimerGetExpiryTime(timer) - xTaskGetTickCount();
|
||||
return std::chrono::milliseconds(remainingTime * 1000 / configTICK_RATE_HZ);
|
||||
}
|
||||
return std::chrono::milliseconds(0);
|
||||
}
|
||||
|
||||
void Timer::StopTimer() {
|
||||
xTimerStop(timer, 0);
|
||||
|
44
src/displayapp/Apps.h
Normal file
44
src/displayapp/Apps.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
enum class Apps {
|
||||
None,
|
||||
Launcher,
|
||||
Clock,
|
||||
SysInfo,
|
||||
FirmwareUpdate,
|
||||
FirmwareValidation,
|
||||
NotificationsPreview,
|
||||
Notifications,
|
||||
Timer,
|
||||
Alarm,
|
||||
FlashLight,
|
||||
BatteryInfo,
|
||||
Music,
|
||||
Paint,
|
||||
Paddle,
|
||||
Twos,
|
||||
HeartRate,
|
||||
Navigation,
|
||||
StopWatch,
|
||||
Metronome,
|
||||
Motion,
|
||||
Steps,
|
||||
PassKey,
|
||||
QuickSettings,
|
||||
Settings,
|
||||
SettingWatchFace,
|
||||
SettingTimeFormat,
|
||||
SettingDisplay,
|
||||
SettingWakeUp,
|
||||
SettingSteps,
|
||||
SettingSetDateTime,
|
||||
SettingChimes,
|
||||
SettingShakeThreshold,
|
||||
SettingBluetooth,
|
||||
Error,
|
||||
Calculator
|
||||
};
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
class DisplayApp;
|
||||
}
|
||||
|
||||
namespace Components {
|
||||
class LittleVgl;
|
||||
}
|
||||
|
||||
namespace Controllers {
|
||||
class Battery;
|
||||
class Ble;
|
||||
class DateTime;
|
||||
class NotificationManager;
|
||||
class HeartRateController;
|
||||
class Settings;
|
||||
class MotorController;
|
||||
class MotionController;
|
||||
class AlarmController;
|
||||
class BrightnessController;
|
||||
class SimpleWeatherService;
|
||||
class FS;
|
||||
class Timer;
|
||||
class MusicService;
|
||||
class NavigationService;
|
||||
}
|
||||
|
||||
namespace System {
|
||||
class SystemTask;
|
||||
}
|
||||
|
||||
namespace Applications {
|
||||
struct AppControllers {
|
||||
const Pinetime::Controllers::Battery& batteryController;
|
||||
const Pinetime::Controllers::Ble& bleController;
|
||||
Pinetime::Controllers::DateTime& dateTimeController;
|
||||
Pinetime::Controllers::NotificationManager& notificationManager;
|
||||
Pinetime::Controllers::HeartRateController& heartRateController;
|
||||
Pinetime::Controllers::Settings& settingsController;
|
||||
Pinetime::Controllers::MotorController& motorController;
|
||||
Pinetime::Controllers::MotionController& motionController;
|
||||
Pinetime::Controllers::AlarmController& alarmController;
|
||||
Pinetime::Controllers::BrightnessController& brightnessController;
|
||||
Pinetime::Controllers::SimpleWeatherService* weatherController;
|
||||
Pinetime::Controllers::FS& filesystem;
|
||||
Pinetime::Controllers::Timer& timer;
|
||||
Pinetime::System::SystemTask* systemTask;
|
||||
Pinetime::Applications::DisplayApp* displayApp;
|
||||
Pinetime::Components::LittleVgl& lvgl;
|
||||
Pinetime::Controllers::MusicService* musicService;
|
||||
Pinetime::Controllers::NavigationService* navigationService;
|
||||
};
|
||||
}
|
||||
}
|
@ -11,17 +11,15 @@
|
||||
#include "components/motion/MotionController.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "displayapp/screens/ApplicationList.h"
|
||||
#include "displayapp/screens/Clock.h"
|
||||
#include "displayapp/screens/FirmwareUpdate.h"
|
||||
#include "displayapp/screens/FirmwareValidation.h"
|
||||
#include "displayapp/screens/InfiniPaint.h"
|
||||
#include "displayapp/screens/Bird.h"
|
||||
#include "displayapp/screens/Paddle.h"
|
||||
#include "displayapp/screens/Bird.h"
|
||||
#include "displayapp/screens/StopWatch.h"
|
||||
#include "displayapp/screens/Metronome.h"
|
||||
#include "displayapp/screens/Music.h"
|
||||
#include "displayapp/screens/Navigation.h"
|
||||
#include "displayapp/screens/Calendar.h"
|
||||
#include "displayapp/screens/Notifications.h"
|
||||
#include "displayapp/screens/SystemInfo.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
@ -29,8 +27,6 @@
|
||||
#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"
|
||||
#include "displayapp/screens/Weather.h"
|
||||
@ -48,7 +44,6 @@
|
||||
#include "displayapp/screens/settings/Settings.h"
|
||||
#include "displayapp/screens/settings/SettingWatchFace.h"
|
||||
#include "displayapp/screens/settings/SettingTimeFormat.h"
|
||||
#include "displayapp/screens/settings/SettingWeatherFormat.h"
|
||||
#include "displayapp/screens/settings/SettingWakeUp.h"
|
||||
#include "displayapp/screens/settings/SettingDisplay.h"
|
||||
#include "displayapp/screens/settings/SettingSteps.h"
|
||||
@ -58,7 +53,6 @@
|
||||
#include "displayapp/screens/settings/SettingBluetooth.h"
|
||||
|
||||
#include "libs/lv_conf.h"
|
||||
#include "UserApps.h"
|
||||
|
||||
using namespace Pinetime::Applications;
|
||||
using namespace Pinetime::Applications::Display;
|
||||
@ -105,25 +99,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
||||
touchHandler {touchHandler},
|
||||
filesystem {filesystem},
|
||||
lvgl {lcd, filesystem},
|
||||
timer(this, TimerCallback),
|
||||
controllers {batteryController,
|
||||
bleController,
|
||||
dateTimeController,
|
||||
notificationManager,
|
||||
heartRateController,
|
||||
settingsController,
|
||||
motorController,
|
||||
motionController,
|
||||
alarmController,
|
||||
brightnessController,
|
||||
nullptr,
|
||||
filesystem,
|
||||
timer,
|
||||
nullptr,
|
||||
this,
|
||||
lvgl,
|
||||
nullptr,
|
||||
nullptr} {
|
||||
timer(this, TimerCallback) {
|
||||
}
|
||||
|
||||
void DisplayApp::Start(System::BootErrors error) {
|
||||
@ -149,6 +125,9 @@ 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();
|
||||
}
|
||||
@ -270,17 +249,14 @@ void DisplayApp::Refresh() {
|
||||
if (state != States::Running) {
|
||||
PushMessageToSystemTask(System::Messages::GoToRunning);
|
||||
}
|
||||
// Load timer app if not loaded
|
||||
if (currentApp != Apps::Timer) {
|
||||
LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up);
|
||||
}
|
||||
// Once loaded, set the timer to ringing mode
|
||||
if (currentApp == Apps::Timer) {
|
||||
lv_disp_trig_activity(nullptr);
|
||||
auto* timer = static_cast<Screens::Timer*>(currentScreen.get());
|
||||
timer->SetTimerRinging();
|
||||
timer->Reset();
|
||||
} else {
|
||||
LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up);
|
||||
}
|
||||
motorController.StartRinging();
|
||||
motorController.RunForDuration(35);
|
||||
break;
|
||||
case Messages::AlarmTriggered:
|
||||
if (currentApp == Apps::Alarm) {
|
||||
@ -318,22 +294,19 @@ void DisplayApp::Refresh() {
|
||||
};
|
||||
|
||||
if (!currentScreen->OnTouchEvent(gesture)) {
|
||||
if (currentApp == Apps::Clock || currentApp == Apps::Music || currentApp == Apps::Weather || currentApp == Apps::QuickSettings) {
|
||||
if (currentApp == Apps::Clock || currentApp == Apps::Music || currentApp == Apps::Calculator || currentApp == Apps::QuickSettings) {
|
||||
switch (gesture) {
|
||||
case TouchEvents::SwipeUp:
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::Launcher, DisplayApp::FullRefreshDirections::Up);
|
||||
}
|
||||
else if (currentApp == Apps::QuickSettings) {
|
||||
} else if (currentApp == Apps::QuickSettings) {
|
||||
LoadNewScreen(Apps::Settings, DisplayApp::FullRefreshDirections::Up);
|
||||
}
|
||||
|
||||
break;
|
||||
case TouchEvents::SwipeDown:
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::Notifications, DisplayApp::FullRefreshDirections::Down);
|
||||
}
|
||||
if (currentApp == Apps::QuickSettings) {
|
||||
} else if (currentApp == Apps::QuickSettings) {
|
||||
LoadNewScreen(Apps::FlashLight, DisplayApp::FullRefreshDirections::Down);
|
||||
}
|
||||
break;
|
||||
@ -341,8 +314,8 @@ void DisplayApp::Refresh() {
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::QuickSettings, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
} else if (currentApp == Apps::QuickSettings) {
|
||||
LoadNewScreen(Apps::Weather, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
} else if (currentApp == Apps::Weather) {
|
||||
LoadNewScreen(Apps::Calculator, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
} else if (currentApp == Apps::Calculator) {
|
||||
LoadNewScreen(Apps::Music, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
} else {
|
||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::RightAnim);
|
||||
@ -352,8 +325,8 @@ void DisplayApp::Refresh() {
|
||||
if (currentApp == Apps::Clock) {
|
||||
LoadNewScreen(Apps::Music, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
} else if (currentApp == Apps::Music) {
|
||||
LoadNewScreen(Apps::Weather, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
} else if (currentApp == Apps::Weather) {
|
||||
LoadNewScreen(Apps::Calculator, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
} else if (currentApp == Apps::Calculator) {
|
||||
LoadNewScreen(Apps::QuickSettings, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
} else {
|
||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::LeftAnim);
|
||||
@ -419,7 +392,7 @@ void DisplayApp::Refresh() {
|
||||
time_var = dateTimeController.Minutes();
|
||||
if (time_var == 30){
|
||||
NRF_LOG_INFO("Short: %d", time_var);
|
||||
motorController.RunForDuration(25);
|
||||
motorController.StartPattern();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -471,32 +444,26 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||
SetFullRefresh(direction);
|
||||
|
||||
switch (app) {
|
||||
case Apps::Launcher: {
|
||||
std::array<Screens::Tile::Applications, UserAppTypes::Count> apps;
|
||||
int i = 0;
|
||||
for (const auto& userApp : userApps) {
|
||||
apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
|
||||
}
|
||||
currentScreen = std::make_unique<Screens::ApplicationList>(this,
|
||||
settingsController,
|
||||
case Apps::Launcher:
|
||||
currentScreen =
|
||||
std::make_unique<Screens::ApplicationList>(this, settingsController, batteryController, bleController, dateTimeController, filesystem);
|
||||
break;
|
||||
case Apps::Motion:
|
||||
// currentScreen = std::make_unique<Screens::Motion>(motionController);
|
||||
// break;
|
||||
case Apps::None:
|
||||
case Apps::Clock:
|
||||
currentScreen = std::make_unique<Screens::Clock>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
dateTimeController,
|
||||
filesystem,
|
||||
std::move(apps));
|
||||
} break;
|
||||
case Apps::Clock: {
|
||||
const auto* watchFace =
|
||||
std::find_if(userWatchFaces.begin(), userWatchFaces.end(), [this](const WatchFaceDescription& watchfaceDescription) {
|
||||
return watchfaceDescription.watchFace == settingsController.GetWatchFace();
|
||||
});
|
||||
if (watchFace != userWatchFaces.end())
|
||||
currentScreen.reset(watchFace->create(controllers));
|
||||
else {
|
||||
currentScreen.reset(userWatchFaces[0].create(controllers));
|
||||
}
|
||||
settingsController.SetAppMenu(0);
|
||||
} break;
|
||||
notificationManager,
|
||||
settingsController,
|
||||
heartRateController,
|
||||
motionController,
|
||||
systemTask->nimble().weather(),
|
||||
filesystem);
|
||||
break;
|
||||
|
||||
case Apps::Error:
|
||||
currentScreen = std::make_unique<Screens::Error>(bootError);
|
||||
break;
|
||||
@ -528,6 +495,14 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||
*systemTask,
|
||||
Screens::Notifications::Modes::Preview);
|
||||
break;
|
||||
case Apps::Timer:
|
||||
currentScreen = std::make_unique<Screens::Timer>(timer);
|
||||
break;
|
||||
case Apps::Alarm:
|
||||
currentScreen = std::make_unique<Screens::Alarm>(alarmController, settingsController.GetClockType(), *systemTask, motorController);
|
||||
break;
|
||||
|
||||
// Settings
|
||||
case Apps::QuickSettings:
|
||||
currentScreen = std::make_unique<Screens::QuickSettings>(this,
|
||||
batteryController,
|
||||
@ -540,21 +515,12 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||
case Apps::Settings:
|
||||
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
|
||||
break;
|
||||
case Apps::SettingWatchFace: {
|
||||
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
|
||||
int i = 0;
|
||||
for (const auto& userWatchFace : userWatchFaces) {
|
||||
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;
|
||||
case Apps::SettingWatchFace:
|
||||
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, settingsController, filesystem);
|
||||
break;
|
||||
case Apps::SettingTimeFormat:
|
||||
currentScreen = std::make_unique<Screens::SettingTimeFormat>(settingsController);
|
||||
break;
|
||||
case Apps::SettingWeatherFormat:
|
||||
currentScreen = std::make_unique<Screens::SettingWeatherFormat>(settingsController);
|
||||
break;
|
||||
case Apps::SettingWakeUp:
|
||||
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
|
||||
break;
|
||||
@ -592,17 +558,42 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||
case Apps::FlashLight:
|
||||
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
|
||||
break;
|
||||
default: {
|
||||
const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) {
|
||||
return appDescription.app == app;
|
||||
});
|
||||
if (d != userApps.end()) {
|
||||
currentScreen.reset(d->create(controllers));
|
||||
} else {
|
||||
currentScreen.reset(userWatchFaces[0].create(controllers));
|
||||
}
|
||||
case Apps::StopWatch:
|
||||
currentScreen = std::make_unique<Screens::StopWatch>(*systemTask);
|
||||
break;
|
||||
case Apps::Twos:
|
||||
currentScreen = std::make_unique<Screens::Twos>();
|
||||
break;
|
||||
case Apps::Paint:
|
||||
currentScreen = std::make_unique<Screens::InfiniPaint>(lvgl, motorController);
|
||||
break;
|
||||
case Apps::Paddle:
|
||||
currentScreen = std::make_unique<Screens::Paddle>(lvgl);
|
||||
break;
|
||||
case Apps::Music:
|
||||
currentScreen = std::make_unique<Screens::Music>(systemTask->nimble().music());
|
||||
break;
|
||||
case Apps::Navigation:
|
||||
currentScreen = std::make_unique<Screens::Navigation>(systemTask->nimble().navigation());
|
||||
break;
|
||||
case Apps::HeartRate:
|
||||
currentScreen = std::make_unique<Screens::HeartRate>(heartRateController, *systemTask);
|
||||
break;
|
||||
case Apps::Metronome:
|
||||
currentScreen = std::make_unique<Screens::Metronome>(motorController, *systemTask);
|
||||
break;
|
||||
/* Weather debug app
|
||||
case Apps::Weather:
|
||||
currentScreen = std::make_unique<Screens::Weather>(this, systemTask->nimble().weather());
|
||||
break;
|
||||
*/
|
||||
case Apps::Steps:
|
||||
currentScreen = std::make_unique<Screens::Steps>(motionController, settingsController);
|
||||
break;
|
||||
// My Apps
|
||||
case Apps::Calculator:
|
||||
currentScreen = std::make_unique<Screens::Calculator>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentApp = app;
|
||||
}
|
||||
@ -611,17 +602,11 @@ 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
|
||||
// deadlock between SystemTask and DisplayApp when their respective message queues are getting full
|
||||
// when a lot of notifications are received on a very short time span.
|
||||
if (msg == Messages::NewNotification) {
|
||||
timeout = static_cast<TickType_t>(0);
|
||||
}
|
||||
|
||||
xQueueSend(msgQueue, &msg, timeout);
|
||||
} else {
|
||||
xQueueSend(msgQueue, &msg, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -658,19 +643,6 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
|
||||
|
||||
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
|
||||
this->systemTask = systemTask;
|
||||
this->controllers.systemTask = systemTask;
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) {
|
||||
this->controllers.weatherController = weatherService;
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::MusicService* musicService) {
|
||||
this->controllers.musicService = musicService;
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::NavigationService* NavigationService) {
|
||||
this->controllers.navigationService = NavigationService;
|
||||
}
|
||||
|
||||
void DisplayApp::ApplyBrightness() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <task.h>
|
||||
#include <memory>
|
||||
#include <systemtask/Messages.h>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/LittleVgl.h"
|
||||
#include "displayapp/TouchEvents.h"
|
||||
#include "components/brightness/BrightnessController.h"
|
||||
@ -20,7 +20,6 @@
|
||||
#include "BootErrors.h"
|
||||
|
||||
#include "utility/StaticStack.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
||||
@ -39,7 +38,6 @@ namespace Pinetime {
|
||||
class HeartRateController;
|
||||
class MotionController;
|
||||
class TouchHandler;
|
||||
class SimpleWeatherService;
|
||||
}
|
||||
|
||||
namespace System {
|
||||
@ -75,9 +73,6 @@ namespace Pinetime {
|
||||
void SetFullRefresh(FullRefreshDirections direction);
|
||||
|
||||
void Register(Pinetime::System::SystemTask* systemTask);
|
||||
void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
|
||||
void Register(Pinetime::Controllers::MusicService* musicService);
|
||||
void Register(Pinetime::Controllers::NavigationService* NavigationService);
|
||||
|
||||
private:
|
||||
Pinetime::Drivers::St7789& lcd;
|
||||
@ -101,7 +96,6 @@ namespace Pinetime {
|
||||
Pinetime::Components::LittleVgl lvgl;
|
||||
Pinetime::Controllers::Timer timer;
|
||||
|
||||
AppControllers controllers;
|
||||
TaskHandle_t taskHandle;
|
||||
|
||||
States state = States::Running;
|
||||
|
@ -38,6 +38,9 @@ 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();
|
||||
@ -91,6 +94,7 @@ 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);
|
||||
}
|
||||
}
|
||||
@ -99,25 +103,21 @@ 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 = pdFALSE;
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
xHigherPriorityTaskWoken = pdFALSE;
|
||||
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken) {
|
||||
/* Actual macro used here is port specific. */
|
||||
// TODO : should I do something here?
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* /*weatherService*/) {
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::MusicService* /*musicService*/) {
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::NavigationService* /*NavigationService*/) {
|
||||
}
|
||||
|
@ -5,12 +5,13 @@
|
||||
#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>
|
||||
#include "BootErrors.h"
|
||||
#include "displayapp/TouchEvents.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/Messages.h"
|
||||
|
||||
namespace Pinetime {
|
||||
@ -33,9 +34,6 @@ namespace Pinetime {
|
||||
class AlarmController;
|
||||
class BrightnessController;
|
||||
class FS;
|
||||
class SimpleWeatherService;
|
||||
class MusicService;
|
||||
class NavigationService;
|
||||
}
|
||||
|
||||
namespace System {
|
||||
@ -68,9 +66,6 @@ namespace Pinetime {
|
||||
|
||||
void PushMessage(Pinetime::Applications::Display::Messages msg);
|
||||
void Register(Pinetime::System::SystemTask* systemTask);
|
||||
void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
|
||||
void Register(Pinetime::Controllers::MusicService* musicService);
|
||||
void Register(Pinetime::Controllers::NavigationService* NavigationService);
|
||||
|
||||
private:
|
||||
TaskHandle_t taskHandle;
|
||||
|
@ -8,7 +8,6 @@ namespace Colors {
|
||||
static constexpr lv_color_t green = LV_COLOR_MAKE(0x0, 0xb0, 0x0);
|
||||
static constexpr lv_color_t blue = LV_COLOR_MAKE(0x0, 0x50, 0xff);
|
||||
static constexpr lv_color_t lightGray = LV_COLOR_MAKE(0xb0, 0xb0, 0xb0);
|
||||
static constexpr lv_color_t gray = LV_COLOR_MAKE(0x50, 0x50, 0x50);
|
||||
|
||||
static constexpr lv_color_t bg = LV_COLOR_MAKE(0x5d, 0x69, 0x7e);
|
||||
static constexpr lv_color_t bgAlt = LV_COLOR_MAKE(0x38, 0x38, 0x38);
|
||||
|
@ -152,6 +152,10 @@ 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)) {
|
||||
@ -215,6 +219,7 @@ 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;
|
||||
|
@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "Controllers.h"
|
||||
|
||||
#include "displayapp/screens/Alarm.h"
|
||||
#include "displayapp/screens/Dice.h"
|
||||
#include "displayapp/screens/Timer.h"
|
||||
#include "displayapp/screens/Twos.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
#include "displayapp/screens/Calculator.h"
|
||||
#include "displayapp/screens/ApplicationList.h"
|
||||
#include "displayapp/screens/WatchFaceDigital.h"
|
||||
#include "displayapp/screens/WatchFaceAnalog.h"
|
||||
#include "displayapp/screens/WatchFaceBinary.h"
|
||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||
#include "displayapp/screens/WatchFaceInfineat.h"
|
||||
#include "displayapp/screens/WatchFacePineTimeStyle.h"
|
||||
#include "displayapp/screens/WatchFaceTerminal.h"
|
||||
#include "displayapp/screens/WatchFaceHorizon.h"
|
||||
#include "displayapp/screens/WatchFaceAccurateWords.h"
|
||||
#include "displayapp/screens/WatchFaceGravel.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Screen;
|
||||
}
|
||||
|
||||
struct AppDescription {
|
||||
Apps app;
|
||||
const char* icon;
|
||||
Screens::Screen* (*create)(AppControllers& controllers);
|
||||
};
|
||||
|
||||
struct WatchFaceDescription {
|
||||
WatchFace watchFace;
|
||||
const char* name;
|
||||
Screens::Screen* (*create)(AppControllers& controllers);
|
||||
bool (*isAvailable)(Controllers::FS& fileSystem);
|
||||
};
|
||||
|
||||
template <Apps t>
|
||||
consteval AppDescription CreateAppDescription() {
|
||||
return {AppTraits<t>::app, AppTraits<t>::icon, &AppTraits<t>::Create};
|
||||
}
|
||||
|
||||
template <WatchFace t>
|
||||
consteval WatchFaceDescription CreateWatchFaceDescription() {
|
||||
return {WatchFaceTraits<t>::watchFace, WatchFaceTraits<t>::name, &WatchFaceTraits<t>::Create, &WatchFaceTraits<t>::IsAvailable};
|
||||
}
|
||||
|
||||
template <template <Apps...> typename T, Apps... ts>
|
||||
consteval std::array<AppDescription, sizeof...(ts)> CreateAppDescriptions(T<ts...>) {
|
||||
return {CreateAppDescription<ts>()...};
|
||||
}
|
||||
|
||||
template <template <WatchFace...> typename T, WatchFace... ts>
|
||||
consteval std::array<WatchFaceDescription, sizeof...(ts)> CreateWatchFaceDescriptions(T<ts...>) {
|
||||
return {CreateWatchFaceDescription<ts>()...};
|
||||
}
|
||||
|
||||
constexpr auto userApps = CreateAppDescriptions(UserAppTypes {});
|
||||
constexpr auto userWatchFaces = CreateWatchFaceDescriptions(UserWatchFaceTypes {});
|
||||
}
|
||||
}
|
14
src/displayapp/WatchFaces.h
Normal file
14
src/displayapp/WatchFaces.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
enum class WatchFace : uint8_t {
|
||||
Digital = 0,
|
||||
Analog = 1,
|
||||
PineTimeStyle = 2,
|
||||
Terminal = 3,
|
||||
Infineat = 4,
|
||||
CasioStyleG7710 = 5,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
enum class Apps : uint8_t {
|
||||
None,
|
||||
Launcher,
|
||||
Clock,
|
||||
SysInfo,
|
||||
FirmwareUpdate,
|
||||
FirmwareValidation,
|
||||
NotificationsPreview,
|
||||
Notifications,
|
||||
Timer,
|
||||
Alarm,
|
||||
FlashLight,
|
||||
BatteryInfo,
|
||||
Music,
|
||||
Paint,
|
||||
Paddle,
|
||||
Twos,
|
||||
Bird,
|
||||
HeartRate,
|
||||
Navigation,
|
||||
Calendar,
|
||||
StopWatch,
|
||||
Metronome,
|
||||
Motion,
|
||||
Steps,
|
||||
Dice,
|
||||
Weather,
|
||||
PassKey,
|
||||
QuickSettings,
|
||||
Settings,
|
||||
SettingWatchFace,
|
||||
SettingTimeFormat,
|
||||
SettingWeatherFormat,
|
||||
SettingDisplay,
|
||||
SettingWakeUp,
|
||||
SettingSteps,
|
||||
SettingSetDateTime,
|
||||
SettingChimes,
|
||||
SettingShakeThreshold,
|
||||
SettingBluetooth,
|
||||
Error,
|
||||
|
||||
// Added
|
||||
Calculator
|
||||
};
|
||||
|
||||
enum class WatchFace : uint8_t {
|
||||
Digital,
|
||||
Analog,
|
||||
Binary,
|
||||
PineTimeStyle,
|
||||
Terminal,
|
||||
Infineat,
|
||||
CasioStyleG7710,
|
||||
Horizon,
|
||||
AccurateWords,
|
||||
Gravel
|
||||
};
|
||||
|
||||
template <Apps>
|
||||
struct AppTraits {};
|
||||
|
||||
template <WatchFace>
|
||||
struct WatchFaceTraits {};
|
||||
|
||||
template <Apps... As>
|
||||
struct TypeList {
|
||||
static constexpr size_t Count = sizeof...(As);
|
||||
};
|
||||
|
||||
using UserAppTypes = TypeList<@USERAPP_TYPES@>;
|
||||
|
||||
template <WatchFace... Ws>
|
||||
struct WatchFaceTypeList {
|
||||
static constexpr size_t Count = sizeof...(Ws);
|
||||
};
|
||||
|
||||
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
|
||||
|
||||
static_assert(UserWatchFaceTypes::Count >= 1);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
if(DEFINED ENABLE_USERAPPS)
|
||||
set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
|
||||
else ()
|
||||
set(DEFAULT_USER_APP_TYPES "Apps::StopWatch")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::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::Bird")
|
||||
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::Calculator")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calendar")
|
||||
#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::Binary")
|
||||
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(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Horizon")
|
||||
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::AccurateWords")
|
||||
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Gravel")
|
||||
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}/")
|
||||
|
||||
# Generate the list of user apps to be compiled into the firmware
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Apps.h.in ${CMAKE_CURRENT_BINARY_DIR}/Apps.h)
|
@ -16,7 +16,7 @@
|
||||
- Define the new symbols in `src/displayapp/screens/Symbols.h`:
|
||||
|
||||
```
|
||||
static constexpr const char* newSymbol = "\xEF\x99\x81";
|
||||
static constexpr const char* newSymbol = "\xEF\x86\x85";
|
||||
```
|
||||
|
||||
### the config file format:
|
||||
|
@ -7,7 +7,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, 0xf0f3, 0xf522, 0xf743, 0xf1ec, 0xf55a, 0xf4ba, 0xf073"
|
||||
"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, 0xf1ec, 0xf55a"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
@ -18,7 +18,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Regular.ttf",
|
||||
"range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0"
|
||||
"range": "0x25, 0x2b, 0x2d, 0x30-0x3a"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
@ -28,7 +28,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Light.ttf",
|
||||
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0"
|
||||
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
@ -68,7 +68,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
||||
"range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf73b, 0xf0e7, 0xf2dc"
|
||||
"range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
@ -19,10 +19,6 @@
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/InfiniTimeTheme.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "components/alarm/AlarmController.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
using Pinetime::Controllers::AlarmController;
|
||||
|
@ -17,19 +17,18 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "displayapp/LittleVgl.h"
|
||||
#include "components/alarm/AlarmController.h"
|
||||
#include "displayapp/widgets/Counter.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Alarm : public Screen {
|
||||
public:
|
||||
explicit Alarm(Controllers::AlarmController& alarmController,
|
||||
Alarm(Controllers::AlarmController& alarmController,
|
||||
Controllers::Settings::ClockType clockType,
|
||||
System::SystemTask& systemTask,
|
||||
Controllers::MotorController& motorController);
|
||||
@ -64,19 +63,6 @@ namespace Pinetime {
|
||||
Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76);
|
||||
Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Alarm> {
|
||||
static constexpr Apps app = Apps::Alarm;
|
||||
static constexpr const char* icon = Screens::Symbols::bell;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Alarm(controllers.alarmController,
|
||||
controllers.settingsController.GetClockType(),
|
||||
*controllers.systemTask,
|
||||
controllers.motorController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
#include "displayapp/screens/ApplicationList.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include "components/settings/Settings.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
@ -17,20 +16,18 @@ auto ApplicationList::CreateScreenList() const {
|
||||
return screens;
|
||||
}
|
||||
|
||||
ApplicationList::ApplicationList(DisplayApp* app,
|
||||
ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app,
|
||||
Pinetime::Controllers::Settings& settingsController,
|
||||
const Pinetime::Controllers::Battery& batteryController,
|
||||
const Pinetime::Controllers::Ble& bleController,
|
||||
Controllers::DateTime& dateTimeController,
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
std::array<Tile::Applications, UserAppTypes::Count>&& apps)
|
||||
Pinetime::Controllers::FS& filesystem)
|
||||
: app {app},
|
||||
settingsController {settingsController},
|
||||
batteryController {batteryController},
|
||||
bleController {bleController},
|
||||
dateTimeController {dateTimeController},
|
||||
filesystem{filesystem},
|
||||
apps {std::move(apps)},
|
||||
screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
||||
}
|
||||
|
||||
@ -43,14 +40,9 @@ bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const {
|
||||
std::array<Tile::Applications, appsPerScreen> pageApps;
|
||||
|
||||
std::array<Tile::Applications, appsPerScreen> apps;
|
||||
for (int i = 0; i < appsPerScreen; i++) {
|
||||
if (i + (screenNum * appsPerScreen) >= apps.size()) {
|
||||
pageApps[i] = {"", Pinetime::Applications::Apps::None, false};
|
||||
} else {
|
||||
pageApps[i] = apps[i + (screenNum * appsPerScreen)];
|
||||
}
|
||||
apps[i] = applications[screenNum * appsPerScreen + i];
|
||||
}
|
||||
|
||||
return std::make_unique<Screens::Tile>(screenNum,
|
||||
@ -60,5 +52,5 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) co
|
||||
batteryController,
|
||||
bleController,
|
||||
dateTimeController,
|
||||
pageApps);
|
||||
apps);
|
||||
}
|
||||
|
@ -2,12 +2,15 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "Screen.h"
|
||||
#include "ScreenList.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
#include "Tile.h"
|
||||
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/screens/ScreenList.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "components/battery/BatteryController.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
#include "displayapp/screens/Navigation.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
@ -19,8 +22,7 @@ namespace Pinetime {
|
||||
const Pinetime::Controllers::Battery& batteryController,
|
||||
const Pinetime::Controllers::Ble& bleController,
|
||||
Controllers::DateTime& dateTimeController,
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
std::array<Tile::Applications, UserAppTypes::Count>&& apps);
|
||||
Pinetime::Controllers::FS& filesystem);
|
||||
~ApplicationList() override;
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
|
||||
@ -34,12 +36,30 @@ namespace Pinetime {
|
||||
const Pinetime::Controllers::Ble& bleController;
|
||||
Controllers::DateTime& dateTimeController;
|
||||
Pinetime::Controllers::FS& filesystem;
|
||||
std::array<Tile::Applications, UserAppTypes::Count> apps;
|
||||
|
||||
static constexpr int appsPerScreen = 6;
|
||||
|
||||
static constexpr int nScreens = UserAppTypes::Count > 0 ? (UserAppTypes::Count - 1) / appsPerScreen + 1 : 1;
|
||||
// Increment this when more space is needed
|
||||
static constexpr int nScreens = 2;
|
||||
|
||||
std::array<Tile::Applications, appsPerScreen * nScreens> applications {{
|
||||
{Symbols::stopWatch, Apps::StopWatch, true},
|
||||
{Symbols::clock, Apps::Alarm, true},
|
||||
{Symbols::hourGlass, Apps::Timer, true},
|
||||
{Symbols::shoe, Apps::Steps, true},
|
||||
{Symbols::heartBeat, Apps::HeartRate, true},
|
||||
{Symbols::music, Apps::Music, true},
|
||||
|
||||
{Symbols::paintbrush, Apps::Paint, true},
|
||||
{Symbols::paddle, Apps::Paddle, true},
|
||||
{"2", Apps::Twos, true},
|
||||
{Symbols::drum, Apps::Metronome, true},
|
||||
{Symbols::map, Apps::Navigation, Applications::Screens::Navigation::IsAvailable(filesystem)},
|
||||
{Symbols::calculator, Apps::Calculator, true},
|
||||
//{Symbols::none, Apps::None, false},
|
||||
|
||||
// {"M", Apps::Motion},
|
||||
}};
|
||||
ScreenList<nScreens> screens;
|
||||
};
|
||||
}
|
||||
|
@ -1,143 +0,0 @@
|
||||
#include "displayapp/screens/Bird.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/LittleVgl.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
|
||||
#include <cstdlib> // for rand()
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
Bird::Bird() {
|
||||
|
||||
lv_obj_t* background = lv_obj_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_size(background, LV_HOR_RES, LV_VER_RES);
|
||||
lv_obj_set_style_local_radius(background, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0);
|
||||
lv_obj_set_style_local_bg_color(background, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x2874a6));
|
||||
|
||||
cactus_top = lv_obj_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_size(cactus_top, CACTUS_WIDTH, CACTUS_HEIGHT);
|
||||
lv_obj_set_style_local_bg_color(cactus_top, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
||||
lv_obj_set_style_local_border_color(cactus_top, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
|
||||
lv_obj_set_style_local_border_width(cactus_top, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 1);
|
||||
|
||||
cactus_bottom = lv_obj_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_size(cactus_bottom, CACTUS_WIDTH, CACTUS_HEIGHT);
|
||||
lv_obj_set_style_local_bg_color(cactus_bottom, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
||||
lv_obj_set_style_local_border_color(cactus_bottom, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
|
||||
lv_obj_set_style_local_border_width(cactus_bottom, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 1);
|
||||
|
||||
MovePipe();
|
||||
|
||||
points = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(points, " ");
|
||||
lv_obj_align(points, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -10, 5);
|
||||
|
||||
info = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(info, "touch to start");
|
||||
lv_obj_align(info, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 40);
|
||||
|
||||
bird = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(bird, Symbols::dove);
|
||||
lv_obj_set_style_local_text_color(bird, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_YELLOW);
|
||||
lv_obj_set_pos(bird, BIRD_X, pos);
|
||||
|
||||
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
||||
}
|
||||
|
||||
Bird::~Bird() {
|
||||
lv_task_del(taskRefresh);
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
void Bird::MovePipe() {
|
||||
lv_obj_set_pos(cactus_top, cactus_x, -cactus_y_offset);
|
||||
lv_obj_set_pos(cactus_bottom, cactus_x, CACTUS_HEIGHT + CACTUS_GAP - cactus_y_offset);
|
||||
return;
|
||||
}
|
||||
|
||||
void Bird::Refresh() {
|
||||
if (is_stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
pos += accel / 6;
|
||||
|
||||
if (is_ascending) {
|
||||
if (accel >= -18) {
|
||||
accel -= 3;
|
||||
}
|
||||
} else {
|
||||
if (accel <= 42) {
|
||||
accel++;
|
||||
}
|
||||
}
|
||||
|
||||
// checks if it has hit the floor or ceiling
|
||||
if (pos <= 1 || pos >= LV_VER_RES - BIRD_SIZE) {
|
||||
GameOver();
|
||||
return;
|
||||
}
|
||||
|
||||
// checks if it has rammed into cacti
|
||||
// BIRD_X-CACTUS_WIDTH to BIRD_X+BIRD_SIZE
|
||||
if (90 < cactus_x && cactus_x < 130) {
|
||||
if (pos < CACTUS_HEIGHT - cactus_y_offset || pos > CACTUS_HEIGHT + CACTUS_GAP - BIRD_SIZE - cactus_y_offset) {
|
||||
GameOver();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lv_obj_set_pos(bird, BIRD_X, pos);
|
||||
|
||||
lv_label_set_text_fmt(points, "%04d", score / 10);
|
||||
is_ascending = false;
|
||||
|
||||
score++;
|
||||
if (cactus_x == 0) {
|
||||
while (true) {
|
||||
uint8_t new_offset = rand() % 5 * 40;
|
||||
if (new_offset != cactus_y_offset) {
|
||||
cactus_y_offset = new_offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cactus_x = 240;
|
||||
}
|
||||
cactus_x--;
|
||||
if (cactus_x % 4 == 0) {
|
||||
MovePipe();
|
||||
}
|
||||
}
|
||||
|
||||
void Bird::GameOver() {
|
||||
is_stopped = true;
|
||||
lv_label_set_text_static(info, "Game Over");
|
||||
lv_obj_align(info, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 40);
|
||||
lv_obj_set_style_local_text_color(bird, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool Bird::OnTouchEvent(Pinetime::Applications::TouchEvents /*event*/) {
|
||||
if (is_stopped) {
|
||||
if (pos != 120) {
|
||||
pos = 120;
|
||||
lv_label_set_text_static(info, "Touch to Start");
|
||||
lv_obj_align(info, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 40);
|
||||
lv_obj_set_style_local_text_color(bird, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_YELLOW);
|
||||
return true;
|
||||
}
|
||||
// reset
|
||||
cactus_x = 240;
|
||||
accel = 0;
|
||||
score = 0;
|
||||
is_stopped = false;
|
||||
lv_label_set_text_static(info, "");
|
||||
}
|
||||
is_ascending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bird::OnTouchEvent(uint16_t /*x*/, uint16_t /*y*/) {
|
||||
is_ascending = true;
|
||||
return true;
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <cstdint>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "Symbols.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
|
||||
#define BIRD_X 110
|
||||
#define BIRD_SIZE 20
|
||||
#define CACTUS_HEIGHT 160
|
||||
#define CACTUS_WIDTH 30
|
||||
#define CACTUS_GAP 80
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Components {
|
||||
class LittleVgl;
|
||||
}
|
||||
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
|
||||
class Bird : public Screen {
|
||||
public:
|
||||
Bird();
|
||||
~Bird() override;
|
||||
|
||||
void Refresh() override;
|
||||
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
bool OnTouchEvent(uint16_t x, uint16_t y) override;
|
||||
|
||||
private:
|
||||
void GameOver();
|
||||
void MovePipe();
|
||||
|
||||
bool is_stopped = true;
|
||||
bool is_ascending = false;
|
||||
|
||||
uint8_t cactus_x = 240;
|
||||
uint8_t cactus_y_offset = 40;
|
||||
|
||||
int8_t accel = 0;
|
||||
uint8_t pos = 120;
|
||||
uint16_t score = 0;
|
||||
|
||||
lv_obj_t *info, *points, *bird, *cactus_top, *cactus_bottom;
|
||||
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Bird> {
|
||||
static constexpr Apps app = Apps::Bird;
|
||||
static constexpr const char* icon = Screens::Symbols::dove;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& /*controllers*/) {
|
||||
return new Screens::Bird();
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
#include "Screen.h"
|
||||
|
||||
namespace {
|
||||
int64_t constexpr powi(int64_t base, uint8_t exponent) {
|
||||
@ -70,14 +67,5 @@ namespace Pinetime {
|
||||
Error error = Error::None;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Calculator> {
|
||||
static constexpr Apps app = Apps::Calculator;
|
||||
static constexpr const char* icon = Screens::Symbols::calculator;
|
||||
static Screens::Screen* Create(AppControllers& ) {
|
||||
return new Screens::Calculator();
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
/* Copyright (C) 2024 thnikk, Boteium, JustScott
|
||||
|
||||
This file is part of InfiniTime.
|
||||
|
||||
InfiniTime is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
InfiniTime is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "displayapp/screens/Calendar.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "displayapp/InfiniTimeTheme.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
Calendar::Calendar(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} {
|
||||
|
||||
// Create calendar object
|
||||
calendar = lv_calendar_create(lv_scr_act(), NULL);
|
||||
// Set size
|
||||
lv_obj_set_size(calendar, LV_HOR_RES, LV_VER_RES);
|
||||
// Set alignment
|
||||
lv_obj_align(calendar, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, -5);
|
||||
// Disable clicks
|
||||
lv_obj_set_click(calendar, false);
|
||||
|
||||
// Set style of today's date
|
||||
lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_FOCUSED, Colors::deepOrange);
|
||||
|
||||
// Set style of inactive month's days
|
||||
lv_obj_set_style_local_text_color(calendar, LV_CALENDAR_PART_DATE, LV_STATE_DISABLED, Colors::gray);
|
||||
|
||||
// Get today's date
|
||||
current.year = static_cast<int>(dateTimeController.Year());
|
||||
current.month = static_cast<int>(dateTimeController.Month());
|
||||
current.day = static_cast<int>(dateTimeController.Day());
|
||||
|
||||
// Set today's date
|
||||
lv_calendar_set_today_date(calendar, ¤t);
|
||||
lv_calendar_set_showed_date(calendar, ¤t);
|
||||
}
|
||||
|
||||
bool Calendar::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
switch (event) {
|
||||
case TouchEvents::SwipeLeft: {
|
||||
if (current.month == 12) {
|
||||
current.month = 1;
|
||||
current.year++;
|
||||
} else {
|
||||
current.month++;
|
||||
}
|
||||
|
||||
lv_calendar_set_showed_date(calendar, ¤t);
|
||||
return true;
|
||||
}
|
||||
case TouchEvents::SwipeRight: {
|
||||
if (current.month == 1) {
|
||||
current.month = 12;
|
||||
current.year--;
|
||||
} else {
|
||||
current.month--;
|
||||
}
|
||||
|
||||
lv_calendar_set_showed_date(calendar, ¤t);
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
case TouchEvents::SwipeUp: {
|
||||
current.year++;
|
||||
lv_calendar_set_showed_date(calendar, ¤t);
|
||||
return true;
|
||||
}
|
||||
case TouchEvents::SwipeDown: {
|
||||
current.year--;
|
||||
lv_calendar_set_showed_date(calendar, ¤t);
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Calendar::~Calendar() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/* Copyright (C) 2024 thnikk, Boteium, JustScott
|
||||
|
||||
This file is part of InfiniTime.
|
||||
|
||||
InfiniTime is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
InfiniTime is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class Settings;
|
||||
}
|
||||
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Calendar : public Screen {
|
||||
public:
|
||||
Calendar(Controllers::DateTime& dateTimeController);
|
||||
~Calendar() override;
|
||||
|
||||
private:
|
||||
bool OnTouchEvent(TouchEvents event);
|
||||
Controllers::DateTime& dateTimeController;
|
||||
lv_obj_t* calendar;
|
||||
lv_calendar_date_t current;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Calendar> {
|
||||
static constexpr Apps app = Apps::Calendar;
|
||||
static constexpr const char* icon = Screens::Symbols::calendar;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Calendar(controllers.dateTimeController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
149
src/displayapp/screens/Clock.cpp
Normal file
149
src/displayapp/screens/Clock.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
#include "displayapp/screens/Clock.h"
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
#include "components/battery/BatteryController.h"
|
||||
#include "components/motion/MotionController.h"
|
||||
#include "components/ble/BleController.h"
|
||||
#include "components/ble/NotificationManager.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/WatchFaceDigital.h"
|
||||
#include "displayapp/screens/WatchFaceTerminal.h"
|
||||
#include "displayapp/screens/WatchFaceInfineat.h"
|
||||
#include "displayapp/screens/WatchFaceAnalog.h"
|
||||
#include "displayapp/screens/WatchFacePineTimeStyle.h"
|
||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||
#include "displayapp/screens/WatchFaceWordClock.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
using namespace Pinetime::Applications;
|
||||
|
||||
Clock::Clock(Controllers::DateTime& dateTimeController,
|
||||
const Controllers::Battery& batteryController,
|
||||
const Controllers::Ble& bleController,
|
||||
Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Settings& settingsController,
|
||||
Controllers::HeartRateController& heartRateController,
|
||||
Controllers::MotionController& motionController,
|
||||
Controllers::WeatherService& weatherService,
|
||||
Controllers::FS& filesystem)
|
||||
: dateTimeController {dateTimeController},
|
||||
batteryController {batteryController},
|
||||
bleController {bleController},
|
||||
notificationManager {notificationManager},
|
||||
settingsController {settingsController},
|
||||
heartRateController {heartRateController},
|
||||
motionController {motionController},
|
||||
weatherService {weatherService},
|
||||
filesystem {filesystem},
|
||||
screen {[this, &settingsController]() {
|
||||
switch (settingsController.GetWatchFace()) {
|
||||
case WatchFace::Digital:
|
||||
return WatchFaceDigitalScreen();
|
||||
break;
|
||||
case WatchFace::Analog:
|
||||
return WatchFaceAnalogScreen();
|
||||
break;
|
||||
case WatchFace::PineTimeStyle:
|
||||
return WatchFacePineTimeStyleScreen();
|
||||
break;
|
||||
case WatchFace::Terminal:
|
||||
return WatchFaceTerminalScreen();
|
||||
break;
|
||||
case WatchFace::Infineat:
|
||||
return WatchFaceInfineatScreen();
|
||||
break;
|
||||
case WatchFace::CasioStyleG7710:
|
||||
return WatchFaceCasioStyleG7710();
|
||||
break;
|
||||
case WatchFace::WatchFaceWordClock:
|
||||
return WatchFaceWordClock();
|
||||
break;
|
||||
}
|
||||
return WatchFaceDigitalScreen();
|
||||
}()} {
|
||||
settingsController.SetAppMenu(0);
|
||||
}
|
||||
|
||||
Clock::~Clock() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
bool Clock::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
return screen->OnTouchEvent(event);
|
||||
}
|
||||
|
||||
bool Clock::OnButtonPushed() {
|
||||
return screen->OnButtonPushed();
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFaceDigitalScreen() {
|
||||
return std::make_unique<Screens::WatchFaceDigital>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController,
|
||||
heartRateController,
|
||||
motionController);
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFaceAnalogScreen() {
|
||||
return std::make_unique<Screens::WatchFaceAnalog>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController);
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFacePineTimeStyleScreen() {
|
||||
return std::make_unique<Screens::WatchFacePineTimeStyle>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController,
|
||||
motionController,
|
||||
weatherService);
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFaceTerminalScreen() {
|
||||
return std::make_unique<Screens::WatchFaceTerminal>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController,
|
||||
heartRateController,
|
||||
motionController);
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFaceInfineatScreen() {
|
||||
return std::make_unique<Screens::WatchFaceInfineat>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController,
|
||||
motionController,
|
||||
filesystem);
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFaceCasioStyleG7710() {
|
||||
return std::make_unique<Screens::WatchFaceCasioStyleG7710>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController,
|
||||
heartRateController,
|
||||
motionController,
|
||||
filesystem);
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> Clock::WatchFaceWordClock() {
|
||||
return std::make_unique<Screens::WatchFaceWordClock>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
notificationManager,
|
||||
settingsController,
|
||||
heartRateController,
|
||||
motionController,
|
||||
filesystem);
|
||||
}
|
||||
|
61
src/displayapp/screens/Clock.h
Normal file
61
src/displayapp/screens/Clock.h
Normal file
@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <components/heartrate/HeartRateController.h>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/ble/weather/WeatherService.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class Settings;
|
||||
class Battery;
|
||||
class Ble;
|
||||
class NotificationManager;
|
||||
class MotionController;
|
||||
}
|
||||
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Clock : public Screen {
|
||||
public:
|
||||
Clock(Controllers::DateTime& dateTimeController,
|
||||
const Controllers::Battery& batteryController,
|
||||
const Controllers::Ble& bleController,
|
||||
Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Settings& settingsController,
|
||||
Controllers::HeartRateController& heartRateController,
|
||||
Controllers::MotionController& motionController,
|
||||
Controllers::WeatherService& weatherService,
|
||||
Controllers::FS& filesystem);
|
||||
~Clock() override;
|
||||
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
bool OnButtonPushed() override;
|
||||
|
||||
private:
|
||||
Controllers::DateTime& dateTimeController;
|
||||
const Controllers::Battery& batteryController;
|
||||
const Controllers::Ble& bleController;
|
||||
Controllers::NotificationManager& notificationManager;
|
||||
Controllers::Settings& settingsController;
|
||||
Controllers::HeartRateController& heartRateController;
|
||||
Controllers::MotionController& motionController;
|
||||
Controllers::WeatherService& weatherService;
|
||||
Controllers::FS& filesystem;
|
||||
|
||||
std::unique_ptr<Screen> screen;
|
||||
std::unique_ptr<Screen> WatchFaceDigitalScreen();
|
||||
std::unique_ptr<Screen> WatchFaceAnalogScreen();
|
||||
std::unique_ptr<Screen> WatchFacePineTimeStyleScreen();
|
||||
std::unique_ptr<Screen> WatchFaceTerminalScreen();
|
||||
std::unique_ptr<Screen> WatchFaceInfineatScreen();
|
||||
std::unique_ptr<Screen> WatchFaceCasioStyleG7710();
|
||||
std::unique_ptr<Screen> WatchFaceWordClock();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
#include "displayapp/screens/Dice.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "components/motion/MotionController.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
lv_obj_t* MakeLabel(lv_font_t* font,
|
||||
lv_color_t color,
|
||||
lv_label_long_mode_t longMode,
|
||||
uint8_t width,
|
||||
lv_label_align_t labelAlignment,
|
||||
const char* text,
|
||||
lv_obj_t* reference,
|
||||
lv_align_t alignment,
|
||||
int8_t x,
|
||||
int8_t y) {
|
||||
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
|
||||
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
|
||||
lv_label_set_long_mode(label, longMode);
|
||||
if (width != 0) {
|
||||
lv_obj_set_width(label, width);
|
||||
}
|
||||
lv_label_set_align(label, labelAlignment);
|
||||
lv_label_set_text(label, text);
|
||||
lv_obj_align(label, reference, alignment, x, y);
|
||||
return label;
|
||||
}
|
||||
|
||||
void btnRollEventHandler(lv_obj_t* obj, lv_event_t event) {
|
||||
auto* screen = static_cast<Dice*>(obj->user_data);
|
||||
if (event == LV_EVENT_CLICKED) {
|
||||
screen->Roll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dice::Dice(Controllers::MotionController& motionController,
|
||||
Controllers::MotorController& motorController,
|
||||
Controllers::Settings& settingsController)
|
||||
: motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
|
||||
std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
|
||||
static_cast<uint32_t>(motionController.X()),
|
||||
static_cast<uint32_t>(motionController.Y()),
|
||||
static_cast<uint32_t>(motionController.Z())};
|
||||
gen.seed(sseq);
|
||||
|
||||
lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
|
||||
LV_COLOR_WHITE,
|
||||
LV_LABEL_LONG_EXPAND,
|
||||
0,
|
||||
LV_LABEL_ALIGN_CENTER,
|
||||
"count",
|
||||
lv_scr_act(),
|
||||
LV_ALIGN_IN_TOP_LEFT,
|
||||
0,
|
||||
0);
|
||||
|
||||
lv_obj_t* dCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
|
||||
LV_COLOR_WHITE,
|
||||
LV_LABEL_LONG_EXPAND,
|
||||
0,
|
||||
LV_LABEL_ALIGN_CENTER,
|
||||
"sides",
|
||||
nCounterLabel,
|
||||
LV_ALIGN_OUT_RIGHT_MID,
|
||||
20,
|
||||
0);
|
||||
|
||||
nCounter.Create();
|
||||
lv_obj_align(nCounter.GetObject(), nCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
|
||||
nCounter.SetValue(1);
|
||||
|
||||
dCounter.Create();
|
||||
lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
|
||||
dCounter.SetValue(6);
|
||||
|
||||
std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
|
||||
currentColorIndex = distrib(gen);
|
||||
|
||||
resultTotalLabel = MakeLabel(&jetbrains_mono_42,
|
||||
resultColors[currentColorIndex],
|
||||
LV_LABEL_LONG_BREAK,
|
||||
120,
|
||||
LV_LABEL_ALIGN_CENTER,
|
||||
"",
|
||||
lv_scr_act(),
|
||||
LV_ALIGN_IN_TOP_RIGHT,
|
||||
11,
|
||||
38);
|
||||
resultIndividualLabel = MakeLabel(&jetbrains_mono_bold_20,
|
||||
resultColors[currentColorIndex],
|
||||
LV_LABEL_LONG_BREAK,
|
||||
90,
|
||||
LV_LABEL_ALIGN_CENTER,
|
||||
"",
|
||||
resultTotalLabel,
|
||||
LV_ALIGN_OUT_BOTTOM_MID,
|
||||
0,
|
||||
10);
|
||||
|
||||
Roll();
|
||||
openingRoll = false;
|
||||
|
||||
btnRoll = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnRoll->user_data = this;
|
||||
lv_obj_set_event_cb(btnRoll, btnRollEventHandler);
|
||||
lv_obj_set_size(btnRoll, 240, 50);
|
||||
lv_obj_align(btnRoll, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
|
||||
|
||||
btnRollLabel = MakeLabel(&jetbrains_mono_bold_20,
|
||||
LV_COLOR_WHITE,
|
||||
LV_LABEL_LONG_EXPAND,
|
||||
0,
|
||||
LV_LABEL_ALIGN_CENTER,
|
||||
Symbols::dice,
|
||||
btnRoll,
|
||||
LV_ALIGN_CENTER,
|
||||
0,
|
||||
0);
|
||||
|
||||
// Spagetti code in motion controller: it only updates the shake speed when shake to wake is on...
|
||||
enableShakeForDice = !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake);
|
||||
if (enableShakeForDice) {
|
||||
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, true);
|
||||
}
|
||||
refreshTask = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
||||
}
|
||||
|
||||
Dice::~Dice() {
|
||||
// reset the shake to wake mode.
|
||||
if (enableShakeForDice) {
|
||||
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, false);
|
||||
enableShakeForDice = false;
|
||||
}
|
||||
lv_task_del(refreshTask);
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
void Dice::Refresh() {
|
||||
// we only reset the hysteresis when at rest
|
||||
if (motionController.CurrentShakeSpeed() >= settingsController.GetShakeThreshold()) {
|
||||
if (currentRollHysteresis <= 0) {
|
||||
// this timestamp is used for the screen timeout
|
||||
lv_disp_get_next(NULL)->last_activity_time = lv_tick_get();
|
||||
|
||||
Roll();
|
||||
}
|
||||
} else if (currentRollHysteresis > 0)
|
||||
--currentRollHysteresis;
|
||||
}
|
||||
|
||||
void Dice::Roll() {
|
||||
uint8_t resultIndividual;
|
||||
uint16_t resultTotal = 0;
|
||||
std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
|
||||
|
||||
lv_label_set_text(resultIndividualLabel, "");
|
||||
|
||||
if (nCounter.GetValue() == 1) {
|
||||
resultTotal = distrib(gen);
|
||||
if (dCounter.GetValue() == 2) {
|
||||
switch (resultTotal) {
|
||||
case 1:
|
||||
lv_label_set_text(resultIndividualLabel, "HEADS");
|
||||
break;
|
||||
case 2:
|
||||
lv_label_set_text(resultIndividualLabel, "TAILS");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
|
||||
resultIndividual = distrib(gen);
|
||||
resultTotal += resultIndividual;
|
||||
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
|
||||
if (i < (nCounter.GetValue() - 1)) {
|
||||
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, "+");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lv_label_set_text_fmt(resultTotalLabel, "%d", resultTotal);
|
||||
if (openingRoll == false) {
|
||||
motorController.RunForDuration(30);
|
||||
NextColor();
|
||||
currentRollHysteresis = rollHysteresis;
|
||||
}
|
||||
}
|
||||
|
||||
void Dice::NextColor() {
|
||||
currentColorIndex = (currentColorIndex + 1) % resultColors.size();
|
||||
lv_obj_set_style_local_text_color(resultTotalLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
|
||||
lv_obj_set_style_local_text_color(resultIndividualLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/widgets/Counter.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
#include <array>
|
||||
#include <random>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Dice : public Screen {
|
||||
public:
|
||||
Dice(Controllers::MotionController& motionController,
|
||||
Controllers::MotorController& motorController,
|
||||
Controllers::Settings& settingsController);
|
||||
~Dice() override;
|
||||
void Roll();
|
||||
void Refresh() override;
|
||||
|
||||
private:
|
||||
lv_obj_t* btnRoll;
|
||||
lv_obj_t* btnRollLabel;
|
||||
lv_obj_t* resultTotalLabel;
|
||||
lv_obj_t* resultIndividualLabel;
|
||||
lv_task_t* refreshTask;
|
||||
bool enableShakeForDice = false;
|
||||
|
||||
std::mt19937 gen;
|
||||
|
||||
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
|
||||
uint8_t currentColorIndex;
|
||||
void NextColor();
|
||||
|
||||
Widgets::Counter nCounter = Widgets::Counter(1, 9, jetbrains_mono_42);
|
||||
Widgets::Counter dCounter = Widgets::Counter(2, 99, jetbrains_mono_42);
|
||||
|
||||
bool openingRoll = true;
|
||||
uint8_t currentRollHysteresis = 0;
|
||||
static constexpr uint8_t rollHysteresis = 10;
|
||||
|
||||
Controllers::MotorController& motorController;
|
||||
Controllers::MotionController& motionController;
|
||||
Controllers::Settings& settingsController;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Dice> {
|
||||
static constexpr Apps app = Apps::Dice;
|
||||
static constexpr const char* icon = Screens::Symbols::dice;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/InfiniTimeTheme.h"
|
||||
#include <libraries/log/nrf_log.h>
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
@ -58,8 +59,17 @@ FlashLight::~FlashLight() {
|
||||
}
|
||||
|
||||
void FlashLight::SetColors() {
|
||||
lv_color_t bgColor = isOn ? LV_COLOR_WHITE : LV_COLOR_BLACK;
|
||||
lv_color_t fgColor = isOn ? Colors::lightGray : LV_COLOR_WHITE;
|
||||
lv_color_t bgColor = LV_COLOR_BLACK;
|
||||
if(State_l == 0){
|
||||
bgColor = LV_COLOR_BLACK;
|
||||
} else if (State_l == 1){
|
||||
bgColor = LV_COLOR_WHITE;
|
||||
}
|
||||
else {
|
||||
bgColor = lv_color_hex(0xff0000);
|
||||
}
|
||||
|
||||
lv_color_t fgColor = State_l ? Colors::lightGray : LV_COLOR_WHITE;
|
||||
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, bgColor);
|
||||
lv_obj_set_style_local_text_color(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, fgColor);
|
||||
@ -86,9 +96,11 @@ void FlashLight::SetIndicators() {
|
||||
}
|
||||
|
||||
void FlashLight::Toggle() {
|
||||
isOn = !isOn;
|
||||
State_l++;
|
||||
if(State_l > 2)
|
||||
State_l = 0;
|
||||
SetColors();
|
||||
if (isOn) {
|
||||
if (State_l) {
|
||||
brightnessController.Set(brightnessLevel);
|
||||
} else {
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
|
||||
@ -99,7 +111,7 @@ bool FlashLight::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
auto SetState = [this]() {
|
||||
if (isOn) {
|
||||
if (State_l) {
|
||||
brightnessController.Set(brightnessLevel);
|
||||
}
|
||||
SetIndicators();
|
||||
|
@ -26,12 +26,13 @@ namespace Pinetime {
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Controllers::BrightnessController& brightnessController;
|
||||
|
||||
|
||||
Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High;
|
||||
|
||||
lv_obj_t* flashLight;
|
||||
lv_obj_t* backgroundAction;
|
||||
lv_obj_t* indicators[3];
|
||||
bool isOn = false;
|
||||
int State_l = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
|
||||
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
|
||||
}
|
||||
|
||||
lv_label_set_text_static(label_hr, "---");
|
||||
lv_label_set_text_static(label_hr, "000");
|
||||
lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40);
|
||||
|
||||
label_bpm = lv_label_create(lv_scr_act(), nullptr);
|
||||
@ -82,15 +82,11 @@ void HeartRate::Refresh() {
|
||||
case Controllers::HeartRateController::States::NoTouch:
|
||||
case Controllers::HeartRateController::States::NotEnoughData:
|
||||
// case Controllers::HeartRateController::States::Stopped:
|
||||
lv_label_set_text_static(label_hr, "---");
|
||||
lv_label_set_text_static(label_hr, "000");
|
||||
break;
|
||||
default:
|
||||
if (heartRateController.HeartRate() == 0) {
|
||||
lv_label_set_text_static(label_hr, "---");
|
||||
} else {
|
||||
lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
|
||||
}
|
||||
}
|
||||
|
||||
lv_label_set_text_static(label_status, ToString(state));
|
||||
lv_obj_align(label_status, label_hr, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include <chrono>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "Symbols.h"
|
||||
#include <lvgl/src/lv_core/lv_style.h>
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
|
||||
@ -38,15 +37,5 @@ namespace Pinetime {
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::HeartRate> {
|
||||
static constexpr Apps app = Apps::HeartRate;
|
||||
static constexpr const char* icon = Screens::Symbols::heartBeat;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::HeartRate(controllers.heartRateController, *controllers.systemTask);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,6 @@
|
||||
#include <algorithm> // std::fill
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "Symbols.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include <displayapp/Controllers.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Components {
|
||||
@ -38,15 +35,5 @@ namespace Pinetime {
|
||||
uint8_t color = 2;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Paint> {
|
||||
static constexpr Apps app = Apps::Paint;
|
||||
static constexpr const char* icon = Screens::Symbols::paintbrush;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::InfiniPaint(controllers.lvgl, controllers.motorController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <array>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/widgets/PageIndicator.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "components/settings/Settings.h"
|
||||
|
||||
#define MAXLISTITEMS 4
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
@ -37,15 +36,5 @@ namespace Pinetime {
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Metronome> {
|
||||
static constexpr Apps app = Apps::Metronome;
|
||||
static constexpr const char* icon = Screens::Symbols::drum;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Metronome(controllers.motorController, *controllers.systemTask);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -53,9 +53,9 @@ void Motion::Refresh() {
|
||||
lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
|
||||
|
||||
lv_label_set_text_fmt(label,
|
||||
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# mg",
|
||||
motionController.X(),
|
||||
motionController.Y(),
|
||||
motionController.Z());
|
||||
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d#",
|
||||
motionController.X() / 0x10,
|
||||
motionController.Y() / 0x10,
|
||||
motionController.Z() / 0x10);
|
||||
lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10);
|
||||
}
|
||||
|
@ -6,8 +6,6 @@
|
||||
#include <lvgl/src/lv_core/lv_style.h>
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include <components/motion/MotionController.h>
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
@ -32,15 +30,5 @@ namespace Pinetime {
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Motion> {
|
||||
static constexpr Apps app = Apps::Motion;
|
||||
static constexpr const char* icon = "M";
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Motion(controllers.motionController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ inline void lv_img_set_src_arr(lv_obj_t* img, const lv_img_dsc_t* src_img) {
|
||||
*
|
||||
* TODO: Investigate Apple Media Service and AVRCPv1.6 support for seamless integration
|
||||
*/
|
||||
|
||||
|
||||
Music::Music(Pinetime::Controllers::MusicService& music) : musicService(music) {
|
||||
lv_obj_t* label;
|
||||
|
||||
|
@ -18,12 +18,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include <string>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
@ -85,15 +83,5 @@ namespace Pinetime {
|
||||
/** Watchapp */
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Music> {
|
||||
static constexpr Apps app = Apps::Music;
|
||||
static constexpr const char* icon = Screens::Symbols::music;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Music(*controllers.musicService);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -203,21 +203,19 @@ Navigation::Navigation(Pinetime::Controllers::NavigationService& nav) : navServi
|
||||
lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
|
||||
|
||||
txtNarrative = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_DOT);
|
||||
lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_BREAK);
|
||||
lv_obj_set_width(txtNarrative, LV_HOR_RES);
|
||||
lv_obj_set_height(txtNarrative, 80);
|
||||
lv_label_set_text_static(txtNarrative, "Navigation");
|
||||
lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 30);
|
||||
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 10);
|
||||
|
||||
txtManDist = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK);
|
||||
lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
||||
lv_obj_set_style_local_text_font(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||
lv_obj_set_width(txtManDist, LV_HOR_RES);
|
||||
lv_label_set_text_static(txtManDist, "--M");
|
||||
lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 90);
|
||||
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 60);
|
||||
|
||||
// Route Progress
|
||||
barProgress = lv_bar_create(lv_scr_act(), nullptr);
|
||||
|
@ -22,9 +22,6 @@
|
||||
#include <string>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <array>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
@ -58,15 +55,5 @@ namespace Pinetime {
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Navigation> {
|
||||
static constexpr Apps app = Apps::Navigation;
|
||||
static constexpr const char* icon = Screens::Symbols::map;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Navigation(*controllers.navigationService);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,6 @@
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <cstdint>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Components {
|
||||
@ -48,15 +45,5 @@ namespace Pinetime {
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Paddle> {
|
||||
static constexpr Apps app = Apps::Paddle;
|
||||
static constexpr const char* icon = Screens::Symbols::paddle;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Paddle(controllers.lvgl);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,6 @@
|
||||
#include <lvgl/lvgl.h>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <components/motion/MotionController.h>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
||||
@ -42,15 +39,5 @@ namespace Pinetime {
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Steps> {
|
||||
static constexpr Apps app = Apps::Steps;
|
||||
static constexpr const char* icon = Screens::Symbols::shoe;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Steps(controllers.motionController, controllers.settingsController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -198,11 +198,11 @@ void StopWatch::stopLapBtnEventHandler() {
|
||||
continue;
|
||||
}
|
||||
TimeSeparated_t times = convertTicksToTimeSegments(laps[i]);
|
||||
char buffer[17];
|
||||
char buffer[16];
|
||||
if (times.hours == 0) {
|
||||
snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths);
|
||||
sprintf(buffer, "#%2d %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d:%02d.%02d\n", i + 1, times.hours, times.mins, times.secs, times.hundredths);
|
||||
sprintf(buffer, "#%2d %2d:%02d:%02d.%02d\n", i + 1, times.hours, times.mins, times.secs, times.hundredths);
|
||||
}
|
||||
lv_label_ins_text(lapText, LV_LABEL_POS_LAST, buffer);
|
||||
}
|
||||
|
@ -7,13 +7,8 @@
|
||||
#include "portmacro_cmsis.h"
|
||||
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
namespace Pinetime::Applications::Screens {
|
||||
|
||||
enum class States { Init, Running, Halted };
|
||||
|
||||
@ -59,15 +54,3 @@ namespace Pinetime {
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::StopWatch> {
|
||||
static constexpr Apps app = Apps::StopWatch;
|
||||
static constexpr const char* icon = Screens::Symbols::stopWatch;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::StopWatch(*controllers.systemTask);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ namespace Pinetime {
|
||||
static constexpr const char* plug = "\xEF\x87\xA6";
|
||||
static constexpr const char* shoe = "\xEF\x95\x8B";
|
||||
static constexpr const char* clock = "\xEF\x80\x97";
|
||||
static constexpr const char* bell = "\xEF\x83\xB3";
|
||||
static constexpr const char* info = "\xEF\x84\xA9";
|
||||
static constexpr const char* list = "\xEF\x80\xBA";
|
||||
static constexpr const char* sun = "\xEF\x86\x85";
|
||||
@ -21,7 +20,6 @@ namespace Pinetime {
|
||||
static constexpr const char* paintbrush = "\xEF\x87\xBC";
|
||||
static constexpr const char* paddle = "\xEF\x91\x9D";
|
||||
static constexpr const char* map = "\xEF\x96\xa0";
|
||||
static constexpr const char* dove = "\xEF\x92\xBA";
|
||||
static constexpr const char* phone = "\xEF\x82\x95";
|
||||
static constexpr const char* phoneSlash = "\xEF\x8F\x9D";
|
||||
static constexpr const char* volumMute = "\xEF\x9A\xA9";
|
||||
@ -36,13 +34,13 @@ namespace Pinetime {
|
||||
static constexpr const char* hourGlass = "\xEF\x89\x92";
|
||||
static constexpr const char* lapsFlag = "\xEF\x80\xA4";
|
||||
static constexpr const char* drum = "\xEF\x95\xA9";
|
||||
static constexpr const char* dice = "\xEF\x94\xA2";
|
||||
static constexpr const char* eye = "\xEF\x81\xAE";
|
||||
static constexpr const char* home = "\xEF\x80\x95";
|
||||
static constexpr const char* sleep = "\xEE\xBD\x84";
|
||||
static constexpr const char* calculator = "\xEF\x87\xAC";
|
||||
static constexpr const char* backspace = "\xEF\x95\x9A";
|
||||
static constexpr const char* calendar = "\xEF\x81\xB3";
|
||||
|
||||
|
||||
|
||||
// fontawesome_weathericons.c
|
||||
// static constexpr const char* sun = "\xEF\x86\x85";
|
||||
@ -51,9 +49,6 @@ namespace Pinetime {
|
||||
static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80";
|
||||
static constexpr const char* smog = "\xEF\x9D\x9F";
|
||||
static constexpr const char* cloud = "\xEF\x83\x82";
|
||||
static constexpr const char* cloudMeatball = "\xEF\x9C\xBB";
|
||||
static constexpr const char* bolt = "\xEF\x83\xA7";
|
||||
static constexpr const char* snowflake = "\xEF\x8B\x9C";
|
||||
static constexpr const char* ban = "\xEF\x81\x9E";
|
||||
|
||||
// lv_font_sys_48.c
|
||||
|
@ -236,9 +236,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
||||
auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr);
|
||||
std::sort(tasksStatus, tasksStatus + nb, sortById);
|
||||
for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) {
|
||||
char buffer[11] = {0};
|
||||
char buffer[7] = {0};
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%lu", tasksStatus[i].xTaskNumber);
|
||||
sprintf(buffer, "%lu", tasksStatus[i].xTaskNumber);
|
||||
lv_table_set_cell_value(infoTask, i + 1, 0, buffer);
|
||||
switch (tasksStatus[i].eCurrentState) {
|
||||
case eReady:
|
||||
@ -262,9 +262,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
||||
lv_table_set_cell_value(infoTask, i + 1, 1, buffer);
|
||||
lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName);
|
||||
if (tasksStatus[i].usStackHighWaterMark < 20) {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu16 " low", tasksStatus[i].usStackHighWaterMark);
|
||||
sprintf(buffer, "%d low", tasksStatus[i].usStackHighWaterMark);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu16, tasksStatus[i].usStackHighWaterMark);
|
||||
sprintf(buffer, "%d", tasksStatus[i].usStackHighWaterMark);
|
||||
}
|
||||
lv_table_set_cell_value(infoTask, i + 1, 3, buffer);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "displayapp/screens/Tile.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/BatteryIcon.h"
|
||||
#include "components/ble/BleController.h"
|
||||
#include "displayapp/InfiniTimeTheme.h"
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "components/battery/BatteryController.h"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user