Merge branch 'develop' into fit_more_tasks

This commit is contained in:
Riku Isokoski 2021-08-28 17:10:01 +03:00
commit c78177eedf
70 changed files with 1211 additions and 851 deletions

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/JuulLabs-OSS/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

60
.devcontainer/README.md Normal file
View File

@ -0,0 +1,60 @@
# VS Code Dev Container
This is a docker-based interactive development environment using VS Code and Docker Dev Containers removing the need to install any tools locally*
## Requirements
- VS Code
- [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension
- Docker
- OpenOCD - For debugging
## Using
### Code editing, and building.
1. Clone InfiniTime and update submodules
2. Launch VS Code
3. Open InfiniTime directory,
4. Allow VS Code to open folder with devcontainer.
After this the environment will be built if you do not currently have a container setup, it will install all the necessary tools and extra VSCode extensions.
In order to build InfiniTime we need to run the initial submodule init and CMake commands.
#### Manually
You can use the VS Code terminal to run the CMake commands as outlined in the [build instructions](blob/develop/doc/buildAndProgram.md)
#### Script
The dev environment comes with some scripts to make this easier, They are located in /opt/.
There are also VS Code tasks provided should you desire to use those.
The task "update submodules" will update the git submodules
### Build
You can use the build.sh script located in /opt/
CMake is also configured and controls for the CMake plugin are available in VS Code
### Debugging
Docker on windows does not support passing USB devices to the underlying WSL2 subsystem, To get around this we use OpenOCD in server mode running on the host.
`openocd -f <yourinterface> -f <nrf52.cfg target file>`
This will launch OpenOCD in server mode and attach it to the MCU.
The default launch.json file expects OpenOCD to be listening on port 3333, edit if needed
## Current Issues
Currently WSL2 Has some real performance issues with IO on a windows host. Accessing files on the virtualized filesystem is much faster. Using VS Codes "clone in container" feature of the Remote - Containers will get around this. After the container is built you will need to update the submodules and follow the build instructions like normal

78
.devcontainer/build.sh Normal file
View File

@ -0,0 +1,78 @@
#!/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
}
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/JuulLabs-OSS/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 \
-DUSE_OPENOCD=1 \
-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
}
[[ $SOURCED == "false" ]] && main "$@" || echo "Sourced!"

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

2
.gitignore vendored
View File

@ -4,7 +4,7 @@
# CMake # CMake
cmake-build-* cmake-build-*
cmake-* cmake-*/
CMakeFiles CMakeFiles
**/CMakeCache.txt **/CMakeCache.txt
cmake_install.cmake cmake_install.cmake

20
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
"configurations": [
{
"name": "nrfCC",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/src/**",
"${workspaceFolder}/src"
],
"defines": [],
"compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "linux-gcc-arm",
"configurationProvider": "ms-vscode.cpp-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
}
],
"version": 4
}

62
.vscode/cmake-variants.json vendored Normal file
View File

@ -0,0 +1,62 @@
{
"buildType": {
"default": "release",
"choices": {
"debug": {
"short": "Debug",
"long": "Emit debug information without performing optimizations",
"buildType": "Debug"
},
"release": {
"short": "Release",
"long": "Perform optimizations",
"buildType": "Release"
}
}
},
"programmer":{
"default": "OpenOCD",
"choices":{
"OpenOCD":{
"short":"OpenOCD",
"long": "Use OpenOCD",
"settings":{
"USE_OPENOCD":1
}
},
"JLink":{
"short":"JLink",
"long": "Use JLink",
"settings":{
"USE_JLINK":1
}
},
"GDB":{
"short":"GDB",
"long": "Use GDB",
"settings":{
"USE_GDB_CLIENT":1
}
}
}
},
"DFU": {
"default": "no",
"choices": {
"no": {
"short": "No DFU",
"long": "Do not build DFU",
"settings": {
"BUILD_DFU":"0"
}
},
"yes": {
"short": "Build DFU",
"long": "Build DFU",
"settings": {
"BUILD_DFU":"1"
}
}
}
}
}

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["ms-vscode.cpptools","ms-vscode.cmake-tools","marus25.cortex-debug"]
}

64
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,64 @@
{
"version": "0.1.0",
"configurations": [
{
"name": "Debug - Openocd docker Remote",
"type":"cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "external",
// This may need to be arm-none-eabi-gdb depending on your system
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance
"gdbTarget": "host.docker.internal:3333",
"svdFile": "${workspaceRoot}/nrf52.svd",
"runToMain": true,
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
"continue"
]
},
{
"name": "Debug - Openocd Local",
"type":"cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "openocd",
// This may need to be arm-none-eabi-gdb depending on your system
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance
"gdbTarget": "localhost:3333",
"svdFile": "${workspaceRoot}/nrf52.svd",
"runToMain": true,
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
"continue"
]
},
{
"cwd": "${workspaceRoot}",
// TODO: find better way to get latest build filename
"executable": "./build/src/pinetime-app-1.3.0.out",
"name": "Debug OpenOCD ST-LINK pinetime-app-1.3.0.out",
"request": "launch",
"type": "cortex-debug",
"showDevDebugOutput": false,
"servertype": "openocd",
"runToMain": true,
// 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-9-2020-q2-update/bin",
"svdFile": "${workspaceRoot}/nrf52.svd",
"configFiles": [
"interface/stlink.cfg",
"target/nrf52.cfg"
],
}
]
}

64
.vscode/settings.json vendored
View File

@ -1,59 +1,9 @@
{ {
"files.associations": { "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"chrono": "cpp", "cmake.configureArgs": [
"list": "cpp", "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}",
"array": "cpp", "-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}",
"atomic": "cpp", ],
"bit": "cpp", "cmake.generator": "Unix Makefiles",
"*.tcc": "cpp", "clang-tidy.buildPath": "build/compile_commands.json"
"cctype": "cpp",
"charconv": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"netfwd": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp"
}
} }

44
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,44 @@
{
"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",
"command": "git submodule update --init",
"options": {
"cwd": "${workspaceFolder}"
},
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "BuildInit",
"dependsOn": [
"update submodules",
"create openocd build"
],
"problemMatcher": []
}
]
}

View File

@ -1,33 +0,0 @@
This contribution guide is in progress, improvements are welcome.
### Code style
Any C++ code PRs should aim to follow the style of existing code in the project.
Using an autoformatter is heavily recommended, but make sure it's configured properly.
There's currently preconfigured autoformatter rules for:
* CLion (IntelliJ) in .idea/codeStyles/Project.xml
You can use those to configure your own IDE if it's not already on the list.
#### Linting errors and compiler warnings
Try to avoid any currently enabled warnings and try to reduce the amount of linter errors.
#### Spelling
Make sure you spellcheck your code before commiting it.
#### TODO, FIXME
Check before commiting that you haven't forgotten anything, preferably don't leave these in your commits.
#### Licence headers
You should add your name to the comma-space separated list of contributors if there's a license header.
### License
By contributing you agree to licence your code under the repository's general license (which is currently GPL-v3+).

1
CONTRIBUTING.md Symbolic link
View File

@ -0,0 +1 @@
doc/contribute.md

View File

@ -93,6 +93,7 @@ As of now, here is the list of achievements of this project:
- [Build the project](doc/buildAndProgram.md) - [Build the project](doc/buildAndProgram.md)
- [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md) - [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md)
- [Build the project with Docker](doc/buildWithDocker.md) - [Build the project with Docker](doc/buildWithDocker.md)
- [Build the project with VSCode](doc/buildWithVScode.md)
- [Bootloader, OTA and DFU](./bootloader/README.md) - [Bootloader, OTA and DFU](./bootloader/README.md)
- [Stub using NRF52-DK](./doc/PinetimeStubWithNrf52DK.md) - [Stub using NRF52-DK](./doc/PinetimeStubWithNrf52DK.md)
- Logging with JLink RTT. - Logging with JLink RTT.

42
doc/buildWithVScode.md Normal file
View File

@ -0,0 +1,42 @@
# Build and Develop the project using VS Code
The .VS Code folder contains configuration files for developing InfiniTime with VS Code. Effort was made to have these rely on Environment variables instead of hardcoded paths.
## Environment Setup
To support as many setups as possible the VS Code configuration files expect there to be certain environment variables to be set.
Variable | Description | Example
----------|-------------|--------
**ARM_NONE_EABI_TOOLCHAIN_PATH**|path to the toolchain directory|`export ARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update`
**NRF5_SDK_PATH**|path to the NRF52 SDK|`export NRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345`
## VS Code Extensions
We leverage a few VS Code extensions for ease of development.
#### Required Extensions
- [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) - C/C++ IntelliSense, debugging, and code browsing.
- [CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) - Extended CMake support in Visual Studio Code
#### Optional Extensions
[Cortex-Debug](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug) - ARM Cortex-M GDB Debugger support for VS Code
Cortex-Debug is only required for interactive debugging using VS Codes built in GDB support.
## VS Code/Docker DevContainer
The .devcontainer folder contains the configuration and scripts for using a Docker dev container for building InfiniTime
Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up.
More documentation is available in the [readme in .devcontainer](.devcontainer/readme.md)

View File

@ -1,68 +1,89 @@
# How to contribute? # How to contribute?
## Report bugs ## Report bugs
You use your Pinetime and find a bug in the firmware? [Create an issue on Github](https://github.com/JF002/InfiniTime/issues) explaining the bug, how to reproduce it, the version of the firmware you use...
Have you found a bug in the firmware? [Create an issue on Github](https://github.com/JF002/InfiniTime/issues) explaining the bug, how to reproduce it, the version of the firmware you use...
## Write and improve documentation ## Write and improve documentation
Documentation might be incomplete, or not clear enough, and it is always possible to improve it with better wording, pictures, photo, video,... Documentation might be incomplete, or not clear enough, and it is always possible to improve it with better wording, pictures, photo, video,...
As the documentation is part of the source code, you can submit your improvements to the documentation by submitting a pull request (see below). As the documentation is part of the source code, you can submit your improvements to the documentation by submitting a pull request (see below).
## Fix bugs, add functionalities and improve the code ## Fix bugs, add functionalities and improve the code
You want to fix a bug, add a cool new functionality or improve the code? See *How to submit a pull request below*. You want to fix a bug, add a cool new functionality or improve the code? See *How to submit a pull request below*.
## Spread the word ## Spread the word
The Pinetime is a cool open source project that deserves to be known. Talk about it around you, on social networks, on your blog,... and let people know that we are working on an open-source firmware for a smartwatch!
The Pinetime is a cool open source project that deserves to be known. Talk about it around you, on social networks, on your blog,... and let people know that we are working on an open source firmware for a smartwatch!
# How to submit a pull request ? # How to submit a pull request ?
## TL;DR ## TL;DR
- Create a branch from develop; - Create a branch from develop;
- Work on a single subject in this branch. Create multiple branches/pulls-requests if you want to work on multiple subjects (bugs, features,...); - Work on a single subject in this branch. Create multiple branches/pulls-requests if you want to work on multiple subjects (bugs, features,...);
- Test your modifications on the actual hardware; - Test your modifications on the actual hardware;
- Check the code formatting against our coding conventions and [clang-format](../.clang-format) and [clang-tidy](../.clang-tidy); - Check the code formatting against our coding conventions and [clang-format](../.clang-format) and [clang-tidy](../.clang-tidy);
- Clean your code and remove files that are not needed; - Clean your code and remove files that are not needed;
- Write documentation related to your new feature is applicable; - Write documentation related to your new feature if applicable;
- Create the pull-request and write a great description about it : what does your PR do, why, how,... Add pictures and video if possible; - Create a pull request and write a great description about it : what does your PR do, why, how,... Add pictures and video if possible;
- Wait for someone to review your PR and take part in the review process; - Wait for someone to review your PR and take part in the review process;
- Your PR will eventually be merged :) - Your PR will eventually be merged :)
Your contribution is more than welcome! Your contributions are more than welcome!
If you want to fix a bug, add a functionality or improve the code, you'll first need to create a branch from the **develop** branch (see [this page about the branching model](./branches.md)). This branch is called a feature branch, and you should choose a name that explains what you are working on (ex: "add-doc-about-contributions"). In this branch, **focus on only one topic, bug or feature**. For example, if you created this branch to work on the UI of a specific application, do not commit modifications about the SPI driver. If you want to work on multiple topics, create one branch per topic. If you want to fix a bug, add functionality or improve the code, you'll first need to create a branch from the **develop** branch (see [this page about the branching model](./branches.md)). This branch is called a feature branch, and you should choose a name that explains what you are working on (ex: "add-doc-about-contributions"). In this branch, **focus on only one topic, bug or feature**. For example, if you created this branch to work on the UI of a specific application, do not commit modifications about the SPI driver. If you want to work on multiple topics, create one branch for each topic.
When your feature branch is ready, **make sure it actually works** and **do not forget to write documentation** about it if it's relevant. When your feature branch is ready, **make sure it actually works** and **do not forget to write documentation** about it if it's relevant.
I **strongly discourage to create a PR containing modifications that haven't been tested**. If, for any reason, you cannot test your modifications but want to publish them anyway, **please mention it in the description**. This way, other contributors might be willing to test it and provide feedback about your code. **Creating a pull request containing modifications that haven't been tested is strongly discouraged.** If, for any reason, you cannot test your modifications but want to publish them anyway, **please mention it in the description**. This way, other contributors might be willing to test it and provide feedback about your code.
Also, before submitting your PR, check the coding style of your code against the **coding conventions** detailed below. This project also provides [clang-format](../.clang-format) and [clang-tidy](../.clang-tidy) configuration files. You can use them to ensure correct formatting of your code. Also, before submitting your PR, check the coding style of your code against the **coding conventions** detailed below. This project also provides [clang-format](../.clang-format) and [clang-tidy](../.clang-tidy) configuration files. You can use them to ensure correct formatting of your code.
Do not forget to check the files you are going to commit and remove those who are not necessary (config files from your IDE, for example). Remove old comments, commented code,... Don't forget to check the files you are going to commit and remove those which aren't necessary (config files from your IDE, for example). Remove old comments, commented code,...
Then, you can submit a pull-request for review. Try to **describe your pull request as much as possible**: what did you do in this branch, how does it work, how is it designed, are there any limitations,... This will help the contributors to understand and review your code easily. You can add pictures and video to the description so that contributors will have a quick overview of your work. Then, you can submit a pull request for review. Try to **describe your pull request as much as possible**: what did you do in this branch, how does it work, how it is designed, are there any limitations,... This will help the contributors to understand and review your code easily. You can add pictures and video to the description so that contributors will have a quick overview of your work.
Other contributors can post comments about the pull request, maybe ask for more info or adjustments in the code. Other contributors can post comments about the pull request, maybe ask for more info or adjustments in the code.
Once the pull request is reviewed and accepted, it'll be merge in **develop** and will be released in the next release version of the firmware. Once the pull request is reviewed and accepted, it'll be merged into **develop** and will be released in the next version of the firmware.
## Why all these rules? ## Why all these rules?
Reviewing pull-requests is a **very time consuming task** for the creator of this project ([JF002](https://github.com/JF002)) and for other contributors who take the time to review them. Every little thing you do to make their lives easier will **increase the chances your PR will be merge quickly**.
When reviewing PR, the author and contributors will first look at the **description**. If it's easy to understand what the PR does, why the modification is needed or interesting and how it's done, a good part of the work is already done : we understand the PR and its context. Reviewing pull requests is a **very time consuming task** for the creator of this project ([JF002](https://github.com/JF002)) and for other contributors who take the time to review them. Everything you do to make reviewing easier will **get your PR merged faster**.
Then, reviewing **a few files that were modified for a single purpose** is a lot more easier than to review 30 files modified for many reasons (bug fix, UI improvements, typos in doc,...), even if all these changes make sense. Also, it's possible that we agree on some modification but not on some other, and we won't be able to merge the PR because of the changes that are not accepted. When reviewing PRs, the author and contributors will first look at the **description**. If it's easy to understand what the PR does, why the modification is needed or interesting and how it's done, a good part of the work is already done : we understand the PR and its context.
We do our best to keep the code as consistent as possible, and that means we pay attention to the **formatting** of the code. If the code formatting is not consistent with our code base, we'll ask you to review it, which will take more time. Then, reviewing **a few files that were modified for a single purpose** is a lot more easier than to review 30 files modified for many reasons (bug fix, UI improvements, typos in doc,...), even if all these changes make sense. Also, it's possible that we agree on some modification but not on some other, so we won't be able to merge the PR because of the changes that are not accepted.
The last step of the review consists in **testing** the modification. If it doesn't work out of the box, we'll ask your to review your code and to ensure that it works as expected. We do our best to keep the code as consistent as possible. If the formatting of the code in your PR is not consistent with our code base, we'll ask you to review it, which will take more time.
It's totally normal for a PR to need some more work even after it was created, that's why we review them. But every round trip takes time, and it's good practice to try to reduce them as much as possible by following those simple rules. The last step of the review consists of **testing** the modification. If it doesn't work out of the box, we'll ask your to review your code and to ensure that it works as expected.
It's totally normal for a PR to need some more work even after it was created, that's why we review them. But every round trip takes time, so it's good practice to try to reduce them as much as possible by following those simple rules.
# Coding convention # Coding convention
## Language ## Language
The language of this project is **C++**, and all new code must be written in C++. (Modern) C++ provides a lot of useful tools and functionalities that are beneficial for embedded software development like `constexpr`, `template` and anything that provides zero-cost abstraction. The language of this project is **C++**, and all new code must be written in C++. (Modern) C++ provides a lot of useful tools and functionalities that are beneficial for embedded software development like `constexpr`, `template` and anything that provides zero-cost abstraction.
It's OK to include C code if this code comes from another library like FreeRTOS, NimBLE, LVGL or the NRF-SDK. C code is accepted if it comes from another library like FreeRTOS, NimBLE, LVGL or the NRF-SDK.
## Coding style ## Coding style
The most important rule to follow is to try to keep the code as easy to read and maintain as possible. The most important rule to follow is to try to keep the code as easy to read and maintain as possible.
Using an autoformatter is highly recommended, but make sure it's configured properly.
There are preconfigured autoformatter rules for:
* CLion (IntelliJ) in .idea/codeStyles/Project.xml
If there are no preconfigured rules for your IDE, you can use one of the existing ones to configure your IDE.
- **Indentation** : 2 spaces, no tabulation - **Indentation** : 2 spaces, no tabulation
- **Opening brace** at the end of the line - **Opening brace** at the end of the line
- **Naming** : Choose self-describing variable name - **Naming** : Choose self-describing variable name

BIN
doc/ui/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

16
doc/ui_guidelines.md Normal file
View File

@ -0,0 +1,16 @@
# UI design guidelines
- Align objects all the way to the edge or corner
- Buttons should generally be at least 50px high
- Buttons should generally be on the bottom edge
- Make interactable objects **big**
- Recommendations for inner padding, aka distance between buttons:
- When aligning 4 objects: 4px, e.g. Settings
- When aligning 3 objects: 6px, e.g. App list
- When aligning 2 objects: 10px, e.g. Quick settings
- When using a page indicator, leave 8px for it on the right side
- It is acceptable to leave 8px on the left side as well to center the content
- Top bar takes at least 20px + padding
- Top bar right icons move 8px to the left when using a page indicator
![example layouts](./ui/example.png)

View File

@ -495,6 +495,8 @@ list(APPEND SOURCE_FILES
components/heartrate/Biquad.cpp components/heartrate/Biquad.cpp
components/heartrate/Ptagc.cpp components/heartrate/Ptagc.cpp
components/heartrate/HeartRateController.cpp components/heartrate/HeartRateController.cpp
touchhandler/TouchHandler.cpp
) )
list(APPEND RECOVERY_SOURCE_FILES list(APPEND RECOVERY_SOURCE_FILES
@ -552,6 +554,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/heartrate/Ptagc.cpp components/heartrate/Ptagc.cpp
components/motor/MotorController.cpp components/motor/MotorController.cpp
components/fs/FS.cpp components/fs/FS.cpp
touchhandler/TouchHandler.cpp
) )
list(APPEND RECOVERYLOADER_SOURCE_FILES list(APPEND RECOVERYLOADER_SOURCE_FILES
@ -660,6 +663,7 @@ set(INCLUDE_FILES
components/heartrate/Ptagc.h components/heartrate/Ptagc.h
components/heartrate/HeartRateController.h components/heartrate/HeartRateController.h
components/motor/MotorController.h components/motor/MotorController.h
touchhandler/TouchHandler.h
) )
include_directories( include_directories(
@ -839,7 +843,7 @@ target_compile_options(${EXECUTABLE_NAME} PUBLIC
set_target_properties(${EXECUTABLE_NAME} PROPERTIES set_target_properties(${EXECUTABLE_NAME} PROPERTIES
SUFFIX ".out" SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_FILE_NAME}.map" LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections -Wl,--print-memory-usage --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_FILE_NAME}.map"
) )
add_custom_command(TARGET ${EXECUTABLE_NAME} add_custom_command(TARGET ${EXECUTABLE_NAME}
@ -869,7 +873,7 @@ target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES
SUFFIX ".out" SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT_MCUBOOT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_MCUBOOT_FILE_NAME}.map" LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT_MCUBOOT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections -Wl,--print-memory-usage --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_MCUBOOT_FILE_NAME}.map"
) )
add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_NAME} add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_NAME}
@ -906,7 +910,7 @@ target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES
SUFFIX ".out" SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_RECOVERY_FILE_NAME}.map" LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections -Wl,--print-memory-usage --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_RECOVERY_FILE_NAME}.map"
) )
add_custom_command(TARGET ${EXECUTABLE_RECOVERY_NAME} add_custom_command(TARGET ${EXECUTABLE_RECOVERY_NAME}
@ -936,7 +940,7 @@ target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES
SUFFIX ".out" SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_GRAPHICS_FILE_NAME}.map" LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections -Wl,--print-memory-usage --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_GRAPHICS_FILE_NAME}.map"
) )
add_custom_command(TARGET ${EXECUTABLE_RECOVERY_MCUBOOT_NAME} add_custom_command(TARGET ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}
@ -977,7 +981,7 @@ add_dependencies(${EXECUTABLE_RECOVERYLOADER_NAME} ${EXECUTABLE_RECOVERY_MCUBOOT
set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES
SUFFIX ".out" SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.map" LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections -Wl,--print-memory-usage --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.map"
) )
add_custom_command(TARGET ${EXECUTABLE_RECOVERYLOADER_NAME} add_custom_command(TARGET ${EXECUTABLE_RECOVERYLOADER_NAME}
@ -1010,7 +1014,7 @@ add_dependencies(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${EXECUTABLE_RECOVERY
set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES
SUFFIX ".out" SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -std=gnu++98 -std=c99 -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT_MCUBOOT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.map" LINK_FLAGS "-mthumb -mabi=aapcs -std=gnu++98 -std=c99 -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT_MCUBOOT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections -Wl,--print-memory-usage --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.map"
) )
add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}

View File

@ -23,7 +23,6 @@ void Battery::Update() {
return; return;
} }
// Non blocking read // Non blocking read
samples = 0;
isReading = true; isReading = true;
SaadcInit(); SaadcInit();
@ -40,9 +39,9 @@ void Battery::SaadcInit() {
nrf_saadc_channel_config_t adcChannelConfig = {.resistor_p = NRF_SAADC_RESISTOR_DISABLED, nrf_saadc_channel_config_t adcChannelConfig = {.resistor_p = NRF_SAADC_RESISTOR_DISABLED,
.resistor_n = NRF_SAADC_RESISTOR_DISABLED, .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
.gain = NRF_SAADC_GAIN1_5, .gain = NRF_SAADC_GAIN1_4,
.reference = NRF_SAADC_REFERENCE_INTERNAL, .reference = NRF_SAADC_REFERENCE_INTERNAL,
.acq_time = NRF_SAADC_ACQTIME_3US, .acq_time = NRF_SAADC_ACQTIME_40US,
.mode = NRF_SAADC_MODE_SINGLE_ENDED, .mode = NRF_SAADC_MODE_SINGLE_ENDED,
.burst = NRF_SAADC_BURST_ENABLED, .burst = NRF_SAADC_BURST_ENABLED,
.pin_p = batteryVoltageAdcInput, .pin_p = batteryVoltageAdcInput,
@ -60,22 +59,21 @@ void Battery::SaadcEventHandler(nrfx_saadc_evt_t const* p_event) {
APP_ERROR_CHECK(nrfx_saadc_buffer_convert(&saadc_value, 1)); APP_ERROR_CHECK(nrfx_saadc_buffer_convert(&saadc_value, 1));
// A hardware voltage divider divides the battery voltage by 2 // A hardware voltage divider divides the battery voltage by 2
// ADC gain is 1/5 // ADC gain is 1/4
// thus adc_voltage = battery_voltage / 2 * gain = battery_voltage / 10 // thus adc_voltage = battery_voltage / 2 * gain = battery_voltage / 8
// reference_voltage is 0.6V // reference_voltage is 600mV
// p_event->data.done.p_buffer[0] = (adc_voltage / reference_voltage) * 1024 // p_event->data.done.p_buffer[0] = (adc_voltage / reference_voltage) * 1024
voltage = p_event->data.done.p_buffer[0] * 6000 / 1024; voltage = p_event->data.done.p_buffer[0] * (8 * 600) / 1024;
percentRemaining = (voltage - battery_min) * 100 / (battery_max - battery_min);
percentRemaining = std::max(percentRemaining, 0); if (voltage > battery_max) {
percentRemaining = std::min(percentRemaining, 100); percentRemaining = 100;
percentRemainingBuffer.Insert(percentRemaining); } else if (voltage < battery_min) {
percentRemaining = 0;
} else {
percentRemaining = (voltage - battery_min) * 100 / (battery_max - battery_min);
}
samples++;
if (samples > percentRemainingSamples) {
nrfx_saadc_uninit(); nrfx_saadc_uninit();
isReading = false; isReading = false;
} else {
nrfx_saadc_sample();
}
} }
} }

View File

@ -7,38 +7,6 @@
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
/** A simple circular buffer that can be used to average
out the sensor values. The total capacity of the CircBuffer
is given as the template parameter N.
*/
template <int N> class CircBuffer {
public:
CircBuffer() : arr {}, sz {}, cap {N}, head {} {
}
/**
insert member function overwrites the next data to the current
HEAD and moves the HEAD to the newly inserted value.
*/
void Insert(const uint8_t num) {
head %= cap;
arr[head++] = num;
if (sz != cap) {
sz++;
}
}
uint8_t GetAverage() const {
int sum = std::accumulate(arr.begin(), arr.end(), 0);
return static_cast<uint8_t>(sum / sz);
}
private:
std::array<uint8_t, N> arr; /**< internal array used to store the values*/
uint8_t sz; /**< The current size of the array.*/
uint8_t cap; /**< Total capacity of the CircBuffer.*/
uint8_t head; /**< The current head of the CircBuffer*/
};
class Battery { class Battery {
public: public:
Battery(); Battery();
@ -47,10 +15,7 @@ namespace Pinetime {
void Update(); void Update();
uint8_t PercentRemaining() const { uint8_t PercentRemaining() const {
auto avg = percentRemainingBuffer.GetAverage(); return percentRemaining;
avg = std::min(avg, static_cast<uint8_t>(100));
avg = std::max(avg, static_cast<uint8_t>(0));
return avg;
} }
uint16_t Voltage() const { uint16_t Voltage() const {
@ -69,14 +34,11 @@ namespace Pinetime {
static Battery* instance; static Battery* instance;
nrf_saadc_value_t saadc_value; nrf_saadc_value_t saadc_value;
static constexpr uint8_t percentRemainingSamples = 5;
CircBuffer<percentRemainingSamples> percentRemainingBuffer {};
static constexpr uint32_t chargingPin = 12; static constexpr uint32_t chargingPin = 12;
static constexpr uint32_t powerPresentPin = 19; static constexpr uint32_t powerPresentPin = 19;
static constexpr nrf_saadc_input_t batteryVoltageAdcInput = NRF_SAADC_INPUT_AIN7; static constexpr nrf_saadc_input_t batteryVoltageAdcInput = NRF_SAADC_INPUT_AIN7;
uint16_t voltage = 0; uint16_t voltage = 0;
int percentRemaining = -1; uint8_t percentRemaining = 0;
bool isCharging = false; bool isCharging = false;
bool isPowerPresent = false; bool isPowerPresent = false;
@ -87,7 +49,6 @@ namespace Pinetime {
static void AdcCallbackStatic(nrfx_saadc_evt_t const* event); static void AdcCallbackStatic(nrfx_saadc_evt_t const* event);
bool isReading = false; bool isReading = false;
uint8_t samples = 0;
}; };
} }
} }

View File

@ -55,9 +55,9 @@ void DateTime::UpdateTime(uint32_t systickCounter) {
auto time = date::make_time(currentDateTime - dp); auto time = date::make_time(currentDateTime - dp);
auto yearMonthDay = date::year_month_day(dp); auto yearMonthDay = date::year_month_day(dp);
year = (int) yearMonthDay.year(); year = static_cast<int>(yearMonthDay.year());
month = static_cast<Months>((unsigned) yearMonthDay.month()); month = static_cast<Months>(static_cast<unsigned>(yearMonthDay.month()));
day = (unsigned) yearMonthDay.day(); day = static_cast<unsigned>(yearMonthDay.day());
dayOfWeek = static_cast<Days>(date::weekday(yearMonthDay).iso_encoding()); dayOfWeek = static_cast<Days>(date::weekday(yearMonthDay).iso_encoding());
hour = time.hours().count(); hour = time.hours().count();
@ -75,31 +75,31 @@ void DateTime::UpdateTime(uint32_t systickCounter) {
} }
const char* DateTime::MonthShortToString() { const char* DateTime::MonthShortToString() {
return DateTime::MonthsString[(uint8_t) month]; return DateTime::MonthsString[static_cast<uint8_t>(month)];
} }
const char* DateTime::MonthShortToStringLow() { const char* DateTime::MonthShortToStringLow() {
return DateTime::MonthsStringLow[(uint8_t) month]; return DateTime::MonthsStringLow[static_cast<uint8_t>(month)];
} }
const char* DateTime::MonthsToStringLow() { const char* DateTime::MonthsToStringLow() {
return DateTime::MonthsLow[(uint8_t) month]; return DateTime::MonthsLow[static_cast<uint8_t>(month)];
} }
const char* DateTime::DayOfWeekToString() { const char* DateTime::DayOfWeekToString() {
return DateTime::DaysString[(uint8_t) dayOfWeek]; return DateTime::DaysString[static_cast<uint8_t>(dayOfWeek)];
} }
const char* DateTime::DayOfWeekShortToString() { const char* DateTime::DayOfWeekShortToString() {
return DateTime::DaysStringShort[(uint8_t) dayOfWeek]; return DateTime::DaysStringShort[static_cast<uint8_t>(dayOfWeek)];
} }
const char* DateTime::DayOfWeekToStringLow() { const char* DateTime::DayOfWeekToStringLow() {
return DateTime::DaysStringLow[(uint8_t) dayOfWeek]; return DateTime::DaysStringLow[static_cast<uint8_t>(dayOfWeek)];
} }
const char* DateTime::DayOfWeekShortToStringLow() { const char* DateTime::DayOfWeekShortToStringLow() {
return DateTime::DaysStringShortLow[(uint8_t) dayOfWeek]; return DateTime::DaysStringShortLow[static_cast<uint8_t>(dayOfWeek)];
} }
void DateTime::Register(Pinetime::System::SystemTask* systemTask) { void DateTime::Register(Pinetime::System::SystemTask* systemTask) {

View File

@ -3,7 +3,8 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "app_timer.h" #include "app_timer.h"
APP_TIMER_DEF(vibTimer); APP_TIMER_DEF(shortVibTimer);
APP_TIMER_DEF(longVibTimer);
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
@ -13,19 +14,39 @@ MotorController::MotorController(Controllers::Settings& settingsController) : se
void MotorController::Init() { void MotorController::Init() {
nrf_gpio_cfg_output(pinMotor); nrf_gpio_cfg_output(pinMotor);
nrf_gpio_pin_set(pinMotor); nrf_gpio_pin_set(pinMotor);
app_timer_create(&vibTimer, APP_TIMER_MODE_SINGLE_SHOT, vibrate); app_timer_init();
app_timer_create(&shortVibTimer, APP_TIMER_MODE_SINGLE_SHOT, StopMotor);
app_timer_create(&longVibTimer, APP_TIMER_MODE_REPEATED, Ring);
} }
void MotorController::SetDuration(uint8_t motorDuration) { void MotorController::Ring(void* p_context) {
auto* motorController = static_cast<MotorController*>(p_context);
motorController->RunForDuration(50);
}
if (settingsController.GetVibrationStatus() == Controllers::Settings::Vibration::OFF) void MotorController::RunForDuration(uint8_t motorDuration) {
if (settingsController.GetVibrationStatus() == Controllers::Settings::Vibration::OFF) {
return; return;
}
nrf_gpio_pin_clear(pinMotor); nrf_gpio_pin_clear(pinMotor);
/* Start timer for motorDuration miliseconds and timer triggers vibrate() when it finishes*/ app_timer_start(shortVibTimer, APP_TIMER_TICKS(motorDuration), nullptr);
app_timer_start(vibTimer, APP_TIMER_TICKS(motorDuration), NULL);
} }
void MotorController::vibrate(void* p_context) { void MotorController::StartRinging() {
if (settingsController.GetVibrationStatus() == Controllers::Settings::Vibration::OFF) {
return;
}
Ring(this);
app_timer_start(longVibTimer, APP_TIMER_TICKS(1000), this);
}
void MotorController::StopRinging() {
app_timer_stop(longVibTimer);
nrf_gpio_pin_set(pinMotor);
}
void MotorController::StopMotor(void* p_context) {
nrf_gpio_pin_set(pinMotor); nrf_gpio_pin_set(pinMotor);
} }

View File

@ -12,11 +12,14 @@ namespace Pinetime {
public: public:
MotorController(Controllers::Settings& settingsController); MotorController(Controllers::Settings& settingsController);
void Init(); void Init();
void SetDuration(uint8_t motorDuration); void RunForDuration(uint8_t motorDuration);
void StartRinging();
static void StopRinging();
private: private:
static void Ring(void* p_context);
Controllers::Settings& settingsController; Controllers::Settings& settingsController;
static void vibrate(void* p_context); static void StopMotor(void* p_context);
}; };
} }
} }

