Compare commits

..

4 Commits

161 changed files with 2867 additions and 5718 deletions

View File

@ -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
View 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
View 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

View File

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

View 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

View 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"
}

View 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}

View File

@ -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

View File

@ -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"

View File

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

45
.vscode/launch.json vendored
View File

@ -1,18 +1,20 @@
{
{
"version": "0.1.0",
"configurations": [
{
"name": "Debug - Openocd docker Remote",
"type": "cortex-debug",
"type":"cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "external",
"gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// 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",
@ -21,16 +23,18 @@
},
{
"name": "Debug - Openocd Local",
"type": "cortex-debug",
"type":"cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "openocd",
"gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// 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
View File

@ -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
View File

@ -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": []
}
]
}

View File

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

108
README.md
View File

@ -1,30 +1,88 @@
# TODO
## Completed
- [x] Make $s in terminal watch face green
- [x] Swipe left to access music control
# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)
## Pending merge
- [x] Flashlight starts on
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
## In progress
Elijah
- [ ] Countdown timer presistant buzz
Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++.
Josh
- [ ] Seconds on digital watchface
## New to InfiniTime?
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
- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
- [Updating the software](doc/gettingStarted/updating-software.md)
- [About the firmware and bootloader](doc/gettingStarted/about-software.md)
- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle)
- [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather)
## Lower priority
- [ ] Rearrange quick acces and apps
- [ ] Maybe some prank watchfaces
### Companion apps
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) **[Looking for a new maintainer]**
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
***Note** : We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
## Development
- [InfiniTime Vision](doc/InfiniTimeVision.md)
- [Rough structure of the code](doc/code/Intro.md)
- [How to implement an application](doc/code/Apps.md)
- [Generate the fonts and symbols](src/displayapp/fonts/README.md)
- [Tips on designing an app UI](doc/ui_guidelines.md)
- [Bootloader, OTA and DFU](bootloader/README.md)
- [External resources](doc/ExternalResources.md)
### Contributing
- [How to contribute?](CONTRIBUTING.md)
- [Coding conventions](doc/coding-convention.md)
### Build, flash and debug
- [InfiniTime simulator](https://github.com/InfiniTimeOrg/InfiniSim)
- [Build the project](doc/buildAndProgram.md)
- [Build the project with Docker](doc/buildWithDocker.md)
- [Build the project with VSCode](doc/buildWithVScode.md)
- [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md)
- [Flash the firmware using SWD interface](doc/SWD.md)
- [Flash the firmware using JLink](doc/jlink.md)
- [Flash the firmware using GDB](doc/gdb.md)
- [Stub using NRF52-DK](doc/PinetimeStubWithNrf52DK.md)
### API
- [BLE implementation and API](doc/ble.md)
### Architecture and technical topics
- [Memory analysis](doc/MemoryAnalysis.md)
### Project management
- [Maintainer's guide](doc/maintainer-guide.md)
- [Versioning](doc/versioning.md)
- [Project branches](doc/branches.md)
- [Files included in the release notes](doc/filesInReleaseNotes.md)
- [Files needed by the factory](doc/files-needed-by-factory.md)
## Licenses
This project is released under the GNU General Public License version 3 or, at your option, any later version.
It integrates the following projects:
- RTOS : **[FreeRTOS](https://freertos.org)** under the MIT license
- UI : **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
- BLE stack : **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
- Font : **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
## Credits
Im not working alone on this project. First, many people create PR for this project. Then, there is the whole #pinetime community : a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums.
Here are some people I would like to highlight:
- [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
- [Koen](https://github.com/bosmoment) : Hes working on a firmware based on RiotOS. He integrated similar libs as me : NimBLE, LittleVGL,… His help was invaluable too!
- [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!

View File

@ -1,88 +0,0 @@
# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++.
## New to InfiniTime?
- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
- [Updating the software](doc/gettingStarted/updating-software.md)
- [About the firmware and bootloader](doc/gettingStarted/about-software.md)
- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle)
- [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather)
### Companion apps
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) **[Looking for a new maintainer]**
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
***Note** : We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
## Development
- [InfiniTime Vision](doc/InfiniTimeVision.md)
- [Rough structure of the code](doc/code/Intro.md)
- [How to implement an application](doc/code/Apps.md)
- [Generate the fonts and symbols](src/displayapp/fonts/README.md)
- [Tips on designing an app UI](doc/ui_guidelines.md)
- [Bootloader, OTA and DFU](bootloader/README.md)
- [External resources](doc/ExternalResources.md)
### Contributing
- [How to contribute?](CONTRIBUTING.md)
- [Coding conventions](doc/coding-convention.md)
### Build, flash and debug
- [InfiniTime simulator](https://github.com/InfiniTimeOrg/InfiniSim)
- [Build the project](doc/buildAndProgram.md)
- [Build the project with Docker](doc/buildWithDocker.md)
- [Build the project with VSCode](doc/buildWithVScode.md)
- [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md)
- [Flash the firmware using SWD interface](doc/SWD.md)
- [Flash the firmware using JLink](doc/jlink.md)
- [Flash the firmware using GDB](doc/gdb.md)
- [Stub using NRF52-DK](doc/PinetimeStubWithNrf52DK.md)
### API
- [BLE implementation and API](doc/ble.md)
### Architecture and technical topics
- [Memory analysis](doc/MemoryAnalysis.md)
### Project management
- [Maintainer's guide](doc/maintainer-guide.md)
- [Versioning](doc/versioning.md)
- [Project branches](doc/branches.md)
- [Files included in the release notes](doc/filesInReleaseNotes.md)
- [Files needed by the factory](doc/files-needed-by-factory.md)
## Licenses
This project is released under the GNU General Public License version 3 or, at your option, any later version.
It integrates the following projects:
- RTOS : **[FreeRTOS](https://freertos.org)** under the MIT license
- UI : **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
- BLE stack : **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
- Font : **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
## Credits
Im not working alone on this project. First, many people create PR for this project. Then, there is the whole #pinetime community : a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums.
Here are some people I would like to highlight:
- [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
- [Koen](https://github.com/bosmoment) : Hes working on a firmware based on RiotOS. He integrated similar libs as me : NimBLE, LittleVGL,… His help was invaluable too!
- [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!

View File

@ -7,9 +7,9 @@ My own contribution is little more than a brute force conversion to
python3. It is sparsely tested so there are likely to be a few
remaining bytes versus string bugs remaining in the places I didn't test
. I used it primarily as part of
[wasp-os](https://github.com/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?

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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`
---

View File

@ -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**

View File

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

View File

@ -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!

View File

@ -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

View File

@ -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"]

View File

@ -1,94 +0,0 @@
## Clone repositories
1. Clone InfiniTime:
```bash
git clone https://github.com/InfiniTimeOrg/InfiniTime.git
cd InfiniTime
git submodule update --init
```
2. Clone InfiniSim:
```bash
cd .. #Ignore this line if you already cd'd into the diretory you want it cloned into
git clone --recursive https://github.com/InfiniTimeOrg/InfiniSim.git
```
## Install dependencies
### For building InfiniTime
1. Install Docker
2. Pull docker container (optional, container will be pulled when running the docker container if not pulled now).
```bash
sudo docker pull infinitime/infinitime-build
```
### For building InfiniSim
This was the most difficult part for me to get right since the steps on the github page are not entirely complete for my setup. If you encounter errors while building, it's probably because one of the dependencies are missing or have the wrong version installed. If you do have an error while building, check out the issues on github and see if you can find some help there: https://github.com/InfiniTimeOrg/InfiniSim/issues
1. The apt install part. This is almost exactly the same as what is on the github page except for npm. When I added npm in to here, I got an older version that didn't work. We'll install the correct version in step 2.
```bash
sudo apt install -y cmake libsdl2-dev g++ libpng-dev
```
2. Install the correct node version. (At least v14.0.0 is required). Commands from https://github.com/nodesource/distributions First, choose the node version by running one of these lines:
```bash
NODE_MAJOR=16
NODE_MAJOR=18
NODE_MAJOR=20
```
I'm not sure which is the best to use, I went with 16 since that is the closest to the version required by InfiniSim, but I doubt it makes much of a difference. Once you've done that, then install it with these commands:
```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt-get update
sudo apt-get install nodejs -y
```
3. Install npm packages
```bash
npm install lv_font_conv@1.5.2 -g
npm install ts-node@10.9.1 @swc/core@1.3.82 lv_img_conv@0.3.0 -g
```
## Build InfiniSim
### Building with stock InfiniTime
Now to see if we did everything the way our computer wants it. First cd into the directory where InfinSim was cloned (`cd InfiniSim` if you are still in the directory you started in). Now you should be able to build InfiniSim with the InfiniTime version that was included with InfiniSim:
```bash
cmake -S . -B build
cmake --build build -j4
```
If that didn't produce any error, now run it!
```bash
./build/infinisim
```
### Building with your own version of InfiniTime
To test your own modifications of InfiniTime:
```bash
cmake -S . -B build -DInfiniTime_DIR=../InfiniTime
cmake --build build -j4
```
Assuming that the git clone command was run in the same directory for both InfiniSim and InfiniTime. If not, replace `../InfiniTime` with the path to the directory where InfiniTime was cloned to.
And now run it if there were no errors!
```bash
./build/infinisim
```
## Build InfiniTime
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
```
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" >
"Firmware" and select the zip file mentioned above. Once that's done, you may also want to send it the `infinitime-resources-x.x.x.zip` if some features are greyed out on the watch (for me it was a couple watch faces and the navigation app that were greyed out). In WatchMate, you'll want to use the "Resources" option instead of the "Firmware" option to upload this file.
## Celebrate
There you go, you've just flashed your own custom os to your watch! If you got all this far without running into any error, you have something to celebrate about as it took me a couple of days to get to this point. If you did have some errors and figured them out, then great, you've got it all figured out now!
One final thing, I would recommend making use of git commits and branches so that you can revert if you totally mess something up and don't have to clone the entire repository and dependencies again like I had to do several times before I decided to start doing that.

View 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

View File

@ -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,37 @@ 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/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 +429,10 @@ 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
##
@ -464,7 +461,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 +530,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 +550,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 +580,7 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
components/rle/RleDecoder.cpp
components/gfx/Gfx.cpp
drivers/St7789.cpp
components/brightness/BrightnessController.cpp
@ -599,6 +598,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 +614,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 +659,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 +806,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 +858,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 +895,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 +934,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 +968,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 +1010,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 +1042,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 +1082,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 +1117,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}

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};
}
}

View 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;
};
};
}
}

View 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);
}
}
}