View File

@ -53,9 +53,8 @@ namespace {
return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0; return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0;
} }
TouchEvents Convert(Pinetime::Drivers::Cst816S::TouchInfos info) { TouchEvents ConvertGesture(Pinetime::Drivers::Cst816S::Gestures gesture) {
if (info.isTouch) { switch (gesture) {
switch (info.gesture) {
case Pinetime::Drivers::Cst816S::Gestures::SingleTap: case Pinetime::Drivers::Cst816S::Gestures::SingleTap:
return TouchEvents::Tap; return TouchEvents::Tap;
case Pinetime::Drivers::Cst816S::Gestures::LongPress: case Pinetime::Drivers::Cst816S::Gestures::LongPress:
@ -75,8 +74,6 @@ namespace {
return TouchEvents::None; return TouchEvents::None;
} }
} }
return TouchEvents::None;
}
} }
DisplayApp::DisplayApp(Drivers::St7789& lcd, DisplayApp::DisplayApp(Drivers::St7789& lcd,
@ -91,7 +88,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Controllers::Settings& settingsController, Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::MotionController& motionController,
Pinetime::Controllers::TimerController& timerController) Pinetime::Controllers::TimerController& timerController,
Pinetime::Controllers::TouchHandler& touchHandler)
: lcd {lcd}, : lcd {lcd},
lvgl {lvgl}, lvgl {lvgl},
touchPanel {touchPanel}, touchPanel {touchPanel},
@ -104,7 +102,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
settingsController {settingsController}, settingsController {settingsController},
motorController {motorController}, motorController {motorController},
motionController {motionController}, motionController {motionController},
timerController {timerController} { timerController {timerController},
touchHandler {touchHandler} {
} }
void DisplayApp::Start() { void DisplayApp::Start() {
@ -212,8 +211,7 @@ void DisplayApp::Refresh() {
if (state != States::Running) { if (state != States::Running) {
break; break;
} }
auto info = touchPanel.GetTouchInfo(); auto gesture = ConvertGesture(touchHandler.GestureGet());
auto gesture = Convert(info);
if (gesture == TouchEvents::None) { if (gesture == TouchEvents::None) {
break; break;
} }
@ -239,11 +237,9 @@ void DisplayApp::Refresh() {
LoadApp(returnToApp, returnDirection); LoadApp(returnToApp, returnDirection);
brightnessController.Set(settingsController.GetBrightness()); brightnessController.Set(settingsController.GetBrightness());
brightnessController.Backup(); brightnessController.Backup();
} else if (touchMode == TouchModes::Gestures) {
if (gesture == TouchEvents::Tap) {
lvgl.SetNewTapEvent(info.x, info.y);
}
} }
} else {
touchHandler.CancelTap();
} }
} break; } break;
case Messages::ButtonPushed: case Messages::ButtonPushed:
@ -273,13 +269,8 @@ void DisplayApp::Refresh() {
nextApp = Apps::None; nextApp = Apps::None;
} }
if (state != States::Idle && touchMode == TouchModes::Polling) { if (touchHandler.IsTouching()) {
auto info = touchPanel.GetTouchInfo(); currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY());
if (info.action == 2) { // 2 = contact
if (!currentScreen->OnTouchEvent(info.x, info.y)) {
lvgl.SetNewTapEvent(info.x, info.y);
}
}
} }
} }
@ -302,6 +293,7 @@ void DisplayApp::ReturnApp(Apps app, DisplayApp::FullRefreshDirections direction
} }
void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction) { void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction) {
touchHandler.CancelTap();
currentScreen.reset(nullptr); currentScreen.reset(nullptr);
SetFullRefresh(direction); SetFullRefresh(direction);
@ -336,12 +328,12 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
case Apps::Notifications: case Apps::Notifications:
currentScreen = std::make_unique<Screens::Notifications>( currentScreen = std::make_unique<Screens::Notifications>(
this, notificationManager, systemTask->nimble().alertService(), Screens::Notifications::Modes::Normal); this, notificationManager, systemTask->nimble().alertService(), motorController, Screens::Notifications::Modes::Normal);
ReturnApp(Apps::Clock, FullRefreshDirections::Up, TouchEvents::SwipeUp); ReturnApp(Apps::Clock, FullRefreshDirections::Up, TouchEvents::SwipeUp);
break; break;
case Apps::NotificationsPreview: case Apps::NotificationsPreview:
currentScreen = std::make_unique<Screens::Notifications>( currentScreen = std::make_unique<Screens::Notifications>(
this, notificationManager, systemTask->nimble().alertService(), Screens::Notifications::Modes::Preview); this, notificationManager, systemTask->nimble().alertService(), motorController, Screens::Notifications::Modes::Preview);
ReturnApp(Apps::Clock, FullRefreshDirections::Up, TouchEvents::SwipeUp); ReturnApp(Apps::Clock, FullRefreshDirections::Up, TouchEvents::SwipeUp);
break; break;
case Apps::Timer: case Apps::Timer:
@ -414,6 +406,7 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
break; break;
case Apps::Metronome: case Apps::Metronome:
currentScreen = std::make_unique<Screens::Metronome>(this, motorController, *systemTask); currentScreen = std::make_unique<Screens::Metronome>(this, motorController, *systemTask);
ReturnApp(Apps::Launcher, FullRefreshDirections::Down, TouchEvents::None);
break; break;
case Apps::Motion: case Apps::Motion:
currentScreen = std::make_unique<Screens::Motion>(this, motionController); currentScreen = std::make_unique<Screens::Motion>(this, motionController);
@ -466,10 +459,6 @@ void DisplayApp::SetFullRefresh(DisplayApp::FullRefreshDirections direction) {
} }
} }
void DisplayApp::SetTouchMode(DisplayApp::TouchModes mode) {
touchMode = mode;
}
void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) { void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
if (systemTask != nullptr) if (systemTask != nullptr)
systemTask->PushMessage(message); systemTask->PushMessage(message);

View File

@ -14,6 +14,7 @@
#include "components/settings/Settings.h" #include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "components/timer/TimerController.h" #include "components/timer/TimerController.h"
#include "touchhandler/TouchHandler.h"
#include "Messages.h" #include "Messages.h"
namespace Pinetime { namespace Pinetime {
@ -31,6 +32,7 @@ namespace Pinetime {
class NotificationManager; class NotificationManager;
class HeartRateController; class HeartRateController;
class MotionController; class MotionController;
class TouchHandler;
} }
namespace System { namespace System {
@ -41,7 +43,6 @@ namespace Pinetime {
public: public:
enum class States { Idle, Running }; enum class States { Idle, Running };
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim }; enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
enum class TouchModes { Gestures, Polling };
DisplayApp(Drivers::St7789& lcd, DisplayApp(Drivers::St7789& lcd,
Components::LittleVgl& lvgl, Components::LittleVgl& lvgl,
@ -55,14 +56,14 @@ namespace Pinetime {
Controllers::Settings& settingsController, Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::MotionController& motionController,
Pinetime::Controllers::TimerController& timerController); Pinetime::Controllers::TimerController& timerController,
Pinetime::Controllers::TouchHandler& touchHandler);
void Start(); void Start();
void PushMessage(Display::Messages msg); void PushMessage(Display::Messages msg);
void StartApp(Apps app, DisplayApp::FullRefreshDirections direction); void StartApp(Apps app, DisplayApp::FullRefreshDirections direction);
void SetFullRefresh(FullRefreshDirections direction); void SetFullRefresh(FullRefreshDirections direction);
void SetTouchMode(TouchModes mode);
void Register(Pinetime::System::SystemTask* systemTask); void Register(Pinetime::System::SystemTask* systemTask);
@ -81,6 +82,7 @@ namespace Pinetime {
Pinetime::Controllers::MotorController& motorController; Pinetime::Controllers::MotorController& motorController;
Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::MotionController& motionController;
Pinetime::Controllers::TimerController& timerController; Pinetime::Controllers::TimerController& timerController;
Pinetime::Controllers::TouchHandler& touchHandler;
Pinetime::Controllers::FirmwareValidator validator; Pinetime::Controllers::FirmwareValidator validator;
Controllers::BrightnessController brightnessController; Controllers::BrightnessController brightnessController;
@ -100,8 +102,7 @@ namespace Pinetime {
FullRefreshDirections returnDirection = FullRefreshDirections::None; FullRefreshDirections returnDirection = FullRefreshDirections::None;
TouchEvents returnTouchEvent = TouchEvents::None; TouchEvents returnTouchEvent = TouchEvents::None;
TouchModes touchMode = TouchModes::Gestures; TouchEvents GetGesture();
void RunningState(); void RunningState();
void IdleState(); void IdleState();
static void Process(void* instance); static void Process(void* instance);

View File

@ -3,6 +3,7 @@
#include <task.h> #include <task.h>
#include <libraries/log/nrf_log.h> #include <libraries/log/nrf_log.h>
#include <components/rle/RleDecoder.h> #include <components/rle/RleDecoder.h>
#include <touchhandler/TouchHandler.h>
#include "displayapp/icons/infinitime/infinitime-nb.c" #include "displayapp/icons/infinitime/infinitime-nb.c"
using namespace Pinetime::Applications; using namespace Pinetime::Applications;
@ -19,7 +20,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Controllers::Settings& settingsController, Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::MotionController& motionController,
Pinetime::Controllers::TimerController& timerController) Pinetime::Controllers::TimerController& timerController,
Pinetime::Controllers::TouchHandler& touchHandler)
: lcd {lcd}, bleController {bleController} { : lcd {lcd}, bleController {bleController} {
} }

View File

@ -29,6 +29,9 @@ namespace Pinetime {
namespace System { namespace System {
class SystemTask; class SystemTask;
}; };
namespace Controllers {
class TouchHandler;
}
namespace Applications { namespace Applications {
class DisplayApp { class DisplayApp {
public: public:
@ -44,7 +47,8 @@ namespace Pinetime {
Controllers::Settings& settingsController, Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::MotionController& motionController,
Pinetime::Controllers::TimerController& timerController); Pinetime::Controllers::TimerController& timerController,
Pinetime::Controllers::TouchHandler& touchHandler);
void Start(); void Start();
void PushMessage(Pinetime::Applications::Display::Messages msg); void PushMessage(Pinetime::Applications::Display::Messages msg);
void Register(Pinetime::System::SystemTask* systemTask); void Register(Pinetime::System::SystemTask* systemTask);

View File

@ -32,6 +32,9 @@ namespace Pinetime {
} }
void SetNewTapEvent(uint16_t x, uint16_t y) { void SetNewTapEvent(uint16_t x, uint16_t y) {
} }
void SetNewTouchPoint(uint16_t x, uint16_t y, bool contact) {
}
}; };
} }
} }

View File

@ -166,43 +166,21 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
lv_disp_flush_ready(&disp_drv); lv_disp_flush_ready(&disp_drv);
} }
void LittleVgl::SetNewTapEvent(uint16_t x, uint16_t y) { void LittleVgl::SetNewTouchPoint(uint16_t x, uint16_t y, bool contact) {
tap_x = x; tap_x = x;
tap_y = y; tap_y = y;
tapped = true; tapped = contact;
} }
bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) { bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) {
if (tapped) {
ptr->point.x = tap_x; ptr->point.x = tap_x;
ptr->point.y = tap_y; ptr->point.y = tap_y;
if (tapped) {
ptr->state = LV_INDEV_STATE_PR; ptr->state = LV_INDEV_STATE_PR;
tapped = false;
} else { } else {
ptr->state = LV_INDEV_STATE_REL; ptr->state = LV_INDEV_STATE_REL;
} }
return false; return false;
/*
auto info = touchPanel.GetTouchInfo();
if((previousClick.x != info.x || previousClick.y != info.y) &&
(info.gesture == Drivers::Cst816S::Gestures::SingleTap)) {
// TODO For an unknown reason, the first touch is taken twice into account.
// 'firstTouch' is a quite'n'dirty workaound until I find a better solution
if(firstTouch) ptr->state = LV_INDEV_STATE_REL;
else ptr->state = LV_INDEV_STATE_PR;
firstTouch = false;
previousClick.x = info.x;
previousClick.y = info.y;
}
else {
ptr->state = LV_INDEV_STATE_REL;
}
ptr->point.x = info.x;
ptr->point.y = info.y;
return false;
*/
} }
void LittleVgl::InitTheme() { void LittleVgl::InitTheme() {

View File

@ -24,7 +24,7 @@ namespace Pinetime {
void FlushDisplay(const lv_area_t* area, lv_color_t* color_p); void FlushDisplay(const lv_area_t* area, lv_color_t* color_p);
bool GetTouchPadInfo(lv_indev_data_t* ptr); bool GetTouchPadInfo(lv_indev_data_t* ptr);
void SetFullRefresh(FullRefreshDirections direction); void SetFullRefresh(FullRefreshDirections direction);
void SetNewTapEvent(uint16_t x, uint16_t y); void SetNewTouchPoint(uint16_t x, uint16_t y, bool contact);
private: private:
void InitDisplay(); void InitDisplay();

View File

@ -5,13 +5,13 @@
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
const char* BatteryIcon::GetBatteryIcon(uint8_t batteryPercent) { const char* BatteryIcon::GetBatteryIcon(uint8_t batteryPercent) {
if (batteryPercent > 90) if (batteryPercent > 87)
return Symbols::batteryFull; return Symbols::batteryFull;
if (batteryPercent > 75) if (batteryPercent > 62)
return Symbols::batteryThreeQuarter; return Symbols::batteryThreeQuarter;
if (batteryPercent > 50) if (batteryPercent > 37)
return Symbols::batteryHalf; return Symbols::batteryHalf;
if (batteryPercent > 25) if (batteryPercent > 12)
return Symbols::batteryOneQuarter; return Symbols::batteryOneQuarter;
return Symbols::batteryEmpty; return Symbols::batteryEmpty;
} }

View File

@ -2,11 +2,6 @@
#include <date/date.h> #include <date/date.h>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <cstdio>
#include "BatteryIcon.h"
#include "BleIcon.h"
#include "NotificationIcon.h"
#include "Symbols.h"
#include "components/battery/BatteryController.h" #include "components/battery/BatteryController.h"
#include "components/motion/MotionController.h" #include "components/motion/MotionController.h"
#include "components/ble/BleController.h" #include "components/ble/BleController.h"
@ -89,16 +84,3 @@ std::unique_ptr<Screen> Clock::PineTimeStyleScreen() {
settingsController, settingsController,
motionController); motionController);
} }
/*
// Examples for more watch faces
std::unique_ptr<Screen> Clock::WatchFaceMinimalScreen() {
return std::make_unique<Screens::WatchFaceMinimal>(app, dateTimeController, batteryController, bleController, notificatioManager,
settingsController);
}
std::unique_ptr<Screen> Clock::WatchFaceCustomScreen() {
return std::make_unique<Screens::WatchFaceCustom>(app, dateTimeController, batteryController, bleController, notificatioManager,
settingsController);
}
*/