View 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);
};
}
}

View File

@ -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);
}

View File

@ -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
View 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], &current_x, current_y, color);
}
uint8_t char_idx = text[i] - p_font->startChar;
uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits;
if (current_x > (width - char_width)) {
if (wrap) {
current_x = x;
current_y += p_font->height + p_font->height / 10;
} else {
break;
}
if (y > (height - p_font->height)) {
break;
}
}
}
}
void Gfx::DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color) {
uint8_t char_idx = c - font->startChar;
uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
uint16_t bg = 0x0000;
if (c == ' ') {
*x += font->height / 2;
return;
}
// Build first line
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
buffer[(j * 8) + k] = color;
} else {
buffer[(j * 8) + k] = bg;
}
}
}
state.remainingIterations = font->height + 0;
state.currentIteration = 0;
state.busy = true;
state.action = Action::DrawChar;
state.font = const_cast<FONT_INFO*>(font);
state.character = c;
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(*x, y, bytes_in_line * 8, font->height, reinterpret_cast<const uint8_t*>(&buffer), bytes_in_line * 8 * 2);
WaitTransferFinished();
*x += font->charInfo[char_idx].widthBits + font->spacePixels;
}
void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
lcd.DrawPixel(x, y, color);
}
void Gfx::Sleep() {
lcd.Sleep();
}
void Gfx::Wakeup() {
lcd.Wakeup();
}
void Gfx::SetBackgroundColor(uint16_t color) {
for (int i = 0; i < width; i++) {
buffer[i] = color;
}
}
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
if (!state.busy)
return false;
state.remainingIterations--;
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
View 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);
};
}
}

View File

@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
service->OnNewStepCountValue(nbSteps);
}
if (service != nullptr && (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;
}

View File

@ -21,7 +21,7 @@ namespace Pinetime {
void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
int16_t X() const {
return 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;

View File

@ -5,58 +5,14 @@
using namespace Pinetime::Controllers;
namespace {
TimerHandle_t vibTimer;
void PatternStep(TimerHandle_t xTimer) {
/* Vibration pattern format:
* {
* durationOfVibration,
* durationOfPause,
* durationOfVibration,
* durationOfPause,
* ...,
* durationOfVibration,
* zeroTerminator
* }
*
* Patterns can be any length
* The pattern must end with a duration of vibration and a terminator.
*/
static constexpr uint8_t vibrationPattern[] = {30, 150, 30, 150, 30, 0};
static size_t patternPosition = 0;
if (vibrationPattern[patternPosition] != 0 && xTimerChangePeriod(vibTimer, vibrationPattern[patternPosition] << 1, 0) == pdPASS &&
xTimerStart(vibTimer, 0) == pdPASS) {
if (patternPosition % 2 == 0) {
nrf_gpio_pin_clear(Pinetime::PinMap::Motor);
} else {
nrf_gpio_pin_set(Pinetime::PinMap::Motor);
}
patternPosition++;
} else {
patternPosition = 0;
nrf_gpio_pin_set(Pinetime::PinMap::Motor);
auto* motorController = static_cast<MotorController*>(pvTimerGetTimerID(xTimer));
motorController->PatternFinished();
}
}
}
void MotorController::Init() {
nrf_gpio_cfg_output(PinMap::Motor);
nrf_gpio_pin_set(PinMap::Motor);
vibTimer = xTimerCreate("vibration", 1, pdFALSE, this, PatternStep);
shortVib = xTimerCreate("shortVib", 1, pdFALSE, nullptr, StopMotor);
longVib = xTimerCreate("longVib", pdMS_TO_TICKS(1000), pdTRUE, this, Ring);
}
void MotorController::PatternFinished() {
patternPlaying = false;
}
void MotorController::Ring(TimerHandle_t xTimer) {
auto* motorController = static_cast<MotorController*>(pvTimerGetTimerID(xTimer));
motorController->RunForDuration(50);
@ -68,15 +24,6 @@ void MotorController::RunForDuration(uint8_t motorDuration) {
}
}
bool MotorController::StartPattern() {
if (!patternPlaying) {
patternPlaying = true;
PatternStep(vibTimer);
return true;
}
return false;
}
void MotorController::StartRinging() {
RunForDuration(50);
xTimerStart(longVib, 0);
@ -87,10 +34,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);
}

View File

@ -15,14 +15,10 @@ namespace Pinetime {
void RunForDuration(uint8_t motorDuration);
void StartRinging();
void StopRinging();
void PatternFinished();
bool StartPattern();
bool IsRinging();
private:
static void Ring(TimerHandle_t xTimer);
static void StopMotor(TimerHandle_t xTimer);
bool patternPlaying;
TimerHandle_t shortVib;
TimerHandle_t longVib;
};

View File

@ -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;

View File

@ -12,13 +12,11 @@ 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() {

43
src/displayapp/Apps.h Normal file
View File

@ -0,0 +1,43 @@
#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
};
}
}