View File

@ -9,9 +9,6 @@
#include "components/datetime/DateTimeController.h" #include "components/datetime/DateTimeController.h"
namespace Pinetime { namespace Pinetime {
namespace Drivers {
class BMA421;
}
namespace Controllers { namespace Controllers {
class Settings; class Settings;
class Battery; class Battery;
@ -51,10 +48,6 @@ namespace Pinetime {
std::unique_ptr<Screen> WatchFaceDigitalScreen(); std::unique_ptr<Screen> WatchFaceDigitalScreen();
std::unique_ptr<Screen> WatchFaceAnalogScreen(); std::unique_ptr<Screen> WatchFaceAnalogScreen();
std::unique_ptr<Screen> PineTimeStyleScreen(); std::unique_ptr<Screen> PineTimeStyleScreen();
// Examples for more watch faces
// std::unique_ptr<Screen> WatchFaceMinimalScreen();
// std::unique_ptr<Screen> WatchFaceCustomScreen();
}; };
} }
} }

View File

@ -38,8 +38,9 @@ FirmwareValidation::FirmwareValidation(Pinetime::Applications::DisplayApp* app,
lv_label_set_text(labelIsValidated, "Please #00ff00 Validate# this version or\n#ff0000 Reset# to rollback to the previous version."); lv_label_set_text(labelIsValidated, "Please #00ff00 Validate# this version or\n#ff0000 Reset# to rollback to the previous version.");
buttonValidate = lv_btn_create(lv_scr_act(), nullptr); buttonValidate = lv_btn_create(lv_scr_act(), nullptr);
lv_obj_align(buttonValidate, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
buttonValidate->user_data = this; buttonValidate->user_data = this;
lv_obj_set_size(buttonValidate, 115, 50);
lv_obj_align(buttonValidate, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
lv_obj_set_event_cb(buttonValidate, ButtonEventHandler); lv_obj_set_event_cb(buttonValidate, ButtonEventHandler);
lv_obj_set_style_local_bg_color(buttonValidate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x009900)); lv_obj_set_style_local_bg_color(buttonValidate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x009900));
@ -48,6 +49,7 @@ FirmwareValidation::FirmwareValidation(Pinetime::Applications::DisplayApp* app,
buttonReset = lv_btn_create(lv_scr_act(), nullptr); buttonReset = lv_btn_create(lv_scr_act(), nullptr);
buttonReset->user_data = this; buttonReset->user_data = this;
lv_obj_set_size(buttonReset, 115, 50);
lv_obj_align(buttonReset, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); lv_obj_align(buttonReset, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
lv_obj_set_style_local_bg_color(buttonReset, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x990000)); lv_obj_set_style_local_bg_color(buttonReset, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x990000));
lv_obj_set_event_cb(buttonReset, ButtonEventHandler); lv_obj_set_event_cb(buttonReset, ButtonEventHandler);
@ -66,10 +68,10 @@ bool FirmwareValidation::Refresh() {
} }
void FirmwareValidation::OnButtonEvent(lv_obj_t* object, lv_event_t event) { void FirmwareValidation::OnButtonEvent(lv_obj_t* object, lv_event_t event) {
if (object == buttonValidate && event == LV_EVENT_PRESSED) { if (object == buttonValidate && event == LV_EVENT_CLICKED) {
validator.Validate(); validator.Validate();
running = false; running = false;
} else if (object == buttonReset && event == LV_EVENT_PRESSED) { } else if (object == buttonReset && event == LV_EVENT_CLICKED) {
validator.Reset(); validator.Reset();
} }
} }

View File