View File

@ -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;
};
}
}

View File

@ -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,15 +27,10 @@
#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"
// My New Apps
#include "displayapp/screens/Calculator.h"
#include "drivers/Cst816s.h"
#include "drivers/St7789.h"
#include "drivers/Watchdog.h"
@ -48,7 +41,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 +50,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 +96,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 +122,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 +246,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) {
@ -316,48 +289,20 @@ void DisplayApp::Refresh() {
return TouchEvents::SwipeLeft;
}
};
if (!currentScreen->OnTouchEvent(gesture)) {
if (currentApp == Apps::Clock || currentApp == Apps::Music || currentApp == Apps::Weather || currentApp == Apps::QuickSettings) {
if (currentApp == Apps::Clock) {
switch (gesture) {
case TouchEvents::SwipeUp:
if (currentApp == Apps::Clock) {
LoadNewScreen(Apps::Launcher, DisplayApp::FullRefreshDirections::Up);
}
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) {
LoadNewScreen(Apps::FlashLight, DisplayApp::FullRefreshDirections::Down);
}
break;
case TouchEvents::SwipeRight:
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::Music, DisplayApp::FullRefreshDirections::RightAnim);
} else {
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::RightAnim);
}
break;
case TouchEvents::SwipeLeft:
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::QuickSettings, DisplayApp::FullRefreshDirections::LeftAnim);
} else {
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::LeftAnim);
}
break;
case TouchEvents::DoubleTap:
PushMessageToSystemTask(System::Messages::GoToSleep);
@ -416,18 +361,7 @@ void DisplayApp::Refresh() {
break;
case Messages::Chime:
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
time_var = dateTimeController.Minutes();
if (time_var == 30){
NRF_LOG_INFO("Short: %d", time_var);
motorController.RunForDuration(25);
}
else
{
NRF_LOG_INFO("Long: %d", time_var);
motorController.RunForDuration(200);
}
motorController.RunForDuration(35);
break;
case Messages::OnChargingEvent:
RestoreBrightness();
@ -471,32 +405,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 +456,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 +476,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 +519,38 @@ 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;
}
}
currentApp = app;
}
@ -611,17 +559,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 +600,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() {

View File

@ -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;
@ -134,7 +128,6 @@ namespace Pinetime {
Utility::StaticStack<Apps, returnAppStackSize> returnAppStack;
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections;
int time_var;
bool isDimmed = false;
};
}

View File

@ -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*/) {
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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 {});
}
}

View 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,
};
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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:

View File

@ -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"
}
],
"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,

View File

@ -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;

View File

@ -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);
};
};
}
}

View File

@ -1,9 +1,8 @@
#include "displayapp/screens/ApplicationList.h"
#include "displayapp/screens/Tile.h"
#include <lvgl/lvgl.h>
#include <functional>
#include <algorithm>
#include "components/settings/Settings.h"
#include "displayapp/Apps.h"
#include "displayapp/DisplayApp.h"
using namespace Pinetime::Applications::Screens;
@ -17,20 +16,18 @@ auto ApplicationList::CreateScreenList() const {
return screens;
}
ApplicationList::ApplicationList(DisplayApp* app,
ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app,
Pinetime::Controllers::Settings& settingsController,
const Pinetime::Controllers::Battery& batteryController,
const Pinetime::Controllers::Ble& bleController,
Controllers::DateTime& dateTimeController,
Pinetime::Controllers::FS& filesystem,
std::array<Tile::Applications, UserAppTypes::Count>&& apps)
Pinetime::Controllers::FS& filesystem)
: app {app},
settingsController {settingsController},
batteryController {batteryController},
bleController {bleController},
dateTimeController {dateTimeController},
filesystem {filesystem},
apps {std::move(apps)},
filesystem{filesystem},
screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} {
}
@ -43,14 +40,9 @@ bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
}
std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const {
std::array<Tile::Applications, appsPerScreen> pageApps;
std::array<Tile::Applications, appsPerScreen> apps;
for (int i = 0; i < appsPerScreen; i++) {
if (i + (screenNum * appsPerScreen) >= apps.size()) {
pageApps[i] = {"", Pinetime::Applications::Apps::None, false};
} else {
pageApps[i] = apps[i + (screenNum * appsPerScreen)];
}
apps[i] = applications[screenNum * appsPerScreen + i];
}
return std::make_unique<Screens::Tile>(screenNum,
@ -60,5 +52,5 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) co
batteryController,
bleController,
dateTimeController,
pageApps);
apps);
}

View File

@ -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,29 @@ 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::none, Apps::None, false},
// {"M", Apps::Motion},
}};
ScreenList<nScreens> screens;
};
}

View File

@ -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;
}

View File

@ -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();
};
};
}
}

View File