@ -5,13 +5,10 @@
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
InfiniPaint::InfiniPaint(Pinetime::Applications::DisplayApp* app, Pinetime::Components::LittleVgl& lvgl) : Screen(app), lvgl {lvgl} { InfiniPaint::InfiniPaint(Pinetime::Applications::DisplayApp* app, Pinetime::Components::LittleVgl& lvgl) : Screen(app), lvgl {lvgl} {
app->SetTouchMode(DisplayApp::TouchModes::Polling);
std::fill(b, b + bufferSize, selectColor); std::fill(b, b + bufferSize, selectColor);
} }
InfiniPaint::~InfiniPaint() { InfiniPaint::~InfiniPaint() {
// Reset the touchmode
app->SetTouchMode(DisplayApp::TouchModes::Gestures);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }

View File

@ -6,10 +6,10 @@ Label::Label(uint8_t screenID, uint8_t numScreens, Pinetime::Applications::Displ
: Screen(app), labelText {labelText} { : Screen(app), labelText {labelText} {
if (numScreens > 1) { if (numScreens > 1) {
pageIndicatorBasePoints[0].x = 240 - 1; pageIndicatorBasePoints[0].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[0].y = 6; pageIndicatorBasePoints[0].y = 0;
pageIndicatorBasePoints[1].x = 240 - 1; pageIndicatorBasePoints[1].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[1].y = 240 - 6; pageIndicatorBasePoints[1].y = LV_VER_RES;
pageIndicatorBase = lv_line_create(lv_scr_act(), NULL); pageIndicatorBase = lv_line_create(lv_scr_act(), NULL);
lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
@ -17,13 +17,13 @@ Label::Label(uint8_t screenID, uint8_t numScreens, Pinetime::Applications::Displ
lv_obj_set_style_local_line_rounded(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, true); lv_obj_set_style_local_line_rounded(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, true);
lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints, 2); lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints, 2);
uint16_t indicatorSize = 228 / numScreens; uint16_t indicatorSize = LV_VER_RES / numScreens;
uint16_t indicatorPos = indicatorSize * screenID; uint16_t indicatorPos = indicatorSize * screenID;
pageIndicatorPoints[0].x = 240 - 1; pageIndicatorPoints[0].x = LV_HOR_RES - 1;
pageIndicatorPoints[0].y = (6 + indicatorPos); pageIndicatorPoints[0].y = indicatorPos;
pageIndicatorPoints[1].x = 240 - 1; pageIndicatorPoints[1].x = LV_HOR_RES - 1;
pageIndicatorPoints[1].y = (6 + indicatorPos) + indicatorSize; pageIndicatorPoints[1].y = indicatorPos + indicatorSize;
pageIndicator = lv_line_create(lv_scr_act(), NULL); pageIndicator = lv_line_create(lv_scr_act(), NULL);
lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);

View File

@ -25,42 +25,38 @@ List::List(uint8_t screenID,
settingsController.SetSettingsMenu(screenID); settingsController.SetSettingsMenu(screenID);
if (numScreens > 1) { if (numScreens > 1) {
pageIndicatorBasePoints[0].x = 240 - 1; pageIndicatorBasePoints[0].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[0].y = 6; pageIndicatorBasePoints[0].y = 0;
pageIndicatorBasePoints[1].x = 240 - 1; pageIndicatorBasePoints[1].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[1].y = 240 - 6; pageIndicatorBasePoints[1].y = LV_VER_RES;
pageIndicatorBase = lv_line_create(lv_scr_act(), NULL); pageIndicatorBase = lv_line_create(lv_scr_act(), NULL);
lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
lv_obj_set_style_local_line_color(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111)); lv_obj_set_style_local_line_color(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111));
lv_obj_set_style_local_line_rounded(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, true);
lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints, 2); lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints, 2);
uint16_t indicatorSize = 228 / numScreens; const uint16_t indicatorSize = LV_VER_RES / numScreens;
uint16_t indicatorPos = indicatorSize * screenID; const uint16_t indicatorPos = indicatorSize * screenID;
pageIndicatorPoints[0].x = 240 - 1; pageIndicatorPoints[0].x = LV_HOR_RES - 1;
pageIndicatorPoints[0].y = 6 + indicatorPos; pageIndicatorPoints[0].y = indicatorPos;
pageIndicatorPoints[1].x = 240 - 1; pageIndicatorPoints[1].x = LV_HOR_RES - 1;
pageIndicatorPoints[1].y = 6 + indicatorPos + indicatorSize; pageIndicatorPoints[1].y = indicatorPos + indicatorSize;
pageIndicator = lv_line_create(lv_scr_act(), NULL); pageIndicator = lv_line_create(lv_scr_act(), NULL);
lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
lv_obj_set_style_local_line_color(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY); lv_obj_set_style_local_line_color(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
lv_obj_set_style_local_line_rounded(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, true);
lv_line_set_points(pageIndicator, pageIndicatorPoints, 2); lv_line_set_points(pageIndicator, pageIndicatorPoints, 2);
} }
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr);
// lv_obj_set_style_local_bg_color(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111));
lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10); lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 4);
lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5);
lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0);
lv_obj_set_pos(container1, 0, 0); lv_obj_set_pos(container1, 0, 0);
lv_obj_set_width(container1, LV_HOR_RES - 15); lv_obj_set_width(container1, LV_HOR_RES - 8);
lv_obj_set_height(container1, LV_VER_RES); lv_obj_set_height(container1, LV_VER_RES);
lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT); lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT);
@ -73,11 +69,11 @@ List::List(uint8_t screenID,
itemApps[i] = lv_btn_create(container1, nullptr); itemApps[i] = lv_btn_create(container1, nullptr);
lv_obj_set_style_local_bg_opa(itemApps[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_20); lv_obj_set_style_local_bg_opa(itemApps[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_20);
lv_obj_set_style_local_radius(itemApps[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_set_style_local_radius(itemApps[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 57);
lv_obj_set_style_local_bg_color(itemApps[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA); lv_obj_set_style_local_bg_color(itemApps[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_width(itemApps[i], LV_HOR_RES - 25); lv_obj_set_width(itemApps[i], LV_HOR_RES - 8);
lv_obj_set_height(itemApps[i], 52); lv_obj_set_height(itemApps[i], 57);
lv_obj_set_event_cb(itemApps[i], ButtonEventHandler); lv_obj_set_event_cb(itemApps[i], ButtonEventHandler);
lv_btn_set_layout(itemApps[i], LV_LAYOUT_ROW_MID); lv_btn_set_layout(itemApps[i], LV_LAYOUT_ROW_MID);
itemApps[i]->user_data = this; itemApps[i]->user_data = this;
@ -108,7 +104,7 @@ bool List::Refresh() {
} }
void List::OnButtonEvent(lv_obj_t* object, lv_event_t event) { void List::OnButtonEvent(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_RELEASED) { if (event == LV_EVENT_CLICKED) {
for (int i = 0; i < MAXLISTITEMS; i++) { for (int i = 0; i < MAXLISTITEMS; i++) {
if (apps[i] != Apps::None && object == itemApps[i]) { if (apps[i] != Apps::None && object == itemApps[i]) {
app->StartApp(apps[i], DisplayApp::FullRefreshDirections::Up); app->StartApp(apps[i], DisplayApp::FullRefreshDirections::Up);

View File

@ -1,35 +1,15 @@
#include "Metronome.h" #include "Metronome.h"
#include "Screen.h"
#include "Symbols.h" #include "Symbols.h"
#include "lvgl/lvgl.h"
#include "FreeRTOSConfig.h"
#include "task.h"
#include <string>
#include <tuple>
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
namespace { namespace {
float calculateDelta(const TickType_t startTime, const TickType_t currentTime) { void eventHandler(lv_obj_t* obj, lv_event_t event) {
TickType_t delta = 0; auto* screen = static_cast<Metronome*>(obj->user_data);
// Take care of overflow
if (startTime > currentTime) {
delta = 0xffffffff - startTime;
delta += (currentTime + 1);
} else {
delta = currentTime - startTime;
}
return static_cast<float>(delta) / static_cast<float>(configTICK_RATE_HZ);
}
static void eventHandler(lv_obj_t* obj, lv_event_t event) {
Metronome* screen = static_cast<Metronome*>(obj->user_data);
screen->OnEvent(obj, event); screen->OnEvent(obj, event);
} }
lv_obj_t* createLabel(const char* name, lv_obj_t* reference, lv_align_t align, lv_font_t* font, uint8_t x = 0, uint8_t y = 0) { lv_obj_t* createLabel(const char* name, lv_obj_t* reference, lv_align_t align, lv_font_t* font, uint8_t x, uint8_t y) {
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); 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_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, LV_COLOR_GRAY); lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
@ -41,7 +21,7 @@ namespace {
} }
Metronome::Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask) Metronome::Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask)
: Screen(app), running {true}, currentState {States::Stopped}, startTime {}, motorController {motorController}, systemTask {systemTask} { : Screen(app), motorController {motorController}, systemTask {systemTask} {
bpmArc = lv_arc_create(lv_scr_act(), nullptr); bpmArc = lv_arc_create(lv_scr_act(), nullptr);
bpmArc->user_data = this; bpmArc->user_data = this;
@ -52,10 +32,10 @@ Metronome::Metronome(DisplayApp* app, Controllers::MotorController& motorControl
lv_arc_set_value(bpmArc, bpm); lv_arc_set_value(bpmArc, bpm);
lv_obj_set_size(bpmArc, 210, 210); lv_obj_set_size(bpmArc, 210, 210);
lv_arc_set_adjustable(bpmArc, true); lv_arc_set_adjustable(bpmArc, true);
lv_obj_align(bpmArc, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 7); lv_obj_align(bpmArc, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 0);
bpmValue = createLabel(std::to_string(lv_arc_get_value(bpmArc)).c_str(), bpmArc, LV_ALIGN_IN_TOP_MID, &jetbrains_mono_76, 0, 55); bpmValue = createLabel("120", bpmArc, LV_ALIGN_IN_TOP_MID, &jetbrains_mono_76, 0, 55);
bpmLegend = createLabel("bpm", bpmValue, LV_ALIGN_OUT_BOTTOM_MID, &jetbrains_mono_bold_20, 0, 0); createLabel("bpm", bpmValue, LV_ALIGN_OUT_BOTTOM_MID, &jetbrains_mono_bold_20, 0, 0);
bpmTap = lv_btn_create(lv_scr_act(), nullptr); bpmTap = lv_btn_create(lv_scr_act(), nullptr);
bpmTap->user_data = this; bpmTap->user_data = this;
@ -69,53 +49,42 @@ Metronome::Metronome(DisplayApp* app, Controllers::MotorController& motorControl
lv_obj_set_event_cb(bpbDropdown, eventHandler); lv_obj_set_event_cb(bpbDropdown, eventHandler);
lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_MAIN, LV_STATE_DEFAULT, 20);
lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_LIST, LV_STATE_DEFAULT, 20); lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_LIST, LV_STATE_DEFAULT, 20);
lv_obj_align(bpbDropdown, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 15, -4); lv_obj_set_size(bpbDropdown, 115, 50);
lv_obj_align(bpbDropdown, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
lv_dropdown_set_options(bpbDropdown, "1\n2\n3\n4\n5\n6\n7\n8\n9"); lv_dropdown_set_options(bpbDropdown, "1\n2\n3\n4\n5\n6\n7\n8\n9");
lv_dropdown_set_selected(bpbDropdown, bpb - 1); lv_dropdown_set_selected(bpbDropdown, bpb - 1);
bpbLegend = lv_label_create(bpbDropdown, nullptr); lv_dropdown_set_show_selected(bpbDropdown, false);
lv_label_set_text(bpbLegend, "bpb"); lv_dropdown_set_text(bpbDropdown, "");
lv_obj_align(bpbLegend, bpbDropdown, LV_ALIGN_IN_RIGHT_MID, -15, 0);
currentBpbText = lv_label_create(bpbDropdown, nullptr);
lv_label_set_text_fmt(currentBpbText, "%d bpb", bpb);
lv_obj_align(currentBpbText, bpbDropdown, LV_ALIGN_CENTER, 0, 0);
playPause = lv_btn_create(lv_scr_act(), nullptr); playPause = lv_btn_create(lv_scr_act(), nullptr);
playPause->user_data = this; playPause->user_data = this;
lv_obj_set_event_cb(playPause, eventHandler); lv_obj_set_event_cb(playPause, eventHandler);
lv_obj_align(playPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -10); lv_obj_set_size(playPause, 115, 50);
lv_obj_set_height(playPause, 39); lv_obj_align(playPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
playPauseLabel = lv_label_create(playPause, nullptr); lv_obj_set_style_local_value_str(playPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::play);
lv_label_set_text(playPauseLabel, Symbols::play);
app->SetTouchMode(DisplayApp::TouchModes::Polling);
} }
Metronome::~Metronome() { Metronome::~Metronome() {
app->SetTouchMode(DisplayApp::TouchModes::Gestures);
systemTask.PushMessage(System::Messages::EnableSleeping); systemTask.PushMessage(System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
bool Metronome::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
return true;
}
bool Metronome::Refresh() { bool Metronome::Refresh() {
switch (currentState) { if (metronomeStarted) {
case States::Stopped: { if (xTaskGetTickCount() - startTime > 60 * configTICK_RATE_HZ / bpm) {
break; startTime += 60 * configTICK_RATE_HZ / bpm;
}
case States::Running: {
if (calculateDelta(startTime, xTaskGetTickCount()) >= (60.0 / bpm)) {
counter--; counter--;
startTime -= 60.0 / bpm;
startTime = xTaskGetTickCount();
if (counter == 0) { if (counter == 0) {
counter = bpb; counter = bpb;
motorController.SetDuration(90); motorController.RunForDuration(90);
} else { } else {
motorController.SetDuration(30); motorController.RunForDuration(30);
} }
} }
break;
}
} }
return running; return running;
} }
@ -128,42 +97,39 @@ void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) {
lv_label_set_text_fmt(bpmValue, "%03d", bpm); lv_label_set_text_fmt(bpmValue, "%03d", bpm);
} else if (obj == bpbDropdown) { } else if (obj == bpbDropdown) {
bpb = lv_dropdown_get_selected(obj) + 1; bpb = lv_dropdown_get_selected(obj) + 1;
lv_label_set_text_fmt(currentBpbText, "%d bpb", bpb);
lv_obj_realign(currentBpbText);
} }
break; break;
} }
case LV_EVENT_PRESSED: { case LV_EVENT_PRESSED: {
if (obj == bpmTap) { if (obj == bpmTap) {
float timeDelta = calculateDelta(tappedTime, xTaskGetTickCount()); TickType_t delta = xTaskGetTickCount() - tappedTime;
if (tappedTime == 0 || timeDelta > 3) { if (tappedTime != 0 && delta < configTICK_RATE_HZ * 3) {
tappedTime = xTaskGetTickCount(); bpm = configTICK_RATE_HZ * 60 / delta;
} else {
bpm = ceil(60.0 / timeDelta);
lv_arc_set_value(bpmArc, bpm); lv_arc_set_value(bpmArc, bpm);
lv_label_set_text_fmt(bpmValue, "%03d", bpm); lv_label_set_text_fmt(bpmValue, "%03d", bpm);
tappedTime = xTaskGetTickCount();
} }
tappedTime = xTaskGetTickCount();
} }
break; break;
} }
case LV_EVENT_CLICKED: { case LV_EVENT_CLICKED: {
if (obj == playPause) { if (obj == playPause) {
currentState = (currentState == States::Stopped ? States::Running : States::Stopped); metronomeStarted = !metronomeStarted;
switch (currentState) { if (metronomeStarted) {
case States::Stopped: { lv_obj_set_style_local_value_str(playPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::pause);
lv_label_set_text(playPauseLabel, Symbols::play);
systemTask.PushMessage(System::Messages::EnableSleeping);
break;
}
case States::Running: {
lv_label_set_text(playPauseLabel, Symbols::pause);
systemTask.PushMessage(System::Messages::DisableSleeping); systemTask.PushMessage(System::Messages::DisableSleeping);
startTime = xTaskGetTickCount(); startTime = xTaskGetTickCount();
counter = 1; counter = 1;
break; } else {
} lv_obj_set_style_local_value_str(playPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Symbols::play);
systemTask.PushMessage(System::Messages::EnableSleeping);
} }
} }
break; break;
} }
default:
break;
} }
} }

View File

@ -3,32 +3,32 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include <array> namespace Pinetime {
namespace Applications {
namespace Pinetime::Applications::Screens { namespace Screens {
class Metronome : public Screen { class Metronome : public Screen {
public: public:
Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask); Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask);
~Metronome() override; ~Metronome() override;
bool Refresh() override; bool Refresh() override;
bool OnTouchEvent(TouchEvents event) override;
void OnEvent(lv_obj_t* obj, lv_event_t event); void OnEvent(lv_obj_t* obj, lv_event_t event);
enum class States { Running, Stopped };
private: private:
bool running; TickType_t startTime = 0;
States currentState;
TickType_t startTime;
TickType_t tappedTime = 0; TickType_t tappedTime = 0;
Controllers::MotorController& motorController; Controllers::MotorController& motorController;
System::SystemTask& systemTask; System::SystemTask& systemTask;
uint16_t bpm = 120; int16_t bpm = 120;
uint8_t bpb = 4; uint8_t bpb = 4;
uint8_t counter = 1; uint8_t counter = 1;
lv_obj_t *bpmArc, *bpmTap, *bpmValue, *bpmLegend; bool metronomeStarted = false;
lv_obj_t *bpbDropdown, *bpbLegend;
lv_obj_t *playPause, *playPauseLabel; lv_obj_t *bpmArc, *bpmTap, *bpmValue;
lv_obj_t *bpbDropdown, *currentBpbText;
lv_obj_t *playPause;
}; };
}
}
} }

View File

@ -50,60 +50,55 @@ inline void lv_img_set_src_arr(lv_obj_t* img, const lv_img_dsc_t* src_img) {
Music::Music(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::MusicService& music) : Screen(app), musicService(music) { Music::Music(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::MusicService& music) : Screen(app), musicService(music) {
lv_obj_t* label; lv_obj_t* label;
lv_style_init(&btn_style);
lv_style_set_radius(&btn_style, LV_STATE_DEFAULT, 20);
lv_style_set_bg_color(&btn_style, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_style_set_bg_opa(&btn_style, LV_STATE_DEFAULT, LV_OPA_20);
btnVolDown = lv_btn_create(lv_scr_act(), nullptr); btnVolDown = lv_btn_create(lv_scr_act(), nullptr);
btnVolDown->user_data = this; btnVolDown->user_data = this;
lv_obj_set_event_cb(btnVolDown, event_handler); lv_obj_set_event_cb(btnVolDown, event_handler);
lv_obj_set_size(btnVolDown, 65, 75); lv_obj_set_size(btnVolDown, 76, 76);
lv_obj_align(btnVolDown, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 15, -10); lv_obj_align(btnVolDown, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
lv_obj_set_style_local_radius(btnVolDown, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_add_style(btnVolDown, LV_STATE_DEFAULT, &btn_style);
lv_obj_set_style_local_bg_color(btnVolDown, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_style_local_bg_opa(btnVolDown, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_20);
label = lv_label_create(btnVolDown, nullptr); label = lv_label_create(btnVolDown, nullptr);
lv_label_set_text(label, Symbols::volumDown); lv_label_set_text(label, Symbols::volumDown);
lv_obj_set_hidden(btnVolDown, !displayVolumeButtons); lv_obj_set_hidden(btnVolDown, true);
btnVolUp = lv_btn_create(lv_scr_act(), nullptr); btnVolUp = lv_btn_create(lv_scr_act(), nullptr);
btnVolUp->user_data = this; btnVolUp->user_data = this;
lv_obj_set_event_cb(btnVolUp, event_handler); lv_obj_set_event_cb(btnVolUp, event_handler);
lv_obj_set_size(btnVolUp, 65, 75); lv_obj_set_size(btnVolUp, 76, 76);
lv_obj_align(btnVolUp, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, -15, -10); lv_obj_align(btnVolUp, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
lv_obj_set_style_local_radius(btnVolUp, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_add_style(btnVolUp, LV_STATE_DEFAULT, &btn_style);
lv_obj_set_style_local_bg_color(btnVolUp, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_style_local_bg_opa(btnVolUp, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_20);
label = lv_label_create(btnVolUp, nullptr); label = lv_label_create(btnVolUp, nullptr);
lv_label_set_text(label, Symbols::volumUp); lv_label_set_text(label, Symbols::volumUp);
lv_obj_set_hidden(btnVolUp, !displayVolumeButtons); lv_obj_set_hidden(btnVolUp, true);
btnPrev = lv_btn_create(lv_scr_act(), nullptr); btnPrev = lv_btn_create(lv_scr_act(), nullptr);
btnPrev->user_data = this; btnPrev->user_data = this;
lv_obj_set_event_cb(btnPrev, event_handler); lv_obj_set_event_cb(btnPrev, event_handler);
lv_obj_set_size(btnPrev, 65, 75); lv_obj_set_size(btnPrev, 76, 76);
lv_obj_align(btnPrev, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 15, -10); lv_obj_align(btnPrev, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
lv_obj_set_style_local_radius(btnPrev, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_add_style(btnPrev, LV_STATE_DEFAULT, &btn_style);
lv_obj_set_style_local_bg_color(btnPrev, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_style_local_bg_opa(btnPrev, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_20);
label = lv_label_create(btnPrev, nullptr); label = lv_label_create(btnPrev, nullptr);
lv_label_set_text(label, Symbols::stepBackward); lv_label_set_text(label, Symbols::stepBackward);
btnNext = lv_btn_create(lv_scr_act(), nullptr); btnNext = lv_btn_create(lv_scr_act(), nullptr);
btnNext->user_data = this; btnNext->user_data = this;
lv_obj_set_event_cb(btnNext, event_handler); lv_obj_set_event_cb(btnNext, event_handler);
lv_obj_set_size(btnNext, 65, 75); lv_obj_set_size(btnNext, 76, 76);
lv_obj_align(btnNext, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, -15, -10); lv_obj_align(btnNext, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
lv_obj_set_style_local_radius(btnNext, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_add_style(btnNext, LV_STATE_DEFAULT, &btn_style);
lv_obj_set_style_local_bg_color(btnNext, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_style_local_bg_opa(btnNext, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_20);
label = lv_label_create(btnNext, nullptr); label = lv_label_create(btnNext, nullptr);
lv_label_set_text(label, Symbols::stepForward); lv_label_set_text(label, Symbols::stepForward);
btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); btnPlayPause = lv_btn_create(lv_scr_act(), nullptr);
btnPlayPause->user_data = this; btnPlayPause->user_data = this;
lv_obj_set_event_cb(btnPlayPause, event_handler); lv_obj_set_event_cb(btnPlayPause, event_handler);
lv_obj_set_size(btnPlayPause, 65, 75); lv_obj_set_size(btnPlayPause, 76, 76);
lv_obj_align(btnPlayPause, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -10); lv_obj_align(btnPlayPause, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
lv_obj_set_style_local_radius(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_add_style(btnPlayPause, LV_STATE_DEFAULT, &btn_style);
lv_obj_set_style_local_bg_color(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_style_local_bg_opa(btnPlayPause, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_20);
txtPlayPause = lv_label_create(btnPlayPause, nullptr); txtPlayPause = lv_label_create(btnPlayPause, nullptr);
lv_label_set_text(txtPlayPause, Symbols::play); lv_label_set_text(txtPlayPause, Symbols::play);
@ -147,6 +142,7 @@ Music::Music(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Mus
} }
Music::~Music() { Music::~Music() {
lv_style_reset(&btn_style);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
@ -272,21 +268,19 @@ void Music::OnObjectEvent(lv_obj_t* obj, lv_event_t event) {
bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) { bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
switch (event) { switch (event) {
case TouchEvents::SwipeUp: { case TouchEvents::SwipeUp: {
displayVolumeButtons = true; lv_obj_set_hidden(btnVolDown, false);
lv_obj_set_hidden(btnVolDown, !displayVolumeButtons); lv_obj_set_hidden(btnVolUp, false);
lv_obj_set_hidden(btnVolUp, !displayVolumeButtons);
lv_obj_set_hidden(btnNext, displayVolumeButtons); lv_obj_set_hidden(btnNext, true);
lv_obj_set_hidden(btnPrev, displayVolumeButtons); lv_obj_set_hidden(btnPrev, true);
return true; return true;
} }
case TouchEvents::SwipeDown: { case TouchEvents::SwipeDown: {
displayVolumeButtons = false; lv_obj_set_hidden(btnNext, false);
lv_obj_set_hidden(btnNext, displayVolumeButtons); lv_obj_set_hidden(btnPrev, false);
lv_obj_set_hidden(btnPrev, displayVolumeButtons);
lv_obj_set_hidden(btnVolDown, !displayVolumeButtons); lv_obj_set_hidden(btnVolDown, true);
lv_obj_set_hidden(btnVolUp, !displayVolumeButtons); lv_obj_set_hidden(btnVolUp, true);
return true; return true;
} }
case TouchEvents::SwipeLeft: { case TouchEvents::SwipeLeft: {
@ -298,7 +292,7 @@ bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
return true; return true;
} }
default: { default: {
return true; return false;
} }
} }
} }

View File

@ -57,10 +57,11 @@ namespace Pinetime {
lv_obj_t* imgDiscAnim; lv_obj_t* imgDiscAnim;
lv_obj_t* txtTrackDuration; lv_obj_t* txtTrackDuration;
lv_style_t btn_style;
/** For the spinning disc animation */ /** For the spinning disc animation */
bool frameB; bool frameB;
bool displayVolumeButtons = false;
Pinetime::Controllers::MusicService& musicService; Pinetime::Controllers::MusicService& musicService;
std::string artist; std::string artist;

View File

@ -11,6 +11,7 @@ extern lv_font_t jetbrains_mono_bold_20;
Notifications::Notifications(DisplayApp* app, Notifications::Notifications(DisplayApp* app,
Pinetime::Controllers::NotificationManager& notificationManager, Pinetime::Controllers::NotificationManager& notificationManager,
Pinetime::Controllers::AlertNotificationService& alertNotificationService, Pinetime::Controllers::AlertNotificationService& alertNotificationService,
Pinetime::Controllers::MotorController& motorController,
Modes mode) Modes mode)
: Screen(app), notificationManager {notificationManager}, alertNotificationService {alertNotificationService}, mode {mode} { : Screen(app), notificationManager {notificationManager}, alertNotificationService {alertNotificationService}, mode {mode} {
notificationManager.ClearNewNotificationFlag(); notificationManager.ClearNewNotificationFlag();
@ -36,7 +37,10 @@ Notifications::Notifications(DisplayApp* app,
} }
if (mode == Modes::Preview) { if (mode == Modes::Preview) {
if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) {
motorController.StartRinging();
} else {
motorController.RunForDuration(35);
timeoutLine = lv_line_create(lv_scr_act(), nullptr); timeoutLine = lv_line_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_line_width(timeoutLine, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_line_width(timeoutLine, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
@ -47,14 +51,17 @@ Notifications::Notifications(DisplayApp* app,
timeoutTickCountStart = xTaskGetTickCount(); timeoutTickCountStart = xTaskGetTickCount();
timeoutTickCountEnd = timeoutTickCountStart + (5 * 1024); timeoutTickCountEnd = timeoutTickCountStart + (5 * 1024);
} }
}
} }
Notifications::~Notifications() { Notifications::~Notifications() {
// make sure we stop any vibrations before exiting
Controllers::MotorController::StopRinging();
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
bool Notifications::Refresh() { bool Notifications::Refresh() {
if (mode == Modes::Preview) { if (mode == Modes::Preview && timeoutLine != nullptr) {
auto tick = xTaskGetTickCount(); auto tick = xTaskGetTickCount();
int32_t pos = 240 - ((tick - timeoutTickCountStart) / ((timeoutTickCountEnd - timeoutTickCountStart) / 240)); int32_t pos = 240 - ((tick - timeoutTickCountStart) / ((timeoutTickCountEnd - timeoutTickCountStart) / 240));
if (pos < 0) if (pos < 0)
@ -63,13 +70,13 @@ bool Notifications::Refresh() {
timeoutLinePoints[1].x = pos; timeoutLinePoints[1].x = pos;
lv_line_set_points(timeoutLine, timeoutLinePoints, 2); lv_line_set_points(timeoutLine, timeoutLinePoints, 2);
} }
return running && currentItem->IsRunning();
return running;
} }
bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) { bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
if (mode != Modes::Normal) if (mode != Modes::Normal) {
return true; return false;
}
switch (event) { switch (event) {
case Pinetime::Applications::TouchEvents::SwipeDown: { case Pinetime::Applications::TouchEvents::SwipeDown: {
@ -130,19 +137,9 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
} }
namespace { namespace {
static void AcceptIncomingCallEventHandler(lv_obj_t* obj, lv_event_t event) { void CallEventHandler(lv_obj_t* obj, lv_event_t event) {
auto* item = static_cast<Notifications::NotificationItem*>(obj->user_data); auto* item = static_cast<Notifications::NotificationItem*>(obj->user_data);
item->OnAcceptIncomingCall(event); item->OnCallButtonEvent(obj, event);
}
static void MuteIncomingCallEventHandler(lv_obj_t* obj, lv_event_t event) {
auto* item = static_cast<Notifications::NotificationItem*>(obj->user_data);
item->OnMuteIncomingCall(event);
}
static void RejectIncomingCallEventHandler(lv_obj_t* obj, lv_event_t event) {
auto* item = static_cast<Notifications::NotificationItem*>(obj->user_data);
item->OnRejectIncomingCall(event);
} }
} }
@ -154,7 +151,6 @@ Notifications::NotificationItem::NotificationItem(const char* title,
Modes mode, Modes mode,
Pinetime::Controllers::AlertNotificationService& alertNotificationService) Pinetime::Controllers::AlertNotificationService& alertNotificationService)
: notifNr {notifNr}, notifNb {notifNb}, mode {mode}, alertNotificationService {alertNotificationService} { : notifNr {notifNr}, notifNb {notifNb}, mode {mode}, alertNotificationService {alertNotificationService} {
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), NULL); lv_obj_t* container1 = lv_cont_create(lv_scr_act(), NULL);
lv_obj_set_style_local_bg_color(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x222222)); lv_obj_set_style_local_bg_color(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x222222));
@ -212,7 +208,7 @@ Notifications::NotificationItem::NotificationItem(const char* title,
bt_accept = lv_btn_create(lv_scr_act(), nullptr); bt_accept = lv_btn_create(lv_scr_act(), nullptr);
bt_accept->user_data = this; bt_accept->user_data = this;
lv_obj_set_event_cb(bt_accept, AcceptIncomingCallEventHandler); lv_obj_set_event_cb(bt_accept, CallEventHandler);
lv_obj_set_size(bt_accept, 76, 76); lv_obj_set_size(bt_accept, 76, 76);
lv_obj_align(bt_accept, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); lv_obj_align(bt_accept, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
label_accept = lv_label_create(bt_accept, nullptr); label_accept = lv_label_create(bt_accept, nullptr);
@ -221,7 +217,7 @@ Notifications::NotificationItem::NotificationItem(const char* title,
bt_reject = lv_btn_create(lv_scr_act(), nullptr); bt_reject = lv_btn_create(lv_scr_act(), nullptr);
bt_reject->user_data = this; bt_reject->user_data = this;
lv_obj_set_event_cb(bt_reject, RejectIncomingCallEventHandler); lv_obj_set_event_cb(bt_reject, CallEventHandler);
lv_obj_set_size(bt_reject, 76, 76); lv_obj_set_size(bt_reject, 76, 76);
lv_obj_align(bt_reject, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0); lv_obj_align(bt_reject, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
label_reject = lv_label_create(bt_reject, nullptr); label_reject = lv_label_create(bt_reject, nullptr);
@ -230,7 +226,7 @@ Notifications::NotificationItem::NotificationItem(const char* title,
bt_mute = lv_btn_create(lv_scr_act(), nullptr); bt_mute = lv_btn_create(lv_scr_act(), nullptr);
bt_mute->user_data = this; bt_mute->user_data = this;
lv_obj_set_event_cb(bt_mute, MuteIncomingCallEventHandler); lv_obj_set_event_cb(bt_mute, CallEventHandler);
lv_obj_set_size(bt_mute, 76, 76); lv_obj_set_size(bt_mute, 76, 76);
lv_obj_align(bt_mute, NULL, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); lv_obj_align(bt_mute, NULL, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
label_mute = lv_label_create(bt_mute, nullptr); label_mute = lv_label_create(bt_mute, nullptr);
@ -246,25 +242,22 @@ Notifications::NotificationItem::NotificationItem(const char* title,
lv_label_set_text(backgroundLabel, ""); lv_label_set_text(backgroundLabel, "");
} }
void Notifications::NotificationItem::OnAcceptIncomingCall(lv_event_t event) { void Notifications::NotificationItem::OnCallButtonEvent(lv_obj_t* obj, lv_event_t event) {
if (event != LV_EVENT_CLICKED) if (event != LV_EVENT_CLICKED) {
return; return;
}
Controllers::MotorController::StopRinging();
if (obj == bt_accept) {
alertNotificationService.AcceptIncomingCall(); alertNotificationService.AcceptIncomingCall();
} } else if (obj == bt_reject) {
void Notifications::NotificationItem::OnMuteIncomingCall(lv_event_t event) {
if (event != LV_EVENT_CLICKED)
return;
alertNotificationService.MuteIncomingCall();
}
void Notifications::NotificationItem::OnRejectIncomingCall(lv_event_t event) {
if (event != LV_EVENT_CLICKED)
return;
alertNotificationService.RejectIncomingCall(); alertNotificationService.RejectIncomingCall();
} else if (obj == bt_mute) {
alertNotificationService.MuteIncomingCall();
}
running = false;
} }
Notifications::NotificationItem::~NotificationItem() { Notifications::NotificationItem::~NotificationItem() {

View File

@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include "Screen.h" #include "Screen.h"
#include "components/ble/NotificationManager.h" #include "components/ble/NotificationManager.h"
#include "components/motor/MotorController.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@ -19,6 +20,7 @@ namespace Pinetime {
explicit Notifications(DisplayApp* app, explicit Notifications(DisplayApp* app,
Pinetime::Controllers::NotificationManager& notificationManager, Pinetime::Controllers::NotificationManager& notificationManager,
Pinetime::Controllers::AlertNotificationService& alertNotificationService, Pinetime::Controllers::AlertNotificationService& alertNotificationService,
Pinetime::Controllers::MotorController& motorController,
Modes mode); Modes mode);
~Notifications() override; ~Notifications() override;
@ -35,12 +37,10 @@ namespace Pinetime {
Modes mode, Modes mode,
Pinetime::Controllers::AlertNotificationService& alertNotificationService); Pinetime::Controllers::AlertNotificationService& alertNotificationService);
~NotificationItem(); ~NotificationItem();
bool Refresh() { bool IsRunning() const {
return false; return running;
} }
void OnAcceptIncomingCall(lv_event_t event); void OnCallButtonEvent(lv_obj_t*, lv_event_t event);
void OnMuteIncomingCall(lv_event_t event);
void OnRejectIncomingCall(lv_event_t event);
private: private:
uint8_t notifNr = 0; uint8_t notifNr = 0;
@ -60,6 +60,7 @@ namespace Pinetime {
lv_obj_t* bottomPlaceholder; lv_obj_t* bottomPlaceholder;
Modes mode; Modes mode;
Pinetime::Controllers::AlertNotificationService& alertNotificationService; Pinetime::Controllers::AlertNotificationService& alertNotificationService;
bool running = true;
}; };
private: private:
@ -75,7 +76,7 @@ namespace Pinetime {
bool validDisplay = false; bool validDisplay = false;
lv_point_t timeoutLinePoints[2] {{0, 1}, {239, 1}}; lv_point_t timeoutLinePoints[2] {{0, 1}, {239, 1}};
lv_obj_t* timeoutLine; lv_obj_t* timeoutLine = nullptr;
uint32_t timeoutTickCountStart; uint32_t timeoutTickCountStart;
uint32_t timeoutTickCountEnd; uint32_t timeoutTickCountEnd;
}; };

View File

@ -5,8 +5,6 @@
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
Paddle::Paddle(Pinetime::Applications::DisplayApp* app, Pinetime::Components::LittleVgl& lvgl) : Screen(app), lvgl {lvgl} { Paddle::Paddle(Pinetime::Applications::DisplayApp* app, Pinetime::Components::LittleVgl& lvgl) : Screen(app), lvgl {lvgl} {
app->SetTouchMode(DisplayApp::TouchModes::Polling);
background = lv_obj_create(lv_scr_act(), nullptr); background = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_size(background, LV_HOR_RES + 1, LV_VER_RES); lv_obj_set_size(background, LV_HOR_RES + 1, LV_VER_RES);
lv_obj_set_pos(background, -1, 0); lv_obj_set_pos(background, -1, 0);
@ -32,8 +30,6 @@ Paddle::Paddle(Pinetime::Applications::DisplayApp* app, Pinetime::Components::Li
} }
Paddle::~Paddle() { Paddle::~Paddle() {
// Reset the touchmode
app->SetTouchMode(DisplayApp::TouchModes::Gestures);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }

View File

@ -51,7 +51,6 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
notificatioManager {notificatioManager}, notificatioManager {notificatioManager},
settingsController {settingsController}, settingsController {settingsController},
motionController {motionController} { motionController {motionController} {
/* This sets the watchface number to return to after leaving the menu */ /* This sets the watchface number to return to after leaving the menu */
settingsController.SetClockFace(2); settingsController.SetClockFace(2);
@ -62,7 +61,6 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
displayedChar[4] = 0; displayedChar[4] = 0;
/* Create a 200px wide background rectangle */ /* Create a 200px wide background rectangle */
timebar = lv_obj_create(lv_scr_act(), nullptr); timebar = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_color(timebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000)); lv_obj_set_style_local_bg_color(timebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
lv_obj_set_style_local_radius(timebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0); lv_obj_set_style_local_radius(timebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0);
@ -70,7 +68,6 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_align(timebar, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 5, 0); lv_obj_align(timebar, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 5, 0);
/* Display the time */ /* Display the time */
timeDD1 = lv_label_create(lv_scr_act(), nullptr); timeDD1 = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(timeDD1, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &open_sans_light); lv_obj_set_style_local_text_font(timeDD1, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &open_sans_light);
lv_obj_set_style_local_text_color(timeDD1, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x008080)); lv_obj_set_style_local_text_color(timeDD1, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x008080));
@ -90,7 +87,6 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_align(timeAMPM, timebar, LV_ALIGN_IN_BOTTOM_LEFT, 2, -20); lv_obj_align(timeAMPM, timebar, LV_ALIGN_IN_BOTTOM_LEFT, 2, -20);
/* Create a 40px wide bar down the right side of the screen */ /* Create a 40px wide bar down the right side of the screen */
sidebar = lv_obj_create(lv_scr_act(), nullptr); sidebar = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_color(sidebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x008080)); lv_obj_set_style_local_bg_color(sidebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x008080));
lv_obj_set_style_local_radius(sidebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0); lv_obj_set_style_local_radius(sidebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0);
@ -98,7 +94,6 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_align(sidebar, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0); lv_obj_align(sidebar, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
/* Display icons */ /* Display icons */
batteryIcon = lv_label_create(lv_scr_act(), nullptr); batteryIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000)); lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
lv_label_set_text(batteryIcon, Symbols::batteryFull); lv_label_set_text(batteryIcon, Symbols::batteryFull);
@ -117,7 +112,6 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 40); lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 40);
/* Calendar icon */ /* Calendar icon */
calendarOuter = lv_obj_create(lv_scr_act(), nullptr); calendarOuter = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_color(calendarOuter, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000)); lv_obj_set_style_local_bg_color(calendarOuter, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
lv_obj_set_style_local_radius(calendarOuter, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0); lv_obj_set_style_local_radius(calendarOuter, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0);
@ -155,7 +149,6 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_align(calendarCrossBar2, calendarBar2, LV_ALIGN_IN_BOTTOM_MID, 0, 0); lv_obj_align(calendarCrossBar2, calendarBar2, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
/* Display date */ /* Display date */
dateDayOfWeek = lv_label_create(lv_scr_act(), nullptr); dateDayOfWeek = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(dateDayOfWeek, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000)); lv_obj_set_style_local_text_color(dateDayOfWeek, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
lv_label_set_text(dateDayOfWeek, "THU"); lv_label_set_text(dateDayOfWeek, "THU");
@ -223,26 +216,17 @@ bool PineTimeStyle::Refresh() {
bleState = bleController.IsConnected(); bleState = bleController.IsConnected();
if (bleState.IsUpdated()) { if (bleState.IsUpdated()) {
if (bleState.Get() == true) { lv_label_set_text(bleIcon, BleIcon::GetIcon(bleState.Get()));
lv_label_set_text(bleIcon, BleIcon::GetIcon(true));
lv_obj_realign(bleIcon); lv_obj_realign(bleIcon);
} else {
lv_label_set_text(bleIcon, BleIcon::GetIcon(false));
}
} }
notificationState = notificatioManager.AreNewNotificationsAvailable(); notificationState = notificatioManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) { if (notificationState.IsUpdated()) {
if (notificationState.Get() == true) { lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
lv_obj_realign(notificationIcon); lv_obj_realign(notificationIcon);
} else {
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
}
} }
currentDateTime = dateTimeController.CurrentDateTime(); currentDateTime = dateTimeController.CurrentDateTime();
if (currentDateTime.IsUpdated()) { if (currentDateTime.IsUpdated()) {
auto newDateTime = currentDateTime.Get(); auto newDateTime = currentDateTime.Get();
@ -250,9 +234,9 @@ bool PineTimeStyle::Refresh() {
auto time = date::make_time(newDateTime - dp); auto time = date::make_time(newDateTime - dp);
auto yearMonthDay = date::year_month_day(dp); auto yearMonthDay = date::year_month_day(dp);
auto year = (int) yearMonthDay.year(); auto year = static_cast<int>(yearMonthDay.year());
auto month = static_cast<Pinetime::Controllers::DateTime::Months>((unsigned) yearMonthDay.month()); auto month = static_cast<Pinetime::Controllers::DateTime::Months>(static_cast<unsigned>(yearMonthDay.month()));
auto day = (unsigned) yearMonthDay.day(); auto day = static_cast<unsigned>(yearMonthDay.day());
auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(date::weekday(yearMonthDay).iso_encoding()); auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(date::weekday(yearMonthDay).iso_encoding());
int hour = time.hours().count(); int hour = time.hours().count();
@ -263,7 +247,6 @@ bool PineTimeStyle::Refresh() {
char hoursChar[3]; char hoursChar[3];
char ampmChar[5]; char ampmChar[5];
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
sprintf(hoursChar, "%02d", hour); sprintf(hoursChar, "%02d", hour);
} else { } else {
@ -282,41 +265,26 @@ bool PineTimeStyle::Refresh() {
sprintf(hoursChar, "%02d", hour); sprintf(hoursChar, "%02d", hour);
} }
if (hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] || if (hoursChar[0] != displayedChar[0] or hoursChar[1] != displayedChar[1] or minutesChar[0] != displayedChar[2] or
minutesChar[1] != displayedChar[3]) { minutesChar[1] != displayedChar[3]) {
displayedChar[0] = hoursChar[0]; displayedChar[0] = hoursChar[0];
displayedChar[1] = hoursChar[1]; displayedChar[1] = hoursChar[1];
displayedChar[2] = minutesChar[0]; displayedChar[2] = minutesChar[0];
displayedChar[3] = minutesChar[1]; displayedChar[3] = minutesChar[1];
char hourStr[3];
char minStr[3];
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_label_set_text(timeAMPM, ampmChar); lv_label_set_text(timeAMPM, ampmChar);
} }
/* Display the time as 2 pairs of digits */ lv_label_set_text_fmt(timeDD1, "%s", hoursChar);
sprintf(hourStr, "%c%c", hoursChar[0], hoursChar[1]); lv_label_set_text_fmt(timeDD2, "%s", minutesChar);
lv_label_set_text(timeDD1, hourStr);
sprintf(minStr, "%c%c", minutesChar[0], minutesChar[1]);
lv_label_set_text(timeDD2, minStr);
} }
if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
char dayOfWeekStr[4]; lv_label_set_text_fmt(dateDayOfWeek, "%s", dateTimeController.DayOfWeekShortToString());
char dayStr[3]; lv_label_set_text_fmt(dateDay, "%d", day);
char monthStr[4];
sprintf(dayOfWeekStr, "%s", dateTimeController.DayOfWeekShortToString());
sprintf(dayStr, "%d", day);
sprintf(monthStr, "%s", dateTimeController.MonthShortToString());
lv_label_set_text(dateDayOfWeek, dayOfWeekStr);
lv_label_set_text(dateDay, dayStr);
lv_obj_realign(dateDay); lv_obj_realign(dateDay);
lv_label_set_text(dateMonth, monthStr); lv_label_set_text_fmt(dateMonth, "%s", dateTimeController.MonthShortToString());
currentYear = year; currentYear = year;
currentMonth = month; currentMonth = month;

View File

@ -32,8 +32,6 @@ namespace Pinetime {
bool Refresh() override; bool Refresh() override;
void OnObjectEvent(lv_obj_t* pObj, lv_event_t i);
private: private:
char displayedChar[5]; char displayedChar[5];
@ -67,9 +65,6 @@ namespace Pinetime {
lv_obj_t* calendarBar2; lv_obj_t* calendarBar2;
lv_obj_t* calendarCrossBar1; lv_obj_t* calendarCrossBar1;
lv_obj_t* calendarCrossBar2; lv_obj_t* calendarCrossBar2;
lv_obj_t* heartbeatIcon;
lv_obj_t* heartbeatValue;
lv_obj_t* heartbeatBpm;
lv_obj_t* notificationIcon; lv_obj_t* notificationIcon;
lv_obj_t* stepGauge; lv_obj_t* stepGauge;
lv_color_t needle_colors[1]; lv_color_t needle_colors[1];

View File

@ -64,6 +64,7 @@ namespace Pinetime {
} }
/** @return false if the event hasn't been handled by the app, true if it has been handled */ /** @return false if the event hasn't been handled by the app, true if it has been handled */
// Returning true will cancel lvgl tap
virtual bool OnTouchEvent(TouchEvents event) { virtual bool OnTouchEvent(TouchEvents event) {
return false; return false;
} }

View File

@ -161,7 +161,7 @@ bool StopWatch::Refresh() {
} }
void StopWatch::playPauseBtnEventHandler(lv_event_t event) { void StopWatch::playPauseBtnEventHandler(lv_event_t event) {
if (event != LV_EVENT_PRESSED) { if (event != LV_EVENT_CLICKED) {
return; return;
} }
if (currentState == States::Init) { if (currentState == States::Init) {
@ -174,7 +174,7 @@ void StopWatch::playPauseBtnEventHandler(lv_event_t event) {
} }
void StopWatch::stopLapBtnEventHandler(lv_event_t event) { void StopWatch::stopLapBtnEventHandler(lv_event_t event) {
if (event != LV_EVENT_PRESSED) { if (event != LV_EVENT_CLICKED) {
return; return;
} }
// If running, then this button is used to save laps // If running, then this button is used to save laps

View File

@ -6,15 +6,17 @@ using namespace Pinetime::Applications::Screens;
namespace { namespace {
static void lv_update_task(struct _lv_task_t* task) { static void lv_update_task(struct _lv_task_t* task) {
auto user_data = static_cast<Tile*>(task->user_data); auto* user_data = static_cast<Tile*>(task->user_data);
user_data->UpdateScreen(); user_data->UpdateScreen();
} }
static void event_handler(lv_obj_t* obj, lv_event_t event) { static void event_handler(lv_obj_t* obj, lv_event_t event) {
if (event != LV_EVENT_VALUE_CHANGED) return;
Tile* screen = static_cast<Tile*>(obj->user_data); Tile* screen = static_cast<Tile*>(obj->user_data);
uint32_t* eventDataPtr = (uint32_t*) lv_event_get_data(); auto* eventDataPtr = (uint32_t*) lv_event_get_data();
uint32_t eventData = *eventDataPtr; uint32_t eventData = *eventDataPtr;
screen->OnObjectEvent(obj, event, eventData); screen->OnValueChangedEvent(obj, eventData);
} }
} }
@ -33,37 +35,35 @@ Tile::Tile(uint8_t screenID,
label_time = lv_label_create(lv_scr_act(), nullptr); label_time = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_fmt(label_time, "%02i:%02i", dateTimeController.Hours(), dateTimeController.Minutes()); lv_label_set_text_fmt(label_time, "%02i:%02i", dateTimeController.Hours(), dateTimeController.Minutes());
lv_label_set_align(label_time, LV_LABEL_ALIGN_CENTER); lv_label_set_align(label_time, LV_LABEL_ALIGN_CENTER);
lv_obj_align(label_time, nullptr, LV_ALIGN_IN_TOP_LEFT, 15, 6); lv_obj_align(label_time, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
// Battery // Battery
batteryIcon = lv_label_create(lv_scr_act(), nullptr); batteryIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryController.PercentRemaining())); lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryController.PercentRemaining()));
lv_obj_align(batteryIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, -15, 6); lv_obj_align(batteryIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, -8, 0);
if (numScreens > 1) { if (numScreens > 1) {
pageIndicatorBasePoints[0].x = 240 - 1; pageIndicatorBasePoints[0].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[0].y = 6; pageIndicatorBasePoints[0].y = 0;
pageIndicatorBasePoints[1].x = 240 - 1; pageIndicatorBasePoints[1].x = LV_HOR_RES - 1;
pageIndicatorBasePoints[1].y = 240 - 6; pageIndicatorBasePoints[1].y = LV_VER_RES;
pageIndicatorBase = lv_line_create(lv_scr_act(), nullptr); pageIndicatorBase = lv_line_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_line_width(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
lv_obj_set_style_local_line_color(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111)); lv_obj_set_style_local_line_color(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111));
lv_obj_set_style_local_line_rounded(pageIndicatorBase, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, true);
lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints, 2); lv_line_set_points(pageIndicatorBase, pageIndicatorBasePoints, 2);
uint16_t indicatorSize = 228 / numScreens; const uint16_t indicatorSize = LV_VER_RES / numScreens;
uint16_t indicatorPos = indicatorSize * screenID; const uint16_t indicatorPos = indicatorSize * screenID;
pageIndicatorPoints[0].x = 240 - 1; pageIndicatorPoints[0].x = LV_HOR_RES - 1;
pageIndicatorPoints[0].y = 6 + indicatorPos; pageIndicatorPoints[0].y = indicatorPos;
pageIndicatorPoints[1].x = 240 - 1; pageIndicatorPoints[1].x = LV_HOR_RES - 1;
pageIndicatorPoints[1].y = 6 + indicatorPos + indicatorSize; pageIndicatorPoints[1].y = indicatorPos + indicatorSize;
pageIndicator = lv_line_create(lv_scr_act(), nullptr); pageIndicator = lv_line_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_line_width(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
lv_obj_set_style_local_line_color(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY); lv_obj_set_style_local_line_color(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
lv_obj_set_style_local_line_rounded(pageIndicator, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, true);
lv_line_set_points(pageIndicator, pageIndicatorPoints, 2); lv_line_set_points(pageIndicator, pageIndicatorPoints, 2);
} }
@ -83,7 +83,7 @@ Tile::Tile(uint8_t screenID,
btnm1 = lv_btnmatrix_create(lv_scr_act(), nullptr); btnm1 = lv_btnmatrix_create(lv_scr_act(), nullptr);
lv_btnmatrix_set_map(btnm1, btnmMap); lv_btnmatrix_set_map(btnm1, btnmMap);
lv_obj_set_size(btnm1, LV_HOR_RES - 10, LV_VER_RES - 60); lv_obj_set_size(btnm1, LV_HOR_RES - 16, LV_VER_RES - 60);
lv_obj_align(btnm1, NULL, LV_ALIGN_CENTER, 0, 10); lv_obj_align(btnm1, NULL, LV_ALIGN_CENTER, 0, 10);
lv_obj_set_style_local_radius(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, 20); lv_obj_set_style_local_radius(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, 20);
@ -91,8 +91,11 @@ Tile::Tile(uint8_t screenID,
lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, LV_COLOR_AQUA); lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_style_local_bg_opa(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, LV_OPA_20); lv_obj_set_style_local_bg_opa(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, LV_OPA_20);
lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, lv_color_hex(0x111111)); lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, lv_color_hex(0x111111));
lv_obj_set_style_local_pad_all(btnm1, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 0);
lv_obj_set_style_local_pad_inner(btnm1, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 10);
for (uint8_t i = 0; i < 6; i++) { for (uint8_t i = 0; i < 6; i++) {
lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_CLICK_TRIG);
if (applications[i].application == Apps::None) { if (applications[i].application == Apps::None) {
lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_DISABLED); lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_DISABLED);
} }
@ -124,9 +127,9 @@ bool Tile::Refresh() {
return running; return running;
} }
void Tile::OnObjectEvent(lv_obj_t* obj, lv_event_t event, uint32_t buttonId) { void Tile::OnValueChangedEvent(lv_obj_t* obj, uint32_t buttonId) {
if (event == LV_EVENT_VALUE_CHANGED) { if(obj != btnm1) return;
app->StartApp(apps[buttonId], DisplayApp::FullRefreshDirections::Up); app->StartApp(apps[buttonId], DisplayApp::FullRefreshDirections::Up);
running = false; running = false;
}
} }

View File

@ -32,7 +32,7 @@ namespace Pinetime {
bool Refresh() override; bool Refresh() override;
void UpdateScreen(); void UpdateScreen();
void OnObjectEvent(lv_obj_t* obj, lv_event_t event, uint32_t buttonId); void OnValueChangedEvent(lv_obj_t* obj, uint32_t buttonId);
private: private:
Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Battery& batteryController;

View File

@ -10,38 +10,37 @@ LV_IMG_DECLARE(bg_clock);
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
namespace { namespace {
constexpr int16_t HourLength = 70;
constexpr auto HOUR_LENGTH = 70; constexpr int16_t MinuteLength = 90;
constexpr auto MINUTE_LENGTH = 90; constexpr int16_t SecondLength = 110;
constexpr auto SECOND_LENGTH = 110;
// sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor // sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
const auto LV_TRIG_SCALE = _lv_trigo_sin(90); const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
int16_t cosine(int16_t angle) { int16_t Cosine(int16_t angle) {
return _lv_trigo_sin(angle + 90); return _lv_trigo_sin(angle + 90);
} }
int16_t sine(int16_t angle) { int16_t Sine(int16_t angle) {
return _lv_trigo_sin(angle); return _lv_trigo_sin(angle);
} }
int16_t coordinate_x_relocate(int16_t x) { int16_t CoordinateXRelocate(int16_t x) {
return (x + LV_HOR_RES / 2); return (x + LV_HOR_RES / 2);
} }
int16_t coordinate_y_relocate(int16_t y) { int16_t CoordinateYRelocate(int16_t y) {
return std::abs(y - LV_HOR_RES / 2); return std::abs(y - LV_HOR_RES / 2);
} }
lv_point_t coordinate_relocate(int16_t radius, int16_t angle) { lv_point_t CoordinateRelocate(int16_t radius, int16_t angle) {
return lv_point_t{ return lv_point_t{
.x = coordinate_x_relocate(radius * static_cast<int32_t>(sine(angle)) / LV_TRIG_SCALE), .x = CoordinateXRelocate(radius * static_cast<int32_t>(Sine(angle)) / LV_TRIG_SCALE),
.y = coordinate_y_relocate(radius * static_cast<int32_t>(cosine(angle)) / LV_TRIG_SCALE) .y = CoordinateYRelocate(radius * static_cast<int32_t>(Cosine(angle)) / LV_TRIG_SCALE)
}; };
} }
} // namespace }
WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app, WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app,
Controllers::DateTime& dateTimeController, Controllers::DateTime& dateTimeController,
@ -68,12 +67,12 @@ WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app,
batteryIcon = lv_label_create(lv_scr_act(), nullptr); batteryIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text(batteryIcon, Symbols::batteryHalf); lv_label_set_text(batteryIcon, Symbols::batteryHalf);
lv_obj_align(batteryIcon, NULL, LV_ALIGN_IN_BOTTOM_RIGHT, -8, -4); lv_obj_align(batteryIcon, NULL, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
notificationIcon = lv_label_create(lv_scr_act(), NULL); notificationIcon = lv_label_create(lv_scr_act(), NULL);
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00)); lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00));
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false)); lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
lv_obj_align(notificationIcon, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 8, -4); lv_obj_align(notificationIcon, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
// Date - Day / Week day // Date - Day / Week day
@ -123,7 +122,6 @@ WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app,
} }
WatchFaceAnalog::~WatchFaceAnalog() { WatchFaceAnalog::~WatchFaceAnalog() {
lv_style_reset(&hour_line_style); lv_style_reset(&hour_line_style);
lv_style_reset(&hour_line_style_trace); lv_style_reset(&hour_line_style_trace);
lv_style_reset(&minute_line_style); lv_style_reset(&minute_line_style);
@ -134,18 +132,17 @@ WatchFaceAnalog::~WatchFaceAnalog() {
} }
void WatchFaceAnalog::UpdateClock() { void WatchFaceAnalog::UpdateClock() {
hour = dateTimeController.Hours(); hour = dateTimeController.Hours();
minute = dateTimeController.Minutes(); minute = dateTimeController.Minutes();
second = dateTimeController.Seconds(); second = dateTimeController.Seconds();
if (sMinute != minute) { if (sMinute != minute) {
auto const angle = minute * 6; auto const angle = minute * 6;
minute_point[0] = coordinate_relocate(30, angle); minute_point[0] = CoordinateRelocate(30, angle);
minute_point[1] = coordinate_relocate(MINUTE_LENGTH, angle); minute_point[1] = CoordinateRelocate(MinuteLength, angle);
minute_point_trace[0] = coordinate_relocate(5, angle); minute_point_trace[0] = CoordinateRelocate(5, angle);
minute_point_trace[1] = coordinate_relocate(31, angle); minute_point_trace[1] = CoordinateRelocate(31, angle);
lv_line_set_points(minute_body, minute_point, 2); lv_line_set_points(minute_body, minute_point, 2);
lv_line_set_points(minute_body_trace, minute_point_trace, 2); lv_line_set_points(minute_body_trace, minute_point_trace, 2);
@ -156,11 +153,11 @@ void WatchFaceAnalog::UpdateClock() {
sMinute = minute; sMinute = minute;
auto const angle = (hour * 30 + minute / 2); auto const angle = (hour * 30 + minute / 2);
hour_point[0] = coordinate_relocate(30, angle); hour_point[0] = CoordinateRelocate(30, angle);
hour_point[1] = coordinate_relocate(HOUR_LENGTH, angle); hour_point[1] = CoordinateRelocate(HourLength, angle);
hour_point_trace[0] = coordinate_relocate(5, angle); hour_point_trace[0] = CoordinateRelocate(5, angle);
hour_point_trace[1] = coordinate_relocate(31, angle); hour_point_trace[1] = CoordinateRelocate(31, angle);
lv_line_set_points(hour_body, hour_point, 2); lv_line_set_points(hour_body, hour_point, 2);
lv_line_set_points(hour_body_trace, hour_point_trace, 2); lv_line_set_points(hour_body_trace, hour_point_trace, 2);
@ -170,8 +167,8 @@ void WatchFaceAnalog::UpdateClock() {
sSecond = second; sSecond = second;
auto const angle = second * 6; auto const angle = second * 6;
second_point[0] = coordinate_relocate(-20, angle); second_point[0] = CoordinateRelocate(-20, angle);
second_point[1] = coordinate_relocate(SECOND_LENGTH, angle); second_point[1] = CoordinateRelocate(SecondLength, angle);
lv_line_set_points(second_body, second_point, 2); lv_line_set_points(second_body, second_point, 2);
} }
} }
@ -186,16 +183,12 @@ bool WatchFaceAnalog::Refresh() {
notificationState = notificationManager.AreNewNotificationsAvailable(); notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) { if (notificationState.IsUpdated()) {
if (notificationState.Get() == true) lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
else
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
} }
currentDateTime = dateTimeController.CurrentDateTime(); currentDateTime = dateTimeController.CurrentDateTime();
if (currentDateTime.IsUpdated()) { if (currentDateTime.IsUpdated()) {
month = dateTimeController.Month(); month = dateTimeController.Month();
day = dateTimeController.Day(); day = dateTimeController.Day();
dayOfWeek = dateTimeController.DayOfWeek(); dayOfWeek = dateTimeController.DayOfWeek();
@ -203,7 +196,6 @@ bool WatchFaceAnalog::Refresh() {
UpdateClock(); UpdateClock();
if ((month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { if ((month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), day); lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), day);
currentMonth = month; currentMonth = month;

View File

@ -58,14 +58,12 @@ namespace Pinetime {
lv_obj_t* minute_body_trace; lv_obj_t* minute_body_trace;
lv_obj_t* second_body; lv_obj_t* second_body;
// ##
lv_point_t hour_point[2]; lv_point_t hour_point[2];
lv_point_t hour_point_trace[2]; lv_point_t hour_point_trace[2];
lv_point_t minute_point[2]; lv_point_t minute_point[2];
lv_point_t minute_point_trace[2]; lv_point_t minute_point_trace[2];
lv_point_t second_point[2]; lv_point_t second_point[2];
// ##
lv_style_t hour_line_style; lv_style_t hour_line_style;
lv_style_t hour_line_style_trace; lv_style_t hour_line_style_trace;
lv_style_t minute_line_style; lv_style_t minute_line_style;

View File

@ -12,9 +12,6 @@
#include "components/ble/NotificationManager.h" #include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h" #include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h" #include "components/motion/MotionController.h"
#include "components/settings/Settings.h"
#include "../DisplayApp.h"
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
WatchFaceDigital::WatchFaceDigital(DisplayApp* app, WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
@ -36,15 +33,9 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
motionController {motionController} { motionController {motionController} {
settingsController.SetClockFace(0); settingsController.SetClockFace(0);
displayedChar[0] = 0;
displayedChar[1] = 0;
displayedChar[2] = 0;
displayedChar[3] = 0;
displayedChar[4] = 0;
batteryIcon = lv_label_create(lv_scr_act(), nullptr); batteryIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text(batteryIcon, Symbols::batteryFull); lv_label_set_text(batteryIcon, Symbols::batteryFull);
lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 2); lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
batteryPlug = lv_label_create(lv_scr_act(), nullptr); batteryPlug = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFF0000)); lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFF0000));
@ -56,10 +47,10 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
lv_label_set_text(bleIcon, Symbols::bluetooth); lv_label_set_text(bleIcon, Symbols::bluetooth);
lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0); lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
notificationIcon = lv_label_create(lv_scr_act(), NULL); notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00)); lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00));
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false)); lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 10, 0); lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
label_date = lv_label_create(lv_scr_act(), nullptr); label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60); lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
@ -84,7 +75,7 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr); heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text(heartbeatIcon, Symbols::heartBeat); lv_label_set_text(heartbeatIcon, Symbols::heartBeat);
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
heartbeatValue = lv_label_create(lv_scr_act(), nullptr); heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B)); lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
@ -94,7 +85,7 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
stepValue = lv_label_create(lv_scr_act(), nullptr); stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7)); lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
lv_label_set_text(stepValue, "0"); lv_label_set_text(stepValue, "0");
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2); lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
stepIcon = lv_label_create(lv_scr_act(), nullptr); stepIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7)); lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
@ -111,28 +102,21 @@ bool WatchFaceDigital::Refresh() {
if (batteryPercentRemaining.IsUpdated()) { if (batteryPercentRemaining.IsUpdated()) {
auto batteryPercent = batteryPercentRemaining.Get(); auto batteryPercent = batteryPercentRemaining.Get();
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent)); lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
auto isCharging = batteryController.IsCharging() || batteryController.IsPowerPresent(); auto isCharging = batteryController.IsCharging() or batteryController.IsPowerPresent();
lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging)); lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging));
} }
bleState = bleController.IsConnected(); bleState = bleController.IsConnected();
if (bleState.IsUpdated()) { if (bleState.IsUpdated()) {
if (bleState.Get() == true) { lv_label_set_text(bleIcon, BleIcon::GetIcon(bleState.Get()));
lv_label_set_text(bleIcon, BleIcon::GetIcon(true));
} else {
lv_label_set_text(bleIcon, BleIcon::GetIcon(false));
} }
} lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 5);
lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0); lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0); lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
notificationState = notificatioManager.AreNewNotificationsAvailable(); notificationState = notificatioManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) { if (notificationState.IsUpdated()) {
if (notificationState.Get() == true) lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
else
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
} }
currentDateTime = dateTimeController.CurrentDateTime(); currentDateTime = dateTimeController.CurrentDateTime();
@ -144,9 +128,9 @@ bool WatchFaceDigital::Refresh() {
auto time = date::make_time(newDateTime - dp); auto time = date::make_time(newDateTime - dp);
auto yearMonthDay = date::year_month_day(dp); auto yearMonthDay = date::year_month_day(dp);
auto year = (int) yearMonthDay.year(); auto year = static_cast<int>(yearMonthDay.year());
auto month = static_cast<Pinetime::Controllers::DateTime::Months>((unsigned) yearMonthDay.month()); auto month = static_cast<Pinetime::Controllers::DateTime::Months>(static_cast<unsigned>(yearMonthDay.month()));
auto day = (unsigned) yearMonthDay.day(); auto day = static_cast<unsigned>(yearMonthDay.day());
auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(date::weekday(yearMonthDay).iso_encoding()); auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(date::weekday(yearMonthDay).iso_encoding());
int hour = time.hours().count(); int hour = time.hours().count();
@ -175,15 +159,13 @@ bool WatchFaceDigital::Refresh() {
sprintf(hoursChar, "%02d", hour); sprintf(hoursChar, "%02d", hour);
} }
if (hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] || if ((hoursChar[0] != displayedChar[0]) or (hoursChar[1] != displayedChar[1]) or (minutesChar[0] != displayedChar[2]) or
minutesChar[1] != displayedChar[3]) { (minutesChar[1] != displayedChar[3])) {
displayedChar[0] = hoursChar[0]; displayedChar[0] = hoursChar[0];
displayedChar[1] = hoursChar[1]; displayedChar[1] = hoursChar[1];
displayedChar[2] = minutesChar[0]; displayedChar[2] = minutesChar[0];
displayedChar[3] = minutesChar[1]; displayedChar[3] = minutesChar[1];
char timeStr[6];
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_label_set_text(label_time_ampm, ampmChar); lv_label_set_text(label_time_ampm, ampmChar);
if (hoursChar[0] == '0') { if (hoursChar[0] == '0') {
@ -191,8 +173,7 @@ bool WatchFaceDigital::Refresh() {
} }
} }
sprintf(timeStr, "%c%c:%c%c", hoursChar[0], hoursChar[1], minutesChar[0], minutesChar[1]); lv_label_set_text_fmt(label_time, "%s:%s", hoursChar, minutesChar);
lv_label_set_text(label_time, timeStr);
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0); lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
@ -202,13 +183,11 @@ bool WatchFaceDigital::Refresh() {
} }
if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) { if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
char dateStr[22];
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
sprintf(dateStr, "%s %d %s %d", dateTimeController.DayOfWeekShortToString(), day, dateTimeController.MonthShortToString(), year); lv_label_set_text_fmt(label_date, "%s %d %s %d", dateTimeController.DayOfWeekShortToString(), day, dateTimeController.MonthShortToString(), year);
} else { } else {
sprintf(dateStr, "%s %s %d %d", dateTimeController.DayOfWeekShortToString(), dateTimeController.MonthShortToString(), day, year); lv_label_set_text_fmt(label_date, "%s %s %d %d", dateTimeController.DayOfWeekShortToString(), dateTimeController.MonthShortToString(), day, year);
} }
lv_label_set_text(label_date, dateStr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60); lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
currentYear = year; currentYear = year;
@ -229,7 +208,7 @@ bool WatchFaceDigital::Refresh() {
lv_label_set_text_static(heartbeatValue, ""); lv_label_set_text_static(heartbeatValue, "");
} }
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2); lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0); lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
} }
@ -237,7 +216,7 @@ bool WatchFaceDigital::Refresh() {
motionSensorOk = motionController.IsSensorOk(); motionSensorOk = motionController.IsSensorOk();
if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get()); lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2); lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
} }