@ -1,442 +0,0 @@
#include <cmath>
#include <cinttypes>
#include <libraries/log/nrf_log.h>
#include "Calculator.h"
#include "displayapp/InfiniTimeTheme.h"
#include "Symbols.h"
using namespace Pinetime::Applications::Screens;
static void eventHandler(lv_obj_t* obj, lv_event_t event) {
auto app = static_cast<Calculator*>(obj->user_data);
app->OnButtonEvent(obj, event);
}
Calculator::~Calculator() {
lv_obj_clean(lv_scr_act());
}
static const char* buttonMap[] = {
"7", "8", "9", "^", "\n", "4", "5", "6", "+ -", "\n", "1", "2", "3", "* /", "\n", "0", ".", "(-)", "=", ""};
Calculator::Calculator() {
resultLabel = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(resultLabel, LV_LABEL_LONG_CROP);
lv_label_set_align(resultLabel, LV_LABEL_ALIGN_RIGHT);
lv_label_set_text_fmt(resultLabel, "%" PRId64, result);
lv_obj_set_size(resultLabel, 200, 20);
lv_obj_set_pos(resultLabel, -30, 5);
valueLabel = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(valueLabel, LV_LABEL_LONG_CROP);
lv_label_set_align(valueLabel, LV_LABEL_ALIGN_RIGHT);
lv_label_set_text_fmt(valueLabel, "%" PRId64, value);
lv_obj_set_size(valueLabel, 200, 20);
lv_obj_set_pos(valueLabel, -30, 35);
buttonMatrix = lv_btnmatrix_create(lv_scr_act(), nullptr);
buttonMatrix->user_data = this;
lv_obj_set_event_cb(buttonMatrix, eventHandler);
lv_btnmatrix_set_map(buttonMatrix, buttonMap);
lv_btnmatrix_set_one_check(buttonMatrix, true);
lv_obj_set_size(buttonMatrix, 238, 180);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt);
lv_obj_set_style_local_pad_inner(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_top(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_bottom(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_left(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_right(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_align(buttonMatrix, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
lv_obj_set_style_local_bg_opa(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_OPA_COVER);
lv_obj_set_style_local_bg_grad_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128);
lv_obj_set_style_local_bg_main_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128);
// BackSpace Button
back = lv_btn_create(lv_scr_act(), nullptr);
back->user_data = this;
lv_obj_set_event_cb(back, eventHandler);
lv_obj_set_size(back, 59, 44);
lv_obj_align(back, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 179, 16);
txtback = lv_label_create(back, nullptr);
lv_obj_set_style_local_bg_color(back, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Colors::bgAlt);
lv_label_set_text_static(txtback, Symbols::backspace);
}
void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) {
if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) {
HandleInput();
}
if (obj == back && event == LV_EVENT_PRESSED) {
if (value != 0) {
// delete one value digit
if (offset < FIXED_POINT_OFFSET) {
if (offset == 0) {
offset = 1;
} else {
offset *= 10;
}
} else {
value /= 10;
}
if (offset < FIXED_POINT_OFFSET) {
value -= value % (10 * offset);
} else {
value -= value % offset;
}
} else if (offset < FIXED_POINT_OFFSET) {
if (offset == 0) {
offset = 1;
} else {
offset *= 10;
}
} else {
// reset the result
result = 0;
}
operation = ' ';
UpdateOperation();
UpdateValueLabel();
UpdateResultLabel();
}
}
void Calculator::HandleInput() {
const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix);
if (buttonText == nullptr) {
return;
}
if ((equalSignPressed && (*buttonText != '=')) || (error != Error::None)) {
ResetInput();
UpdateOperation();
}
// we only compare the first char because it is enough
switch (*buttonText) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
// *buttonText is the first char in buttonText
// "- '0'" results in the int value of the char
auto digit = (*buttonText) - '0';
auto sign = (value < 0) ? -1 : 1;
// if this is true, we already pressed the . button
if (offset < FIXED_POINT_OFFSET) {
value += sign * offset * digit;
offset /= 10;
} else if (value <= MAX_VALUE / 10) {
value *= 10;
value += sign * offset * digit;
}
NRF_LOG_INFO(". offset: %" PRId64, offset);
NRF_LOG_INFO(". value: %" PRId64, value);
NRF_LOG_INFO(". result: %" PRId64, result);
} break;
// unary minus
case '(':
value = -value;
NRF_LOG_INFO(". offset: %" PRId64, offset);
NRF_LOG_INFO(". value: %" PRId64, value);
NRF_LOG_INFO(". result: %" PRId64, result);
break;
// Decimal
case '.':
if (offset == FIXED_POINT_OFFSET) {
offset /= 10;
}
NRF_LOG_INFO(". offset: %" PRId64, offset);
NRF_LOG_INFO(". value: %" PRId64, value);
NRF_LOG_INFO(". result: %" PRId64, result);
break;
// for every operator we:
// - eval the current operator if value > FIXED_POINT_OFFSET
// - then set the new operator
// - + and - as well as * and / cycle on the same button
case '^':
if (value != 0) {
Eval();
ResetInput();
}
switch (operation) {
case ' ':
operation = '^';
break;
default:
operation = ' ';
break;
}
UpdateOperation();
break;
case '+':
if (value != 0) {
Eval();
ResetInput();
}
switch (operation) {
case '+':
operation = '-';
break;
case '-':
operation = ' ';
break;
default:
operation = '+';
break;
}
UpdateOperation();
break;
case '*':
if (value != 0) {
Eval();
ResetInput();
}
switch (operation) {
case '*':
operation = '/';
break;
case '/':
operation = ' ';
break;
default:
operation = '*';
break;
}
UpdateOperation();
break;
case '=':
equalSignPressed = true;
Eval();
// If the operation is ' ' then we move the value to the result.
// We reset the input after this.
// This seems more convenient.
if (operation == ' ') {
ResetInput();
}
NRF_LOG_INFO(". offset: %" PRId64, offset);
NRF_LOG_INFO(". value: %" PRId64, value);
NRF_LOG_INFO(". result: %" PRId64, result);
break;
}
NRF_LOG_INFO("Operation End: %c", operation);
UpdateValueLabel();
UpdateResultLabel();
}
void Calculator::UpdateOperation() const {
switch (operation) {
case '+':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
case '-':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
case '*':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
case '/':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
case '^':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 3, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
default:
lv_btnmatrix_clear_btn_ctrl_all(buttonMatrix, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
}
}
void Calculator::ResetInput() {
value = 0;
offset = FIXED_POINT_OFFSET;
operation = ' ';
equalSignPressed = false;
error = Error::None;
}
void Calculator::UpdateResultLabel() const {
int64_t integer = result / FIXED_POINT_OFFSET;
int64_t remainder = result % FIXED_POINT_OFFSET;
bool negative = (remainder < 0);
if (remainder == 0) {
lv_label_set_text_fmt(resultLabel, "%" PRId64, integer);
return;
}
if (remainder < 0) {
remainder = -remainder;
}
uint8_t min_width = N_DECIMALS;
// cut "0"-digits on the right
while ((remainder > 0) && (remainder % 10 == 0)) {
remainder /= 10;
min_width--;
}
if ((integer == 0) && negative) {
lv_label_set_text_fmt(resultLabel, "-0.%0*" PRId64, min_width, remainder);
} else {
lv_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, min_width, remainder);
}
}
void Calculator::UpdateValueLabel() {
switch (error) {
case Error::TooLarge:
lv_label_set_text_static(valueLabel, "too large");
break;
case Error::ZeroDivision:
lv_label_set_text_static(valueLabel, "zero division");
break;
case Error::None:
default: {
int64_t integer = value / FIXED_POINT_OFFSET;
int64_t remainder = value % FIXED_POINT_OFFSET;
bool negative = (remainder < 0);
int64_t printRemainder = remainder < 0 ? -remainder : remainder;
uint8_t min_width = 0;
int64_t tmp_offset = offset;
// TODO there has to be a simpler way to do this
if (tmp_offset == 0) {
tmp_offset = 1;
min_width = 1;
}
while (tmp_offset < FIXED_POINT_OFFSET) {
tmp_offset *= 10;
min_width++;
}
min_width--;
for (uint8_t i = min_width; i < N_DECIMALS; i++) {
printRemainder /= 10;
}
if ((integer == 0) && negative) {
lv_label_set_text_fmt(valueLabel, "-0.%0*" PRId64, min_width, printRemainder);
} else if (offset == FIXED_POINT_OFFSET) {
lv_label_set_text_fmt(valueLabel, "%" PRId64, integer);
} else if ((offset == (FIXED_POINT_OFFSET / 10)) && (remainder == 0)) {
lv_label_set_text_fmt(valueLabel, "%" PRId64 ".", integer);
} else {
lv_label_set_text_fmt(valueLabel, "%" PRId64 ".%0*" PRId64, integer, min_width, printRemainder);
}
} break;
}
}
// update the result based on value and operation
void Calculator::Eval() {
switch (operation) {
case ' ':
result = value;
break;
case '+':
// check for overflow
if (((result > 0) && (value > (MAX_VALUE - result))) || ((result < 0) && (value < (MIN_VALUE - result)))) {
error = Error::TooLarge;
break;
}
result += value;
break;
case '-':
// check for overflow
if (((result < 0) && (value > (MAX_VALUE + result))) || ((result > 0) && (value < (MIN_VALUE + result)))) {
error = Error::TooLarge;
break;
}
result -= value;
break;
case '*':
// check for overflow
// while dividing we eliminate the fixed point offset
// therefore we have to multiply it again for the comparison with value
// we also assume here that MAX_VALUE == -MIN_VALUE
if ((result != 0) && (std::abs(value) > (FIXED_POINT_OFFSET * (MAX_VALUE / std::abs(result))))) {
error = Error::TooLarge;
break;
}
result *= value;
// fixed point offset was multiplied too
result /= FIXED_POINT_OFFSET;
break;
case '/':
// check for zero division
if (value == 0) {
error = Error::ZeroDivision;
break;
}
// fixed point offset will be divided too
result *= FIXED_POINT_OFFSET;
result /= value;
break;
case '^':
// check for zero division
if (value == 0) {
error = Error::ZeroDivision;
break;
}
place_holder = result;
place_holder /= FIXED_POINT_OFFSET;
result = pow(place_holder, (value*0.0001))*10000;
break;
default:
break;
}
}

View File

@ -1,83 +0,0 @@
#pragma once
#include "displayapp/apps/Apps.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace {
int64_t constexpr powi(int64_t base, uint8_t exponent) {
int64_t value = 1;
while (exponent) {
value *= base;
exponent--;
}
return value;
}
}
namespace Pinetime {
namespace Applications {
namespace Screens {
class Calculator : public Screen {
public:
~Calculator() override;
Calculator();
void OnButtonEvent(lv_obj_t* obj, lv_event_t event);
private:
lv_obj_t* buttonMatrix {};
lv_obj_t* valueLabel {};
lv_obj_t* resultLabel {};
lv_obj_t* back;
lv_obj_t* txtback;
double place_holder;
void Eval();
void ResetInput();
void HandleInput();
void UpdateValueLabel();
void UpdateResultLabel() const;
void UpdateOperation() const;
// change this if you want to change the number of decimals
static constexpr uint8_t N_DECIMALS = 4;
// this is the constant default offset
static constexpr int64_t FIXED_POINT_OFFSET = powi(10, N_DECIMALS);
// this is the current offset, may wary after pressing '.'
int64_t offset = FIXED_POINT_OFFSET;
// the screen can show 12 chars
// but two are needed for '.' and '-'
static constexpr uint8_t MAX_DIGITS = 15;
static constexpr int64_t MAX_VALUE = powi(10, MAX_DIGITS) - 1;
// this is assumed in the multiplication overflow!
static constexpr int64_t MIN_VALUE = -MAX_VALUE;
int64_t value = 0;
int64_t result = 0;
char operation = ' ';
bool equalSignPressed = false;
enum Error {
TooLarge,
ZeroDivision,
None,
};
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();
};
};
}
}

View File