View File

@ -35,10 +35,8 @@ namespace Pinetime {
bool Refresh() override; bool Refresh() override;
void OnObjectEvent(lv_obj_t* pObj, lv_event_t i);
private: private:
char displayedChar[5]; char displayedChar[5] {};
uint16_t currentYear = 1970; uint16_t currentYear = 1970;
Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown; Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
@ -63,7 +61,6 @@ namespace Pinetime {
lv_obj_t* batteryPlug; lv_obj_t* batteryPlug;
lv_obj_t* heartbeatIcon; lv_obj_t* heartbeatIcon;
lv_obj_t* heartbeatValue; lv_obj_t* heartbeatValue;
lv_obj_t* heartbeatBpm;
lv_obj_t* stepIcon; lv_obj_t* stepIcon;
lv_obj_t* stepValue; lv_obj_t* stepValue;
lv_obj_t* notificationIcon; lv_obj_t* notificationIcon;

View File

@ -30,27 +30,35 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
motorController {motorController}, motorController {motorController},
settingsController {settingsController} { settingsController {settingsController} {
// This is the distance (padding) between all objects on this screen.
static constexpr uint8_t innerDistance = 10;
// Time // Time
label_time = lv_label_create(lv_scr_act(), nullptr); label_time = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_fmt(label_time, "%02i:%02i", dateTimeController.Hours(), dateTimeController.Minutes()); lv_label_set_text_fmt(label_time, "%02i:%02i", dateTimeController.Hours(), dateTimeController.Minutes());
lv_label_set_align(label_time, LV_LABEL_ALIGN_CENTER); lv_label_set_align(label_time, LV_LABEL_ALIGN_CENTER);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 15, 4); lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 0);
batteryIcon = lv_label_create(lv_scr_act(), nullptr); batteryIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryController.PercentRemaining())); lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryController.PercentRemaining()));
lv_obj_align(batteryIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, -15, 4); lv_obj_align(batteryIcon, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
lv_obj_t* lbl_btn; static constexpr uint8_t barHeight = 20 + innerDistance;
static constexpr uint8_t buttonHeight = (LV_VER_RES_MAX - barHeight - innerDistance) / 2;
static constexpr uint8_t buttonWidth = (LV_HOR_RES_MAX - innerDistance) / 2; // wide buttons
//static constexpr uint8_t buttonWidth = buttonHeight; // square buttons
static constexpr uint8_t buttonXOffset = (LV_HOR_RES_MAX - buttonWidth * 2 - innerDistance) / 2;
lv_style_init(&btn_style);
lv_style_set_radius(&btn_style, LV_STATE_DEFAULT, buttonHeight / 4);
lv_style_set_bg_color(&btn_style, LV_STATE_DEFAULT, lv_color_hex(0x111111));
btn1 = lv_btn_create(lv_scr_act(), nullptr); btn1 = lv_btn_create(lv_scr_act(), nullptr);
btn1->user_data = this; btn1->user_data = this;
lv_obj_set_event_cb(btn1, ButtonEventHandler); lv_obj_set_event_cb(btn1, ButtonEventHandler);
lv_obj_align(btn1, nullptr, LV_ALIGN_CENTER, -50, -30); lv_obj_add_style(btn1, LV_BTN_PART_MAIN, &btn_style);
lv_obj_set_style_local_radius(btn1, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_set_size(btn1, buttonWidth, buttonHeight);
lv_obj_set_style_local_bg_color(btn1, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111)); lv_obj_align(btn1, nullptr, LV_ALIGN_IN_TOP_LEFT, buttonXOffset, barHeight);
lv_obj_set_style_local_bg_grad_dir(btn1, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_GRAD_DIR_NONE);
lv_btn_set_fit2(btn1, LV_FIT_TIGHT, LV_FIT_TIGHT);
btn1_lvl = lv_label_create(btn1, nullptr); btn1_lvl = lv_label_create(btn1, nullptr);
lv_obj_set_style_local_text_font(btn1_lvl, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); lv_obj_set_style_local_text_font(btn1_lvl, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48);
@ -59,12 +67,11 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
btn2 = lv_btn_create(lv_scr_act(), nullptr); btn2 = lv_btn_create(lv_scr_act(), nullptr);
btn2->user_data = this; btn2->user_data = this;
lv_obj_set_event_cb(btn2, ButtonEventHandler); lv_obj_set_event_cb(btn2, ButtonEventHandler);
lv_obj_align(btn2, nullptr, LV_ALIGN_CENTER, 50, -30); lv_obj_add_style(btn2, LV_BTN_PART_MAIN, &btn_style);
lv_obj_set_style_local_radius(btn2, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_set_size(btn2, buttonWidth, buttonHeight);
lv_obj_set_style_local_bg_color(btn2, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111)); lv_obj_align(btn2, nullptr, LV_ALIGN_IN_TOP_RIGHT, - buttonXOffset, barHeight);
lv_obj_set_style_local_bg_grad_dir(btn2, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_GRAD_DIR_NONE);
lv_btn_set_fit2(btn2, LV_FIT_TIGHT, LV_FIT_TIGHT);
lv_obj_t* lbl_btn;
lbl_btn = lv_label_create(btn2, nullptr); lbl_btn = lv_label_create(btn2, nullptr);
lv_obj_set_style_local_text_font(lbl_btn, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); lv_obj_set_style_local_text_font(lbl_btn, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48);
lv_label_set_text_static(lbl_btn, Symbols::highlight); lv_label_set_text_static(lbl_btn, Symbols::highlight);
@ -72,14 +79,11 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
btn3 = lv_btn_create(lv_scr_act(), nullptr); btn3 = lv_btn_create(lv_scr_act(), nullptr);
btn3->user_data = this; btn3->user_data = this;
lv_obj_set_event_cb(btn3, ButtonEventHandler); lv_obj_set_event_cb(btn3, ButtonEventHandler);
lv_obj_align(btn3, nullptr, LV_ALIGN_CENTER, -50, 60);
lv_btn_set_checkable(btn3, true); lv_btn_set_checkable(btn3, true);
lv_obj_set_style_local_radius(btn3, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_add_style(btn3, LV_BTN_PART_MAIN, &btn_style);
lv_obj_set_style_local_bg_color(btn3, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111));
lv_obj_set_style_local_bg_grad_dir(btn3, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_GRAD_DIR_NONE);
lv_obj_set_style_local_bg_color(btn3, LV_BTN_PART_MAIN, LV_STATE_CHECKED, LV_COLOR_GREEN); lv_obj_set_style_local_bg_color(btn3, LV_BTN_PART_MAIN, LV_STATE_CHECKED, LV_COLOR_GREEN);
lv_obj_set_style_local_bg_grad_dir(btn1, LV_BTN_PART_MAIN, LV_STATE_CHECKED, LV_GRAD_DIR_NONE); lv_obj_set_size(btn3, buttonWidth, buttonHeight);
lv_btn_set_fit2(btn3, LV_FIT_TIGHT, LV_FIT_TIGHT); lv_obj_align(btn3, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, buttonXOffset, 0);
btn3_lvl = lv_label_create(btn3, nullptr); btn3_lvl = lv_label_create(btn3, nullptr);
lv_obj_set_style_local_text_font(btn3_lvl, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); lv_obj_set_style_local_text_font(btn3_lvl, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48);
@ -94,11 +98,9 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
btn4 = lv_btn_create(lv_scr_act(), nullptr); btn4 = lv_btn_create(lv_scr_act(), nullptr);
btn4->user_data = this; btn4->user_data = this;
lv_obj_set_event_cb(btn4, ButtonEventHandler); lv_obj_set_event_cb(btn4, ButtonEventHandler);
lv_obj_align(btn4, nullptr, LV_ALIGN_CENTER, 50, 60); lv_obj_add_style(btn4, LV_BTN_PART_MAIN, &btn_style);
lv_obj_set_style_local_radius(btn4, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 20); lv_obj_set_size(btn4, buttonWidth, buttonHeight);
lv_obj_set_style_local_bg_color(btn4, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111)); lv_obj_align(btn4, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, - buttonXOffset, 0);
lv_obj_set_style_local_bg_grad_dir(btn4, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_GRAD_DIR_NONE);
lv_btn_set_fit2(btn4, LV_FIT_TIGHT, LV_FIT_TIGHT);
lbl_btn = lv_label_create(btn4, nullptr); lbl_btn = lv_label_create(btn4, nullptr);
lv_obj_set_style_local_text_font(lbl_btn, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48); lv_obj_set_style_local_text_font(lbl_btn, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48);
@ -114,6 +116,7 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
} }
QuickSettings::~QuickSettings() { QuickSettings::~QuickSettings() {
lv_style_reset(&btn_style);
lv_task_del(taskUpdate); lv_task_del(taskUpdate);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
settingsController.SaveSettings(); settingsController.SaveSettings();
@ -125,12 +128,12 @@ void QuickSettings::UpdateScreen() {
} }
void QuickSettings::OnButtonEvent(lv_obj_t* object, lv_event_t event) { void QuickSettings::OnButtonEvent(lv_obj_t* object, lv_event_t event) {
if (object == btn2 && event == LV_EVENT_PRESSED) { if (object == btn2 && event == LV_EVENT_CLICKED) {
running = false; running = false;
app->StartApp(Apps::FlashLight, DisplayApp::FullRefreshDirections::None); app->StartApp(Apps::FlashLight, DisplayApp::FullRefreshDirections::None);
} else if (object == btn1 && event == LV_EVENT_PRESSED) { } else if (object == btn1 && event == LV_EVENT_CLICKED) {
brightness.Step(); brightness.Step();
lv_label_set_text_static(btn1_lvl, brightness.GetIcon()); lv_label_set_text_static(btn1_lvl, brightness.GetIcon());
@ -140,14 +143,14 @@ void QuickSettings::OnButtonEvent(lv_obj_t* object, lv_event_t event) {
if (lv_obj_get_state(btn3, LV_BTN_PART_MAIN) & LV_STATE_CHECKED) { if (lv_obj_get_state(btn3, LV_BTN_PART_MAIN) & LV_STATE_CHECKED) {
settingsController.SetVibrationStatus(Controllers::Settings::Vibration::ON); settingsController.SetVibrationStatus(Controllers::Settings::Vibration::ON);
motorController.SetDuration(35); motorController.RunForDuration(35);
lv_label_set_text_static(btn3_lvl, Symbols::notificationsOn); lv_label_set_text_static(btn3_lvl, Symbols::notificationsOn);
} else { } else {
settingsController.SetVibrationStatus(Controllers::Settings::Vibration::OFF); settingsController.SetVibrationStatus(Controllers::Settings::Vibration::OFF);
lv_label_set_text_static(btn3_lvl, Symbols::notificationsOff); lv_label_set_text_static(btn3_lvl, Symbols::notificationsOff);
} }
} else if (object == btn4 && event == LV_EVENT_PRESSED) { } else if (object == btn4 && event == LV_EVENT_CLICKED) {
running = false; running = false;
settingsController.SetSettingsMenu(0); settingsController.SetSettingsMenu(0);
app->StartApp(Apps::Settings, DisplayApp::FullRefreshDirections::Up); app->StartApp(Apps::Settings, DisplayApp::FullRefreshDirections::Up);

View File

@ -44,6 +44,8 @@ namespace Pinetime {
lv_obj_t* batteryIcon; lv_obj_t* batteryIcon;
lv_obj_t* label_time; lv_obj_t* label_time;
lv_style_t btn_style;
lv_obj_t* btn1; lv_obj_t* btn1;
lv_obj_t* btn1_lvl; lv_obj_t* btn1_lvl;
lv_obj_t* btn2; lv_obj_t* btn2;

View File

@ -85,7 +85,7 @@ bool SettingDisplay::Refresh() {
} }
void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) { if (event == LV_EVENT_CLICKED) {
for (int i = 0; i < optionsTotal; i++) { for (int i = 0; i < optionsTotal; i++) {
if (object == cbOption[i]) { if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true); lv_checkbox_set_checked(cbOption[i], true);

View File

@ -40,38 +40,40 @@ void Cst816S::Init() {
*/ */
static constexpr uint8_t motionMask = 0b00000101; static constexpr uint8_t motionMask = 0b00000101;
twiMaster.Write(twiAddress, 0xEC, &motionMask, 1); twiMaster.Write(twiAddress, 0xEC, &motionMask, 1);
/*
[7] EnTest - Interrupt pin to test, enable automatic periodic issued after a low pulse.
[6] EnTouch - When a touch is detected, a periodic pulsed Low.
[5] EnChange - Upon detecting a touch state changes, pulsed Low.
[4] EnMotion - When the detected gesture is pulsed Low.
[0] OnceWLP - Press gesture only issue a pulse signal is low.
*/
static constexpr uint8_t irqCtl = 0b01110000;
twiMaster.Write(twiAddress, 0xFA, &irqCtl, 1);
} }
Cst816S::TouchInfos Cst816S::GetTouchInfo() { Cst816S::TouchInfos Cst816S::GetTouchInfo() {
Cst816S::TouchInfos info; Cst816S::TouchInfos info;
auto ret = twiMaster.Read(twiAddress, 0, touchData, sizeof(touchData)); auto ret = twiMaster.Read(twiAddress, 0, touchData, sizeof(touchData));
if (ret != TwiMaster::ErrorCodes::NoError) if (ret != TwiMaster::ErrorCodes::NoError) {
return {}; info.isValid = false;
return info;
}
auto nbTouchPoints = touchData[2] & 0x0f; auto nbTouchPoints = touchData[2] & 0x0f;
uint8_t i = 0; auto xHigh = touchData[touchXHighIndex] & 0x0f;
auto xLow = touchData[touchXLowIndex];
uint8_t pointId = (touchData[touchIdIndex + (touchStep * i)]) >> 4;
if (nbTouchPoints == 0 && pointId == lastTouchId)
return info;
info.isTouch = true;
auto xHigh = touchData[touchXHighIndex + (touchStep * i)] & 0x0f;
auto xLow = touchData[touchXLowIndex + (touchStep * i)];
uint16_t x = (xHigh << 8) | xLow; uint16_t x = (xHigh << 8) | xLow;
auto yHigh = touchData[touchYHighIndex + (touchStep * i)] & 0x0f; auto yHigh = touchData[touchYHighIndex] & 0x0f;
auto yLow = touchData[touchYLowIndex + (touchStep * i)]; auto yLow = touchData[touchYLowIndex];
uint16_t y = (yHigh << 8) | yLow; uint16_t y = (yHigh << 8) | yLow;
auto action = touchData[touchEventIndex + (touchStep * i)] >> 6; /* 0 = Down, 1 = Up, 2 = contact*/
info.x = x; info.x = x;
info.y = y; info.y = y;
info.action = action; info.touching = (nbTouchPoints > 0);
info.gesture = static_cast<Gestures>(touchData[gestureIndex]); info.gesture = static_cast<Gestures>(touchData[gestureIndex]);
return info; return info;

View File

@ -19,12 +19,9 @@ namespace Pinetime {
struct TouchInfos { struct TouchInfos {
uint16_t x = 0; uint16_t x = 0;
uint16_t y = 0; uint16_t y = 0;
uint8_t action = 0;
uint8_t finger = 0;
uint8_t pressure = 0;
uint8_t area = 0;
Gestures gesture = Gestures::None; Gestures gesture = Gestures::None;
bool isTouch = false; bool touching = false;
bool isValid = true;
}; };
Cst816S(TwiMaster& twiMaster, uint8_t twiAddress); Cst816S(TwiMaster& twiMaster, uint8_t twiAddress);
@ -41,20 +38,21 @@ namespace Pinetime {
private: private:
static constexpr uint8_t pinIrq = 28; static constexpr uint8_t pinIrq = 28;
static constexpr uint8_t pinReset = 10; static constexpr uint8_t pinReset = 10;
static constexpr uint8_t lastTouchId = 0x0f;
// Unused/Unavailable commented out
static constexpr uint8_t gestureIndex = 1;
static constexpr uint8_t touchPointNumIndex = 2; static constexpr uint8_t touchPointNumIndex = 2;
static constexpr uint8_t touchMiscIndex = 8; //static constexpr uint8_t touchEventIndex = 3;
static constexpr uint8_t touchXYIndex = 7;
static constexpr uint8_t touchEventIndex = 3;
static constexpr uint8_t touchXHighIndex = 3; static constexpr uint8_t touchXHighIndex = 3;
static constexpr uint8_t touchXLowIndex = 4; static constexpr uint8_t touchXLowIndex = 4;
//static constexpr uint8_t touchIdIndex = 5;
static constexpr uint8_t touchYHighIndex = 5; static constexpr uint8_t touchYHighIndex = 5;
static constexpr uint8_t touchYLowIndex = 6; static constexpr uint8_t touchYLowIndex = 6;
static constexpr uint8_t touchIdIndex = 5; //static constexpr uint8_t touchStep = 6;
static constexpr uint8_t touchStep = 6; //static constexpr uint8_t touchXYIndex = 7;
static constexpr uint8_t gestureIndex = 1; //static constexpr uint8_t touchMiscIndex = 8;
uint8_t touchData[10]; uint8_t touchData[7];
TwiMaster& twiMaster; TwiMaster& twiMaster;
uint8_t twiAddress; uint8_t twiAddress;
}; };

View File

@ -8,45 +8,39 @@ using namespace Pinetime::Drivers;
// TODO use shortcut to automatically send STOP when receive LastTX, for example // TODO use shortcut to automatically send STOP when receive LastTX, for example
// TODO use DMA/IRQ // TODO use DMA/IRQ
TwiMaster::TwiMaster(const Modules module, const Parameters& params) : module {module}, params {params} { TwiMaster::TwiMaster(NRF_TWIM_Type* module, uint32_t frequency, uint8_t pinSda, uint8_t pinScl)
: module {module}, frequency {frequency}, pinSda {pinSda}, pinScl {pinScl} {
}
void TwiMaster::ConfigurePins() const {
NRF_GPIO->PIN_CNF[pinScl] =
(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
(GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) |
(GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) |
(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
NRF_GPIO->PIN_CNF[pinSda] =
(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
(GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) |
(GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) |
(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
} }
void TwiMaster::Init() { void TwiMaster::Init() {
if(mutex == nullptr) if (mutex == nullptr) {
mutex = xSemaphoreCreateBinary(); mutex = xSemaphoreCreateBinary();
NRF_GPIO->PIN_CNF[params.pinScl] =
((uint32_t) GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | ((uint32_t) GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
((uint32_t) GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) | ((uint32_t) GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) |
((uint32_t) GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
NRF_GPIO->PIN_CNF[params.pinSda] =
((uint32_t) GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | ((uint32_t) GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
((uint32_t) GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) | ((uint32_t) GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) |
((uint32_t) GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
switch (module) {
case Modules::TWIM1:
twiBaseAddress = NRF_TWIM1;
break;
default:
return;
} }
switch (static_cast<Frequencies>(params.frequency)) { ConfigurePins();
case Frequencies::Khz100:
twiBaseAddress->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K100;
break;
case Frequencies::Khz250:
twiBaseAddress->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K250;
break;
case Frequencies::Khz400:
twiBaseAddress->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K400;
break;
}
twiBaseAddress->PSEL.SCL = params.pinScl; twiBaseAddress = module;
twiBaseAddress->PSEL.SDA = params.pinSda;
twiBaseAddress->FREQUENCY = frequency;
twiBaseAddress->PSEL.SCL = pinScl;
twiBaseAddress->PSEL.SDA = pinSda;
twiBaseAddress->EVENTS_LASTRX = 0; twiBaseAddress->EVENTS_LASTRX = 0;
twiBaseAddress->EVENTS_STOPPED = 0; twiBaseAddress->EVENTS_STOPPED = 0;
twiBaseAddress->EVENTS_LASTTX = 0; twiBaseAddress->EVENTS_LASTTX = 0;
@ -57,19 +51,15 @@ void TwiMaster::Init() {
twiBaseAddress->ENABLE = (TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos); twiBaseAddress->ENABLE = (TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos);
/* // IRQ
NVIC_ClearPendingIRQ(_IRQn);
NVIC_SetPriority(_IRQn, 2);
NVIC_EnableIRQ(_IRQn);
*/
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
} }
TwiMaster::ErrorCodes TwiMaster::Read(uint8_t deviceAddress, uint8_t registerAddress, uint8_t* data, size_t size) { TwiMaster::ErrorCodes TwiMaster::Read(uint8_t deviceAddress, uint8_t registerAddress, uint8_t* data, size_t size) {
xSemaphoreTake(mutex, portMAX_DELAY); xSemaphoreTake(mutex, portMAX_DELAY);
Wakeup();
auto ret = Write(deviceAddress, &registerAddress, 1, false); auto ret = Write(deviceAddress, &registerAddress, 1, false);
ret = Read(deviceAddress, data, size, true); ret = Read(deviceAddress, data, size, true);
Sleep();
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
return ret; return ret;
} }
@ -77,9 +67,11 @@ TwiMaster::ErrorCodes TwiMaster::Read(uint8_t deviceAddress, uint8_t registerAdd
TwiMaster::ErrorCodes TwiMaster::Write(uint8_t deviceAddress, uint8_t registerAddress, const uint8_t* data, size_t size) { TwiMaster::ErrorCodes TwiMaster::Write(uint8_t deviceAddress, uint8_t registerAddress, const uint8_t* data, size_t size) {
ASSERT(size <= maxDataSize); ASSERT(size <= maxDataSize);
xSemaphoreTake(mutex, portMAX_DELAY); xSemaphoreTake(mutex, portMAX_DELAY);
Wakeup();
internalBuffer[0] = registerAddress; internalBuffer[0] = registerAddress;
std::memcpy(internalBuffer + 1, data, size); std::memcpy(internalBuffer + 1, data, size);
auto ret = Write(deviceAddress, internalBuffer, size + 1, true); auto ret = Write(deviceAddress, internalBuffer, size + 1, true);
Sleep();
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
return ret; return ret;
} }
@ -170,17 +162,11 @@ TwiMaster::ErrorCodes TwiMaster::Write(uint8_t deviceAddress, const uint8_t* dat
} }
void TwiMaster::Sleep() { void TwiMaster::Sleep() {
while (twiBaseAddress->ENABLE != 0) {
twiBaseAddress->ENABLE = (TWIM_ENABLE_ENABLE_Disabled << TWIM_ENABLE_ENABLE_Pos); twiBaseAddress->ENABLE = (TWIM_ENABLE_ENABLE_Disabled << TWIM_ENABLE_ENABLE_Pos);
}
nrf_gpio_cfg_default(6);
nrf_gpio_cfg_default(7);
NRF_LOG_INFO("[TWIMASTER] Sleep");
} }
void TwiMaster::Wakeup() { void TwiMaster::Wakeup() {
Init(); twiBaseAddress->ENABLE = (TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos);
NRF_LOG_INFO("[TWIMASTER] Wakeup");
} }
/* Sometimes, the TWIM device just freeze and never set the event EVENTS_LASTTX. /* Sometimes, the TWIM device just freeze and never set the event EVENTS_LASTTX.
@ -190,20 +176,10 @@ void TwiMaster::Wakeup() {
* */ * */
void TwiMaster::FixHwFreezed() { void TwiMaster::FixHwFreezed() {
NRF_LOG_INFO("I2C device frozen, reinitializing it!"); NRF_LOG_INFO("I2C device frozen, reinitializing it!");
// Disable I²C
uint32_t twi_state = NRF_TWI1->ENABLE; uint32_t twi_state = NRF_TWI1->ENABLE;
twiBaseAddress->ENABLE = TWIM_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;
NRF_GPIO->PIN_CNF[params.pinScl] = Sleep();
((uint32_t) GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | ((uint32_t) GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
((uint32_t) GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) | ((uint32_t) GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
((uint32_t) GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
NRF_GPIO->PIN_CNF[params.pinSda] =
((uint32_t) GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | ((uint32_t) GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
((uint32_t) GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) | ((uint32_t) GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
((uint32_t) GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
// Re-enable I²C
twiBaseAddress->ENABLE = twi_state; twiBaseAddress->ENABLE = twi_state;
} }

View File

@ -8,16 +8,9 @@ namespace Pinetime {
namespace Drivers { namespace Drivers {
class TwiMaster { class TwiMaster {
public: public:
enum class Modules { TWIM1 };
enum class Frequencies { Khz100, Khz250, Khz400 };
enum class ErrorCodes { NoError, TransactionFailed }; enum class ErrorCodes { NoError, TransactionFailed };
struct Parameters {
uint32_t frequency;
uint8_t pinSda;
uint8_t pinScl;
};
TwiMaster(const Modules module, const Parameters& params); TwiMaster(NRF_TWIM_Type* module, uint32_t frequency, uint8_t pinSda, uint8_t pinScl);
void Init(); void Init();
ErrorCodes Read(uint8_t deviceAddress, uint8_t registerAddress, uint8_t* buffer, size_t size); ErrorCodes Read(uint8_t deviceAddress, uint8_t registerAddress, uint8_t* buffer, size_t size);
@ -30,10 +23,14 @@ namespace Pinetime {
ErrorCodes Read(uint8_t deviceAddress, uint8_t* buffer, size_t size, bool stop); ErrorCodes Read(uint8_t deviceAddress, uint8_t* buffer, size_t size, bool stop);
ErrorCodes Write(uint8_t deviceAddress, const uint8_t* data, size_t size, bool stop); ErrorCodes Write(uint8_t deviceAddress, const uint8_t* data, size_t size, bool stop);
void FixHwFreezed(); void FixHwFreezed();
void ConfigurePins() const;
NRF_TWIM_Type* twiBaseAddress; NRF_TWIM_Type* twiBaseAddress;
SemaphoreHandle_t mutex = nullptr; SemaphoreHandle_t mutex = nullptr;
const Modules module; NRF_TWIM_Type* module;
const Parameters params; uint32_t frequency;
uint8_t pinSda;
uint8_t pinScl;
static constexpr uint8_t maxDataSize {16}; static constexpr uint8_t maxDataSize {16};
static constexpr uint8_t registerSize {1}; static constexpr uint8_t registerSize {1};
uint8_t internalBuffer[maxDataSize + registerSize]; uint8_t internalBuffer[maxDataSize + registerSize];

View File

@ -5,6 +5,7 @@
#include <libraries/gpiote/app_gpiote.h> #include <libraries/gpiote/app_gpiote.h>
#include <libraries/timer/app_timer.h> #include <libraries/timer/app_timer.h>
#include <softdevice/common/nrf_sdh.h> #include <softdevice/common/nrf_sdh.h>
#include <nrf_delay.h>
// nimble // nimble
#define min // workaround: nimble's min/max macros conflict with libstdc++ #define min // workaround: nimble's min/max macros conflict with libstdc++
@ -43,6 +44,7 @@
#include "drivers/TwiMaster.h" #include "drivers/TwiMaster.h"
#include "drivers/Cst816s.h" #include "drivers/Cst816s.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "touchhandler/TouchHandler.h"
#if NRF_LOG_ENABLED #if NRF_LOG_ENABLED
#include "logging/NrfLogger.h" #include "logging/NrfLogger.h"
@ -82,8 +84,7 @@ Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi};
// respecting correct timings. According to erratas heet, this magic value makes it run // respecting correct timings. According to erratas heet, this magic value makes it run
// at ~390Khz with correct timings. // at ~390Khz with correct timings.
static constexpr uint32_t MaxTwiFrequencyWithoutHardwareBug {0x06200000}; static constexpr uint32_t MaxTwiFrequencyWithoutHardwareBug {0x06200000};
Pinetime::Drivers::TwiMaster twiMaster {Pinetime::Drivers::TwiMaster::Modules::TWIM1, Pinetime::Drivers::TwiMaster twiMaster {NRF_TWIM1, MaxTwiFrequencyWithoutHardwareBug, pinTwiSda, pinTwiScl};
Pinetime::Drivers::TwiMaster::Parameters {MaxTwiFrequencyWithoutHardwareBug, pinTwiSda, pinTwiScl}};
Pinetime::Drivers::Cst816S touchPanel {twiMaster, touchPanelTwiAddress}; Pinetime::Drivers::Cst816S touchPanel {twiMaster, touchPanelTwiAddress};
#ifdef PINETIME_IS_RECOVERY #ifdef PINETIME_IS_RECOVERY
static constexpr bool isFactory = true; static constexpr bool isFactory = true;
@ -118,6 +119,7 @@ Pinetime::Drivers::WatchdogView watchdogView(watchdog);
Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::NotificationManager notificationManager;
Pinetime::Controllers::MotionController motionController; Pinetime::Controllers::MotionController motionController;
Pinetime::Controllers::TimerController timerController; Pinetime::Controllers::TimerController timerController;
Pinetime::Controllers::TouchHandler touchHandler(touchPanel, lvgl);
Pinetime::Controllers::FS fs {spiNorFlash}; Pinetime::Controllers::FS fs {spiNorFlash};
Pinetime::Controllers::Settings settingsController {fs}; Pinetime::Controllers::Settings settingsController {fs};
@ -136,7 +138,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd,
settingsController, settingsController,
motorController, motorController,
motionController, motionController,
timerController); timerController,
touchHandler);
Pinetime::System::SystemTask systemTask(spi, Pinetime::System::SystemTask systemTask(spi,
lcd, lcd,
@ -158,7 +161,8 @@ Pinetime::System::SystemTask systemTask(spi,
heartRateController, heartRateController,
displayApp, displayApp,
heartRateApp, heartRateApp,
fs); fs,
touchHandler);
void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
if (pin == pinTouchIrq) { if (pin == pinTouchIrq) {
@ -300,6 +304,20 @@ int main(void) {
nrf_drv_clock_init(); nrf_drv_clock_init();
// Unblock i2c?
nrf_gpio_cfg(pinTwiScl,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_S0D1,
NRF_GPIO_PIN_NOSENSE);
nrf_gpio_pin_set(pinTwiScl);
for (uint8_t i = 0; i < 16; i++) {
nrf_gpio_pin_toggle(pinTwiScl);
nrf_delay_us(5);
}
nrf_gpio_cfg_default(pinTwiScl);
debounceTimer = xTimerCreate("debounceTimer", 200, pdFALSE, (void*) 0, DebounceTimerCallback); debounceTimer = xTimerCreate("debounceTimer", 200, pdFALSE, (void*) 0, DebounceTimerCallback);
debounceChargeTimer = xTimerCreate("debounceTimerCharge", 200, pdFALSE, (void*) 0, DebounceTimerChargeCallback); debounceChargeTimer = xTimerCreate("debounceTimerCharge", 200, pdFALSE, (void*) 0, DebounceTimerChargeCallback);
@ -309,6 +327,7 @@ int main(void) {
lvgl.Init(); lvgl.Init();
systemTask.Start(); systemTask.Start();
nimble_port_init(); nimble_port_init();
vTaskStartScheduler(); vTaskStartScheduler();

View File

@ -67,7 +67,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
Pinetime::Controllers::HeartRateController& heartRateController, Pinetime::Controllers::HeartRateController& heartRateController,
Pinetime::Applications::DisplayApp& displayApp, Pinetime::Applications::DisplayApp& displayApp,
Pinetime::Applications::HeartRateTask& heartRateApp, Pinetime::Applications::HeartRateTask& heartRateApp,
Pinetime::Controllers::FS& fs) Pinetime::Controllers::FS& fs,
Pinetime::Controllers::TouchHandler& touchHandler)
: spi {spi}, : spi {spi},
lcd {lcd}, lcd {lcd},
spiNorFlash {spiNorFlash}, spiNorFlash {spiNorFlash},
@ -79,18 +80,18 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
dateTimeController {dateTimeController}, dateTimeController {dateTimeController},
timerController {timerController}, timerController {timerController},
watchdog {watchdog}, watchdog {watchdog},
notificationManager{notificationManager}, notificationManager {notificationManager},
motorController {motorController}, motorController {motorController},
heartRateSensor {heartRateSensor}, heartRateSensor {heartRateSensor},
motionSensor {motionSensor}, motionSensor {motionSensor},
settingsController {settingsController}, settingsController {settingsController},
heartRateController{heartRateController}, heartRateController {heartRateController},
motionController{motionController}, motionController {motionController},
displayApp{displayApp}, displayApp {displayApp},
heartRateApp(heartRateApp), heartRateApp(heartRateApp),
fs{fs}, fs {fs},
touchHandler {touchHandler},
nimbleController(*this, bleController, dateTimeController, notificationManager, batteryController, spiNorFlash, heartRateController) { nimbleController(*this, bleController, dateTimeController, notificationManager, batteryController, spiNorFlash, heartRateController) {
} }
void SystemTask::Start() { void SystemTask::Start() {
@ -219,7 +220,6 @@ void SystemTask::Work() {
break; break;
case Messages::GoToRunning: case Messages::GoToRunning:
spi.Wakeup(); spi.Wakeup();
twiMaster.Wakeup();
// Double Tap needs the touch screen to be in normal mode // Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
@ -240,10 +240,8 @@ void SystemTask::Work() {
isDimmed = false; isDimmed = false;
break; break;
case Messages::TouchWakeUp: { case Messages::TouchWakeUp: {
twiMaster.Wakeup();
auto touchInfo = touchPanel.GetTouchInfo(); auto touchInfo = touchPanel.GetTouchInfo();
twiMaster.Sleep(); if (touchInfo.touching and ((touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::DoubleTap and
if (touchInfo.isTouch and ((touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::DoubleTap and
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) or settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) or
(touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::SingleTap and (touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::SingleTap and
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)))) { settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)))) {
@ -266,14 +264,13 @@ void SystemTask::Work() {
if (isSleeping && !isWakingUp) { if (isSleeping && !isWakingUp) {
GoToRunning(); GoToRunning();
} }
motorController.SetDuration(35);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification); displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
break; break;
case Messages::OnTimerDone: case Messages::OnTimerDone:
if (isSleeping && !isWakingUp) { if (isSleeping && !isWakingUp) {
GoToRunning(); GoToRunning();
} }
motorController.SetDuration(35); motorController.RunForDuration(35);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TimerDone); displayApp.PushMessage(Pinetime::Applications::Display::Messages::TimerDone);
break; break;
case Messages::BleConnected: case Messages::BleConnected:
@ -295,6 +292,9 @@ void SystemTask::Work() {
xTimerStart(dimTimer, 0); xTimerStart(dimTimer, 0);
break; break;
case Messages::OnTouchEvent: case Messages::OnTouchEvent:
if (touchHandler.GetNewTouchInfo()) {
touchHandler.UpdateLvglTouchPoint();
}
ReloadIdleTimer(); ReloadIdleTimer();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent); displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
break; break;
@ -315,7 +315,6 @@ void SystemTask::Work() {
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
touchPanel.Sleep(); touchPanel.Sleep();
} }
twiMaster.Sleep();
isSleeping = true; isSleeping = true;
isGoingToSleep = false; isGoingToSleep = false;
@ -326,7 +325,7 @@ void SystemTask::Work() {
stepCounterMustBeReset = true; stepCounterMustBeReset = true;
break; break;
case Messages::OnChargingEvent: case Messages::OnChargingEvent:
motorController.SetDuration(15); motorController.RunForDuration(15);
// Battery level is updated on every message - there's no need to do anything // Battery level is updated on every message - there's no need to do anything
break; break;
@ -367,17 +366,12 @@ void SystemTask::UpdateMotion() {
if (isSleeping && !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist)) if (isSleeping && !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist))
return; return;
if (isSleeping)
twiMaster.Wakeup();
if (stepCounterMustBeReset) { if (stepCounterMustBeReset) {
motionSensor.ResetStepCounter(); motionSensor.ResetStepCounter();
stepCounterMustBeReset = false; stepCounterMustBeReset = false;
} }
auto motionValues = motionSensor.Process(); auto motionValues = motionSensor.Process();
if (isSleeping)
twiMaster.Sleep();
motionController.IsSensorOk(motionSensor.IsOk()); motionController.IsSensorOk(motionSensor.IsOk());
motionController.Update(motionValues.x, motionValues.y, motionValues.z, motionValues.steps); motionController.Update(motionValues.x, motionValues.y, motionValues.z, motionValues.steps);
@ -425,14 +419,13 @@ void SystemTask::PushMessage(System::Messages msg) {
isGoingToSleep = true; isGoingToSleep = true;
} }
if(in_isr()) { if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken; BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE; xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken); xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) { if (xHigherPriorityTaskWoken) {
/* Actual macro used here is port specific. */ /* Actual macro used here is port specific. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
} }
} else { } else {
xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY); xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY);

View File

@ -17,6 +17,7 @@
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "components/timer/TimerController.h" #include "components/timer/TimerController.h"
#include "components/fs/FS.h" #include "components/fs/FS.h"
#include "touchhandler/TouchHandler.h"
#ifdef PINETIME_IS_RECOVERY #ifdef PINETIME_IS_RECOVERY
#include "displayapp/DisplayAppRecovery.h" #include "displayapp/DisplayAppRecovery.h"
@ -39,6 +40,9 @@ namespace Pinetime {
class TwiMaster; class TwiMaster;
class Hrs3300; class Hrs3300;
} }
namespace Controllers {
class TouchHandler;
}
namespace System { namespace System {
class SystemTask { class SystemTask {
public: public:
@ -62,7 +66,8 @@ namespace Pinetime {
Pinetime::Controllers::HeartRateController& heartRateController, Pinetime::Controllers::HeartRateController& heartRateController,
Pinetime::Applications::DisplayApp& displayApp, Pinetime::Applications::DisplayApp& displayApp,
Pinetime::Applications::HeartRateTask& heartRateApp, Pinetime::Applications::HeartRateTask& heartRateApp,
Pinetime::Controllers::FS& fs); Pinetime::Controllers::FS& fs,
Pinetime::Controllers::TouchHandler& touchHandler);
void Start(); void Start();
void PushMessage(Messages msg); void PushMessage(Messages msg);
@ -92,7 +97,6 @@ namespace Pinetime {
Pinetime::Components::LittleVgl& lvgl; Pinetime::Components::LittleVgl& lvgl;
Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Battery& batteryController;
Pinetime::Controllers::Ble& bleController; Pinetime::Controllers::Ble& bleController;
Pinetime::Controllers::DateTime& dateTimeController; Pinetime::Controllers::DateTime& dateTimeController;
Pinetime::Controllers::TimerController& timerController; Pinetime::Controllers::TimerController& timerController;
@ -113,6 +117,7 @@ namespace Pinetime {
Pinetime::Applications::DisplayApp& displayApp; Pinetime::Applications::DisplayApp& displayApp;
Pinetime::Applications::HeartRateTask& heartRateApp; Pinetime::Applications::HeartRateTask& heartRateApp;
Pinetime::Controllers::FS& fs; Pinetime::Controllers::FS& fs;
Pinetime::Controllers::TouchHandler& touchHandler;
Pinetime::Controllers::NimbleController nimbleController; Pinetime::Controllers::NimbleController nimbleController;
static constexpr uint8_t pinSpiSck = 2; static constexpr uint8_t pinSpiSck = 2;

View File

@ -0,0 +1,65 @@
#include "TouchHandler.h"
using namespace Pinetime::Controllers;
TouchHandler::TouchHandler(Drivers::Cst816S& touchPanel, Components::LittleVgl& lvgl) : touchPanel {touchPanel}, lvgl {lvgl} {
}
void TouchHandler::CancelTap() {
if (info.touching) {
isCancelled = true;
lvgl.SetNewTouchPoint(-1, -1, true);
}
}
Pinetime::Drivers::Cst816S::Gestures TouchHandler::GestureGet() {
auto returnGesture = gesture;
gesture = Drivers::Cst816S::Gestures::None;
return returnGesture;
}
bool TouchHandler::GetNewTouchInfo() {
info = touchPanel.GetTouchInfo();
if (!info.isValid) {
return false;
}
if (info.gesture != Pinetime::Drivers::Cst816S::Gestures::None) {
if (gestureReleased) {
if (info.gesture == Pinetime::Drivers::Cst816S::Gestures::SlideDown ||
info.gesture == Pinetime::Drivers::Cst816S::Gestures::SlideLeft ||
info.gesture == Pinetime::Drivers::Cst816S::Gestures::SlideUp ||
info.gesture == Pinetime::Drivers::Cst816S::Gestures::SlideRight ||
info.gesture == Pinetime::Drivers::Cst816S::Gestures::LongPress) {
if (info.touching) {
gesture = info.gesture;
gestureReleased = false;
}
} else {
gesture = info.gesture;
}
}
}
if (!info.touching) {
gestureReleased = true;
}
return true;
}
void TouchHandler::UpdateLvglTouchPoint() {
if (info.touching) {
if (!isCancelled) {
lvgl.SetNewTouchPoint(info.x, info.y, true);
}
} else {
if (isCancelled) {
lvgl.SetNewTouchPoint(-1, -1, false);
isCancelled = false;
} else {
lvgl.SetNewTouchPoint(info.x, info.y, false);
}
}
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "drivers/Cst816s.h"
#include "systemtask/SystemTask.h"
#include <FreeRTOS.h>
#include <task.h>
namespace Pinetime {
namespace Components {
class LittleVgl;
}
namespace Drivers {
class Cst816S;
}
namespace System {
class SystemTask;
}
namespace Controllers {
class TouchHandler {
public:
explicit TouchHandler(Drivers::Cst816S&, Components::LittleVgl&);
void CancelTap();
bool GetNewTouchInfo();
void UpdateLvglTouchPoint();
bool IsTouching() const {
return info.touching;
}
uint8_t GetX() const {
return info.x;
}
uint8_t GetY() const {
return info.y;
}
Drivers::Cst816S::Gestures GestureGet();
private:
Pinetime::Drivers::Cst816S::TouchInfos info;
Pinetime::Drivers::Cst816S& touchPanel;
Pinetime::Components::LittleVgl& lvgl;
Pinetime::Drivers::Cst816S::Gestures gesture;
bool isCancelled = false;
bool gestureReleased = true;
};
}
}