@ -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, &current);
lv_calendar_set_showed_date(calendar, &current);
}
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, &current);
return true;
}
case TouchEvents::SwipeRight: {
if (current.month == 1) {
current.month = 12;
current.year--;
} else {
current.month--;
}
lv_calendar_set_showed_date(calendar, &current);
return true;
}
/*
case TouchEvents::SwipeUp: {
current.year++;
lv_calendar_set_showed_date(calendar, &current);
return true;
}
case TouchEvents::SwipeDown: {
current.year--;
lv_calendar_set_showed_date(calendar, &current);
return true;
}
*/
default: {
return false;
}
}
}
Calendar::~Calendar() {
lv_obj_clean(lv_scr_act());
}

View File

@ -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);
};
};
}
}

View File

@ -1,6 +1,6 @@
#pragma once
#include "displayapp/apps/Apps.h"
#include "displayapp/Apps.h"
#include "displayapp/screens/Screen.h"
#include <array>
#include <cstdint>

View File

@ -0,0 +1,133 @@
#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"
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;
}
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);
}

View File

@ -0,0 +1,60 @@
#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();
};
}
}
}

View File

@ -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]);
}

View File

@ -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);
};
};
}
}

View File

@ -47,8 +47,6 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo
lv_obj_set_event_cb(backgroundAction, EventHandler);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
Toggle();
}
FlashLight::~FlashLight() {

View File

@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
}
lv_label_set_text_static(label_hr, "---");
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);

View File

@ -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);
};
};
}
}

View File

@ -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);
};
};
}
}

View File

@ -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

View File

@ -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);
};
};
}
}

View File

@ -53,9 +53,9 @@ void Motion::Refresh() {
lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
lv_label_set_text_fmt(label,
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# 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);
}

View File

@ -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);
};
};
}
}

View File

@ -250,13 +250,6 @@ void Music::OnObjectEvent(lv_obj_t* obj, lv_event_t event) {
bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
switch (event) {
case TouchEvents::SwipeDown: {
lv_obj_set_hidden(btnNext, false);
lv_obj_set_hidden(btnPrev, false);
lv_obj_set_hidden(btnVolDown, true);
lv_obj_set_hidden(btnVolUp, true);
return true;
}
case TouchEvents::SwipeUp: {
lv_obj_set_hidden(btnVolDown, false);
lv_obj_set_hidden(btnVolUp, false);
@ -265,6 +258,24 @@ bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
lv_obj_set_hidden(btnPrev, true);
return true;
}
case TouchEvents::SwipeDown: {
if (lv_obj_get_hidden(btnNext)) {
lv_obj_set_hidden(btnNext, false);
lv_obj_set_hidden(btnPrev, false);
lv_obj_set_hidden(btnVolDown, true);
lv_obj_set_hidden(btnVolUp, true);
return true;
}
return false;
}
case TouchEvents::SwipeLeft: {
musicService.event(Controllers::MusicService::EVENT_MUSIC_NEXT);
return true;
}
case TouchEvents::SwipeRight: {
musicService.event(Controllers::MusicService::EVENT_MUSIC_PREV);
return true;
}
default: {
return false;
}

View File

@ -21,9 +21,6 @@
#include <lvgl/src/lv_core/lv_obj.h>
#include <string>
#include "displayapp/screens/Screen.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime {
namespace Controllers {
@ -85,15 +82,5 @@ namespace Pinetime {
/** Watchapp */
};
}
template <>
struct AppTraits<Apps::Music> {
static constexpr Apps app = Apps::Music;
static constexpr const char* icon = Screens::Symbols::music;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Music(*controllers.musicService);
};
};
}
}

View File

@ -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);

View File

@ -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);
};
};
}
}

View File

@ -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);
};
};
}
}

View File

@ -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);
};
};
}
}

View File

@ -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);
}

View File

@ -7,13 +7,8 @@
#include "portmacro_cmsis.h"
#include "systemtask/SystemTask.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime {
namespace Applications {
namespace Screens {
namespace Pinetime::Applications::Screens {
enum class States { Init, Running, Halted };
@ -58,16 +53,4 @@ namespace Pinetime {
lv_task_t* taskRefresh;
};
}
template <>
struct AppTraits<Apps::StopWatch> {
static constexpr Apps app = Apps::StopWatch;
static constexpr const char* icon = Screens::Symbols::stopWatch;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::StopWatch(*controllers.systemTask);
};
};
}
}

View File

@ -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,9 @@ 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 +45,6 @@ namespace Pinetime {
static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80";
static constexpr const char* smog = "\xEF\x9D\x9F";
static constexpr const char* cloud = "\xEF\x83\x82";
static constexpr const char* cloudMeatball = "\xEF\x9C\xBB";
static constexpr const char* bolt = "\xEF\x83\xA7";
static constexpr const char* snowflake = "\xEF\x8B\x9C";
static constexpr const char* ban = "\xEF\x81\x9E";
// lv_font_sys_48.c

View File

@ -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);
}

View File

@ -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"

View File

@ -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