Compare commits
	
		
			8 Commits
		
	
	
		
			wb/roman-n
			...
			wb/fuzzy-n
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b7301d51cd | ||
| 
						 | 
					5c87829d65 | ||
| 
						 | 
					7cd9e8493d | ||
| 
						 | 
					d2989ae23a | ||
| 
						 | 
					66f6c34b50 | ||
| 
						 | 
					6d615b374c | ||
| 
						 | 
					3df7c6a4f3 | ||
| 
						 | 
					e1c652bdfb | 
@ -1,32 +0,0 @@
 | 
				
			|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
 | 
					 | 
				
			||||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	"build": {
 | 
					 | 
				
			||||||
		"dockerfile": "docker/Dockerfile"
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	"customizations": {
 | 
					 | 
				
			||||||
		"vscode": {
 | 
					 | 
				
			||||||
			"settings": {
 | 
					 | 
				
			||||||
				// Set *default* container specific settings.json values on container create.
 | 
					 | 
				
			||||||
				"terminal.integrated.profiles.linux": {
 | 
					 | 
				
			||||||
					"bash": {
 | 
					 | 
				
			||||||
						"path": "/bin/bash"
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				"terminal.integrated.defaultProfile.linux": "bash",
 | 
					 | 
				
			||||||
				"editor.formatOnSave": true,
 | 
					 | 
				
			||||||
				// FIXME: This and the Dockerfile might get out of sync
 | 
					 | 
				
			||||||
				"clang-format.executable": "clang-format-14"
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			// Add the IDs of extensions you want installed when the container is created.
 | 
					 | 
				
			||||||
			"extensions": [
 | 
					 | 
				
			||||||
				"ms-vscode.cpptools",
 | 
					 | 
				
			||||||
				"ms-vscode.cmake-tools",
 | 
					 | 
				
			||||||
				"marus25.cortex-debug",
 | 
					 | 
				
			||||||
				"notskm.clang-tidy",
 | 
					 | 
				
			||||||
				"mjohns.clang-format"
 | 
					 | 
				
			||||||
			]
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	"remoteUser": "infinitime"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										66
									
								
								.devcontainer/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								.devcontainer/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					FROM ubuntu:latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARG DEBIAN_FRONTEND=noninteractive
 | 
				
			||||||
 | 
					RUN apt-get update -qq \
 | 
				
			||||||
 | 
					    && apt-get install -y \
 | 
				
			||||||
 | 
					# x86_64 / generic packages
 | 
				
			||||||
 | 
					      bash \
 | 
				
			||||||
 | 
					      build-essential \
 | 
				
			||||||
 | 
					      cmake \
 | 
				
			||||||
 | 
					      git \
 | 
				
			||||||
 | 
					      make \
 | 
				
			||||||
 | 
					      python3 \
 | 
				
			||||||
 | 
					      python3-pip \
 | 
				
			||||||
 | 
					      python3-pil \
 | 
				
			||||||
 | 
					      tar \
 | 
				
			||||||
 | 
					      unzip \
 | 
				
			||||||
 | 
					      wget \ 
 | 
				
			||||||
 | 
					      curl \
 | 
				
			||||||
 | 
					      dos2unix \
 | 
				
			||||||
 | 
					      clang-format-12 \
 | 
				
			||||||
 | 
					      clang-tidy \
 | 
				
			||||||
 | 
					      locales \
 | 
				
			||||||
 | 
					      libncurses5 \
 | 
				
			||||||
 | 
					# aarch64 packages
 | 
				
			||||||
 | 
					      libffi-dev \
 | 
				
			||||||
 | 
					      libssl-dev \
 | 
				
			||||||
 | 
					      python3-dev \ 
 | 
				
			||||||
 | 
					      rustc \
 | 
				
			||||||
 | 
					    && rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					#SET LOCALE 
 | 
				
			||||||
 | 
					RUN locale-gen en_US.UTF-8
 | 
				
			||||||
 | 
					ENV LANG en_US.UTF-8
 | 
				
			||||||
 | 
					ENV LANGUAGE en_US:en
 | 
				
			||||||
 | 
					ENV LC_ALL en_US.UTF-8 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN pip3 install adafruit-nrfutil
 | 
				
			||||||
 | 
					# required for McuBoot
 | 
				
			||||||
 | 
					RUN pip3 install setuptools_rust
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /opt/
 | 
				
			||||||
 | 
					# build.sh knows how to compile but it problimatic on Win10
 | 
				
			||||||
 | 
					COPY build.sh .
 | 
				
			||||||
 | 
					RUN chmod +x build.sh
 | 
				
			||||||
 | 
					# create_build_openocd.sh uses cmake to crate to build directory
 | 
				
			||||||
 | 
					COPY create_build_openocd.sh .
 | 
				
			||||||
 | 
					RUN chmod +x create_build_openocd.sh
 | 
				
			||||||
 | 
					# Lets get each in a separate docker layer for better downloads
 | 
				
			||||||
 | 
					# GCC
 | 
				
			||||||
 | 
					# RUN bash -c "source /opt/build.sh; GetGcc;"
 | 
				
			||||||
 | 
					RUN wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -O - | tar -xj -C /opt
 | 
				
			||||||
 | 
					# NrfSdk
 | 
				
			||||||
 | 
					# RUN bash -c "source /opt/build.sh; GetNrfSdk;"
 | 
				
			||||||
 | 
					RUN wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip" -O /tmp/nRF5_SDK_15.3.0_59ac345
 | 
				
			||||||
 | 
					RUN unzip -q /tmp/nRF5_SDK_15.3.0_59ac345 -d /opt
 | 
				
			||||||
 | 
					RUN rm /tmp/nRF5_SDK_15.3.0_59ac345
 | 
				
			||||||
 | 
					# McuBoot
 | 
				
			||||||
 | 
					# RUN bash -c "source /opt/build.sh; GetMcuBoot;"
 | 
				
			||||||
 | 
					RUN git clone https://github.com/mcu-tools/mcuboot.git
 | 
				
			||||||
 | 
					RUN pip3 install -r ./mcuboot/scripts/requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN adduser infinitime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV NRF5_SDK_PATH /opt/nRF5_SDK_15.3.0_59ac345
 | 
				
			||||||
 | 
					ENV ARM_NONE_EABI_TOOLCHAIN_PATH /opt/gcc-arm-none-eabi-9-2020-q2-update
 | 
				
			||||||
 | 
					ENV SOURCES_DIR /workspaces/InfiniTime
 | 
				
			||||||
							
								
								
									
										87
									
								
								.devcontainer/build.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								.devcontainer/build.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					(return 0 2>/dev/null) && SOURCED="true" || SOURCED="false"
 | 
				
			||||||
 | 
					export LC_ALL=C.UTF-8
 | 
				
			||||||
 | 
					export LANG=C.UTF-8
 | 
				
			||||||
 | 
					set -x
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Default locations if the var isn't already set
 | 
				
			||||||
 | 
					export TOOLS_DIR="${TOOLS_DIR:=/opt}"
 | 
				
			||||||
 | 
					export SOURCES_DIR="${SOURCES_DIR:=/sources}"
 | 
				
			||||||
 | 
					export BUILD_DIR="${BUILD_DIR:=$SOURCES_DIR/build}"
 | 
				
			||||||
 | 
					export OUTPUT_DIR="${OUTPUT_DIR:=$BUILD_DIR/output}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export BUILD_TYPE=${BUILD_TYPE:=Release}
 | 
				
			||||||
 | 
					export GCC_ARM_VER=${GCC_ARM_VER:="gcc-arm-none-eabi-9-2020-q2-update"}
 | 
				
			||||||
 | 
					export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MACHINE="$(uname -m)"
 | 
				
			||||||
 | 
					[[ "$MACHINE" == "arm64" ]] && MACHINE="aarch64"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main() {
 | 
				
			||||||
 | 
					  local target="$1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mkdir -p "$TOOLS_DIR"
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  [[ ! -d "$TOOLS_DIR/$GCC_ARM_VER" ]] && GetGcc
 | 
				
			||||||
 | 
					  [[ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]] && GetNrfSdk
 | 
				
			||||||
 | 
					  [[ ! -d "$TOOLS_DIR/mcuboot" ]] && GetMcuBoot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mkdir -p "$BUILD_DIR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CmakeGenerate
 | 
				
			||||||
 | 
					  CmakeBuild $target
 | 
				
			||||||
 | 
					  BUILD_RESULT=$? 
 | 
				
			||||||
 | 
					  if [ "$DISABLE_POSTBUILD" != "true" -a "$BUILD_RESULT" == 0 ]; then
 | 
				
			||||||
 | 
					    source "$BUILD_DIR/post_build.sh"
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					  # assuming post_build.sh will never fail on a successful build
 | 
				
			||||||
 | 
					  return $BUILD_RESULT
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GetGcc() {
 | 
				
			||||||
 | 
					  GCC_SRC="$GCC_ARM_VER-$MACHINE-linux.tar.bz"
 | 
				
			||||||
 | 
					  wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/$GCC_SRC -O - | tar -xj -C $TOOLS_DIR/
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GetMcuBoot() {
 | 
				
			||||||
 | 
					  git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot"
 | 
				
			||||||
 | 
					  pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GetNrfSdk() {
 | 
				
			||||||
 | 
					  wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER
 | 
				
			||||||
 | 
					  unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/"
 | 
				
			||||||
 | 
					  rm /tmp/$NRF_SDK_VER
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CmakeGenerate() {
 | 
				
			||||||
 | 
					  # We can swap the CD and trailing SOURCES_DIR for -B and -S respectively
 | 
				
			||||||
 | 
					  # once we go to newer CMake (Ubuntu 18.10 gives us CMake 3.10)
 | 
				
			||||||
 | 
					  cd "$BUILD_DIR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cmake -G "Unix Makefiles" \
 | 
				
			||||||
 | 
					    -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
 | 
				
			||||||
 | 
					    -DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \
 | 
				
			||||||
 | 
					    -DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
 | 
				
			||||||
 | 
					    "$SOURCES_DIR"
 | 
				
			||||||
 | 
					  cmake -L -N .
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CmakeBuild() {
 | 
				
			||||||
 | 
					  local target="$1"
 | 
				
			||||||
 | 
					  [[ -n "$target" ]] && target="--target $target"
 | 
				
			||||||
 | 
					  if cmake --build "$BUILD_DIR" --config $BUILD_TYPE $target -- -j$(nproc)
 | 
				
			||||||
 | 
					    then return 0; else return 1; 
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ $SOURCED == "false" ]]; then
 | 
				
			||||||
 | 
					  # It is important to return exit code of main
 | 
				
			||||||
 | 
					  # To be future-proof, this is handled explicitely
 | 
				
			||||||
 | 
					  main "$@"
 | 
				
			||||||
 | 
					  BUILD_RESULT=$?
 | 
				
			||||||
 | 
					  exit $BUILD_RESULT
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					  echo "Sourced!"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										2
									
								
								.devcontainer/build_app.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.devcontainer/build_app.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					cmake --build /workspaces/Pinetime/build --config Release -- -j6 pinetime-app
 | 
				
			||||||
							
								
								
									
										3
									
								
								.devcontainer/create_build_openocd.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.devcontainer/create_build_openocd.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					rm -rf build/
 | 
				
			||||||
 | 
					cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 -S . -Bbuild
 | 
				
			||||||
							
								
								
									
										38
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
 | 
				
			||||||
 | 
					// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// "name": "Pinetime",
 | 
				
			||||||
 | 
						// "image": "feabhas/pinetime-dev"
 | 
				
			||||||
 | 
						"build": {
 | 
				
			||||||
 | 
							"dockerfile": "Dockerfile",
 | 
				
			||||||
 | 
							// Update 'VARIANT' to pick an Debian / Ubuntu OS version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04
 | 
				
			||||||
 | 
							// "args": { "VARIANT": "ubuntu-20.04" }
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set *default* container specific settings.json values on container create.
 | 
				
			||||||
 | 
						"settings": { 
 | 
				
			||||||
 | 
							"terminal.integrated.shell.linux": "/bin/bash",
 | 
				
			||||||
 | 
							"editor.formatOnSave": true,
 | 
				
			||||||
 | 
							"clang-format.executable": "clang-format-12"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add the IDs of extensions you want installed when the container is created.
 | 
				
			||||||
 | 
						"extensions": [
 | 
				
			||||||
 | 
							"ms-vscode.cpptools",
 | 
				
			||||||
 | 
							"ms-vscode.cmake-tools",
 | 
				
			||||||
 | 
							"marus25.cortex-debug",
 | 
				
			||||||
 | 
							"notskm.clang-tidy",
 | 
				
			||||||
 | 
							"mjohns.clang-format"
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use 'forwardPorts' to make a list of ports inside the container available locally.
 | 
				
			||||||
 | 
						// "forwardPorts": [],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use 'postCreateCommand' to run commands after the container is created.
 | 
				
			||||||
 | 
						// "postCreateCommand": "bash /opt/create_build_openocd.sh",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
 | 
				
			||||||
 | 
						// "remoteUser": "vscode"
 | 
				
			||||||
 | 
						"remoteUser": "infinitime"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								.devcontainer/make_build_dir.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.devcontainer/make_build_dir.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 ${SOURCES_DIR}
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							@ -3,7 +3,7 @@ name: CI
 | 
				
			|||||||
# Run this workflow whenever the build may be affected
 | 
					# Run this workflow whenever the build may be affected
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    branches: [ main ]
 | 
					    branches: [ main, wb/fuzzy, wb/fuzzy-norm ]
 | 
				
			||||||
    paths-ignore:
 | 
					    paths-ignore:
 | 
				
			||||||
      - 'doc/**'
 | 
					      - 'doc/**'
 | 
				
			||||||
      - '**.md'
 | 
					      - '**.md'
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -50,5 +50,3 @@ src/arm-none-eabi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# clangd
 | 
					# clangd
 | 
				
			||||||
.cache/
 | 
					.cache/
 | 
				
			||||||
 | 
					 | 
				
			||||||
nRF5_SDK/
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							@ -10,6 +10,3 @@
 | 
				
			|||||||
[submodule "src/libs/arduinoFFT"]
 | 
					[submodule "src/libs/arduinoFFT"]
 | 
				
			||||||
	path = src/libs/arduinoFFT
 | 
						path = src/libs/arduinoFFT
 | 
				
			||||||
	url = https://github.com/kosme/arduinoFFT.git
 | 
						url = https://github.com/kosme/arduinoFFT.git
 | 
				
			||||||
[submodule "src/libs/sunset"]
 | 
					 | 
				
			||||||
	path = src/libs/sunset
 | 
					 | 
				
			||||||
	url = https://github.com/buelowp/sunset.git
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								.vscode/c_cpp_properties.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.vscode/c_cpp_properties.json
									
									
									
									
										vendored
									
									
								
							@ -1,9 +1,4 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "env": {
 | 
					 | 
				
			||||||
        // TODO: This is a duplication of the configuration set in /docker/build.sh!
 | 
					 | 
				
			||||||
        "TOOLS_DIR": "/opt",
 | 
					 | 
				
			||||||
        "GCC_ARM_PATH": "gcc-arm-none-eabi-10.3-2021.10"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "configurations": [
 | 
					    "configurations": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "name": "nrfCC",
 | 
					            "name": "nrfCC",
 | 
				
			||||||
@ -19,22 +14,7 @@
 | 
				
			|||||||
            "intelliSenseMode": "linux-gcc-arm",
 | 
					            "intelliSenseMode": "linux-gcc-arm",
 | 
				
			||||||
            "configurationProvider": "ms-vscode.cpp-tools",
 | 
					            "configurationProvider": "ms-vscode.cpp-tools",
 | 
				
			||||||
            "compileCommands": "${workspaceFolder}/build/compile_commands.json"
 | 
					            "compileCommands": "${workspaceFolder}/build/compile_commands.json"
 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            "name": "nrfCC Devcontainer",
 | 
					 | 
				
			||||||
            "includePath": [
 | 
					 | 
				
			||||||
                "${workspaceFolder}/**",
 | 
					 | 
				
			||||||
                "${workspaceFolder}/src/**",
 | 
					 | 
				
			||||||
                "${workspaceFolder}/src"
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            "defines": [],
 | 
					 | 
				
			||||||
            "compilerPath": "${TOOLS_DIR}/${GCC_ARM_PATH}/bin/arm-none-eabi-gcc",
 | 
					 | 
				
			||||||
            "cStandard": "c99",
 | 
					 | 
				
			||||||
            "cppStandard": "c++20",
 | 
					 | 
				
			||||||
            "intelliSenseMode": "linux-gcc-arm",
 | 
					 | 
				
			||||||
            "configurationProvider": "ms-vscode.cpp-tools",
 | 
					 | 
				
			||||||
            "compileCommands": "${workspaceFolder}/build/compile_commands.json"
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "version": 4
 | 
					    "version": 4
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								.vscode/cmake-kits.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.vscode/cmake-kits.json
									
									
									
									
										vendored
									
									
								
							@ -1,6 +0,0 @@
 | 
				
			|||||||
[
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        "name": "InfiniTime Compiler",
 | 
					 | 
				
			||||||
        "environmentSetupScript": "${workspaceFolder}/docker/build.sh"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
							
								
								
									
										45
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										45
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@ -1,18 +1,20 @@
 | 
				
			|||||||
{
 | 
					  {
 | 
				
			||||||
    "version": "0.1.0",
 | 
					    "version": "0.1.0",
 | 
				
			||||||
    "configurations": [
 | 
					    "configurations": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "name": "Debug - Openocd docker Remote",
 | 
					            "name": "Debug - Openocd docker Remote",
 | 
				
			||||||
            "type": "cortex-debug",
 | 
					            "type":"cortex-debug",
 | 
				
			||||||
 | 
					            "cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
 | 
				
			||||||
            "cwd": "${workspaceRoot}",
 | 
					            "cwd": "${workspaceRoot}",
 | 
				
			||||||
            "executable": "${command:cmake.launchTargetPath}",
 | 
					            "executable": "${command:cmake.launchTargetPath}",
 | 
				
			||||||
            "request": "launch",
 | 
					            "request": "launch",
 | 
				
			||||||
            "servertype": "external",
 | 
					            "servertype": "external",
 | 
				
			||||||
            "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
 | 
					            // This may need to be arm-none-eabi-gdb depending on your system
 | 
				
			||||||
 | 
					            "gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
 | 
				
			||||||
            // Connect to an already running OpenOCD instance
 | 
					            // Connect to an already running OpenOCD instance
 | 
				
			||||||
            "gdbTarget": "host.docker.internal:3333",
 | 
					            "gdbTarget": "host.docker.internal:3333",
 | 
				
			||||||
            "svdFile": "${workspaceRoot}/nrf52.svd",
 | 
					            "svdFile": "${workspaceRoot}/nrf52.svd",
 | 
				
			||||||
            "runToEntryPoint": "main",
 | 
					            "runToMain": true,
 | 
				
			||||||
            // Work around for stopping at main on restart
 | 
					            // Work around for stopping at main on restart
 | 
				
			||||||
            "postRestartCommands": [
 | 
					            "postRestartCommands": [
 | 
				
			||||||
                "break main",
 | 
					                "break main",
 | 
				
			||||||
@ -21,16 +23,18 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "name": "Debug - Openocd Local",
 | 
					            "name": "Debug - Openocd Local",
 | 
				
			||||||
            "type": "cortex-debug",
 | 
					            "type":"cortex-debug",
 | 
				
			||||||
 | 
					            "cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
 | 
				
			||||||
            "cwd": "${workspaceRoot}",
 | 
					            "cwd": "${workspaceRoot}",
 | 
				
			||||||
            "executable": "${command:cmake.launchTargetPath}",
 | 
					            "executable": "${command:cmake.launchTargetPath}",
 | 
				
			||||||
            "request": "launch",
 | 
					            "request": "launch",
 | 
				
			||||||
            "servertype": "openocd",
 | 
					            "servertype": "openocd",
 | 
				
			||||||
            "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
 | 
					            // This may need to be arm-none-eabi-gdb depending on your system
 | 
				
			||||||
 | 
					            "gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
 | 
				
			||||||
            // Connect to an already running OpenOCD instance
 | 
					            // Connect to an already running OpenOCD instance
 | 
				
			||||||
            "gdbTarget": "localhost:3333",
 | 
					            "gdbTarget": "localhost:3333",
 | 
				
			||||||
            "svdFile": "${workspaceRoot}/nrf52.svd",
 | 
					            "svdFile": "${workspaceRoot}/nrf52.svd",
 | 
				
			||||||
            "runToEntryPoint": "main",
 | 
					            "runToMain": true,
 | 
				
			||||||
            // Work around for stopping at main on restart
 | 
					            // Work around for stopping at main on restart
 | 
				
			||||||
            "postRestartCommands": [
 | 
					            "postRestartCommands": [
 | 
				
			||||||
                "break main",
 | 
					                "break main",
 | 
				
			||||||
@ -47,11 +51,6 @@
 | 
				
			|||||||
            "showDevDebugOutput": false,
 | 
					            "showDevDebugOutput": false,
 | 
				
			||||||
            "servertype": "openocd",
 | 
					            "servertype": "openocd",
 | 
				
			||||||
            "runToMain": true,
 | 
					            "runToMain": true,
 | 
				
			||||||
            // Work around for stopping at main on restart
 | 
					 | 
				
			||||||
            "postRestartCommands": [
 | 
					 | 
				
			||||||
                "break main",
 | 
					 | 
				
			||||||
                "continue"
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            // Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb)
 | 
					            // Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb)
 | 
				
			||||||
            "armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin",
 | 
					            "armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin",
 | 
				
			||||||
            "svdFile": "${workspaceRoot}/nrf52.svd",
 | 
					            "svdFile": "${workspaceRoot}/nrf52.svd",
 | 
				
			||||||
@ -59,25 +58,7 @@
 | 
				
			|||||||
                "interface/stlink.cfg",
 | 
					                "interface/stlink.cfg",
 | 
				
			||||||
                "target/nrf52.cfg"
 | 
					                "target/nrf52.cfg"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
        {
 | 
					
 | 
				
			||||||
            "name": "Debug - Openocd Devcontainer",
 | 
					 | 
				
			||||||
            "type": "cortex-debug",
 | 
					 | 
				
			||||||
            "cwd": "${workspaceRoot}",
 | 
					 | 
				
			||||||
            "executable": "${command:cmake.launchTargetPath}",
 | 
					 | 
				
			||||||
            "request": "launch",
 | 
					 | 
				
			||||||
            "servertype": "external",
 | 
					 | 
				
			||||||
            // FIXME: This is hardcoded. I have no idea how to use the values set in build.sh here
 | 
					 | 
				
			||||||
            "gdbPath": "/opt/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gdb",
 | 
					 | 
				
			||||||
            // Connect to an already running OpenOCD instance
 | 
					 | 
				
			||||||
            "gdbTarget": "host.docker.internal:3333",
 | 
					 | 
				
			||||||
            "svdFile": "${workspaceRoot}/nrf52.svd",
 | 
					 | 
				
			||||||
            "runToEntryPoint": "main",
 | 
					 | 
				
			||||||
            // Work around for stopping at main on restart
 | 
					 | 
				
			||||||
            "postRestartCommands": [
 | 
					 | 
				
			||||||
                "break main",
 | 
					 | 
				
			||||||
                "continue"
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -1,20 +1,9 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
 | 
					    "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
 | 
				
			||||||
    "cmake.configureArgs": [
 | 
					    "cmake.configureArgs": [
 | 
				
			||||||
        "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}",
 | 
					        "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}",
 | 
				
			||||||
        "-DNRF5_SDK_PATH=${env:TOOLS_DIR}/${env:NRF_SDK_VER}",
 | 
					        "-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "cmake.statusbar.advanced": {
 | 
					 | 
				
			||||||
        "launch": {
 | 
					 | 
				
			||||||
            "visibility": "hidden"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "launchTarget": {
 | 
					 | 
				
			||||||
            "visibility": "hidden"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "debug": {
 | 
					 | 
				
			||||||
            "visibility": "hidden"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "cmake.generator": "Unix Makefiles",
 | 
					    "cmake.generator": "Unix Makefiles",
 | 
				
			||||||
    "clang-tidy.buildPath": "build/compile_commands.json",
 | 
					    "clang-tidy.buildPath": "build/compile_commands.json",
 | 
				
			||||||
    "files.associations": {
 | 
					    "files.associations": {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@ -1,6 +1,20 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"version": "2.0.0",
 | 
						"version": "2.0.0",
 | 
				
			||||||
	"tasks": [
 | 
						"tasks": [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"label": "create openocd build",
 | 
				
			||||||
 | 
								"type": "shell",
 | 
				
			||||||
 | 
								"command": "/opt/create_build_openocd.sh",
 | 
				
			||||||
 | 
								"group": {
 | 
				
			||||||
 | 
									"kind": "build",
 | 
				
			||||||
 | 
									"isDefault": true
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"presentation": {
 | 
				
			||||||
 | 
									"reveal": "always",
 | 
				
			||||||
 | 
									"panel": "shared"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"problemMatcher": []
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			"label": "update submodules",
 | 
								"label": "update submodules",
 | 
				
			||||||
			"type": "shell",
 | 
								"type": "shell",
 | 
				
			||||||
@ -17,6 +31,14 @@
 | 
				
			|||||||
				"panel": "shared"
 | 
									"panel": "shared"
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			"problemMatcher": []
 | 
								"problemMatcher": []
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"label": "BuildInit",
 | 
				
			||||||
 | 
								"dependsOn": [
 | 
				
			||||||
 | 
									"update submodules",
 | 
				
			||||||
 | 
									"create openocd build"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"problemMatcher": []
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	]
 | 
						]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
 | 
					set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
project(pinetime VERSION 1.14.0 LANGUAGES C CXX ASM)
 | 
					project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(CMAKE_C_STANDARD 99)
 | 
					set(CMAKE_C_STANDARD 99)
 | 
				
			||||||
set(CMAKE_CXX_STANDARD 20)
 | 
					set(CMAKE_CXX_STANDARD 20)
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.
 | 
					Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## New to InfiniTime?
 | 
					## New to InfiniTime?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,9 +7,9 @@ My own contribution is little more than a brute force conversion to
 | 
				
			|||||||
python3. It is sparsely tested so there are likely to be a few
 | 
					python3. It is sparsely tested so there are likely to be a few
 | 
				
			||||||
remaining bytes versus string bugs remaining in the places I didn't test
 | 
					remaining bytes versus string bugs remaining in the places I didn't test
 | 
				
			||||||
. I used it primarily as part of
 | 
					. I used it primarily as part of
 | 
				
			||||||
[wasp-os](https://github.com/wasp-os/wasp-os) as a way to
 | 
					[wasp-os](https://github.com/daniel-thompson/wasp-os) as a way to
 | 
				
			||||||
deliver OTA updates to nRF52-based smart watches, especially the
 | 
					deliver OTA updates to nRF52-based smart watches, especially the
 | 
				
			||||||
[Pine64 PineTime](https://pine64.org/devices/pinetime/).
 | 
					[Pine64 PineTime](https://www.pine64.org/pinetime/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## What does it do?
 | 
					## What does it do?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,5 +21,3 @@ The current raw motion values. This is a 3 `int16_t` array:
 | 
				
			|||||||
- [0] : X
 | 
					- [0] : X
 | 
				
			||||||
- [1] : Y
 | 
					- [1] : Y
 | 
				
			||||||
- [2] : Z
 | 
					- [2] : Z
 | 
				
			||||||
 | 
					 | 
				
			||||||
The three motion values are in units of "binary milli-g", where 1g is represented by a value of 1024.
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ The .devcontainer folder contains the configuration and scripts for using a Dock
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up.
 | 
					Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
More documentation is available in the [readme in .devcontainer](usingDevcontainers.md)
 | 
					More documentation is available in the [readme in .devcontainer](../.devcontainer/README.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### DevContainer on Ubuntu
 | 
					### DevContainer on Ubuntu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -35,20 +35,18 @@ that will call the method `Refresh()` periodically.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## App types
 | 
					## App types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
There are basically 3 types of applications : **system** apps and **user** apps and **watch faces**.
 | 
					There are basically 2 types of applications : **system** apps and **user** apps.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
 | 
					**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
 | 
				
			||||||
Settings, notifications and the application launcher are examples of such system applications.
 | 
					The watchfaces, settings, notifications and the application launcher are examples of such system applications.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**User** applications are optionally built into the firmware. They extend the functionalities of the system.
 | 
					**User** applications are optionally built into the firmware. They extend the functionalities of the system.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Watch faces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware.
 | 
					The distinction between **system** and **user** applications allows for more flexibility and customization.
 | 
				
			||||||
 | 
					This allows to easily select which user applications must be built into the firmware
 | 
				
			||||||
The distinction between **system** apps,  **user** apps and watch faces allows for more flexibility and customization.
 | 
					 | 
				
			||||||
This allows to easily select which user applications and watch faces must be built into the firmware
 | 
					 | 
				
			||||||
without overflowing the system memory.
 | 
					without overflowing the system memory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Apps and watch faces initialization
 | 
					## Apps initialization
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
 | 
					Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
 | 
				
			||||||
This method simply call the creates an instance of the class that corresponds to the app specified in parameters.
 | 
					This method simply call the creates an instance of the class that corresponds to the app specified in parameters.
 | 
				
			||||||
@ -57,8 +55,6 @@ The constructor of **system** apps is called directly. If the application is a *
 | 
				
			|||||||
the corresponding `AppDescription` is first retrieved from `userApps`
 | 
					the corresponding `AppDescription` is first retrieved from `userApps`
 | 
				
			||||||
and then the function `create` is called to create an instance of the app.
 | 
					and then the function `create` is called to create an instance of the app.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Watch faces are handled in a very similar way as the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## User application selection at build time
 | 
					## User application selection at build time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
 | 
					The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
 | 
				
			||||||
@ -89,32 +85,6 @@ struct AppTraits<Apps::Alarm> {
 | 
				
			|||||||
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
 | 
					This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
 | 
				
			||||||
to list all available applications.
 | 
					to list all available applications.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Watch face selection at build time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The list of available watch faces is also generated at build time by the `consteval`
 | 
					 | 
				
			||||||
function `CreateWatchFaceDescriptions()` in `UserApps.h` in the same way as the **user** apps.
 | 
					 | 
				
			||||||
Watch faces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated.
 | 
					 | 
				
			||||||
Here is an example of `WatchFaceTraits`:
 | 
					 | 
				
			||||||
```c++
 | 
					 | 
				
			||||||
    template <>
 | 
					 | 
				
			||||||
    struct WatchFaceTraits<WatchFace::Analog> {
 | 
					 | 
				
			||||||
      static constexpr WatchFace watchFace = WatchFace::Analog;
 | 
					 | 
				
			||||||
      static constexpr const char* name = "Analog face";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static Screens::Screen* Create(AppControllers& controllers) {
 | 
					 | 
				
			||||||
        return new Screens::WatchFaceAnalog(controllers.dateTimeController,
 | 
					 | 
				
			||||||
                                            controllers.batteryController,
 | 
					 | 
				
			||||||
                                            controllers.bleController,
 | 
					 | 
				
			||||||
                                            controllers.notificationManager,
 | 
					 | 
				
			||||||
                                            controllers.settingsController);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Creating your own app
 | 
					## Creating your own app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A minimal user app could look like this:
 | 
					A minimal user app could look like this:
 | 
				
			||||||
@ -140,10 +110,10 @@ namespace Pinetime {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    template <>
 | 
					    template <>
 | 
				
			||||||
    struct AppTraits<Apps::MyApp> {
 | 
					    struct AppTraits<Apps:MyApp> {
 | 
				
			||||||
      static constexpr Apps app = Apps::MyApp;
 | 
					      static constexpr Apps app = Apps::MyApp;
 | 
				
			||||||
      static constexpr const char* icon = Screens::Symbols::myApp;
 | 
					      static constexpr const char* icon = Screens::Symbol::myApp;
 | 
				
			||||||
      static Screens::Screen* Create(AppControllers& controllers) {
 | 
					      static Screens::Screens* Create(AppController& controllers) {
 | 
				
			||||||
        return new Screens::MyApp();
 | 
					        return new Screens::MyApp();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -176,7 +146,7 @@ Now we have our very own app, but InfiniTime does not know about it yet.
 | 
				
			|||||||
The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
 | 
					The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
 | 
				
			||||||
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
 | 
					in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
 | 
				
			||||||
The next step to making it launch-able is to give your app an id.
 | 
					The next step to making it launch-able is to give your app an id.
 | 
				
			||||||
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/apps/Apps.h](/src/displayapp/apps/Apps.h.in)).
 | 
					To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
 | 
				
			||||||
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
 | 
					Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
 | 
				
			||||||
to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
 | 
					to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -198,15 +168,6 @@ Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the ap
 | 
				
			|||||||
$ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ... 
 | 
					$ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ... 
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Similarly, the list of watch faces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake.
 | 
					 | 
				
			||||||
It must be set with the comma separated list of watch faces that will be built into the firmware.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Ex: build the firmware with 3 watch faces : Analog, PineTimeStyle and Infineat:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```cmake
 | 
					 | 
				
			||||||
$ cmake ... -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::PineTimeStyle,WatchFace::Infineat" ...
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
You should now be able to [build](../buildAndProgram.md) the firmware
 | 
					You should now be able to [build](../buildAndProgram.md) the firmware
 | 
				
			||||||
and flash it to your PineTime. Yay!
 | 
					and flash it to your PineTime. Yay!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -37,13 +37,6 @@ RUN apt-get update -qq \
 | 
				
			|||||||
      libpangocairo-1.0-0 \
 | 
					      libpangocairo-1.0-0 \
 | 
				
			||||||
    && rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
 | 
					    && rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Add the necessary apt-gets for the devcontainer
 | 
					 | 
				
			||||||
RUN apt-get update -qq \
 | 
					 | 
				
			||||||
    && apt-get install -y \
 | 
					 | 
				
			||||||
        clang-format-14 \
 | 
					 | 
				
			||||||
        clang-tidy \
 | 
					 | 
				
			||||||
        libncurses5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
 | 
					# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN pip3 install adafruit-nrfutil
 | 
					RUN pip3 install adafruit-nrfutil
 | 
				
			||||||
@ -62,8 +55,5 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;"
 | 
				
			|||||||
# McuBoot
 | 
					# McuBoot
 | 
				
			||||||
RUN bash -c "source /opt/build.sh; GetMcuBoot;"
 | 
					RUN bash -c "source /opt/build.sh; GetMcuBoot;"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Add the infinitime user for connecting devcontainer
 | 
					 | 
				
			||||||
RUN adduser infinitime
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
ENV SOURCES_DIR /sources
 | 
					ENV SOURCES_DIR /sources
 | 
				
			||||||
CMD ["/opt/build.sh"]
 | 
					CMD ["/opt/build.sh"]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,33 +1,25 @@
 | 
				
			|||||||
#!/bin/sh
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					if clang-format --version | grep -q 'version 11\.'; then
 | 
				
			||||||
name="clang-format"
 | 
					   CLANG_FORMAT_EXECUTABLE="clang-format"
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
if [ -z "$(command -v "git-$name")" ]; then
 | 
					   CLANG_FORMAT_EXECUTABLE="clang-format-11"
 | 
				
			||||||
  name="$(basename -a $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'git-clang-format*') | sort | tail -n 1 | sed 's/^git-//')"
 | 
					 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
minVersion="14.0.0"
 | 
					if ! command -v $CLANG_FORMAT_EXECUTABLE &> /dev/null
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
for file in $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'clang-format*'); do
 | 
					    echo $CLANG_FORMAT_EXECUTABLE does not exist, make sure to install it
 | 
				
			||||||
  curBin="$file"
 | 
					    exit 1
 | 
				
			||||||
  curVersion="$("$curBin" --version | cut -d ' ' -f 3)"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if [ "$(printf '%s\n' "$curVersion" "$version" "$minVersion" | sort -V | tail -n 1)" = "$curVersion" ]; then
 | 
					 | 
				
			||||||
    bin="$curBin"
 | 
					 | 
				
			||||||
    version="$curVersion"
 | 
					 | 
				
			||||||
  fi
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if [ -z "$name" ] || [ -z "$bin" ]; then
 | 
					 | 
				
			||||||
  echo "Could not find a suitable clang-format installation. Install clang-format that includes the git-clang-format script, with at least version $minVersion"
 | 
					 | 
				
			||||||
  exit 1
 | 
					 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
args="--binary $bin -q --extensions cpp,h --style file --staged -- :!src/FreeRTOS :!src/libs"
 | 
					for FILE in $(git diff --cached --name-only)
 | 
				
			||||||
 | 
					do
 | 
				
			||||||
changedFiles="$(git "$name" --diffstat $args)"
 | 
						if [[ "$FILE" =~ src/[A-Za-z0-9\ \-]+*\.(c|h|cpp|cc)$ ]]; then
 | 
				
			||||||
git "$name" $args
 | 
							echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
 | 
				
			||||||
 | 
							$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
 | 
				
			||||||
echo "$changedFiles" | head -n -1 | cut -d ' ' -f 2 | while read -r file; do
 | 
							git add -- $FILE
 | 
				
			||||||
  git add -- "$file"
 | 
						elif [[ "$FILE" =~ src/(components|displayapp|drivers|heartratetask|logging|systemtask)/.*\.(c|h|cpp|cc)$ ]]; then
 | 
				
			||||||
 | 
							echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
 | 
				
			||||||
 | 
							$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
 | 
				
			||||||
 | 
							git add -- $FILE
 | 
				
			||||||
 | 
						fi
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										50
									
								
								shell.nix
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								shell.nix
									
									
									
									
									
								
							@ -1,50 +0,0 @@
 | 
				
			|||||||
{ pkgs ? import <nixpkgs> {} }:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
with pkgs; let
 | 
					 | 
				
			||||||
  py4McuBoot = python3.withPackages (ps: with ps; [
 | 
					 | 
				
			||||||
    cbor
 | 
					 | 
				
			||||||
    intelhex
 | 
					 | 
				
			||||||
    click
 | 
					 | 
				
			||||||
    cryptography
 | 
					 | 
				
			||||||
    pillow
 | 
					 | 
				
			||||||
  ]);
 | 
					 | 
				
			||||||
  lv_img_convWrapper = pkgs.writeScriptBin "lv_img_conv" ''
 | 
					 | 
				
			||||||
    npm install lv_img_conv
 | 
					 | 
				
			||||||
    nodejs node_modules/lv_img_conv/lv_img_conv.js
 | 
					 | 
				
			||||||
  '';
 | 
					 | 
				
			||||||
  buildInfinitime = pkgs.writeScriptBin "build-infinitime" ''
 | 
					 | 
				
			||||||
    mkdir -p build/
 | 
					 | 
				
			||||||
    cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=$ARM_NONE_EABI_TOOLCHAIN_PATH \
 | 
					 | 
				
			||||||
          -DNRF5_SDK_PATH=$NRF5_SDK_PATH \
 | 
					 | 
				
			||||||
          -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
 | 
					 | 
				
			||||||
          -DBUILD_DFU=$BUILD_DFU \
 | 
					 | 
				
			||||||
          -DBUILD_RESOURCES=$BUILD_RESOURCES \
 | 
					 | 
				
			||||||
          -DTARGET_DEVICE=$TARGET_DEVICE \
 | 
					 | 
				
			||||||
          -S . -B build
 | 
					 | 
				
			||||||
    cmake --build build -j6
 | 
					 | 
				
			||||||
  '';
 | 
					 | 
				
			||||||
in mkShell {
 | 
					 | 
				
			||||||
  packages = [
 | 
					 | 
				
			||||||
    gcc-arm-embedded-10
 | 
					 | 
				
			||||||
    nrf5-sdk
 | 
					 | 
				
			||||||
    cmake
 | 
					 | 
				
			||||||
    nodePackages.lv_font_conv
 | 
					 | 
				
			||||||
    lv_img_convWrapper
 | 
					 | 
				
			||||||
    # lv_img_conv
 | 
					 | 
				
			||||||
    nodejs
 | 
					 | 
				
			||||||
    py4McuBoot
 | 
					 | 
				
			||||||
    clang-tools
 | 
					 | 
				
			||||||
    SDL2
 | 
					 | 
				
			||||||
    libpng
 | 
					 | 
				
			||||||
    adafruit-nrfutil
 | 
					 | 
				
			||||||
    buildInfinitime
 | 
					 | 
				
			||||||
    # watchmate # wish this worked -- use flatpak run io.gitlab.azymohliad.WatchMate
 | 
					 | 
				
			||||||
  ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ARM_NONE_EABI_TOOLCHAIN_PATH="${gcc-arm-embedded-10}";
 | 
					 | 
				
			||||||
  NRF5_SDK_PATH="${nrf5-sdk}/share/nRF5_SDK";
 | 
					 | 
				
			||||||
  CMAKE_BUILD_TYPE="Release";
 | 
					 | 
				
			||||||
  BUILD_DFU=1;
 | 
					 | 
				
			||||||
  BUILD_RESOURCES=1;
 | 
					 | 
				
			||||||
  TARGET_DEVICE="PINETIME";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -174,11 +174,6 @@ set(LITTLEFS_SRC
 | 
				
			|||||||
        libs/littlefs/lfs.c
 | 
					        libs/littlefs/lfs.c
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(SUNSET_SRC
 | 
					 | 
				
			||||||
        libs/sunset/src/sunset.h
 | 
					 | 
				
			||||||
        libs/sunset/src/sunset.cpp
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set(LVGL_SRC
 | 
					set(LVGL_SRC
 | 
				
			||||||
        libs/lv_conf.h
 | 
					        libs/lv_conf.h
 | 
				
			||||||
        libs/lvgl/lvgl.h
 | 
					        libs/lvgl/lvgl.h
 | 
				
			||||||
@ -371,6 +366,8 @@ list(APPEND SOURCE_FILES
 | 
				
			|||||||
        displayapp/DisplayApp.cpp
 | 
					        displayapp/DisplayApp.cpp
 | 
				
			||||||
        displayapp/screens/Screen.cpp
 | 
					        displayapp/screens/Screen.cpp
 | 
				
			||||||
        displayapp/screens/Tile.cpp
 | 
					        displayapp/screens/Tile.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/InfiniPaint.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/Paddle.cpp
 | 
				
			||||||
        displayapp/screens/StopWatch.cpp
 | 
					        displayapp/screens/StopWatch.cpp
 | 
				
			||||||
        displayapp/screens/BatteryIcon.cpp
 | 
					        displayapp/screens/BatteryIcon.cpp
 | 
				
			||||||
        displayapp/screens/BleIcon.cpp
 | 
					        displayapp/screens/BleIcon.cpp
 | 
				
			||||||
@ -379,9 +376,13 @@ list(APPEND SOURCE_FILES
 | 
				
			|||||||
        displayapp/screens/Label.cpp
 | 
					        displayapp/screens/Label.cpp
 | 
				
			||||||
        displayapp/screens/FirmwareUpdate.cpp
 | 
					        displayapp/screens/FirmwareUpdate.cpp
 | 
				
			||||||
        displayapp/screens/Music.cpp
 | 
					        displayapp/screens/Music.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/Navigation.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/Metronome.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/Motion.cpp
 | 
				
			||||||
        displayapp/screens/FirmwareValidation.cpp
 | 
					        displayapp/screens/FirmwareValidation.cpp
 | 
				
			||||||
        displayapp/screens/ApplicationList.cpp
 | 
					        displayapp/screens/ApplicationList.cpp
 | 
				
			||||||
        displayapp/screens/Notifications.cpp
 | 
					        displayapp/screens/Notifications.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/Twos.cpp
 | 
				
			||||||
        displayapp/screens/HeartRate.cpp
 | 
					        displayapp/screens/HeartRate.cpp
 | 
				
			||||||
        displayapp/screens/FlashLight.cpp
 | 
					        displayapp/screens/FlashLight.cpp
 | 
				
			||||||
        displayapp/screens/List.cpp
 | 
					        displayapp/screens/List.cpp
 | 
				
			||||||
@ -389,12 +390,10 @@ list(APPEND SOURCE_FILES
 | 
				
			|||||||
        displayapp/screens/BatteryInfo.cpp
 | 
					        displayapp/screens/BatteryInfo.cpp
 | 
				
			||||||
        displayapp/screens/Steps.cpp
 | 
					        displayapp/screens/Steps.cpp
 | 
				
			||||||
        displayapp/screens/Timer.cpp
 | 
					        displayapp/screens/Timer.cpp
 | 
				
			||||||
        displayapp/screens/Dice.cpp
 | 
					 | 
				
			||||||
        displayapp/screens/PassKey.cpp
 | 
					        displayapp/screens/PassKey.cpp
 | 
				
			||||||
        displayapp/screens/Error.cpp
 | 
					        displayapp/screens/Error.cpp
 | 
				
			||||||
        displayapp/screens/Alarm.cpp
 | 
					        displayapp/screens/Alarm.cpp
 | 
				
			||||||
        displayapp/screens/Styles.cpp
 | 
					        displayapp/screens/Styles.cpp
 | 
				
			||||||
        displayapp/screens/WeatherSymbols.cpp
 | 
					 | 
				
			||||||
        displayapp/Colors.cpp
 | 
					        displayapp/Colors.cpp
 | 
				
			||||||
        displayapp/widgets/Counter.cpp
 | 
					        displayapp/widgets/Counter.cpp
 | 
				
			||||||
        displayapp/widgets/PageIndicator.cpp
 | 
					        displayapp/widgets/PageIndicator.cpp
 | 
				
			||||||
@ -416,12 +415,14 @@ list(APPEND SOURCE_FILES
 | 
				
			|||||||
        displayapp/screens/settings/SettingChimes.cpp
 | 
					        displayapp/screens/settings/SettingChimes.cpp
 | 
				
			||||||
        displayapp/screens/settings/SettingShakeThreshold.cpp
 | 
					        displayapp/screens/settings/SettingShakeThreshold.cpp
 | 
				
			||||||
        displayapp/screens/settings/SettingBluetooth.cpp
 | 
					        displayapp/screens/settings/SettingBluetooth.cpp
 | 
				
			||||||
        displayapp/screens/settings/SettingLocation.cpp
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ## Watch faces
 | 
					        ## Watch faces
 | 
				
			||||||
 | 
					        displayapp/screens/WatchFaceAnalog.cpp
 | 
				
			||||||
        displayapp/screens/WatchFaceDigital.cpp
 | 
					        displayapp/screens/WatchFaceDigital.cpp
 | 
				
			||||||
        displayapp/screens/WatchFaceFuzzy.cpp
 | 
					        displayapp/screens/WatchFaceInfineat.cpp
 | 
				
			||||||
        displayapp/screens/WatchFaceSundial.cpp
 | 
					        displayapp/screens/WatchFaceTerminal.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/WatchFacePineTimeStyle.cpp
 | 
				
			||||||
 | 
					        displayapp/screens/WatchFaceCasioStyleG7710.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ##
 | 
					        ##
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -539,6 +540,7 @@ list(APPEND RECOVERY_SOURCE_FILES
 | 
				
			|||||||
        systemtask/SystemTask.cpp
 | 
					        systemtask/SystemTask.cpp
 | 
				
			||||||
        systemtask/SystemMonitor.cpp
 | 
					        systemtask/SystemMonitor.cpp
 | 
				
			||||||
        drivers/TwiMaster.cpp
 | 
					        drivers/TwiMaster.cpp
 | 
				
			||||||
 | 
					        components/gfx/Gfx.cpp
 | 
				
			||||||
        components/rle/RleDecoder.cpp
 | 
					        components/rle/RleDecoder.cpp
 | 
				
			||||||
        components/heartrate/HeartRateController.cpp
 | 
					        components/heartrate/HeartRateController.cpp
 | 
				
			||||||
        heartratetask/HeartRateTask.cpp
 | 
					        heartratetask/HeartRateTask.cpp
 | 
				
			||||||
@ -568,6 +570,7 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        components/rle/RleDecoder.cpp
 | 
					        components/rle/RleDecoder.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        components/gfx/Gfx.cpp
 | 
				
			||||||
        drivers/St7789.cpp
 | 
					        drivers/St7789.cpp
 | 
				
			||||||
        components/brightness/BrightnessController.cpp
 | 
					        components/brightness/BrightnessController.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -586,7 +589,9 @@ set(INCLUDE_FILES
 | 
				
			|||||||
        displayapp/TouchEvents.h
 | 
					        displayapp/TouchEvents.h
 | 
				
			||||||
        displayapp/screens/Screen.h
 | 
					        displayapp/screens/Screen.h
 | 
				
			||||||
        displayapp/screens/Tile.h
 | 
					        displayapp/screens/Tile.h
 | 
				
			||||||
 | 
					        displayapp/screens/InfiniPaint.h
 | 
				
			||||||
        displayapp/screens/StopWatch.h
 | 
					        displayapp/screens/StopWatch.h
 | 
				
			||||||
 | 
					        displayapp/screens/Paddle.h
 | 
				
			||||||
        displayapp/screens/BatteryIcon.h
 | 
					        displayapp/screens/BatteryIcon.h
 | 
				
			||||||
        displayapp/screens/BleIcon.h
 | 
					        displayapp/screens/BleIcon.h
 | 
				
			||||||
        displayapp/screens/NotificationIcon.h
 | 
					        displayapp/screens/NotificationIcon.h
 | 
				
			||||||
@ -600,7 +605,10 @@ set(INCLUDE_FILES
 | 
				
			|||||||
        displayapp/Apps.h
 | 
					        displayapp/Apps.h
 | 
				
			||||||
        displayapp/screens/Notifications.h
 | 
					        displayapp/screens/Notifications.h
 | 
				
			||||||
        displayapp/screens/HeartRate.h
 | 
					        displayapp/screens/HeartRate.h
 | 
				
			||||||
 | 
					        displayapp/screens/Metronome.h
 | 
				
			||||||
 | 
					        displayapp/screens/Motion.h
 | 
				
			||||||
        displayapp/screens/Timer.h
 | 
					        displayapp/screens/Timer.h
 | 
				
			||||||
 | 
					        displayapp/screens/Alarm.h
 | 
				
			||||||
        displayapp/Colors.h
 | 
					        displayapp/Colors.h
 | 
				
			||||||
        displayapp/widgets/Counter.h
 | 
					        displayapp/widgets/Counter.h
 | 
				
			||||||
        displayapp/widgets/PageIndicator.h
 | 
					        displayapp/widgets/PageIndicator.h
 | 
				
			||||||
@ -889,25 +897,13 @@ target_compile_options(littlefs PRIVATE
 | 
				
			|||||||
        $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
 | 
					        $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# SUNSET_SRC
 | 
					 | 
				
			||||||
add_library(sunset STATIC ${SUNSET_SRC})
 | 
					 | 
				
			||||||
target_include_directories(sunset SYSTEM PUBLIC . ../)
 | 
					 | 
				
			||||||
target_include_directories(sunset SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
 | 
					 | 
				
			||||||
target_compile_options(sunset PRIVATE
 | 
					 | 
				
			||||||
        ${COMMON_FLAGS}
 | 
					 | 
				
			||||||
        $<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
 | 
					 | 
				
			||||||
        $<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
 | 
					 | 
				
			||||||
        $<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
 | 
					 | 
				
			||||||
        $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Build autonomous binary (without support for bootloader)
 | 
					# Build autonomous binary (without support for bootloader)
 | 
				
			||||||
set(EXECUTABLE_NAME "pinetime-app")
 | 
					set(EXECUTABLE_NAME "pinetime-app")
 | 
				
			||||||
set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 | 
					set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 | 
				
			||||||
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
 | 
					set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
 | 
				
			||||||
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
 | 
					add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
 | 
				
			||||||
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
 | 
					set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
 | 
				
			||||||
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs sunset infinitime_fonts infinitime_apps)
 | 
					target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
 | 
				
			||||||
target_compile_options(${EXECUTABLE_NAME} PUBLIC
 | 
					target_compile_options(${EXECUTABLE_NAME} PUBLIC
 | 
				
			||||||
        ${COMMON_FLAGS}
 | 
					        ${COMMON_FLAGS}
 | 
				
			||||||
        ${WARNING_FLAGS}
 | 
					        ${WARNING_FLAGS}
 | 
				
			||||||
@ -941,7 +937,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERS
 | 
				
			|||||||
set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
 | 
					set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
 | 
				
			||||||
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
 | 
					set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
 | 
				
			||||||
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
 | 
					add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
 | 
				
			||||||
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs sunset infinitime_fonts infinitime_apps)
 | 
					target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
 | 
				
			||||||
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
 | 
					set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
 | 
				
			||||||
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
 | 
					target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
 | 
				
			||||||
        ${COMMON_FLAGS}
 | 
					        ${COMMON_FLAGS}
 | 
				
			||||||
@ -983,7 +979,7 @@ endif()
 | 
				
			|||||||
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
 | 
					set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
 | 
				
			||||||
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 | 
					set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 | 
				
			||||||
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
 | 
					add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
 | 
				
			||||||
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs sunset infinitime_fonts infinitime_apps)
 | 
					target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
 | 
				
			||||||
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
 | 
					set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
 | 
				
			||||||
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
 | 
					target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
 | 
				
			||||||
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
 | 
					target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
 | 
				
			||||||
@ -1015,7 +1011,7 @@ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-$
 | 
				
			|||||||
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
 | 
					set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
 | 
				
			||||||
set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
 | 
					set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
 | 
				
			||||||
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
 | 
					add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
 | 
				
			||||||
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs sunset infinitime_fonts infinitime_apps)
 | 
					target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
 | 
				
			||||||
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
 | 
					set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
 | 
				
			||||||
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
 | 
					target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
 | 
				
			||||||
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
 | 
					target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
 | 
				
			||||||
 | 
				
			|||||||
@ -62,8 +62,7 @@
 | 
				
			|||||||
#define configTICK_RATE_HZ                      1024
 | 
					#define configTICK_RATE_HZ                      1024
 | 
				
			||||||
#define configMAX_PRIORITIES                    (3)
 | 
					#define configMAX_PRIORITIES                    (3)
 | 
				
			||||||
#define configMINIMAL_STACK_SIZE                (120)
 | 
					#define configMINIMAL_STACK_SIZE                (120)
 | 
				
			||||||
// how much heap can one smartwatch need, michael, 40kb?
 | 
					#define configTOTAL_HEAP_SIZE                   (1024 * 40)
 | 
				
			||||||
#define configTOTAL_HEAP_SIZE                   (1024 * 39)
 | 
					 | 
				
			||||||
#define configMAX_TASK_NAME_LEN                 (4)
 | 
					#define configMAX_TASK_NAME_LEN                 (4)
 | 
				
			||||||
#define configUSE_16_BIT_TICKS                  0
 | 
					#define configUSE_16_BIT_TICKS                  0
 | 
				
			||||||
#define configIDLE_SHOULD_YIELD                 1
 | 
					#define configIDLE_SHOULD_YIELD                 1
 | 
				
			||||||
@ -76,7 +75,6 @@
 | 
				
			|||||||
#define configUSE_TIME_SLICING                  0
 | 
					#define configUSE_TIME_SLICING                  0
 | 
				
			||||||
#define configUSE_NEWLIB_REENTRANT              0
 | 
					#define configUSE_NEWLIB_REENTRANT              0
 | 
				
			||||||
#define configENABLE_BACKWARD_COMPATIBILITY     1
 | 
					#define configENABLE_BACKWARD_COMPATIBILITY     1
 | 
				
			||||||
#define configUSE_TASK_NOTIFICATIONS            0
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Hook function related definitions. */
 | 
					/* Hook function related definitions. */
 | 
				
			||||||
#define configUSE_IDLE_HOOK            0
 | 
					#define configUSE_IDLE_HOOK            0
 | 
				
			||||||
 | 
				
			|||||||
@ -357,8 +357,6 @@ void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t exp
 | 
				
			|||||||
  this->totalSize = totalSize;
 | 
					  this->totalSize = totalSize;
 | 
				
			||||||
  this->expectedCrc = expectedCrc;
 | 
					  this->expectedCrc = expectedCrc;
 | 
				
			||||||
  this->ready = true;
 | 
					  this->ready = true;
 | 
				
			||||||
  totalWriteIndex = 0;
 | 
					 | 
				
			||||||
  bufferWriteIndex = 0;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DfuService::DfuImage::Append(uint8_t* data, size_t size) {
 | 
					void DfuService::DfuImage::Append(uint8_t* data, size_t size) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,9 +2,9 @@
 | 
				
			|||||||
#define min // workaround: nimble's min/max macros conflict with libstdc++
 | 
					#define min // workaround: nimble's min/max macros conflict with libstdc++
 | 
				
			||||||
#define max
 | 
					#define max
 | 
				
			||||||
#include <host/ble_gap.h>
 | 
					#include <host/ble_gap.h>
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
#undef max
 | 
					#undef max
 | 
				
			||||||
#undef min
 | 
					#undef min
 | 
				
			||||||
#include <atomic>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pinetime {
 | 
					namespace Pinetime {
 | 
				
			||||||
  namespace Controllers {
 | 
					  namespace Controllers {
 | 
				
			||||||
 | 
				
			|||||||
@ -120,7 +120,3 @@ void MotionService::UnsubscribeNotification(uint16_t attributeHandle) {
 | 
				
			|||||||
  else if (attributeHandle == motionValuesHandle)
 | 
					  else if (attributeHandle == motionValuesHandle)
 | 
				
			||||||
    motionValuesNoficationEnabled = false;
 | 
					    motionValuesNoficationEnabled = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
bool MotionService::IsMotionNotificationSubscribed() const {
 | 
					 | 
				
			||||||
  return motionValuesNoficationEnabled;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,6 @@ namespace Pinetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      void SubscribeNotification(uint16_t attributeHandle);
 | 
					      void SubscribeNotification(uint16_t attributeHandle);
 | 
				
			||||||
      void UnsubscribeNotification(uint16_t attributeHandle);
 | 
					      void UnsubscribeNotification(uint16_t attributeHandle);
 | 
				
			||||||
      bool IsMotionNotificationSubscribed() const;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
      NimbleController& nimble;
 | 
					      NimbleController& nimble;
 | 
				
			||||||
 | 
				
			|||||||
@ -158,16 +158,3 @@ bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService
 | 
				
			|||||||
         this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
 | 
					         this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
 | 
				
			||||||
         std::strcmp(this->location.data(), other.location.data()) == 0;
 | 
					         std::strcmp(this->location.data(), other.location.data()) == 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
 | 
					 | 
				
			||||||
  return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const {
 | 
					 | 
				
			||||||
  for (int i = 0; i < this->nbDays; i++) {
 | 
					 | 
				
			||||||
    if (this->days[i] != other.days[i]) {
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return this->timestamp == other.timestamp && this->nbDays == other.nbDays;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -96,13 +96,9 @@ namespace Pinetime {
 | 
				
			|||||||
          int16_t minTemperature;
 | 
					          int16_t minTemperature;
 | 
				
			||||||
          int16_t maxTemperature;
 | 
					          int16_t maxTemperature;
 | 
				
			||||||
          Icons iconId;
 | 
					          Icons iconId;
 | 
				
			||||||
 | 
					 | 
				
			||||||
          bool operator==(const Day& other) const;
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::array<Day, MaxNbForecastDays> days;
 | 
					        std::array<Day, MaxNbForecastDays> days;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        bool operator==(const Forecast& other) const;
 | 
					 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      std::optional<CurrentWeather> Current() const;
 | 
					      std::optional<CurrentWeather> Current() const;
 | 
				
			||||||
 | 
				
			|||||||
@ -115,8 +115,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
 | 
				
			|||||||
  return MonthsStringLow[static_cast<uint8_t>(month)];
 | 
					  return MonthsStringLow[static_cast<uint8_t>(month)];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const char* DateTime::DayOfWeekShortToStringLow(Days day) {
 | 
					const char* DateTime::DayOfWeekShortToStringLow() const {
 | 
				
			||||||
  return DaysStringShortLow[static_cast<uint8_t>(day)];
 | 
					  return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
 | 
					void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
 | 
				
			||||||
@ -129,7 +129,7 @@ std::string DateTime::FormattedTime() {
 | 
				
			|||||||
  auto hour = Hours();
 | 
					  auto hour = Hours();
 | 
				
			||||||
  auto minute = Minutes();
 | 
					  auto minute = Minutes();
 | 
				
			||||||
  // Return time as a string in 12- or 24-hour format
 | 
					  // Return time as a string in 12- or 24-hour format
 | 
				
			||||||
  char buff[11];
 | 
					  char buff[9];
 | 
				
			||||||
  if (settingsController.GetClockType() == ClockType::H12) {
 | 
					  if (settingsController.GetClockType() == ClockType::H12) {
 | 
				
			||||||
    uint8_t hour12;
 | 
					    uint8_t hour12;
 | 
				
			||||||
    const char* amPmStr;
 | 
					    const char* amPmStr;
 | 
				
			||||||
 | 
				
			|||||||
@ -122,7 +122,7 @@ namespace Pinetime {
 | 
				
			|||||||
      const char* MonthShortToString() const;
 | 
					      const char* MonthShortToString() const;
 | 
				
			||||||
      const char* DayOfWeekShortToString() const;
 | 
					      const char* DayOfWeekShortToString() const;
 | 
				
			||||||
      static const char* MonthShortToStringLow(Months month);
 | 
					      static const char* MonthShortToStringLow(Months month);
 | 
				
			||||||
      static const char* DayOfWeekShortToStringLow(Days day);
 | 
					      const char* DayOfWeekShortToStringLow() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
 | 
					      std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
 | 
				
			||||||
        return currentDateTime;
 | 
					        return currentDateTime;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										196
									
								
								src/components/gfx/Gfx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/components/gfx/Gfx.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,196 @@
 | 
				
			|||||||
 | 
					#include "components/gfx/Gfx.h"
 | 
				
			||||||
 | 
					#include "drivers/St7789.h"
 | 
				
			||||||
 | 
					using namespace Pinetime::Components;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Gfx::Gfx(Pinetime::Drivers::St7789& lcd) : lcd {lcd} {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::Init() {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::ClearScreen() {
 | 
				
			||||||
 | 
					  SetBackgroundColor(0x0000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.remainingIterations = 240 + 1;
 | 
				
			||||||
 | 
					  state.currentIteration = 0;
 | 
				
			||||||
 | 
					  state.busy = true;
 | 
				
			||||||
 | 
					  state.action = Action::FillRectangle;
 | 
				
			||||||
 | 
					  state.taskToNotify = xTaskGetCurrentTaskHandle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lcd.DrawBuffer(0, 0, width, height, reinterpret_cast<const uint8_t*>(buffer), width * 2);
 | 
				
			||||||
 | 
					  WaitTransferFinished();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
 | 
				
			||||||
 | 
					  SetBackgroundColor(color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.remainingIterations = h;
 | 
				
			||||||
 | 
					  state.currentIteration = 0;
 | 
				
			||||||
 | 
					  state.busy = true;
 | 
				
			||||||
 | 
					  state.action = Action::FillRectangle;
 | 
				
			||||||
 | 
					  state.color = color;
 | 
				
			||||||
 | 
					  state.taskToNotify = xTaskGetCurrentTaskHandle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(buffer), width * 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  WaitTransferFinished();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
 | 
				
			||||||
 | 
					  state.remainingIterations = h;
 | 
				
			||||||
 | 
					  state.currentIteration = 0;
 | 
				
			||||||
 | 
					  state.busy = true;
 | 
				
			||||||
 | 
					  state.action = Action::FillRectangle;
 | 
				
			||||||
 | 
					  state.color = 0x00;
 | 
				
			||||||
 | 
					  state.taskToNotify = xTaskGetCurrentTaskHandle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(b), width * 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  WaitTransferFinished();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap) {
 | 
				
			||||||
 | 
					  if (y > (height - p_font->height)) {
 | 
				
			||||||
 | 
					    // Not enough space to write even single char.
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t current_x = x;
 | 
				
			||||||
 | 
					  uint8_t current_y = y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (size_t i = 0; text[i] != '\0'; i++) {
 | 
				
			||||||
 | 
					    if (text[i] == '\n') {
 | 
				
			||||||
 | 
					      current_x = x;
 | 
				
			||||||
 | 
					      current_y += p_font->height + p_font->height / 10;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      DrawChar(p_font, (uint8_t) text[i], ¤t_x, current_y, color);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t char_idx = text[i] - p_font->startChar;
 | 
				
			||||||
 | 
					    uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (current_x > (width - char_width)) {
 | 
				
			||||||
 | 
					      if (wrap) {
 | 
				
			||||||
 | 
					        current_x = x;
 | 
				
			||||||
 | 
					        current_y += p_font->height + p_font->height / 10;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (y > (height - p_font->height)) {
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color) {
 | 
				
			||||||
 | 
					  uint8_t char_idx = c - font->startChar;
 | 
				
			||||||
 | 
					  uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
 | 
				
			||||||
 | 
					  uint16_t bg = 0x0000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (c == ' ') {
 | 
				
			||||||
 | 
					    *x += font->height / 2;
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Build first line
 | 
				
			||||||
 | 
					  for (uint16_t j = 0; j < bytes_in_line; j++) {
 | 
				
			||||||
 | 
					    for (uint8_t k = 0; k < 8; k++) {
 | 
				
			||||||
 | 
					      if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
 | 
				
			||||||
 | 
					        buffer[(j * 8) + k] = color;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        buffer[(j * 8) + k] = bg;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.remainingIterations = font->height + 0;
 | 
				
			||||||
 | 
					  state.currentIteration = 0;
 | 
				
			||||||
 | 
					  state.busy = true;
 | 
				
			||||||
 | 
					  state.action = Action::DrawChar;
 | 
				
			||||||
 | 
					  state.font = const_cast<FONT_INFO*>(font);
 | 
				
			||||||
 | 
					  state.character = c;
 | 
				
			||||||
 | 
					  state.color = color;
 | 
				
			||||||
 | 
					  state.taskToNotify = xTaskGetCurrentTaskHandle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lcd.DrawBuffer(*x, y, bytes_in_line * 8, font->height, reinterpret_cast<const uint8_t*>(&buffer), bytes_in_line * 8 * 2);
 | 
				
			||||||
 | 
					  WaitTransferFinished();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  *x += font->charInfo[char_idx].widthBits + font->spacePixels;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
 | 
				
			||||||
 | 
					  lcd.DrawPixel(x, y, color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::Sleep() {
 | 
				
			||||||
 | 
					  lcd.Sleep();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::Wakeup() {
 | 
				
			||||||
 | 
					  lcd.Wakeup();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::SetBackgroundColor(uint16_t color) {
 | 
				
			||||||
 | 
					  for (int i = 0; i < width; i++) {
 | 
				
			||||||
 | 
					    buffer[i] = color;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
 | 
				
			||||||
 | 
					  if (!state.busy)
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  state.remainingIterations = state.remainingIterations - 1;
 | 
				
			||||||
 | 
					  if (state.remainingIterations == 0) {
 | 
				
			||||||
 | 
					    state.busy = false;
 | 
				
			||||||
 | 
					    NotifyEndOfTransfer(state.taskToNotify);
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (state.action == Action::FillRectangle) {
 | 
				
			||||||
 | 
					    *data = reinterpret_cast<uint8_t*>(buffer);
 | 
				
			||||||
 | 
					    size = width * 2;
 | 
				
			||||||
 | 
					  } else if (state.action == Action::DrawChar) {
 | 
				
			||||||
 | 
					    uint16_t bg = 0x0000;
 | 
				
			||||||
 | 
					    uint8_t char_idx = state.character - state.font->startChar;
 | 
				
			||||||
 | 
					    uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (uint16_t j = 0; j < bytes_in_line; j++) {
 | 
				
			||||||
 | 
					      for (uint8_t k = 0; k < 8; k++) {
 | 
				
			||||||
 | 
					        if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration + 1) * bytes_in_line) + j]) {
 | 
				
			||||||
 | 
					          buffer[(j * 8) + k] = state.color;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          buffer[(j * 8) + k] = bg;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    *data = reinterpret_cast<uint8_t*>(buffer);
 | 
				
			||||||
 | 
					    size = bytes_in_line * 8 * 2;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.currentIteration = state.currentIteration + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::NotifyEndOfTransfer(TaskHandle_t task) {
 | 
				
			||||||
 | 
					  if (task != nullptr) {
 | 
				
			||||||
 | 
					    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
 | 
					    vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::WaitTransferFinished() const {
 | 
				
			||||||
 | 
					  ulTaskNotifyTake(pdTRUE, 500);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
 | 
				
			||||||
 | 
					  lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Gfx::SetScrollStartLine(uint16_t line) {
 | 
				
			||||||
 | 
					  lcd.VerticalScrollStartAddress(line);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/components/gfx/Gfx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/components/gfx/Gfx.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <FreeRTOS.h>
 | 
				
			||||||
 | 
					#include <nrf_font.h>
 | 
				
			||||||
 | 
					#include <task.h>
 | 
				
			||||||
 | 
					#include <cstddef>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include "drivers/BufferProvider.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pinetime {
 | 
				
			||||||
 | 
					  namespace Drivers {
 | 
				
			||||||
 | 
					    class St7789;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  namespace Components {
 | 
				
			||||||
 | 
					    class Gfx : public Pinetime::Drivers::BufferProvider {
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					      explicit Gfx(Drivers::St7789& lcd);
 | 
				
			||||||
 | 
					      void Init();
 | 
				
			||||||
 | 
					      void ClearScreen();
 | 
				
			||||||
 | 
					      void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap);
 | 
				
			||||||
 | 
					      void DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color);
 | 
				
			||||||
 | 
					      void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
 | 
				
			||||||
 | 
					      void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
 | 
				
			||||||
 | 
					      void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
 | 
				
			||||||
 | 
					      void SetScrollStartLine(uint16_t line);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      void Sleep();
 | 
				
			||||||
 | 
					      void Wakeup();
 | 
				
			||||||
 | 
					      bool GetNextBuffer(uint8_t** buffer, size_t& size) override;
 | 
				
			||||||
 | 
					      void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					      static constexpr uint8_t width = 240;
 | 
				
			||||||
 | 
					      static constexpr uint8_t height = 240;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      enum class Action { None, FillRectangle, DrawChar };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      struct State {
 | 
				
			||||||
 | 
					        State() : busy {false}, action {Action::None}, remainingIterations {0}, currentIteration {0} {
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        volatile bool busy;
 | 
				
			||||||
 | 
					        volatile Action action;
 | 
				
			||||||
 | 
					        volatile uint16_t remainingIterations;
 | 
				
			||||||
 | 
					        volatile uint16_t currentIteration;
 | 
				
			||||||
 | 
					        volatile FONT_INFO* font;
 | 
				
			||||||
 | 
					        volatile uint16_t color;
 | 
				
			||||||
 | 
					        volatile uint8_t character;
 | 
				
			||||||
 | 
					        volatile TaskHandle_t taskToNotify = nullptr;
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      volatile State state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      uint16_t buffer[width]; // 1 line buffer
 | 
				
			||||||
 | 
					      Drivers::St7789& lcd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      void SetBackgroundColor(uint16_t color);
 | 
				
			||||||
 | 
					      void WaitTransferFinished() const;
 | 
				
			||||||
 | 
					      void NotifyEndOfTransfer(TaskHandle_t task);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
 | 
				
			|||||||
    service->OnNewStepCountValue(nbSteps);
 | 
					    service->OnNewStepCountValue(nbSteps);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (service != nullptr && (xHistory[0] != x || yHistory[0] != y || zHistory[0] != z)) {
 | 
					  if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) {
 | 
				
			||||||
    service->OnNewMotionValues(x, y, z);
 | 
					    service->OnNewMotionValues(x, y, z);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lastTime = time;
 | 
					  lastTime = time;
 | 
				
			||||||
  time = xTaskGetTickCount();
 | 
					  time = xTaskGetTickCount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  xHistory++;
 | 
					  lastX = this->x;
 | 
				
			||||||
  xHistory[0] = x;
 | 
					  this->x = x;
 | 
				
			||||||
  yHistory++;
 | 
					  yHistory++;
 | 
				
			||||||
  yHistory[0] = y;
 | 
					  yHistory[0] = y;
 | 
				
			||||||
  zHistory++;
 | 
					  zHistory++;
 | 
				
			||||||
@ -67,26 +67,20 @@ MotionController::AccelStats MotionController::GetAccelStats() const {
 | 
				
			|||||||
  AccelStats stats;
 | 
					  AccelStats stats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
 | 
					  for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
 | 
				
			||||||
    stats.xMean += xHistory[histSize - i];
 | 
					 | 
				
			||||||
    stats.yMean += yHistory[histSize - i];
 | 
					    stats.yMean += yHistory[histSize - i];
 | 
				
			||||||
    stats.zMean += zHistory[histSize - i];
 | 
					    stats.zMean += zHistory[histSize - i];
 | 
				
			||||||
    stats.prevXMean += xHistory[1 + i];
 | 
					 | 
				
			||||||
    stats.prevYMean += yHistory[1 + i];
 | 
					    stats.prevYMean += yHistory[1 + i];
 | 
				
			||||||
    stats.prevZMean += zHistory[1 + i];
 | 
					    stats.prevZMean += zHistory[1 + i];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  stats.xMean /= AccelStats::numHistory;
 | 
					 | 
				
			||||||
  stats.yMean /= AccelStats::numHistory;
 | 
					  stats.yMean /= AccelStats::numHistory;
 | 
				
			||||||
  stats.zMean /= AccelStats::numHistory;
 | 
					  stats.zMean /= AccelStats::numHistory;
 | 
				
			||||||
  stats.prevXMean /= AccelStats::numHistory;
 | 
					 | 
				
			||||||
  stats.prevYMean /= AccelStats::numHistory;
 | 
					  stats.prevYMean /= AccelStats::numHistory;
 | 
				
			||||||
  stats.prevZMean /= AccelStats::numHistory;
 | 
					  stats.prevZMean /= AccelStats::numHistory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
 | 
					  for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
 | 
				
			||||||
    stats.xVariance += (xHistory[histSize - i] - stats.xMean) * (xHistory[histSize - i] - stats.xMean);
 | 
					 | 
				
			||||||
    stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
 | 
					    stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
 | 
				
			||||||
    stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
 | 
					    stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  stats.xVariance /= AccelStats::numHistory;
 | 
					 | 
				
			||||||
  stats.yVariance /= AccelStats::numHistory;
 | 
					  stats.yVariance /= AccelStats::numHistory;
 | 
				
			||||||
  stats.zVariance /= AccelStats::numHistory;
 | 
					  stats.zVariance /= AccelStats::numHistory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -99,7 +93,7 @@ bool MotionController::ShouldRaiseWake() const {
 | 
				
			|||||||
  constexpr int16_t yThresh = -64;
 | 
					  constexpr int16_t yThresh = -64;
 | 
				
			||||||
  constexpr int16_t rollDegreesThresh = -45;
 | 
					  constexpr int16_t rollDegreesThresh = -45;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (std::abs(stats.xMean) > xThresh) {
 | 
					  if (x < -xThresh || x > xThresh) {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -113,9 +107,8 @@ bool MotionController::ShouldRaiseWake() const {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
bool MotionController::ShouldShakeWake(uint16_t thresh) {
 | 
					bool MotionController::ShouldShakeWake(uint16_t thresh) {
 | 
				
			||||||
  /* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
 | 
					  /* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
 | 
				
			||||||
  int32_t speed = std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 +
 | 
					  int32_t speed =
 | 
				
			||||||
                           (xHistory[0] - xHistory[histSize - 1]) / 4) *
 | 
					    std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime);
 | 
				
			||||||
                  100 / (time - lastTime);
 | 
					 | 
				
			||||||
  // (.2 * speed) + ((1 - .2) * accumulatedSpeed);
 | 
					  // (.2 * speed) + ((1 - .2) * accumulatedSpeed);
 | 
				
			||||||
  accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
 | 
					  accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -123,11 +116,6 @@ bool MotionController::ShouldShakeWake(uint16_t thresh) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool MotionController::ShouldLowerSleep() const {
 | 
					bool MotionController::ShouldLowerSleep() const {
 | 
				
			||||||
  if ((stats.xMean > 887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) > 30) ||
 | 
					 | 
				
			||||||
      (stats.xMean < -887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) < -30)) {
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
 | 
					  if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ namespace Pinetime {
 | 
				
			|||||||
      void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
 | 
					      void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      int16_t X() const {
 | 
					      int16_t X() const {
 | 
				
			||||||
        return xHistory[0];
 | 
					        return x;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      int16_t Y() const {
 | 
					      int16_t Y() const {
 | 
				
			||||||
@ -62,10 +62,6 @@ namespace Pinetime {
 | 
				
			|||||||
        this->service = service;
 | 
					        this->service = service;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Pinetime::Controllers::MotionService* GetService() const {
 | 
					 | 
				
			||||||
        return service;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
      uint32_t nbSteps = 0;
 | 
					      uint32_t nbSteps = 0;
 | 
				
			||||||
      uint32_t currentTripSteps = 0;
 | 
					      uint32_t currentTripSteps = 0;
 | 
				
			||||||
@ -76,14 +72,11 @@ namespace Pinetime {
 | 
				
			|||||||
      struct AccelStats {
 | 
					      struct AccelStats {
 | 
				
			||||||
        static constexpr uint8_t numHistory = 2;
 | 
					        static constexpr uint8_t numHistory = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int16_t xMean = 0;
 | 
					 | 
				
			||||||
        int16_t yMean = 0;
 | 
					        int16_t yMean = 0;
 | 
				
			||||||
        int16_t zMean = 0;
 | 
					        int16_t zMean = 0;
 | 
				
			||||||
        int16_t prevXMean = 0;
 | 
					 | 
				
			||||||
        int16_t prevYMean = 0;
 | 
					        int16_t prevYMean = 0;
 | 
				
			||||||
        int16_t prevZMean = 0;
 | 
					        int16_t prevZMean = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        uint32_t xVariance = 0;
 | 
					 | 
				
			||||||
        uint32_t yVariance = 0;
 | 
					        uint32_t yVariance = 0;
 | 
				
			||||||
        uint32_t zVariance = 0;
 | 
					        uint32_t zVariance = 0;
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
@ -92,8 +85,9 @@ namespace Pinetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      AccelStats stats = {};
 | 
					      AccelStats stats = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      int16_t lastX = 0;
 | 
				
			||||||
 | 
					      int16_t x = 0;
 | 
				
			||||||
      static constexpr uint8_t histSize = 8;
 | 
					      static constexpr uint8_t histSize = 8;
 | 
				
			||||||
      Utility::CircularBuffer<int16_t, histSize> xHistory = {};
 | 
					 | 
				
			||||||
      Utility::CircularBuffer<int16_t, histSize> yHistory = {};
 | 
					      Utility::CircularBuffer<int16_t, histSize> yHistory = {};
 | 
				
			||||||
      Utility::CircularBuffer<int16_t, histSize> zHistory = {};
 | 
					      Utility::CircularBuffer<int16_t, histSize> zHistory = {};
 | 
				
			||||||
      int32_t accumulatedSpeed = 0;
 | 
					      int32_t accumulatedSpeed = 0;
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ namespace Pinetime {
 | 
				
			|||||||
  namespace Controllers {
 | 
					  namespace Controllers {
 | 
				
			||||||
    class Settings {
 | 
					    class Settings {
 | 
				
			||||||
    public:
 | 
					    public:
 | 
				
			||||||
      enum class ClockType : uint8_t { H24, H12, FUZZY };
 | 
					      enum class ClockType : uint8_t { H24, H12, Fuzzy };
 | 
				
			||||||
      enum class WeatherFormat : uint8_t { Metric, Imperial };
 | 
					      enum class WeatherFormat : uint8_t { Metric, Imperial };
 | 
				
			||||||
      enum class Notification : uint8_t { On, Off, Sleep };
 | 
					      enum class Notification : uint8_t { On, Off, Sleep };
 | 
				
			||||||
      enum class ChimesOption : uint8_t { None, Hours, HalfHours };
 | 
					      enum class ChimesOption : uint8_t { None, Hours, HalfHours };
 | 
				
			||||||
@ -50,12 +50,6 @@ namespace Pinetime {
 | 
				
			|||||||
        int colorIndex = 0;
 | 
					        int colorIndex = 0;
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      struct Location {
 | 
					 | 
				
			||||||
        int16_t latitude;
 | 
					 | 
				
			||||||
        int16_t longitude;
 | 
					 | 
				
			||||||
        int8_t tzOffset;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      Settings(Pinetime::Controllers::FS& fs);
 | 
					      Settings(Pinetime::Controllers::FS& fs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Settings(const Settings&) = delete;
 | 
					      Settings(const Settings&) = delete;
 | 
				
			||||||
@ -281,21 +275,6 @@ namespace Pinetime {
 | 
				
			|||||||
        return settings.stepsGoal;
 | 
					        return settings.stepsGoal;
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      void SetLocation(Location loc) {
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            loc.latitude != settings.location.latitude ||
 | 
					 | 
				
			||||||
            loc.longitude != settings.location.longitude ||
 | 
					 | 
				
			||||||
            loc.tzOffset != settings.location.tzOffset
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
          settingsChanged = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        settings.location = loc;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      Location GetLocation() const {
 | 
					 | 
				
			||||||
        return settings.location;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      void SetBleRadioEnabled(bool enabled) {
 | 
					      void SetBleRadioEnabled(bool enabled) {
 | 
				
			||||||
        bleRadioEnabled = enabled;
 | 
					        bleRadioEnabled = enabled;
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
@ -329,8 +308,6 @@ namespace Pinetime {
 | 
				
			|||||||
        uint16_t shakeWakeThreshold = 150;
 | 
					        uint16_t shakeWakeThreshold = 150;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
 | 
					        Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Location location = {(int16_t)44,(int16_t)-123,(int8_t)-8};
 | 
					 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      SettingsData settings;
 | 
					      SettingsData settings;
 | 
				
			||||||
 | 
				
			|||||||
@ -26,8 +26,6 @@
 | 
				
			|||||||
#include "displayapp/screens/FlashLight.h"
 | 
					#include "displayapp/screens/FlashLight.h"
 | 
				
			||||||
#include "displayapp/screens/BatteryInfo.h"
 | 
					#include "displayapp/screens/BatteryInfo.h"
 | 
				
			||||||
#include "displayapp/screens/Steps.h"
 | 
					#include "displayapp/screens/Steps.h"
 | 
				
			||||||
#include "displayapp/screens/Dice.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Weather.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/PassKey.h"
 | 
					#include "displayapp/screens/PassKey.h"
 | 
				
			||||||
#include "displayapp/screens/Error.h"
 | 
					#include "displayapp/screens/Error.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -49,7 +47,6 @@
 | 
				
			|||||||
#include "displayapp/screens/settings/SettingChimes.h"
 | 
					#include "displayapp/screens/settings/SettingChimes.h"
 | 
				
			||||||
#include "displayapp/screens/settings/SettingShakeThreshold.h"
 | 
					#include "displayapp/screens/settings/SettingShakeThreshold.h"
 | 
				
			||||||
#include "displayapp/screens/settings/SettingBluetooth.h"
 | 
					#include "displayapp/screens/settings/SettingBluetooth.h"
 | 
				
			||||||
#include "displayapp/screens/settings/SettingLocation.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "libs/lv_conf.h"
 | 
					#include "libs/lv_conf.h"
 | 
				
			||||||
#include "UserApps.h"
 | 
					#include "UserApps.h"
 | 
				
			||||||
@ -126,7 +123,6 @@ void DisplayApp::Start(System::BootErrors error) {
 | 
				
			|||||||
  bootError = error;
 | 
					  bootError = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lvgl.Init();
 | 
					  lvgl.Init();
 | 
				
			||||||
  motorController.Init();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (error == System::BootErrors::TouchController) {
 | 
					  if (error == System::BootErrors::TouchController) {
 | 
				
			||||||
    LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
 | 
					    LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
 | 
				
			||||||
@ -144,6 +140,9 @@ void DisplayApp::Process(void* instance) {
 | 
				
			|||||||
  NRF_LOG_INFO("displayapp task started!");
 | 
					  NRF_LOG_INFO("displayapp task started!");
 | 
				
			||||||
  app->InitHw();
 | 
					  app->InitHw();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Send a dummy notification to unlock the lvgl display driver for the first iteration
 | 
				
			||||||
 | 
					  xTaskNotifyGive(xTaskGetCurrentTaskHandle());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  while (true) {
 | 
					  while (true) {
 | 
				
			||||||
    app->Refresh();
 | 
					    app->Refresh();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -152,6 +151,7 @@ void DisplayApp::Process(void* instance) {
 | 
				
			|||||||
void DisplayApp::InitHw() {
 | 
					void DisplayApp::InitHw() {
 | 
				
			||||||
  brightnessController.Init();
 | 
					  brightnessController.Init();
 | 
				
			||||||
  ApplyBrightness();
 | 
					  ApplyBrightness();
 | 
				
			||||||
 | 
					  motorController.Init();
 | 
				
			||||||
  lcd.Init();
 | 
					  lcd.Init();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -444,7 +444,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
 | 
				
			|||||||
      else {
 | 
					      else {
 | 
				
			||||||
        currentScreen.reset(userWatchFaces[0].create(controllers));
 | 
					        currentScreen.reset(userWatchFaces[0].create(controllers));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      settingsController.SetAppMenu(0);
 | 
					 | 
				
			||||||
    } break;
 | 
					    } break;
 | 
				
			||||||
    case Apps::Error:
 | 
					    case Apps::Error:
 | 
				
			||||||
      currentScreen = std::make_unique<Screens::Error>(bootError);
 | 
					      currentScreen = std::make_unique<Screens::Error>(bootError);
 | 
				
			||||||
@ -490,11 +489,10 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
 | 
				
			|||||||
      currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
 | 
					      currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case Apps::SettingWatchFace: {
 | 
					    case Apps::SettingWatchFace: {
 | 
				
			||||||
      std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
 | 
					      std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
 | 
				
			||||||
      int i = 0;
 | 
					      int i = 0;
 | 
				
			||||||
      for (const auto& userWatchFace : userWatchFaces) {
 | 
					      for (const auto& userWatchFace : userWatchFaces) {
 | 
				
			||||||
        items[i++] =
 | 
					        items[i++] = Screens::CheckboxList::Item {userWatchFace.name, userWatchFace.isAvailable(controllers.filesystem)};
 | 
				
			||||||
          Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)};
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
 | 
					      currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
 | 
				
			||||||
    } break;
 | 
					    } break;
 | 
				
			||||||
@ -516,9 +514,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
 | 
				
			|||||||
    case Apps::SettingSetDateTime:
 | 
					    case Apps::SettingSetDateTime:
 | 
				
			||||||
      currentScreen = std::make_unique<Screens::SettingSetDateTime>(this, dateTimeController, settingsController);
 | 
					      currentScreen = std::make_unique<Screens::SettingSetDateTime>(this, dateTimeController, settingsController);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case Apps::SettingLocation:
 | 
					 | 
				
			||||||
      currentScreen = std::make_unique<Screens::SettingLocation>(settingsController);
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Apps::SettingChimes:
 | 
					    case Apps::SettingChimes:
 | 
				
			||||||
      currentScreen = std::make_unique<Screens::SettingChimes>(settingsController);
 | 
					      currentScreen = std::make_unique<Screens::SettingChimes>(settingsController);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
@ -563,7 +558,9 @@ void DisplayApp::PushMessage(Messages msg) {
 | 
				
			|||||||
  if (in_isr()) {
 | 
					  if (in_isr()) {
 | 
				
			||||||
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
					    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
    xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
 | 
					    xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
 | 
				
			||||||
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
					    if (xHigherPriorityTaskWoken == pdTRUE) {
 | 
				
			||||||
 | 
					      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    TickType_t timeout = portMAX_DELAY;
 | 
					    TickType_t timeout = portMAX_DELAY;
 | 
				
			||||||
    // Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
 | 
					    // Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,9 @@ void DisplayApp::Process(void* instance) {
 | 
				
			|||||||
  auto* app = static_cast<DisplayApp*>(instance);
 | 
					  auto* app = static_cast<DisplayApp*>(instance);
 | 
				
			||||||
  NRF_LOG_INFO("displayapp task started!");
 | 
					  NRF_LOG_INFO("displayapp task started!");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Send a dummy notification to unlock the lvgl display driver for the first iteration
 | 
				
			||||||
 | 
					  xTaskNotifyGive(xTaskGetCurrentTaskHandle());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  app->InitHw();
 | 
					  app->InitHw();
 | 
				
			||||||
  while (true) {
 | 
					  while (true) {
 | 
				
			||||||
    app->Refresh();
 | 
					    app->Refresh();
 | 
				
			||||||
@ -91,6 +94,7 @@ void DisplayApp::DisplayLogo(uint16_t color) {
 | 
				
			|||||||
  Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
 | 
					  Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
 | 
				
			||||||
  for (int i = 0; i < displayWidth; i++) {
 | 
					  for (int i = 0; i < displayWidth; i++) {
 | 
				
			||||||
    rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
 | 
					    rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
 | 
				
			||||||
 | 
					    ulTaskNotifyTake(pdTRUE, 500);
 | 
				
			||||||
    lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
 | 
					    lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -99,15 +103,20 @@ void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) {
 | 
				
			|||||||
  const uint8_t barHeight = 20;
 | 
					  const uint8_t barHeight = 20;
 | 
				
			||||||
  std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
 | 
					  std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
 | 
				
			||||||
  for (int i = 0; i < barHeight; i++) {
 | 
					  for (int i = 0; i < barHeight; i++) {
 | 
				
			||||||
 | 
					    ulTaskNotifyTake(pdTRUE, 500);
 | 
				
			||||||
    uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
 | 
					    uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
 | 
				
			||||||
    lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
 | 
					    lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DisplayApp::PushMessage(Display::Messages msg) {
 | 
					void DisplayApp::PushMessage(Display::Messages msg) {
 | 
				
			||||||
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
					  BaseType_t xHigherPriorityTaskWoken;
 | 
				
			||||||
 | 
					  xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
  xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
 | 
					  xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
 | 
				
			||||||
  portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
					  if (xHigherPriorityTaskWoken) {
 | 
				
			||||||
 | 
					    /* Actual macro used here is port specific. */
 | 
				
			||||||
 | 
					    // TODO : should I do something here?
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
 | 
					void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@
 | 
				
			|||||||
#include <drivers/SpiMaster.h>
 | 
					#include <drivers/SpiMaster.h>
 | 
				
			||||||
#include <bits/unique_ptr.h>
 | 
					#include <bits/unique_ptr.h>
 | 
				
			||||||
#include <queue.h>
 | 
					#include <queue.h>
 | 
				
			||||||
 | 
					#include "components/gfx/Gfx.h"
 | 
				
			||||||
#include "drivers/Cst816s.h"
 | 
					#include "drivers/Cst816s.h"
 | 
				
			||||||
#include <drivers/Watchdog.h>
 | 
					#include <drivers/Watchdog.h>
 | 
				
			||||||
#include <components/motor/MotorController.h>
 | 
					#include <components/motor/MotorController.h>
 | 
				
			||||||
 | 
				
			|||||||
@ -152,6 +152,10 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
 | 
				
			|||||||
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
 | 
					void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
 | 
				
			||||||
  uint16_t y1, y2, width, height = 0;
 | 
					  uint16_t y1, y2, width, height = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ulTaskNotifyTake(pdTRUE, 200);
 | 
				
			||||||
 | 
					  // Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
 | 
				
			||||||
 | 
					  // which cannot be set/clear during a transfer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
 | 
					  if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
 | 
				
			||||||
    writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
 | 
					    writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
 | 
				
			||||||
  } else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
 | 
					  } else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
 | 
				
			||||||
@ -215,6 +219,7 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (height > 0) {
 | 
					    if (height > 0) {
 | 
				
			||||||
      lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
 | 
					      lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
 | 
				
			||||||
 | 
					      ulTaskNotifyTake(pdTRUE, 100);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uint16_t pixOffset = width * height;
 | 
					    uint16_t pixOffset = width * height;
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@
 | 
				
			|||||||
#include "Controllers.h"
 | 
					#include "Controllers.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "displayapp/screens/Alarm.h"
 | 
					#include "displayapp/screens/Alarm.h"
 | 
				
			||||||
#include "displayapp/screens/Dice.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Timer.h"
 | 
					#include "displayapp/screens/Timer.h"
 | 
				
			||||||
#include "displayapp/screens/Twos.h"
 | 
					#include "displayapp/screens/Twos.h"
 | 
				
			||||||
#include "displayapp/screens/Tile.h"
 | 
					#include "displayapp/screens/Tile.h"
 | 
				
			||||||
@ -14,8 +13,6 @@
 | 
				
			|||||||
#include "displayapp/screens/WatchFaceInfineat.h"
 | 
					#include "displayapp/screens/WatchFaceInfineat.h"
 | 
				
			||||||
#include "displayapp/screens/WatchFacePineTimeStyle.h"
 | 
					#include "displayapp/screens/WatchFacePineTimeStyle.h"
 | 
				
			||||||
#include "displayapp/screens/WatchFaceTerminal.h"
 | 
					#include "displayapp/screens/WatchFaceTerminal.h"
 | 
				
			||||||
#include "displayapp/screens/WatchFaceFuzzy.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/WatchFaceSundial.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pinetime {
 | 
					namespace Pinetime {
 | 
				
			||||||
  namespace Applications {
 | 
					  namespace Applications {
 | 
				
			||||||
 | 
				
			|||||||
@ -27,8 +27,6 @@ namespace Pinetime {
 | 
				
			|||||||
      Metronome,
 | 
					      Metronome,
 | 
				
			||||||
      Motion,
 | 
					      Motion,
 | 
				
			||||||
      Steps,
 | 
					      Steps,
 | 
				
			||||||
      Dice,
 | 
					 | 
				
			||||||
      Weather,
 | 
					 | 
				
			||||||
      PassKey,
 | 
					      PassKey,
 | 
				
			||||||
      QuickSettings,
 | 
					      QuickSettings,
 | 
				
			||||||
      Settings,
 | 
					      Settings,
 | 
				
			||||||
@ -39,11 +37,11 @@ namespace Pinetime {
 | 
				
			|||||||
      SettingWakeUp,
 | 
					      SettingWakeUp,
 | 
				
			||||||
      SettingSteps,
 | 
					      SettingSteps,
 | 
				
			||||||
      SettingSetDateTime,
 | 
					      SettingSetDateTime,
 | 
				
			||||||
      SettingLocation,
 | 
					 | 
				
			||||||
      SettingChimes,
 | 
					      SettingChimes,
 | 
				
			||||||
      SettingShakeThreshold,
 | 
					      SettingShakeThreshold,
 | 
				
			||||||
      SettingBluetooth,
 | 
					      SettingBluetooth,
 | 
				
			||||||
      Error
 | 
					      Error,
 | 
				
			||||||
 | 
					      Weather
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    enum class WatchFace : uint8_t {
 | 
					    enum class WatchFace : uint8_t {
 | 
				
			||||||
@ -51,8 +49,6 @@ namespace Pinetime {
 | 
				
			|||||||
      Analog,
 | 
					      Analog,
 | 
				
			||||||
      PineTimeStyle,
 | 
					      PineTimeStyle,
 | 
				
			||||||
      Terminal,
 | 
					      Terminal,
 | 
				
			||||||
      Fuzzy,
 | 
					 | 
				
			||||||
      Sundial,
 | 
					 | 
				
			||||||
      Infineat,
 | 
					      Infineat,
 | 
				
			||||||
      CasioStyleG7710,
 | 
					      CasioStyleG7710,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -75,7 +71,12 @@ namespace Pinetime {
 | 
				
			|||||||
      static constexpr size_t Count = sizeof...(Ws);
 | 
					      static constexpr size_t Count = sizeof...(Ws);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
 | 
					    using UserWatchFaceTypes = WatchFaceTypeList<WatchFace::Digital,
 | 
				
			||||||
 | 
					                                                 WatchFace::Analog,
 | 
				
			||||||
 | 
					                                                 WatchFace::PineTimeStyle,
 | 
				
			||||||
 | 
					                                                 WatchFace::Terminal,
 | 
				
			||||||
 | 
					                                                 WatchFace::Infineat,
 | 
				
			||||||
 | 
					                                                 WatchFace::CasioStyleG7710>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static_assert(UserWatchFaceTypes::Count >= 1);
 | 
					    static_assert(UserWatchFaceTypes::Count >= 1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,24 +1,12 @@
 | 
				
			|||||||
if(DEFINED ENABLE_USERAPPS)
 | 
					if(DEFINED ENABLE_USERAPPS)
 | 
				
			||||||
    set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
 | 
					    set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
 | 
				
			||||||
else ()
 | 
					else ()
 | 
				
			||||||
    set(DEFAULT_USER_APP_TYPES "Apps::StopWatch")
 | 
					    set(USERAPP_TYPES "Apps::Navigation, Apps::StopWatch, Apps::Alarm, Apps::Timer, Apps::Steps, Apps::HeartRate, Apps::Music, Apps::Twos" CACHE STRING "List of user apps to build into the firmware")
 | 
				
			||||||
    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer")
 | 
					    #Apps::Paint,
 | 
				
			||||||
    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps")
 | 
					    #Apps::Metronome,
 | 
				
			||||||
    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::HeartRate")
 | 
					    #Apps::Paddle,
 | 
				
			||||||
    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Music")
 | 
					 | 
				
			||||||
    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
 | 
					 | 
				
			||||||
    set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
 | 
					 | 
				
			||||||
endif ()
 | 
					endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if(DEFINED ENABLE_WATCHFACES)
 | 
					 | 
				
			||||||
    set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware")
 | 
					 | 
				
			||||||
else()
 | 
					 | 
				
			||||||
    set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital")
 | 
					 | 
				
			||||||
    set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Fuzzy")
 | 
					 | 
				
			||||||
    set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Sundial")
 | 
					 | 
				
			||||||
    set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
 | 
					 | 
				
			||||||
endif()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
add_library(infinitime_apps INTERFACE)
 | 
					add_library(infinitime_apps INTERFACE)
 | 
				
			||||||
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
 | 
					target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
 | 
				
			||||||
target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
 | 
					target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20 jetbrains_mono_bold_24
 | 
					set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20
 | 
				
			||||||
   jetbrains_mono_extrabold_compressed lv_font_sys_48
 | 
					   jetbrains_mono_extrabold_compressed lv_font_sys_48
 | 
				
			||||||
   open_sans_light fontawesome_weathericons)
 | 
					   open_sans_light fontawesome_weathericons)
 | 
				
			||||||
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
 | 
					find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@
 | 
				
			|||||||
- Define the new symbols in `src/displayapp/screens/Symbols.h`:
 | 
					- Define the new symbols in `src/displayapp/screens/Symbols.h`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
static constexpr const char* newSymbol = "\xEF\x99\x81";
 | 
					static constexpr const char* newSymbol = "\xEF\x86\x85";
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### the config file format:
 | 
					### the config file format:
 | 
				
			||||||
 | 
				
			|||||||
@ -2,36 +2,23 @@
 | 
				
			|||||||
   "jetbrains_mono_bold_20": {
 | 
					   "jetbrains_mono_bold_20": {
 | 
				
			||||||
      "sources": [
 | 
					      "sources": [
 | 
				
			||||||
         {
 | 
					         {
 | 
				
			||||||
            "file": "Vulf_Mono-Italic.woff",
 | 
					            "file": "JetBrainsMono-Bold.ttf",
 | 
				
			||||||
            "range": "0x20-0x7e, 0xB0"
 | 
					            "range": "0x20-0x7e, 0x410-0x44f, 0xB0"
 | 
				
			||||||
         },
 | 
					         },
 | 
				
			||||||
         {
 | 
					         {
 | 
				
			||||||
            "file": "FontAwesome5-Solid+Brands+Regular.woff",
 | 
					            "file": "FontAwesome5-Solid+Brands+Regular.woff",
 | 
				
			||||||
            "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
 | 
					            "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf743"
 | 
				
			||||||
         }
 | 
					         }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "bpp": 1,
 | 
					      "bpp": 1,
 | 
				
			||||||
      "size": 20
 | 
					      "size": 20,
 | 
				
			||||||
   },
 | 
					      "patches": ["jetbrains_mono_bold_20.c_zero.patch", "jetbrains_mono_bold_20.c_M.patch"]
 | 
				
			||||||
   "jetbrains_mono_bold_24": {
 | 
					 | 
				
			||||||
      "sources": [
 | 
					 | 
				
			||||||
         {
 | 
					 | 
				
			||||||
            "file": "Vulf_Mono-Italic.woff",
 | 
					 | 
				
			||||||
            "range": "0x20-0x7e, 0xB0"
 | 
					 | 
				
			||||||
         },
 | 
					 | 
				
			||||||
         {
 | 
					 | 
				
			||||||
            "file": "FontAwesome5-Solid+Brands+Regular.woff",
 | 
					 | 
				
			||||||
            "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      "bpp": 1,
 | 
					 | 
				
			||||||
      "size": 26
 | 
					 | 
				
			||||||
   },
 | 
					   },
 | 
				
			||||||
   "jetbrains_mono_42": {
 | 
					   "jetbrains_mono_42": {
 | 
				
			||||||
      "sources": [
 | 
					      "sources": [
 | 
				
			||||||
         {
 | 
					         {
 | 
				
			||||||
            "file": "Vulf Mono Light Italic.ttf",
 | 
					            "file": "JetBrainsMono-Regular.ttf",
 | 
				
			||||||
            "range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0"
 | 
					            "range": "0x20, 0x25, 0x27, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x61-0x7a"
 | 
				
			||||||
         }
 | 
					         }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "bpp": 1,
 | 
					      "bpp": 1,
 | 
				
			||||||
@ -41,7 +28,7 @@
 | 
				
			|||||||
      "sources": [
 | 
					      "sources": [
 | 
				
			||||||
         {
 | 
					         {
 | 
				
			||||||
            "file": "JetBrainsMono-Light.ttf",
 | 
					            "file": "JetBrainsMono-Light.ttf",
 | 
				
			||||||
            "range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0"
 | 
					            "range": "0x25, 0x2D, 0x2F, 0x30-0x3a"
 | 
				
			||||||
         }
 | 
					         }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "bpp": 1,
 | 
					      "bpp": 1,
 | 
				
			||||||
 | 
				
			|||||||
@ -69,7 +69,7 @@ namespace Pinetime {
 | 
				
			|||||||
    template <>
 | 
					    template <>
 | 
				
			||||||
    struct AppTraits<Apps::Alarm> {
 | 
					    struct AppTraits<Apps::Alarm> {
 | 
				
			||||||
      static constexpr Apps app = Apps::Alarm;
 | 
					      static constexpr Apps app = Apps::Alarm;
 | 
				
			||||||
      static constexpr const char* icon = Screens::Symbols::bell;
 | 
					      static constexpr const char* icon = Screens::Symbols::clock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      static Screens::Screen* Create(AppControllers& controllers) {
 | 
					      static Screens::Screen* Create(AppControllers& controllers) {
 | 
				
			||||||
        return new Screens::Alarm(controllers.alarmController,
 | 
					        return new Screens::Alarm(controllers.alarmController,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,199 +0,0 @@
 | 
				
			|||||||
#include "displayapp/screens/Dice.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Screen.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Symbols.h"
 | 
					 | 
				
			||||||
#include "components/settings/Settings.h"
 | 
					 | 
				
			||||||
#include "components/motor/MotorController.h"
 | 
					 | 
				
			||||||
#include "components/motion/MotionController.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace Pinetime::Applications::Screens;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace {
 | 
					 | 
				
			||||||
  lv_obj_t* MakeLabel(lv_font_t* font,
 | 
					 | 
				
			||||||
                      lv_color_t color,
 | 
					 | 
				
			||||||
                      lv_label_long_mode_t longMode,
 | 
					 | 
				
			||||||
                      uint8_t width,
 | 
					 | 
				
			||||||
                      lv_label_align_t labelAlignment,
 | 
					 | 
				
			||||||
                      const char* text,
 | 
					 | 
				
			||||||
                      lv_obj_t* reference,
 | 
					 | 
				
			||||||
                      lv_align_t alignment,
 | 
					 | 
				
			||||||
                      int8_t x,
 | 
					 | 
				
			||||||
                      int8_t y) {
 | 
					 | 
				
			||||||
    lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
    lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
 | 
					 | 
				
			||||||
    lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
 | 
					 | 
				
			||||||
    lv_label_set_long_mode(label, longMode);
 | 
					 | 
				
			||||||
    if (width != 0) {
 | 
					 | 
				
			||||||
      lv_obj_set_width(label, width);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    lv_label_set_align(label, labelAlignment);
 | 
					 | 
				
			||||||
    lv_label_set_text(label, text);
 | 
					 | 
				
			||||||
    lv_obj_align(label, reference, alignment, x, y);
 | 
					 | 
				
			||||||
    return label;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void btnRollEventHandler(lv_obj_t* obj, lv_event_t event) {
 | 
					 | 
				
			||||||
    auto* screen = static_cast<Dice*>(obj->user_data);
 | 
					 | 
				
			||||||
    if (event == LV_EVENT_CLICKED) {
 | 
					 | 
				
			||||||
      screen->Roll();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Dice::Dice(Controllers::MotionController& motionController,
 | 
					 | 
				
			||||||
           Controllers::MotorController& motorController,
 | 
					 | 
				
			||||||
           Controllers::Settings& settingsController)
 | 
					 | 
				
			||||||
  : motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
 | 
					 | 
				
			||||||
  std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
 | 
					 | 
				
			||||||
                      static_cast<uint32_t>(motionController.X()),
 | 
					 | 
				
			||||||
                      static_cast<uint32_t>(motionController.Y()),
 | 
					 | 
				
			||||||
                      static_cast<uint32_t>(motionController.Z())};
 | 
					 | 
				
			||||||
  gen.seed(sseq);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
 | 
					 | 
				
			||||||
                                      LV_COLOR_WHITE,
 | 
					 | 
				
			||||||
                                      LV_LABEL_LONG_EXPAND,
 | 
					 | 
				
			||||||
                                      0,
 | 
					 | 
				
			||||||
                                      LV_LABEL_ALIGN_CENTER,
 | 
					 | 
				
			||||||
                                      "count",
 | 
					 | 
				
			||||||
                                      lv_scr_act(),
 | 
					 | 
				
			||||||
                                      LV_ALIGN_IN_TOP_LEFT,
 | 
					 | 
				
			||||||
                                      0,
 | 
					 | 
				
			||||||
                                      0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_obj_t* dCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
 | 
					 | 
				
			||||||
                                      LV_COLOR_WHITE,
 | 
					 | 
				
			||||||
                                      LV_LABEL_LONG_EXPAND,
 | 
					 | 
				
			||||||
                                      0,
 | 
					 | 
				
			||||||
                                      LV_LABEL_ALIGN_CENTER,
 | 
					 | 
				
			||||||
                                      "sides",
 | 
					 | 
				
			||||||
                                      nCounterLabel,
 | 
					 | 
				
			||||||
                                      LV_ALIGN_OUT_RIGHT_MID,
 | 
					 | 
				
			||||||
                                      20,
 | 
					 | 
				
			||||||
                                      0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  nCounter.Create();
 | 
					 | 
				
			||||||
  lv_obj_align(nCounter.GetObject(), nCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
 | 
					 | 
				
			||||||
  nCounter.SetValue(1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  dCounter.Create();
 | 
					 | 
				
			||||||
  lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
 | 
					 | 
				
			||||||
  dCounter.SetValue(6);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
 | 
					 | 
				
			||||||
  currentColorIndex = distrib(gen);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  resultTotalLabel = MakeLabel(&jetbrains_mono_42,
 | 
					 | 
				
			||||||
                               resultColors[currentColorIndex],
 | 
					 | 
				
			||||||
                               LV_LABEL_LONG_BREAK,
 | 
					 | 
				
			||||||
                               120,
 | 
					 | 
				
			||||||
                               LV_LABEL_ALIGN_CENTER,
 | 
					 | 
				
			||||||
                               "",
 | 
					 | 
				
			||||||
                               lv_scr_act(),
 | 
					 | 
				
			||||||
                               LV_ALIGN_IN_TOP_RIGHT,
 | 
					 | 
				
			||||||
                               11,
 | 
					 | 
				
			||||||
                               38);
 | 
					 | 
				
			||||||
  resultIndividualLabel = MakeLabel(&jetbrains_mono_bold_20,
 | 
					 | 
				
			||||||
                                    resultColors[currentColorIndex],
 | 
					 | 
				
			||||||
                                    LV_LABEL_LONG_BREAK,
 | 
					 | 
				
			||||||
                                    90,
 | 
					 | 
				
			||||||
                                    LV_LABEL_ALIGN_CENTER,
 | 
					 | 
				
			||||||
                                    "",
 | 
					 | 
				
			||||||
                                    resultTotalLabel,
 | 
					 | 
				
			||||||
                                    LV_ALIGN_OUT_BOTTOM_MID,
 | 
					 | 
				
			||||||
                                    0,
 | 
					 | 
				
			||||||
                                    10);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Roll();
 | 
					 | 
				
			||||||
  openingRoll = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  btnRoll = lv_btn_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  btnRoll->user_data = this;
 | 
					 | 
				
			||||||
  lv_obj_set_event_cb(btnRoll, btnRollEventHandler);
 | 
					 | 
				
			||||||
  lv_obj_set_size(btnRoll, 240, 50);
 | 
					 | 
				
			||||||
  lv_obj_align(btnRoll, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  btnRollLabel = MakeLabel(&jetbrains_mono_bold_20,
 | 
					 | 
				
			||||||
                           LV_COLOR_WHITE,
 | 
					 | 
				
			||||||
                           LV_LABEL_LONG_EXPAND,
 | 
					 | 
				
			||||||
                           0,
 | 
					 | 
				
			||||||
                           LV_LABEL_ALIGN_CENTER,
 | 
					 | 
				
			||||||
                           Symbols::dice,
 | 
					 | 
				
			||||||
                           btnRoll,
 | 
					 | 
				
			||||||
                           LV_ALIGN_CENTER,
 | 
					 | 
				
			||||||
                           0,
 | 
					 | 
				
			||||||
                           0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Spagetti code in motion controller: it only updates the shake speed when shake to wake is on...
 | 
					 | 
				
			||||||
  enableShakeForDice = !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake);
 | 
					 | 
				
			||||||
  if (enableShakeForDice) {
 | 
					 | 
				
			||||||
    settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, true);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  refreshTask = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Dice::~Dice() {
 | 
					 | 
				
			||||||
  // reset the shake to wake mode.
 | 
					 | 
				
			||||||
  if (enableShakeForDice) {
 | 
					 | 
				
			||||||
    settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, false);
 | 
					 | 
				
			||||||
    enableShakeForDice = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  lv_task_del(refreshTask);
 | 
					 | 
				
			||||||
  lv_obj_clean(lv_scr_act());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Dice::Refresh() {
 | 
					 | 
				
			||||||
  // we only reset the hysteresis when at rest
 | 
					 | 
				
			||||||
  if (motionController.CurrentShakeSpeed() >= settingsController.GetShakeThreshold()) {
 | 
					 | 
				
			||||||
    if (currentRollHysteresis <= 0) {
 | 
					 | 
				
			||||||
      // this timestamp is used for the screen timeout
 | 
					 | 
				
			||||||
      lv_disp_get_next(NULL)->last_activity_time = lv_tick_get();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      Roll();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else if (currentRollHysteresis > 0)
 | 
					 | 
				
			||||||
    --currentRollHysteresis;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Dice::Roll() {
 | 
					 | 
				
			||||||
  uint8_t resultIndividual;
 | 
					 | 
				
			||||||
  uint16_t resultTotal = 0;
 | 
					 | 
				
			||||||
  std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_label_set_text(resultIndividualLabel, "");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (nCounter.GetValue() == 1) {
 | 
					 | 
				
			||||||
    resultTotal = distrib(gen);
 | 
					 | 
				
			||||||
    if (dCounter.GetValue() == 2) {
 | 
					 | 
				
			||||||
      switch (resultTotal) {
 | 
					 | 
				
			||||||
        case 1:
 | 
					 | 
				
			||||||
          lv_label_set_text(resultIndividualLabel, "HEADS");
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case 2:
 | 
					 | 
				
			||||||
          lv_label_set_text(resultIndividualLabel, "TAILS");
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
 | 
					 | 
				
			||||||
      resultIndividual = distrib(gen);
 | 
					 | 
				
			||||||
      resultTotal += resultIndividual;
 | 
					 | 
				
			||||||
      lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
 | 
					 | 
				
			||||||
      if (i < (nCounter.GetValue() - 1)) {
 | 
					 | 
				
			||||||
        lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, "+");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_label_set_text_fmt(resultTotalLabel, "%d", resultTotal);
 | 
					 | 
				
			||||||
  if (openingRoll == false) {
 | 
					 | 
				
			||||||
    motorController.RunForDuration(30);
 | 
					 | 
				
			||||||
    NextColor();
 | 
					 | 
				
			||||||
    currentRollHysteresis = rollHysteresis;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Dice::NextColor() {
 | 
					 | 
				
			||||||
  currentColorIndex = (currentColorIndex + 1) % resultColors.size();
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(resultTotalLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(resultIndividualLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,61 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "displayapp/apps/Apps.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Screen.h"
 | 
					 | 
				
			||||||
#include "displayapp/widgets/Counter.h"
 | 
					 | 
				
			||||||
#include "displayapp/Controllers.h"
 | 
					 | 
				
			||||||
#include "Symbols.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <array>
 | 
					 | 
				
			||||||
#include <random>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pinetime {
 | 
					 | 
				
			||||||
  namespace Applications {
 | 
					 | 
				
			||||||
    namespace Screens {
 | 
					 | 
				
			||||||
      class Dice : public Screen {
 | 
					 | 
				
			||||||
      public:
 | 
					 | 
				
			||||||
        Dice(Controllers::MotionController& motionController,
 | 
					 | 
				
			||||||
             Controllers::MotorController& motorController,
 | 
					 | 
				
			||||||
             Controllers::Settings& settingsController);
 | 
					 | 
				
			||||||
        ~Dice() override;
 | 
					 | 
				
			||||||
        void Roll();
 | 
					 | 
				
			||||||
        void Refresh() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      private:
 | 
					 | 
				
			||||||
        lv_obj_t* btnRoll;
 | 
					 | 
				
			||||||
        lv_obj_t* btnRollLabel;
 | 
					 | 
				
			||||||
        lv_obj_t* resultTotalLabel;
 | 
					 | 
				
			||||||
        lv_obj_t* resultIndividualLabel;
 | 
					 | 
				
			||||||
        lv_task_t* refreshTask;
 | 
					 | 
				
			||||||
        bool enableShakeForDice = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::mt19937 gen;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
 | 
					 | 
				
			||||||
        uint8_t currentColorIndex;
 | 
					 | 
				
			||||||
        void NextColor();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Widgets::Counter nCounter = Widgets::Counter(1, 9, jetbrains_mono_42);
 | 
					 | 
				
			||||||
        Widgets::Counter dCounter = Widgets::Counter(2, 99, jetbrains_mono_42);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        bool openingRoll = true;
 | 
					 | 
				
			||||||
        uint8_t currentRollHysteresis = 0;
 | 
					 | 
				
			||||||
        static constexpr uint8_t rollHysteresis = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Controllers::MotorController& motorController;
 | 
					 | 
				
			||||||
        Controllers::MotionController& motionController;
 | 
					 | 
				
			||||||
        Controllers::Settings& settingsController;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    template <>
 | 
					 | 
				
			||||||
    struct AppTraits<Apps::Dice> {
 | 
					 | 
				
			||||||
      static constexpr Apps app = Apps::Dice;
 | 
					 | 
				
			||||||
      static constexpr const char* icon = Screens::Symbols::dice;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static Screens::Screen* Create(AppControllers& controllers) {
 | 
					 | 
				
			||||||
        return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
 | 
				
			|||||||
    lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
 | 
					    lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lv_label_set_text_static(label_hr, "---");
 | 
					  lv_label_set_text_static(label_hr, "000");
 | 
				
			||||||
  lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40);
 | 
					  lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  label_bpm = lv_label_create(lv_scr_act(), nullptr);
 | 
					  label_bpm = lv_label_create(lv_scr_act(), nullptr);
 | 
				
			||||||
@ -82,14 +82,10 @@ void HeartRate::Refresh() {
 | 
				
			|||||||
    case Controllers::HeartRateController::States::NoTouch:
 | 
					    case Controllers::HeartRateController::States::NoTouch:
 | 
				
			||||||
    case Controllers::HeartRateController::States::NotEnoughData:
 | 
					    case Controllers::HeartRateController::States::NotEnoughData:
 | 
				
			||||||
      // case Controllers::HeartRateController::States::Stopped:
 | 
					      // case Controllers::HeartRateController::States::Stopped:
 | 
				
			||||||
      lv_label_set_text_static(label_hr, "---");
 | 
					      lv_label_set_text_static(label_hr, "000");
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      if (heartRateController.HeartRate() == 0) {
 | 
					      lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
 | 
				
			||||||
        lv_label_set_text_static(label_hr, "---");
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lv_label_set_text_static(label_status, ToString(state));
 | 
					  lv_label_set_text_static(label_status, ToString(state));
 | 
				
			||||||
 | 
				
			|||||||
@ -53,9 +53,9 @@ void Motion::Refresh() {
 | 
				
			|||||||
  lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
 | 
					  lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lv_label_set_text_fmt(label,
 | 
					  lv_label_set_text_fmt(label,
 | 
				
			||||||
                        "X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# mg",
 | 
					                        "X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d#",
 | 
				
			||||||
                        motionController.X(),
 | 
					                        motionController.X() / 0x10,
 | 
				
			||||||
                        motionController.Y(),
 | 
					                        motionController.Y() / 0x10,
 | 
				
			||||||
                        motionController.Z());
 | 
					                        motionController.Z() / 0x10);
 | 
				
			||||||
  lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10);
 | 
					  lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,6 @@ namespace Pinetime {
 | 
				
			|||||||
        static constexpr const char* plug = "\xEF\x87\xA6";
 | 
					        static constexpr const char* plug = "\xEF\x87\xA6";
 | 
				
			||||||
        static constexpr const char* shoe = "\xEF\x95\x8B";
 | 
					        static constexpr const char* shoe = "\xEF\x95\x8B";
 | 
				
			||||||
        static constexpr const char* clock = "\xEF\x80\x97";
 | 
					        static constexpr const char* clock = "\xEF\x80\x97";
 | 
				
			||||||
        static constexpr const char* bell = "\xEF\x83\xB3";
 | 
					 | 
				
			||||||
        static constexpr const char* info = "\xEF\x84\xA9";
 | 
					        static constexpr const char* info = "\xEF\x84\xA9";
 | 
				
			||||||
        static constexpr const char* list = "\xEF\x80\xBA";
 | 
					        static constexpr const char* list = "\xEF\x80\xBA";
 | 
				
			||||||
        static constexpr const char* sun = "\xEF\x86\x85";
 | 
					        static constexpr const char* sun = "\xEF\x86\x85";
 | 
				
			||||||
@ -35,7 +34,6 @@ namespace Pinetime {
 | 
				
			|||||||
        static constexpr const char* hourGlass = "\xEF\x89\x92";
 | 
					        static constexpr const char* hourGlass = "\xEF\x89\x92";
 | 
				
			||||||
        static constexpr const char* lapsFlag = "\xEF\x80\xA4";
 | 
					        static constexpr const char* lapsFlag = "\xEF\x80\xA4";
 | 
				
			||||||
        static constexpr const char* drum = "\xEF\x95\xA9";
 | 
					        static constexpr const char* drum = "\xEF\x95\xA9";
 | 
				
			||||||
        static constexpr const char* dice = "\xEF\x94\xA2";
 | 
					 | 
				
			||||||
        static constexpr const char* eye = "\xEF\x81\xAE";
 | 
					        static constexpr const char* eye = "\xEF\x81\xAE";
 | 
				
			||||||
        static constexpr const char* home = "\xEF\x80\x95";
 | 
					        static constexpr const char* home = "\xEF\x80\x95";
 | 
				
			||||||
        static constexpr const char* sleep = "\xEE\xBD\x84";
 | 
					        static constexpr const char* sleep = "\xEE\xBD\x84";
 | 
				
			||||||
 | 
				
			|||||||
@ -256,7 +256,7 @@ void WatchFaceAnalog::Refresh() {
 | 
				
			|||||||
  if (currentDateTime.IsUpdated()) {
 | 
					  if (currentDateTime.IsUpdated()) {
 | 
				
			||||||
    UpdateClock();
 | 
					    UpdateClock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
 | 
					    currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
 | 
				
			||||||
    if (currentDate.IsUpdated()) {
 | 
					    if (currentDate.IsUpdated()) {
 | 
				
			||||||
      lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
 | 
					      lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -43,7 +43,8 @@ namespace Pinetime {
 | 
				
			|||||||
        Utility::DirtyValue<bool> bleState {};
 | 
					        Utility::DirtyValue<bool> bleState {};
 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
 | 
				
			||||||
        Utility::DirtyValue<bool> notificationState {false};
 | 
					        Utility::DirtyValue<bool> notificationState {false};
 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
 | 
					        using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
 | 
				
			||||||
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lv_obj_t* minor_scales;
 | 
					        lv_obj_t* minor_scales;
 | 
				
			||||||
        lv_obj_t* major_scales;
 | 
					        lv_obj_t* major_scales;
 | 
				
			||||||
 | 
				
			|||||||
@ -244,7 +244,7 @@ void WatchFaceCasioStyleG7710::Refresh() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    lv_obj_realign(label_time);
 | 
					    lv_obj_realign(label_time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
 | 
					    currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
 | 
				
			||||||
    if (currentDate.IsUpdated()) {
 | 
					    if (currentDate.IsUpdated()) {
 | 
				
			||||||
      const char* weekNumberFormat = "%V";
 | 
					      const char* weekNumberFormat = "%V";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -51,7 +51,8 @@ namespace Pinetime {
 | 
				
			|||||||
        Utility::DirtyValue<uint8_t> heartbeat {};
 | 
					        Utility::DirtyValue<uint8_t> heartbeat {};
 | 
				
			||||||
        Utility::DirtyValue<bool> heartbeatRunning {};
 | 
					        Utility::DirtyValue<bool> heartbeatRunning {};
 | 
				
			||||||
        Utility::DirtyValue<bool> notificationState {};
 | 
					        Utility::DirtyValue<bool> notificationState {};
 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
 | 
					        using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
 | 
				
			||||||
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}};
 | 
					        lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}};
 | 
				
			||||||
        lv_point_t line_day_of_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}};
 | 
					        lv_point_t line_day_of_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}};
 | 
				
			||||||
 | 
				
			|||||||
@ -4,13 +4,11 @@
 | 
				
			|||||||
#include <cstdio>
 | 
					#include <cstdio>
 | 
				
			||||||
#include "displayapp/screens/NotificationIcon.h"
 | 
					#include "displayapp/screens/NotificationIcon.h"
 | 
				
			||||||
#include "displayapp/screens/Symbols.h"
 | 
					#include "displayapp/screens/Symbols.h"
 | 
				
			||||||
#include "displayapp/screens/WeatherSymbols.h"
 | 
					 | 
				
			||||||
#include "components/battery/BatteryController.h"
 | 
					#include "components/battery/BatteryController.h"
 | 
				
			||||||
#include "components/ble/BleController.h"
 | 
					#include "components/ble/BleController.h"
 | 
				
			||||||
#include "components/ble/NotificationManager.h"
 | 
					#include "components/ble/NotificationManager.h"
 | 
				
			||||||
#include "components/heartrate/HeartRateController.h"
 | 
					#include "components/heartrate/HeartRateController.h"
 | 
				
			||||||
#include "components/motion/MotionController.h"
 | 
					#include "components/motion/MotionController.h"
 | 
				
			||||||
#include "components/ble/SimpleWeatherService.h"
 | 
					 | 
				
			||||||
#include "components/settings/Settings.h"
 | 
					#include "components/settings/Settings.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace Pinetime::Applications::Screens;
 | 
					using namespace Pinetime::Applications::Screens;
 | 
				
			||||||
@ -21,15 +19,13 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
 | 
				
			|||||||
                                   Controllers::NotificationManager& notificationManager,
 | 
					                                   Controllers::NotificationManager& notificationManager,
 | 
				
			||||||
                                   Controllers::Settings& settingsController,
 | 
					                                   Controllers::Settings& settingsController,
 | 
				
			||||||
                                   Controllers::HeartRateController& heartRateController,
 | 
					                                   Controllers::HeartRateController& heartRateController,
 | 
				
			||||||
                                   Controllers::MotionController& motionController,
 | 
					                                   Controllers::MotionController& motionController)
 | 
				
			||||||
                                   Controllers::SimpleWeatherService& weatherService)
 | 
					 | 
				
			||||||
  : currentDateTime {{}},
 | 
					  : currentDateTime {{}},
 | 
				
			||||||
    dateTimeController {dateTimeController},
 | 
					    dateTimeController {dateTimeController},
 | 
				
			||||||
    notificationManager {notificationManager},
 | 
					    notificationManager {notificationManager},
 | 
				
			||||||
    settingsController {settingsController},
 | 
					    settingsController {settingsController},
 | 
				
			||||||
    heartRateController {heartRateController},
 | 
					    heartRateController {heartRateController},
 | 
				
			||||||
    motionController {motionController},
 | 
					    motionController {motionController},
 | 
				
			||||||
    weatherService {weatherService},
 | 
					 | 
				
			||||||
    statusIcons(batteryController, bleController) {
 | 
					    statusIcons(batteryController, bleController) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  statusIcons.Create();
 | 
					  statusIcons.Create();
 | 
				
			||||||
@ -39,24 +35,16 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
 | 
				
			|||||||
  lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
 | 
					  lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
 | 
				
			||||||
  lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
 | 
					  lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  weatherIcon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
 | 
					 | 
				
			||||||
  lv_label_set_text(weatherIcon, "");
 | 
					 | 
				
			||||||
  lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50);
 | 
					 | 
				
			||||||
  lv_obj_set_auto_realign(weatherIcon, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  temperature = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
 | 
					 | 
				
			||||||
  lv_label_set_text(temperature, "");
 | 
					 | 
				
			||||||
  lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  label_date = lv_label_create(lv_scr_act(), nullptr);
 | 
					  label_date = lv_label_create(lv_scr_act(), nullptr);
 | 
				
			||||||
  lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
 | 
					  lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
 | 
				
			||||||
  lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
 | 
					  lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  label_time = lv_label_create(lv_scr_act(), nullptr);
 | 
					  label_time = lv_label_create(lv_scr_act(), nullptr);
 | 
				
			||||||
  lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
 | 
					  if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
 | 
				
			||||||
 | 
					    lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
 | 
					  lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,7 +95,30 @@ void WatchFaceDigital::Refresh() {
 | 
				
			|||||||
    uint8_t hour = dateTimeController.Hours();
 | 
					    uint8_t hour = dateTimeController.Hours();
 | 
				
			||||||
    uint8_t minute = dateTimeController.Minutes();
 | 
					    uint8_t minute = dateTimeController.Minutes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
 | 
					    /* Begin difference from WatchFaceDigital*/
 | 
				
			||||||
 | 
					    if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
 | 
				
			||||||
 | 
					      std::string hourStr, timeStr;
 | 
				
			||||||
 | 
					      hour = hour % 12; // 12 becomes 0, 13 becomes 1
 | 
				
			||||||
 | 
					      auto sector = minute / 15 + (minute % 15 > 7);
 | 
				
			||||||
 | 
					      // advance the hour modulo 12 and reset the minutes if we're close to the top
 | 
				
			||||||
 | 
					      // so we get "quarter to $hour+1" instead of needing "three quarters past $hour"
 | 
				
			||||||
 | 
					      if (sector > 3) {
 | 
				
			||||||
 | 
					        hour = (hour + 1) % 12;
 | 
				
			||||||
 | 
					        sector = 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      timeStr = timeSectors[sector];
 | 
				
			||||||
 | 
					      if (timeStr.find("%1") != std::string::npos) {
 | 
				
			||||||
 | 
					        hour = (hour + 1) % 12;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      //hourStr = std::string("#") + timeAccent + " " + hourNames[hour] + "#";
 | 
				
			||||||
 | 
					      hourStr = hourNames[hour];
 | 
				
			||||||
 | 
					      timeStr.replace(timeStr.find("%"), 2, hourStr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lv_label_set_text(label_time, timeStr.c_str());
 | 
				
			||||||
 | 
					      lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
 | 
				
			||||||
 | 
					    /* End difference from WatchFaceDigital*/
 | 
				
			||||||
 | 
					    } else if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
 | 
				
			||||||
      char ampmChar[3] = "AM";
 | 
					      char ampmChar[3] = "AM";
 | 
				
			||||||
      if (hour == 0) {
 | 
					      if (hour == 0) {
 | 
				
			||||||
        hour = 12;
 | 
					        hour = 12;
 | 
				
			||||||
@ -125,7 +136,7 @@ void WatchFaceDigital::Refresh() {
 | 
				
			|||||||
      lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
 | 
					      lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
 | 
					    currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
 | 
				
			||||||
    if (currentDate.IsUpdated()) {
 | 
					    if (currentDate.IsUpdated()) {
 | 
				
			||||||
      uint16_t year = dateTimeController.Year();
 | 
					      uint16_t year = dateTimeController.Year();
 | 
				
			||||||
      uint8_t day = dateTimeController.Day();
 | 
					      uint8_t day = dateTimeController.Day();
 | 
				
			||||||
@ -169,25 +180,32 @@ void WatchFaceDigital::Refresh() {
 | 
				
			|||||||
    lv_obj_realign(stepValue);
 | 
					    lv_obj_realign(stepValue);
 | 
				
			||||||
    lv_obj_realign(stepIcon);
 | 
					    lv_obj_realign(stepIcon);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  currentWeather = weatherService.Current();
 | 
					 | 
				
			||||||
  if (currentWeather.IsUpdated()) {
 | 
					 | 
				
			||||||
    auto optCurrentWeather = currentWeather.Get();
 | 
					 | 
				
			||||||
    if (optCurrentWeather) {
 | 
					 | 
				
			||||||
      int16_t temp = optCurrentWeather->temperature;
 | 
					 | 
				
			||||||
      char tempUnit = 'C';
 | 
					 | 
				
			||||||
      if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
 | 
					 | 
				
			||||||
        temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
 | 
					 | 
				
			||||||
        tempUnit = 'F';
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
 | 
					 | 
				
			||||||
      lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      lv_label_set_text_static(temperature, "");
 | 
					 | 
				
			||||||
      lv_label_set_text(weatherIcon, "");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    lv_obj_realign(temperature);
 | 
					 | 
				
			||||||
    lv_obj_realign(weatherIcon);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Inspired by XFCE4-panel's fuzzy clock.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *      https://salsa.debian.org/xfce-team/desktop/xfce4-panel/-/blob/debian/master/plugins/clock/clock-fuzzy.c
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Strings contain either a `%0` or a `%1`, indicating the position of
 | 
				
			||||||
 | 
					 * the `hour` or `hour+1`, respectively.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const char* WatchFaceDigital::timeSectors[] = {
 | 
				
			||||||
 | 
					  "%0\no'clock",
 | 
				
			||||||
 | 
					  "quarter\npast\n%0",
 | 
				
			||||||
 | 
					  "half past\n%0",
 | 
				
			||||||
 | 
					  "quarter\nto %1",
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					const char* WatchFaceDigital::hourNames[] = {
 | 
				
			||||||
 | 
					  "twelve",
 | 
				
			||||||
 | 
					  "one",
 | 
				
			||||||
 | 
					  "two",
 | 
				
			||||||
 | 
					  "three",
 | 
				
			||||||
 | 
					  "four",
 | 
				
			||||||
 | 
					  "five",
 | 
				
			||||||
 | 
					  "six",
 | 
				
			||||||
 | 
					  "seven",
 | 
				
			||||||
 | 
					  "eight",
 | 
				
			||||||
 | 
					  "nine",
 | 
				
			||||||
 | 
					  "ten",
 | 
				
			||||||
 | 
					  "eleven",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -6,7 +6,6 @@
 | 
				
			|||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include "displayapp/screens/Screen.h"
 | 
					#include "displayapp/screens/Screen.h"
 | 
				
			||||||
#include "components/datetime/DateTimeController.h"
 | 
					#include "components/datetime/DateTimeController.h"
 | 
				
			||||||
#include "components/ble/SimpleWeatherService.h"
 | 
					 | 
				
			||||||
#include "components/ble/BleController.h"
 | 
					#include "components/ble/BleController.h"
 | 
				
			||||||
#include "displayapp/widgets/StatusIcons.h"
 | 
					#include "displayapp/widgets/StatusIcons.h"
 | 
				
			||||||
#include "utility/DirtyValue.h"
 | 
					#include "utility/DirtyValue.h"
 | 
				
			||||||
@ -33,8 +32,7 @@ namespace Pinetime {
 | 
				
			|||||||
                         Controllers::NotificationManager& notificationManager,
 | 
					                         Controllers::NotificationManager& notificationManager,
 | 
				
			||||||
                         Controllers::Settings& settingsController,
 | 
					                         Controllers::Settings& settingsController,
 | 
				
			||||||
                         Controllers::HeartRateController& heartRateController,
 | 
					                         Controllers::HeartRateController& heartRateController,
 | 
				
			||||||
                         Controllers::MotionController& motionController,
 | 
					                         Controllers::MotionController& motionController);
 | 
				
			||||||
                         Controllers::SimpleWeatherService& weather);
 | 
					 | 
				
			||||||
        ~WatchFaceDigital() override;
 | 
					        ~WatchFaceDigital() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void Refresh() override;
 | 
					        void Refresh() override;
 | 
				
			||||||
@ -42,15 +40,20 @@ namespace Pinetime {
 | 
				
			|||||||
      private:
 | 
					      private:
 | 
				
			||||||
        uint8_t displayedHour = -1;
 | 
					        uint8_t displayedHour = -1;
 | 
				
			||||||
        uint8_t displayedMinute = -1;
 | 
					        uint8_t displayedMinute = -1;
 | 
				
			||||||
 | 
					        static const char* timeSectors[4];
 | 
				
			||||||
 | 
					        static const char* hourNames[12];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
 | 
				
			||||||
 | 
					        Utility::DirtyValue<bool> powerPresent {};
 | 
				
			||||||
 | 
					        Utility::DirtyValue<bool> bleState {};
 | 
				
			||||||
 | 
					        Utility::DirtyValue<bool> bleRadioEnabled {};
 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
 | 
				
			||||||
        Utility::DirtyValue<uint32_t> stepCount {};
 | 
					        Utility::DirtyValue<uint32_t> stepCount {};
 | 
				
			||||||
        Utility::DirtyValue<uint8_t> heartbeat {};
 | 
					        Utility::DirtyValue<uint8_t> heartbeat {};
 | 
				
			||||||
        Utility::DirtyValue<bool> heartbeatRunning {};
 | 
					        Utility::DirtyValue<bool> heartbeatRunning {};
 | 
				
			||||||
        Utility::DirtyValue<bool> notificationState {};
 | 
					        Utility::DirtyValue<bool> notificationState {};
 | 
				
			||||||
        Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
 | 
					        using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
 | 
				
			||||||
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lv_obj_t* label_time;
 | 
					        lv_obj_t* label_time;
 | 
				
			||||||
        lv_obj_t* label_time_ampm;
 | 
					        lv_obj_t* label_time_ampm;
 | 
				
			||||||
@ -60,15 +63,12 @@ namespace Pinetime {
 | 
				
			|||||||
        lv_obj_t* stepIcon;
 | 
					        lv_obj_t* stepIcon;
 | 
				
			||||||
        lv_obj_t* stepValue;
 | 
					        lv_obj_t* stepValue;
 | 
				
			||||||
        lv_obj_t* notificationIcon;
 | 
					        lv_obj_t* notificationIcon;
 | 
				
			||||||
        lv_obj_t* weatherIcon;
 | 
					 | 
				
			||||||
        lv_obj_t* temperature;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Controllers::DateTime& dateTimeController;
 | 
					        Controllers::DateTime& dateTimeController;
 | 
				
			||||||
        Controllers::NotificationManager& notificationManager;
 | 
					        Controllers::NotificationManager& notificationManager;
 | 
				
			||||||
        Controllers::Settings& settingsController;
 | 
					        Controllers::Settings& settingsController;
 | 
				
			||||||
        Controllers::HeartRateController& heartRateController;
 | 
					        Controllers::HeartRateController& heartRateController;
 | 
				
			||||||
        Controllers::MotionController& motionController;
 | 
					        Controllers::MotionController& motionController;
 | 
				
			||||||
        Controllers::SimpleWeatherService& weatherService;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lv_task_t* taskRefresh;
 | 
					        lv_task_t* taskRefresh;
 | 
				
			||||||
        Widgets::StatusIcons statusIcons;
 | 
					        Widgets::StatusIcons statusIcons;
 | 
				
			||||||
@ -87,8 +87,7 @@ namespace Pinetime {
 | 
				
			|||||||
                                             controllers.notificationManager,
 | 
					                                             controllers.notificationManager,
 | 
				
			||||||
                                             controllers.settingsController,
 | 
					                                             controllers.settingsController,
 | 
				
			||||||
                                             controllers.heartRateController,
 | 
					                                             controllers.heartRateController,
 | 
				
			||||||
                                             controllers.motionController,
 | 
					                                             controllers.motionController);
 | 
				
			||||||
                                             *controllers.weatherController);
 | 
					 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
 | 
					      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,189 +0,0 @@
 | 
				
			|||||||
#include "displayapp/screens/WatchFaceFuzzy.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <lvgl/lvgl.h>
 | 
					 | 
				
			||||||
#include <cstdio>
 | 
					 | 
				
			||||||
#include "displayapp/screens/NotificationIcon.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Symbols.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/WeatherSymbols.h"
 | 
					 | 
				
			||||||
#include "components/ble/NotificationManager.h"
 | 
					 | 
				
			||||||
#include "components/heartrate/HeartRateController.h"
 | 
					 | 
				
			||||||
#include "components/motion/MotionController.h"
 | 
					 | 
				
			||||||
#include "components/ble/SimpleWeatherService.h"
 | 
					 | 
				
			||||||
#include "components/settings/Settings.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace Pinetime::Applications::Screens;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WatchFaceFuzzy::WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
 | 
					 | 
				
			||||||
                               Controllers::NotificationManager& notificationManager,
 | 
					 | 
				
			||||||
                               Controllers::Settings& settingsController,
 | 
					 | 
				
			||||||
                               Controllers::HeartRateController& heartRateController,
 | 
					 | 
				
			||||||
                               Controllers::MotionController& motionController,
 | 
					 | 
				
			||||||
                               Controllers::SimpleWeatherService& weatherService)
 | 
					 | 
				
			||||||
  : currentDateTime {{}},
 | 
					 | 
				
			||||||
    dateTimeController {dateTimeController},
 | 
					 | 
				
			||||||
    notificationManager {notificationManager},
 | 
					 | 
				
			||||||
    settingsController {settingsController},
 | 
					 | 
				
			||||||
    heartRateController {heartRateController},
 | 
					 | 
				
			||||||
    motionController {motionController},
 | 
					 | 
				
			||||||
    weatherService {weatherService}
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  notificationIcon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME);
 | 
					 | 
				
			||||||
  lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
 | 
					 | 
				
			||||||
  lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  weatherIcon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
 | 
					 | 
				
			||||||
  lv_label_set_text(weatherIcon, "");
 | 
					 | 
				
			||||||
  lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50);
 | 
					 | 
				
			||||||
  lv_obj_set_auto_realign(weatherIcon, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  temperature = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
 | 
					 | 
				
			||||||
  lv_label_set_text(temperature, "");
 | 
					 | 
				
			||||||
  lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  label_date = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  label_time = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_24);
 | 
					 | 
				
			||||||
  lv_label_set_recolor(label_time, true);
 | 
					 | 
				
			||||||
  lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
 | 
					 | 
				
			||||||
  lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
 | 
					 | 
				
			||||||
  lv_label_set_text_static(heartbeatValue, "");
 | 
					 | 
				
			||||||
  lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  stepValue = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
 | 
					 | 
				
			||||||
  lv_label_set_text_static(stepValue, "0");
 | 
					 | 
				
			||||||
  lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  stepIcon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
 | 
					 | 
				
			||||||
  lv_label_set_text_static(stepIcon, Symbols::shoe);
 | 
					 | 
				
			||||||
  lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
 | 
					 | 
				
			||||||
  Refresh();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WatchFaceFuzzy::~WatchFaceFuzzy() {
 | 
					 | 
				
			||||||
  lv_task_del(taskRefresh);
 | 
					 | 
				
			||||||
  lv_obj_clean(lv_scr_act());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void WatchFaceFuzzy::Refresh() {
 | 
					 | 
				
			||||||
  notificationState = notificationManager.AreNewNotificationsAvailable();
 | 
					 | 
				
			||||||
  if (notificationState.IsUpdated()) {
 | 
					 | 
				
			||||||
    lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (currentDateTime.IsUpdated()) {
 | 
					 | 
				
			||||||
    uint8_t hour = dateTimeController.Hours();
 | 
					 | 
				
			||||||
    uint8_t minute = dateTimeController.Minutes();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    printTimeWords(hour, minute);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
 | 
					 | 
				
			||||||
    if (currentDate.IsUpdated()) {
 | 
					 | 
				
			||||||
      uint16_t year = dateTimeController.Year();
 | 
					 | 
				
			||||||
      uint8_t day = dateTimeController.Day();
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(label_date,
 | 
					 | 
				
			||||||
                            "%s %d %s %d",
 | 
					 | 
				
			||||||
                            dateTimeController.DayOfWeekShortToString(),
 | 
					 | 
				
			||||||
                            day,
 | 
					 | 
				
			||||||
                            dateTimeController.MonthShortToString(),
 | 
					 | 
				
			||||||
                            year);
 | 
					 | 
				
			||||||
      lv_obj_realign(label_date);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  heartbeat = heartRateController.HeartRate();
 | 
					 | 
				
			||||||
  heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
 | 
					 | 
				
			||||||
  if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
 | 
					 | 
				
			||||||
    if (heartbeatRunning.Get()) {
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
 | 
					 | 
				
			||||||
      lv_label_set_text_static(heartbeatValue, "");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lv_obj_realign(heartbeatIcon);
 | 
					 | 
				
			||||||
    lv_obj_realign(heartbeatValue);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  stepCount = motionController.NbSteps();
 | 
					 | 
				
			||||||
  if (stepCount.IsUpdated()) {
 | 
					 | 
				
			||||||
    lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
 | 
					 | 
				
			||||||
    lv_obj_realign(stepValue);
 | 
					 | 
				
			||||||
    lv_obj_realign(stepIcon);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  currentWeather = weatherService.Current();
 | 
					 | 
				
			||||||
  if (currentWeather.IsUpdated()) {
 | 
					 | 
				
			||||||
    auto optCurrentWeather = currentWeather.Get();
 | 
					 | 
				
			||||||
    if (optCurrentWeather) {
 | 
					 | 
				
			||||||
      int16_t temp = optCurrentWeather->temperature;
 | 
					 | 
				
			||||||
      char tempUnit = 'C';
 | 
					 | 
				
			||||||
      if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
 | 
					 | 
				
			||||||
        temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
 | 
					 | 
				
			||||||
        tempUnit = 'F';
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
 | 
					 | 
				
			||||||
      lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      lv_label_set_text_static(temperature, "");
 | 
					 | 
				
			||||||
      lv_label_set_text(weatherIcon, "");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    lv_obj_realign(temperature);
 | 
					 | 
				
			||||||
    lv_obj_realign(weatherIcon);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
char const* WatchFaceFuzzy::mods[] = {"", "five", "ten", "quarter", "twenty", "twenty five", "half"};
 | 
					 | 
				
			||||||
char const* WatchFaceFuzzy::nums[] = {"twelve", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void WatchFaceFuzzy::printTimeWords(int h, int m) {
 | 
					 | 
				
			||||||
  const char* mod;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (m  <= 30) {
 | 
					 | 
				
			||||||
    mod = mods[m / 5];
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    mod = mods[(60-m) / 5];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  h  = (h % 12);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (m >= 57) {
 | 
					 | 
				
			||||||
    sprintf(timeStr, "#ffffff nearly %s#\n#808080 o' clock#", nums[(h+1) % 12]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else if (m == 0 || m <= 4) {
 | 
					 | 
				
			||||||
    sprintf(timeStr, "#ffffff %s#\n#808080 o' clock#", nums[h]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  else if (m <= 32) {
 | 
					 | 
				
			||||||
    sprintf(timeStr, "#ffffff %s#\n#808080 past# #FFFFFF %s#", mod, nums[h]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  else if (m > 32) {
 | 
					 | 
				
			||||||
    sprintf(timeStr, "#ffffff %s#\n#808080 to# #FFFFFF %s#", mod, nums[(h+1) % 12]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  printf("%s\n", timeStr);
 | 
					 | 
				
			||||||
  lv_label_set_text(label_time, timeStr);
 | 
					 | 
				
			||||||
  lv_obj_realign(label_time);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,100 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <lvgl/src/lv_core/lv_obj.h>
 | 
					 | 
				
			||||||
#include <chrono>
 | 
					 | 
				
			||||||
#include <cstdint>
 | 
					 | 
				
			||||||
#include <memory>
 | 
					 | 
				
			||||||
#include "displayapp/screens/Screen.h"
 | 
					 | 
				
			||||||
#include "components/datetime/DateTimeController.h"
 | 
					 | 
				
			||||||
#include "components/ble/SimpleWeatherService.h"
 | 
					 | 
				
			||||||
#include "components/ble/BleController.h"
 | 
					 | 
				
			||||||
#include "displayapp/widgets/StatusIcons.h"
 | 
					 | 
				
			||||||
#include "utility/DirtyValue.h"
 | 
					 | 
				
			||||||
#include "displayapp/apps/Apps.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
extern lv_font_t jetbrains_mono_bold_24;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pinetime {
 | 
					 | 
				
			||||||
  namespace Controllers {
 | 
					 | 
				
			||||||
    class Settings;
 | 
					 | 
				
			||||||
    class Battery;
 | 
					 | 
				
			||||||
    class Ble;
 | 
					 | 
				
			||||||
    class NotificationManager;
 | 
					 | 
				
			||||||
    class HeartRateController;
 | 
					 | 
				
			||||||
    class MotionController;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  namespace Applications {
 | 
					 | 
				
			||||||
    namespace Screens {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      class WatchFaceFuzzy : public Screen {
 | 
					 | 
				
			||||||
      public:
 | 
					 | 
				
			||||||
        WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
 | 
					 | 
				
			||||||
                       Controllers::NotificationManager& notificationManager,
 | 
					 | 
				
			||||||
                       Controllers::Settings& settingsController,
 | 
					 | 
				
			||||||
                       Controllers::HeartRateController& heartRateController,
 | 
					 | 
				
			||||||
                       Controllers::MotionController& motionController,
 | 
					 | 
				
			||||||
                       Controllers::SimpleWeatherService& weather);
 | 
					 | 
				
			||||||
        ~WatchFaceFuzzy() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        void Refresh() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      private:
 | 
					 | 
				
			||||||
        uint8_t displayedHour = -1;
 | 
					 | 
				
			||||||
        uint8_t displayedMinute = -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<uint32_t> stepCount {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<uint8_t> heartbeat {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<bool> heartbeatRunning {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<bool> notificationState {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_obj_t* label_time;
 | 
					 | 
				
			||||||
        lv_obj_t* label_date;
 | 
					 | 
				
			||||||
        lv_obj_t* heartbeatIcon;
 | 
					 | 
				
			||||||
        lv_obj_t* heartbeatValue;
 | 
					 | 
				
			||||||
        lv_obj_t* stepIcon;
 | 
					 | 
				
			||||||
        lv_obj_t* stepValue;
 | 
					 | 
				
			||||||
        lv_obj_t* notificationIcon;
 | 
					 | 
				
			||||||
        lv_obj_t* weatherIcon;
 | 
					 | 
				
			||||||
        lv_obj_t* temperature;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Controllers::DateTime& dateTimeController;
 | 
					 | 
				
			||||||
        Controllers::NotificationManager& notificationManager;
 | 
					 | 
				
			||||||
        Controllers::Settings& settingsController;
 | 
					 | 
				
			||||||
        Controllers::HeartRateController& heartRateController;
 | 
					 | 
				
			||||||
        Controllers::MotionController& motionController;
 | 
					 | 
				
			||||||
        Controllers::SimpleWeatherService& weatherService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_task_t* taskRefresh;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        static char const *nums[];
 | 
					 | 
				
			||||||
        static char const *mods[];
 | 
					 | 
				
			||||||
        char timeStr[64];
 | 
					 | 
				
			||||||
        void printTimeWords(int hour, int minute);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    template <>
 | 
					 | 
				
			||||||
    struct WatchFaceTraits<WatchFace::Fuzzy> {
 | 
					 | 
				
			||||||
      static constexpr WatchFace watchFace = WatchFace::Fuzzy;
 | 
					 | 
				
			||||||
      static constexpr const char* name = "Fuzzy face";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static Screens::Screen* Create(AppControllers& controllers) {
 | 
					 | 
				
			||||||
        return new Screens::WatchFaceFuzzy(controllers.dateTimeController,
 | 
					 | 
				
			||||||
                                           controllers.notificationManager,
 | 
					 | 
				
			||||||
                                           controllers.settingsController,
 | 
					 | 
				
			||||||
                                           controllers.heartRateController,
 | 
					 | 
				
			||||||
                                           controllers.motionController,
 | 
					 | 
				
			||||||
                                           *controllers.weatherController);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -423,11 +423,10 @@ void WatchFaceInfineat::Refresh() {
 | 
				
			|||||||
      lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
 | 
					      lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
 | 
					    currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
 | 
				
			||||||
    if (currentDate.IsUpdated()) {
 | 
					    if (currentDate.IsUpdated()) {
 | 
				
			||||||
      uint8_t day = dateTimeController.Day();
 | 
					      uint8_t day = dateTimeController.Day();
 | 
				
			||||||
      Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek();
 | 
					      lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(), day);
 | 
				
			||||||
      lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day);
 | 
					 | 
				
			||||||
      lv_obj_realign(labelDate);
 | 
					      lv_obj_realign(labelDate);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -55,7 +55,8 @@ namespace Pinetime {
 | 
				
			|||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
 | 
				
			||||||
        Utility::DirtyValue<uint32_t> stepCount {};
 | 
					        Utility::DirtyValue<uint32_t> stepCount {};
 | 
				
			||||||
        Utility::DirtyValue<bool> notificationState {};
 | 
					        Utility::DirtyValue<bool> notificationState {};
 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
 | 
					        using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
 | 
				
			||||||
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Lines making up the side cover
 | 
					        // Lines making up the side cover
 | 
				
			||||||
        lv_obj_t* lineBattery;
 | 
					        lv_obj_t* lineBattery;
 | 
				
			||||||
 | 
				
			|||||||
@ -540,6 +540,7 @@ void WatchFacePineTimeStyle::Refresh() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  currentWeather = weatherService.Current();
 | 
					  currentWeather = weatherService.Current();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (currentWeather.IsUpdated()) {
 | 
					  if (currentWeather.IsUpdated()) {
 | 
				
			||||||
    auto optCurrentWeather = currentWeather.Get();
 | 
					    auto optCurrentWeather = currentWeather.Get();
 | 
				
			||||||
    if (optCurrentWeather) {
 | 
					    if (optCurrentWeather) {
 | 
				
			||||||
@ -550,10 +551,12 @@ void WatchFacePineTimeStyle::Refresh() {
 | 
				
			|||||||
      temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
 | 
					      temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
 | 
				
			||||||
      lv_label_set_text_fmt(temperature, "%d°", temp);
 | 
					      lv_label_set_text_fmt(temperature, "%d°", temp);
 | 
				
			||||||
      lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
 | 
					      lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
 | 
				
			||||||
    } else {
 | 
					      lv_obj_realign(temperature);
 | 
				
			||||||
      lv_label_set_text(temperature, "--");
 | 
					      lv_obj_realign(weatherIcon);
 | 
				
			||||||
      lv_label_set_text(weatherIcon, Symbols::ban);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    lv_label_set_text(temperature, "--");
 | 
				
			||||||
 | 
					    lv_label_set_text(weatherIcon, Symbols::ban);
 | 
				
			||||||
    lv_obj_realign(temperature);
 | 
					    lv_obj_realign(temperature);
 | 
				
			||||||
    lv_obj_realign(weatherIcon);
 | 
					    lv_obj_realign(weatherIcon);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,259 +0,0 @@
 | 
				
			|||||||
#include "displayapp/screens/WatchFaceSundial.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <lvgl/lvgl.h>
 | 
					 | 
				
			||||||
#include <cstdio>
 | 
					 | 
				
			||||||
// #include <cmath>
 | 
					 | 
				
			||||||
#include "displayapp/screens/NotificationIcon.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Symbols.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/WeatherSymbols.h"
 | 
					 | 
				
			||||||
#include "displayapp/InfiniTimeTheme.h"
 | 
					 | 
				
			||||||
#include "components/battery/BatteryController.h"
 | 
					 | 
				
			||||||
#include "components/ble/BleController.h"
 | 
					 | 
				
			||||||
#include "components/ble/NotificationManager.h"
 | 
					 | 
				
			||||||
#include "components/heartrate/HeartRateController.h"
 | 
					 | 
				
			||||||
#include "components/motion/MotionController.h"
 | 
					 | 
				
			||||||
#include "components/ble/SimpleWeatherService.h"
 | 
					 | 
				
			||||||
#include "components/settings/Settings.h"
 | 
					 | 
				
			||||||
#include "sunset/src/sunset.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace Pinetime::Applications::Screens;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace {
 | 
					 | 
				
			||||||
  int16_t HourLength = 70;
 | 
					 | 
				
			||||||
  constexpr int16_t MinuteLength = 90;
 | 
					 | 
				
			||||||
  constexpr int16_t SecondLength = 110;
 | 
					 | 
				
			||||||
  constexpr int16_t SunDialVerticalOffset = 40;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
 | 
					 | 
				
			||||||
  const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
 | 
					 | 
				
			||||||
  const lv_color_t DARK_GRAY = lv_color_make(48, 48, 48);
 | 
					 | 
				
			||||||
  const lv_color_t DARK_ORANGE = lv_color_make(48, 26, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int16_t Cosine(int16_t angle) {
 | 
					 | 
				
			||||||
    return _lv_trigo_sin(angle + 90);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int16_t Sine(int16_t angle) {
 | 
					 | 
				
			||||||
    return _lv_trigo_sin(angle);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int16_t CoordinateXRelocate(int16_t x) {
 | 
					 | 
				
			||||||
    return (x + LV_HOR_RES / 2);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int16_t CoordinateYRelocateSundial(int16_t y) {
 | 
					 | 
				
			||||||
    return std::abs(y - SunDialVerticalOffset);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_point_t CoordinateRelocateSundial(int16_t radius, int16_t angle) {
 | 
					 | 
				
			||||||
    return lv_point_t {.x = CoordinateXRelocate(radius * static_cast<int32_t>(Sine(angle)) / LV_TRIG_SCALE),
 | 
					 | 
				
			||||||
                       .y = CoordinateYRelocateSundial(radius * static_cast<int32_t>(Cosine(angle)) / LV_TRIG_SCALE)};
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WatchFaceSundial::WatchFaceSundial(Controllers::DateTime& dateTimeController,
 | 
					 | 
				
			||||||
                                   Controllers::NotificationManager& notificationManager,
 | 
					 | 
				
			||||||
                                   Controllers::Settings& settingsController)
 | 
					 | 
				
			||||||
  : currentDateTime {{}},
 | 
					 | 
				
			||||||
    dateTimeController {dateTimeController},
 | 
					 | 
				
			||||||
    notificationManager {notificationManager},
 | 
					 | 
				
			||||||
    settingsController {settingsController}
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  // minor_scales = lv_linemeter_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  // lv_linemeter_set_scale(minor_scales, 300, 51);
 | 
					 | 
				
			||||||
  // lv_linemeter_set_angle_offset(minor_scales, 180);
 | 
					 | 
				
			||||||
  // lv_obj_set_size(minor_scales, 240, 240);
 | 
					 | 
				
			||||||
  // lv_obj_align(minor_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_bg_opa(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_end_line_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_end_color(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // major_scales = lv_linemeter_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  // lv_linemeter_set_scale(major_scales, 300, 11);
 | 
					 | 
				
			||||||
  // lv_linemeter_set_angle_offset(major_scales, 180);
 | 
					 | 
				
			||||||
  // lv_obj_set_size(major_scales, 240, 240);
 | 
					 | 
				
			||||||
  // lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 6);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // large_scales = lv_linemeter_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  // lv_linemeter_set_scale(large_scales, 180, 3);
 | 
					 | 
				
			||||||
  // lv_linemeter_set_angle_offset(large_scales, 180);
 | 
					 | 
				
			||||||
  // lv_obj_set_size(large_scales, 240, 240);
 | 
					 | 
				
			||||||
  // lv_obj_align(large_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_bg_opa(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 20);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_end_line_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
 | 
					 | 
				
			||||||
  // lv_obj_set_style_local_scale_end_color(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  twelve = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_label_set_align(twelve, LV_LABEL_ALIGN_RIGHT);
 | 
					 | 
				
			||||||
  lv_label_set_text_static(twelve, "XII");
 | 
					 | 
				
			||||||
  lv_obj_align(twelve, NULL, LV_ALIGN_IN_TOP_RIGHT, -20, SunDialVerticalOffset-20);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  one = lv_label_create(lv_scr_act(), NULL);
 | 
					 | 
				
			||||||
  lv_label_set_align(one, LV_LABEL_ALIGN_LEFT);
 | 
					 | 
				
			||||||
  lv_label_set_text(one, "I");
 | 
					 | 
				
			||||||
  lv_obj_align(one, NULL, LV_ALIGN_IN_TOP_LEFT, 20, SunDialVerticalOffset-20);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  notificationIcon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME);
 | 
					 | 
				
			||||||
  lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
 | 
					 | 
				
			||||||
  lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Date - Day / Week day
 | 
					 | 
				
			||||||
  label_date_day = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_ORANGE);
 | 
					 | 
				
			||||||
  lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
 | 
					 | 
				
			||||||
  lv_label_set_align(label_date_day, LV_LABEL_ALIGN_CENTER);
 | 
					 | 
				
			||||||
  lv_obj_align(label_date_day, nullptr, LV_ALIGN_CENTER, 50, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  minute_body = lv_line_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  minute_body_trace = lv_line_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  hour_body = lv_line_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  hour_body_trace = lv_line_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  second_body = lv_line_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_style_init(&second_line_style);
 | 
					 | 
				
			||||||
  lv_style_set_line_width(&second_line_style, LV_STATE_DEFAULT, 3);
 | 
					 | 
				
			||||||
  lv_style_set_line_color(&second_line_style, LV_STATE_DEFAULT, LV_COLOR_RED);
 | 
					 | 
				
			||||||
  lv_style_set_line_rounded(&second_line_style, LV_STATE_DEFAULT, true);
 | 
					 | 
				
			||||||
  lv_obj_add_style(second_body, LV_LINE_PART_MAIN, &second_line_style);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_style_init(&minute_line_style);
 | 
					 | 
				
			||||||
  lv_style_set_line_width(&minute_line_style, LV_STATE_DEFAULT, 7);
 | 
					 | 
				
			||||||
  lv_style_set_line_color(&minute_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
  lv_style_set_line_rounded(&minute_line_style, LV_STATE_DEFAULT, true);
 | 
					 | 
				
			||||||
  lv_obj_add_style(minute_body, LV_LINE_PART_MAIN, &minute_line_style);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_style_init(&minute_line_style_trace);
 | 
					 | 
				
			||||||
  lv_style_set_line_width(&minute_line_style_trace, LV_STATE_DEFAULT, 3);
 | 
					 | 
				
			||||||
  lv_style_set_line_color(&minute_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
  lv_style_set_line_rounded(&minute_line_style_trace, LV_STATE_DEFAULT, false);
 | 
					 | 
				
			||||||
  lv_obj_add_style(minute_body_trace, LV_LINE_PART_MAIN, &minute_line_style_trace);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_style_init(&hour_line_style);
 | 
					 | 
				
			||||||
  lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 7);
 | 
					 | 
				
			||||||
  lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
  lv_style_set_line_rounded(&hour_line_style, LV_STATE_DEFAULT, true);
 | 
					 | 
				
			||||||
  lv_obj_add_style(hour_body, LV_LINE_PART_MAIN, &hour_line_style);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_style_init(&hour_line_style_trace);
 | 
					 | 
				
			||||||
  lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 3);
 | 
					 | 
				
			||||||
  lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
  lv_style_set_line_rounded(&hour_line_style_trace, LV_STATE_DEFAULT, false);
 | 
					 | 
				
			||||||
  lv_obj_add_style(hour_body_trace, LV_LINE_PART_MAIN, &hour_line_style_trace);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
 | 
					 | 
				
			||||||
  UpdateClock();
 | 
					 | 
				
			||||||
  Refresh();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WatchFaceSundial::~WatchFaceSundial() {
 | 
					 | 
				
			||||||
  lv_task_del(taskRefresh);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_style_reset(&hour_line_style);
 | 
					 | 
				
			||||||
  lv_style_reset(&hour_line_style_trace);
 | 
					 | 
				
			||||||
  lv_style_reset(&minute_line_style);
 | 
					 | 
				
			||||||
  lv_style_reset(&minute_line_style_trace);
 | 
					 | 
				
			||||||
  lv_style_reset(&second_line_style);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_obj_clean(lv_scr_act());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void WatchFaceSundial::Refresh() {
 | 
					 | 
				
			||||||
  currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
 | 
					 | 
				
			||||||
  if (currentDateTime.IsUpdated()) {
 | 
					 | 
				
			||||||
    currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
 | 
					 | 
				
			||||||
    UpdateClock();
 | 
					 | 
				
			||||||
    if (currentDate.IsUpdated()) {
 | 
					 | 
				
			||||||
      char const* MonthsString[] = {"--", "IANUARIUS","FEBRUARIUS","MARTIUS","APRILIS","MARTIUSIUNIUS","QUINTILIS","SEXTILIS","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"};
 | 
					 | 
				
			||||||
      char const* DaysString[] = {"--", "LUNAE", "MARTIS", "MERCURII", "IOVIS", "VENERIS", "SATURNI", "SOLIS"};
 | 
					 | 
				
			||||||
      char const* RomanNumeralsString[] = {"--", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"};
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(label_date_day, "%s\n%s %s",
 | 
					 | 
				
			||||||
                            DaysString[static_cast<uint8_t>(dateTimeController.DayOfWeek())],
 | 
					 | 
				
			||||||
                            RomanNumeralsString[static_cast<uint8_t>(dateTimeController.Day())],
 | 
					 | 
				
			||||||
                            MonthsString[static_cast<uint8_t>(dateTimeController.Month())]);
 | 
					 | 
				
			||||||
      lv_obj_align(label_date_day, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -20);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void WatchFaceSundial::UpdateClock() {
 | 
					 | 
				
			||||||
  uint8_t hour = dateTimeController.Hours();
 | 
					 | 
				
			||||||
  uint8_t minute = dateTimeController.Minutes();
 | 
					 | 
				
			||||||
  location = settingsController.GetLocation();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (sHour != hour || sMinute != minute) {
 | 
					 | 
				
			||||||
    // sun.setPosition(settings.lat.toFloat(), settings.lon.toFloat(), settings.gmtOffset / 3600);
 | 
					 | 
				
			||||||
    sun.setPosition((float)location.latitude, (float)location.longitude, location.tzOffset);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //from minutes past midnight
 | 
					 | 
				
			||||||
    sun.setCurrentDate(dateTimeController.Year(), static_cast<uint8_t>(dateTimeController.Month())+1, dateTimeController.Day());
 | 
					 | 
				
			||||||
    sun.setTZOffset(location.tzOffset);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    minutesSunrise = sun.calcSunrise(); //360;
 | 
					 | 
				
			||||||
    minutesSunset = sun.calcSunset(); //1080;
 | 
					 | 
				
			||||||
    minutesDaytime = (minutesSunset - minutesSunrise);
 | 
					 | 
				
			||||||
    minutesNighttime = (1440 - minutesDaytime);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    minutesBeforeSunset = minutesSunset - (hour * 60 + minute); // i.e.zero degrees
 | 
					 | 
				
			||||||
    HourLength = 90; // sundial hand length
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int16_t hourAngle;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(minutesBeforeSunset > 0 && minutesBeforeSunset < minutesDaytime) { // day (after sunrise)
 | 
					 | 
				
			||||||
      hourAngle = 180.0 * minutesBeforeSunset / minutesDaytime + 90;
 | 
					 | 
				
			||||||
      lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
      lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
    } else { // night (before sunrise or after sunset)
 | 
					 | 
				
			||||||
      lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, DARK_GRAY);
 | 
					 | 
				
			||||||
      lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, DARK_GRAY);
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_ORANGE);
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_GRAY);
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_GRAY);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if(minutesBeforeSunset > minutesDaytime) { // before sunrise
 | 
					 | 
				
			||||||
        hourAngle = 180.0 * (minutesBeforeSunset - minutesDaytime) / minutesNighttime + 90;
 | 
					 | 
				
			||||||
      } else { // after sunset
 | 
					 | 
				
			||||||
        hourAngle = 180 + 180.0 * minutesBeforeSunset / minutesNighttime + 90;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    /*NRF_LOG_INFO("a: %d, la: %f, lo: %f, ri: %d, se: %d, be: %d",
 | 
					 | 
				
			||||||
      hourAngle,
 | 
					 | 
				
			||||||
      (float)location.latitude,
 | 
					 | 
				
			||||||
      (float)location.longitude,
 | 
					 | 
				
			||||||
      minutesSunrise,
 | 
					 | 
				
			||||||
      minutesSunset,
 | 
					 | 
				
			||||||
      minutesBeforeSunset);*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sHour = hour;
 | 
					 | 
				
			||||||
    sMinute = minute;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    printf("H%d:%d lat%f lng%f z%d\n", hour, minute, (float)location.latitude, (float)location.longitude, location.tzOffset);
 | 
					 | 
				
			||||||
    printf("%d before sunset, sunrise at %d:%d sunset at %d:%d angle %d\n",
 | 
					 | 
				
			||||||
           minutesBeforeSunset,
 | 
					 | 
				
			||||||
           minutesSunrise/60, minutesSunrise % 60,
 | 
					 | 
				
			||||||
           minutesSunset/60, minutesSunset % 60, hourAngle);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    hour_point_trace[0] = CoordinateRelocateSundial(HourLength*.75, hourAngle);
 | 
					 | 
				
			||||||
    hour_point_trace[1] = CoordinateRelocateSundial(HourLength, hourAngle);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    hour_point[0] = CoordinateRelocateSundial(0, hourAngle);
 | 
					 | 
				
			||||||
    hour_point[1] = CoordinateRelocateSundial(HourLength*.75, hourAngle);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lv_line_set_points(hour_body, hour_point, 2);
 | 
					 | 
				
			||||||
    lv_line_set_points(hour_body_trace, hour_point_trace, 2);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,111 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <lvgl/src/lv_core/lv_obj.h>
 | 
					 | 
				
			||||||
#include <chrono>
 | 
					 | 
				
			||||||
#include <cstdint>
 | 
					 | 
				
			||||||
#include <memory>
 | 
					 | 
				
			||||||
#include "displayapp/screens/Screen.h"
 | 
					 | 
				
			||||||
#include "components/datetime/DateTimeController.h"
 | 
					 | 
				
			||||||
#include "components/battery/BatteryController.h"
 | 
					 | 
				
			||||||
#include "components/ble/BleController.h"
 | 
					 | 
				
			||||||
#include "components/ble/NotificationManager.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/BatteryIcon.h"
 | 
					 | 
				
			||||||
#include "utility/DirtyValue.h"
 | 
					 | 
				
			||||||
#include "sunset/src/sunset.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pinetime {
 | 
					 | 
				
			||||||
  namespace Controllers {
 | 
					 | 
				
			||||||
    class Settings;
 | 
					 | 
				
			||||||
    class Battery;
 | 
					 | 
				
			||||||
    class Ble;
 | 
					 | 
				
			||||||
    class NotificationManager;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  namespace Applications {
 | 
					 | 
				
			||||||
    namespace Screens {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      class WatchFaceSundial : public Screen {
 | 
					 | 
				
			||||||
      public:
 | 
					 | 
				
			||||||
        WatchFaceSundial(Controllers::DateTime& dateTimeController,
 | 
					 | 
				
			||||||
                        Controllers::NotificationManager& notificationManager,
 | 
					 | 
				
			||||||
                         Controllers::Settings& settingsController);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ~WatchFaceSundial() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        void Refresh() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      private:
 | 
					 | 
				
			||||||
        uint8_t sHour, sMinute, sSecond;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Utility::DirtyValue<uint8_t> batteryPercentRemaining {0};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<bool> isCharging {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<bool> bleState {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
 | 
					 | 
				
			||||||
        Utility::DirtyValue<bool> notificationState {false};
 | 
					 | 
				
			||||||
        using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
 | 
					 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_obj_t* major_scales;
 | 
					 | 
				
			||||||
        lv_obj_t* one;
 | 
					 | 
				
			||||||
        lv_obj_t* twelve;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_obj_t* hour_body;
 | 
					 | 
				
			||||||
        lv_obj_t* hour_body_trace;
 | 
					 | 
				
			||||||
        lv_obj_t* minute_body;
 | 
					 | 
				
			||||||
        lv_obj_t* minute_body_trace;
 | 
					 | 
				
			||||||
        lv_obj_t* second_body;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_point_t hour_point[2];
 | 
					 | 
				
			||||||
        lv_point_t hour_point_trace[2];
 | 
					 | 
				
			||||||
        lv_point_t minute_point[2];
 | 
					 | 
				
			||||||
        lv_point_t minute_point_trace[2];
 | 
					 | 
				
			||||||
        lv_point_t second_point[2];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_style_t hour_line_style;
 | 
					 | 
				
			||||||
        lv_style_t hour_line_style_trace;
 | 
					 | 
				
			||||||
        lv_style_t minute_line_style;
 | 
					 | 
				
			||||||
        lv_style_t minute_line_style_trace;
 | 
					 | 
				
			||||||
        lv_style_t second_line_style;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_obj_t* label_date_day;
 | 
					 | 
				
			||||||
        lv_obj_t* plugIcon;
 | 
					 | 
				
			||||||
        lv_obj_t* notificationIcon;
 | 
					 | 
				
			||||||
        lv_obj_t* bleIcon;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Controllers::Settings::Location location;
 | 
					 | 
				
			||||||
        SunSet sun;
 | 
					 | 
				
			||||||
        int16_t minutesSunrise;
 | 
					 | 
				
			||||||
        int16_t minutesSunset;
 | 
					 | 
				
			||||||
        int16_t minutesDaytime;
 | 
					 | 
				
			||||||
        int16_t minutesNighttime;
 | 
					 | 
				
			||||||
        int16_t minutesBeforeSunset;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const Controllers::DateTime& dateTimeController;
 | 
					 | 
				
			||||||
        Controllers::NotificationManager& notificationManager;
 | 
					 | 
				
			||||||
        Controllers::Settings& settingsController;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        void drawWatchFaceModeNight();
 | 
					 | 
				
			||||||
        void UpdateClock();
 | 
					 | 
				
			||||||
        void SetBatteryIcon();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_task_t* taskRefresh;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    template <>
 | 
					 | 
				
			||||||
    struct WatchFaceTraits<WatchFace::Sundial> {
 | 
					 | 
				
			||||||
      static constexpr WatchFace watchFace = WatchFace::Sundial;
 | 
					 | 
				
			||||||
      static constexpr const char* name = "Sundial face";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static Screens::Screen* Create(AppControllers& controllers) {
 | 
					 | 
				
			||||||
        return new Screens::WatchFaceSundial(controllers.dateTimeController,
 | 
					 | 
				
			||||||
                                            controllers.notificationManager,
 | 
					 | 
				
			||||||
                                             controllers.settingsController);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -125,7 +125,7 @@ void WatchFaceTerminal::Refresh() {
 | 
				
			|||||||
      lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
 | 
					      lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
 | 
					    currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
 | 
				
			||||||
    if (currentDate.IsUpdated()) {
 | 
					    if (currentDate.IsUpdated()) {
 | 
				
			||||||
      uint16_t year = dateTimeController.Year();
 | 
					      uint16_t year = dateTimeController.Year();
 | 
				
			||||||
      Controllers::DateTime::Months month = dateTimeController.Month();
 | 
					      Controllers::DateTime::Months month = dateTimeController.Month();
 | 
				
			||||||
 | 
				
			|||||||
@ -45,7 +45,8 @@ namespace Pinetime {
 | 
				
			|||||||
        Utility::DirtyValue<uint8_t> heartbeat {};
 | 
					        Utility::DirtyValue<uint8_t> heartbeat {};
 | 
				
			||||||
        Utility::DirtyValue<bool> heartbeatRunning {};
 | 
					        Utility::DirtyValue<bool> heartbeatRunning {};
 | 
				
			||||||
        Utility::DirtyValue<bool> notificationState {};
 | 
					        Utility::DirtyValue<bool> notificationState {};
 | 
				
			||||||
        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
 | 
					        using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
 | 
				
			||||||
 | 
					        Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lv_obj_t* label_time;
 | 
					        lv_obj_t* label_time;
 | 
				
			||||||
        lv_obj_t* label_date;
 | 
					        lv_obj_t* label_date;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,198 +0,0 @@
 | 
				
			|||||||
#include "displayapp/screens/Weather.h"
 | 
					 | 
				
			||||||
#include <lvgl/lvgl.h>
 | 
					 | 
				
			||||||
#include "components/ble/SimpleWeatherService.h"
 | 
					 | 
				
			||||||
#include "components/datetime/DateTimeController.h"
 | 
					 | 
				
			||||||
#include "components/settings/Settings.h"
 | 
					 | 
				
			||||||
#include "displayapp/DisplayApp.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/WeatherSymbols.h"
 | 
					 | 
				
			||||||
#include "displayapp/InfiniTimeTheme.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace Pinetime::Applications::Screens;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace {
 | 
					 | 
				
			||||||
  lv_color_t TemperatureColor(int16_t temperature) {
 | 
					 | 
				
			||||||
    if (temperature <= 0) { // freezing
 | 
					 | 
				
			||||||
      return Colors::blue;
 | 
					 | 
				
			||||||
    } else if (temperature <= 400) { // ice
 | 
					 | 
				
			||||||
      return LV_COLOR_CYAN;
 | 
					 | 
				
			||||||
    } else if (temperature >= 2700) { // hot
 | 
					 | 
				
			||||||
      return Colors::deepOrange;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return Colors::orange; // normal
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  uint8_t TemperatureStyle(int16_t temperature) {
 | 
					 | 
				
			||||||
    if (temperature <= 0) { // freezing
 | 
					 | 
				
			||||||
      return LV_TABLE_PART_CELL3;
 | 
					 | 
				
			||||||
    } else if (temperature <= 400) { // ice
 | 
					 | 
				
			||||||
      return LV_TABLE_PART_CELL4;
 | 
					 | 
				
			||||||
    } else if (temperature >= 2700) { // hot
 | 
					 | 
				
			||||||
      return LV_TABLE_PART_CELL6;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return LV_TABLE_PART_CELL5; // normal
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int16_t RoundTemperature(int16_t temp) {
 | 
					 | 
				
			||||||
    return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
 | 
					 | 
				
			||||||
  : settingsController {settingsController}, weatherService {weatherService} {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  temperature = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_font(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
 | 
					 | 
				
			||||||
  lv_label_set_text(temperature, "---");
 | 
					 | 
				
			||||||
  lv_obj_align(temperature, nullptr, LV_ALIGN_CENTER, 0, -30);
 | 
					 | 
				
			||||||
  lv_obj_set_auto_realign(temperature, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  minTemperature = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(minTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg);
 | 
					 | 
				
			||||||
  lv_label_set_text(minTemperature, "");
 | 
					 | 
				
			||||||
  lv_obj_align(minTemperature, temperature, LV_ALIGN_OUT_LEFT_MID, -10, 0);
 | 
					 | 
				
			||||||
  lv_obj_set_auto_realign(minTemperature, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  maxTemperature = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(maxTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg);
 | 
					 | 
				
			||||||
  lv_label_set_text(maxTemperature, "");
 | 
					 | 
				
			||||||
  lv_obj_align(maxTemperature, temperature, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
 | 
					 | 
				
			||||||
  lv_obj_set_auto_realign(maxTemperature, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  condition = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(condition, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
 | 
					 | 
				
			||||||
  lv_label_set_text(condition, "");
 | 
					 | 
				
			||||||
  lv_obj_align(condition, temperature, LV_ALIGN_OUT_TOP_MID, 0, -10);
 | 
					 | 
				
			||||||
  lv_obj_set_auto_realign(condition, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  icon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_font(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
 | 
					 | 
				
			||||||
  lv_label_set_text(icon, "");
 | 
					 | 
				
			||||||
  lv_obj_align(icon, condition, LV_ALIGN_OUT_TOP_MID, 0, 0);
 | 
					 | 
				
			||||||
  lv_obj_set_auto_realign(icon, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  forecast = lv_table_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_table_set_col_cnt(forecast, Controllers::SimpleWeatherService::MaxNbForecastDays);
 | 
					 | 
				
			||||||
  lv_table_set_row_cnt(forecast, 4);
 | 
					 | 
				
			||||||
  // LV_TABLE_PART_CELL1: Default table style
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_BLACK);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, Colors::lightGray);
 | 
					 | 
				
			||||||
  // LV_TABLE_PART_CELL2: Condition icon
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_BLACK);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_font(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, &fontawesome_weathericons);
 | 
					 | 
				
			||||||
  // LV_TABLE_PART_CELL3: Freezing
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, LV_COLOR_BLACK);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, Colors::blue);
 | 
					 | 
				
			||||||
  // LV_TABLE_PART_CELL4: Ice
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_BLACK);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_CYAN);
 | 
					 | 
				
			||||||
  // LV_TABLE_PART_CELL5: Normal
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, LV_COLOR_BLACK);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, Colors::orange);
 | 
					 | 
				
			||||||
  // LV_TABLE_PART_CELL6: Hot
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, LV_COLOR_BLACK);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, Colors::deepOrange);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_obj_align(forecast, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
 | 
					 | 
				
			||||||
    lv_table_set_col_width(forecast, i, 48);
 | 
					 | 
				
			||||||
    lv_table_set_cell_type(forecast, 1, i, LV_TABLE_PART_CELL2);
 | 
					 | 
				
			||||||
    lv_table_set_cell_align(forecast, 0, i, LV_LABEL_ALIGN_CENTER);
 | 
					 | 
				
			||||||
    lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_CENTER);
 | 
					 | 
				
			||||||
    lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_CENTER);
 | 
					 | 
				
			||||||
    lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_CENTER);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this);
 | 
					 | 
				
			||||||
  Refresh();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Weather::~Weather() {
 | 
					 | 
				
			||||||
  lv_task_del(taskRefresh);
 | 
					 | 
				
			||||||
  lv_obj_clean(lv_scr_act());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Weather::Refresh() {
 | 
					 | 
				
			||||||
  currentWeather = weatherService.Current();
 | 
					 | 
				
			||||||
  if (currentWeather.IsUpdated()) {
 | 
					 | 
				
			||||||
    auto optCurrentWeather = currentWeather.Get();
 | 
					 | 
				
			||||||
    if (optCurrentWeather) {
 | 
					 | 
				
			||||||
      int16_t temp = optCurrentWeather->temperature;
 | 
					 | 
				
			||||||
      int16_t minTemp = optCurrentWeather->minTemperature;
 | 
					 | 
				
			||||||
      int16_t maxTemp = optCurrentWeather->maxTemperature;
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, TemperatureColor(temp));
 | 
					 | 
				
			||||||
      char tempUnit = 'C';
 | 
					 | 
				
			||||||
      if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
 | 
					 | 
				
			||||||
        temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
 | 
					 | 
				
			||||||
        minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
 | 
					 | 
				
			||||||
        maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
 | 
					 | 
				
			||||||
        tempUnit = 'F';
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
 | 
					 | 
				
			||||||
      lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId));
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
 | 
					 | 
				
			||||||
      lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      lv_label_set_text(icon, "");
 | 
					 | 
				
			||||||
      lv_label_set_text(condition, "");
 | 
					 | 
				
			||||||
      lv_label_set_text(temperature, "---");
 | 
					 | 
				
			||||||
      lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
 | 
					 | 
				
			||||||
      lv_label_set_text(minTemperature, "");
 | 
					 | 
				
			||||||
      lv_label_set_text(maxTemperature, "");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  currentForecast = weatherService.GetForecast();
 | 
					 | 
				
			||||||
  if (currentForecast.IsUpdated()) {
 | 
					 | 
				
			||||||
    auto optCurrentForecast = currentForecast.Get();
 | 
					 | 
				
			||||||
    if (optCurrentForecast) {
 | 
					 | 
				
			||||||
      std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
 | 
					 | 
				
			||||||
        int16_t maxTemp = optCurrentForecast->days[i].maxTemperature;
 | 
					 | 
				
			||||||
        int16_t minTemp = optCurrentForecast->days[i].minTemperature;
 | 
					 | 
				
			||||||
        lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp));
 | 
					 | 
				
			||||||
        lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp));
 | 
					 | 
				
			||||||
        if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
 | 
					 | 
				
			||||||
          maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
 | 
					 | 
				
			||||||
          minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        uint8_t wday = localTime.tm_wday + i + 1;
 | 
					 | 
				
			||||||
        if (wday > 7) {
 | 
					 | 
				
			||||||
          wday -= 7;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        maxTemp = RoundTemperature(maxTemp);
 | 
					 | 
				
			||||||
        minTemp = RoundTemperature(minTemp);
 | 
					 | 
				
			||||||
        const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday));
 | 
					 | 
				
			||||||
        lv_table_set_cell_value(forecast, 0, i, dayOfWeek);
 | 
					 | 
				
			||||||
        lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId));
 | 
					 | 
				
			||||||
        // Pad cells based on the largest number of digits on each column
 | 
					 | 
				
			||||||
        char maxPadding[3] = "  ";
 | 
					 | 
				
			||||||
        char minPadding[3] = "  ";
 | 
					 | 
				
			||||||
        int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp);
 | 
					 | 
				
			||||||
        if (diff <= 0) {
 | 
					 | 
				
			||||||
          maxPadding[-diff] = '\0';
 | 
					 | 
				
			||||||
          minPadding[0] = '\0';
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          maxPadding[0] = '\0';
 | 
					 | 
				
			||||||
          minPadding[diff] = '\0';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp);
 | 
					 | 
				
			||||||
        lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
 | 
					 | 
				
			||||||
        lv_table_set_cell_value(forecast, 0, i, "");
 | 
					 | 
				
			||||||
        lv_table_set_cell_value(forecast, 1, i, "");
 | 
					 | 
				
			||||||
        lv_table_set_cell_value(forecast, 2, i, "");
 | 
					 | 
				
			||||||
        lv_table_set_cell_value(forecast, 3, i, "");
 | 
					 | 
				
			||||||
        lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL1);
 | 
					 | 
				
			||||||
        lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL1);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,56 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <cstdint>
 | 
					 | 
				
			||||||
#include <lvgl/lvgl.h>
 | 
					 | 
				
			||||||
#include "displayapp/screens/Screen.h"
 | 
					 | 
				
			||||||
#include "components/ble/SimpleWeatherService.h"
 | 
					 | 
				
			||||||
#include "displayapp/apps/Apps.h"
 | 
					 | 
				
			||||||
#include "displayapp/Controllers.h"
 | 
					 | 
				
			||||||
#include "Symbols.h"
 | 
					 | 
				
			||||||
#include "utility/DirtyValue.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pinetime {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  namespace Controllers {
 | 
					 | 
				
			||||||
    class Settings;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  namespace Applications {
 | 
					 | 
				
			||||||
    namespace Screens {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      class Weather : public Screen {
 | 
					 | 
				
			||||||
      public:
 | 
					 | 
				
			||||||
        Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService);
 | 
					 | 
				
			||||||
        ~Weather() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        void Refresh() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      private:
 | 
					 | 
				
			||||||
        Controllers::Settings& settingsController;
 | 
					 | 
				
			||||||
        Controllers::SimpleWeatherService& weatherService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
 | 
					 | 
				
			||||||
        Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::Forecast>> currentForecast {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_obj_t* icon;
 | 
					 | 
				
			||||||
        lv_obj_t* condition;
 | 
					 | 
				
			||||||
        lv_obj_t* temperature;
 | 
					 | 
				
			||||||
        lv_obj_t* minTemperature;
 | 
					 | 
				
			||||||
        lv_obj_t* maxTemperature;
 | 
					 | 
				
			||||||
        lv_obj_t* forecast;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lv_task_t* taskRefresh;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    template <>
 | 
					 | 
				
			||||||
    struct AppTraits<Apps::Weather> {
 | 
					 | 
				
			||||||
      static constexpr Apps app = Apps::Weather;
 | 
					 | 
				
			||||||
      static constexpr const char* icon = Screens::Symbols::cloudSunRain;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      static Screens::Screen* Create(AppControllers& controllers) {
 | 
					 | 
				
			||||||
        return new Screens::Weather(controllers.settingsController, *controllers.weatherController);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,61 +0,0 @@
 | 
				
			|||||||
#include "displayapp/screens/WeatherSymbols.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
 | 
					 | 
				
			||||||
  switch (icon) {
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
 | 
					 | 
				
			||||||
      return Symbols::sun;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
 | 
					 | 
				
			||||||
      return Symbols::cloudSun;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
 | 
					 | 
				
			||||||
      return Symbols::cloud;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
 | 
					 | 
				
			||||||
      return Symbols::cloudMeatball;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
 | 
					 | 
				
			||||||
      return Symbols::bolt;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
 | 
					 | 
				
			||||||
      return Symbols::snowflake;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
 | 
					 | 
				
			||||||
      return Symbols::cloudShowersHeavy;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
 | 
					 | 
				
			||||||
      return Symbols::cloudSunRain;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
 | 
					 | 
				
			||||||
      return Symbols::smog;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    default:
 | 
					 | 
				
			||||||
      return Symbols::ban;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const char* Pinetime::Applications::Screens::Symbols::GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
 | 
					 | 
				
			||||||
  switch (icon) {
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
 | 
					 | 
				
			||||||
      return "Clear sky";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
 | 
					 | 
				
			||||||
      return "Few clouds";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
 | 
					 | 
				
			||||||
      return "Scattered clouds";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
 | 
					 | 
				
			||||||
      return "Broken clouds";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
 | 
					 | 
				
			||||||
      return "Shower rain";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
 | 
					 | 
				
			||||||
      return "Rain";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
 | 
					 | 
				
			||||||
      return "Thunderstorm";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
 | 
					 | 
				
			||||||
      return "Snow";
 | 
					 | 
				
			||||||
    case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
 | 
					 | 
				
			||||||
      return "Mist";
 | 
					 | 
				
			||||||
    default:
 | 
					 | 
				
			||||||
      return "";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -6,8 +6,40 @@ namespace Pinetime {
 | 
				
			|||||||
  namespace Applications {
 | 
					  namespace Applications {
 | 
				
			||||||
    namespace Screens {
 | 
					    namespace Screens {
 | 
				
			||||||
      namespace Symbols {
 | 
					      namespace Symbols {
 | 
				
			||||||
        const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
 | 
					        const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
 | 
				
			||||||
        const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
 | 
					          switch (icon) {
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
 | 
				
			||||||
 | 
					              return Symbols::sun;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
 | 
				
			||||||
 | 
					              return Symbols::cloudSun;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
 | 
				
			||||||
 | 
					              return Symbols::cloud;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
 | 
				
			||||||
 | 
					              return Symbols::cloudMeatball;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
 | 
				
			||||||
 | 
					              return Symbols::bolt;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
 | 
				
			||||||
 | 
					              return Symbols::snowflake;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
 | 
				
			||||||
 | 
					              return Symbols::cloudShowersHeavy;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
 | 
				
			||||||
 | 
					              return Symbols::cloudSunRain;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
 | 
				
			||||||
 | 
					              return Symbols::smog;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					              return Symbols::ban;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -36,19 +36,17 @@ namespace {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
 | 
					SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
 | 
				
			||||||
  : app {app},
 | 
					  : app {app},
 | 
				
			||||||
    settings {settingsController},
 | 
					 | 
				
			||||||
    checkboxList(
 | 
					    checkboxList(
 | 
				
			||||||
      0,
 | 
					      0,
 | 
				
			||||||
      1,
 | 
					      1,
 | 
				
			||||||
      "Bluetooth",
 | 
					      "Bluetooth",
 | 
				
			||||||
      Symbols::bluetooth,
 | 
					      Symbols::bluetooth,
 | 
				
			||||||
      settingsController.GetBleRadioEnabled() ? 0 : 1,
 | 
					      settingsController.GetBleRadioEnabled() ? 0 : 1,
 | 
				
			||||||
      [this](uint32_t index) {
 | 
					      [&settings = settingsController](uint32_t index) {
 | 
				
			||||||
        const bool priorMode = settings.GetBleRadioEnabled();
 | 
					        const bool priorMode = settings.GetBleRadioEnabled();
 | 
				
			||||||
        const bool newMode = options[index].radioEnabled;
 | 
					        const bool newMode = options[index].radioEnabled;
 | 
				
			||||||
        if (newMode != priorMode) {
 | 
					        if (newMode != priorMode) {
 | 
				
			||||||
          settings.SetBleRadioEnabled(newMode);
 | 
					          settings.SetBleRadioEnabled(newMode);
 | 
				
			||||||
          this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      CreateOptionArray()) {
 | 
					      CreateOptionArray()) {
 | 
				
			||||||
@ -56,4 +54,6 @@ SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pine
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
SettingBluetooth::~SettingBluetooth() {
 | 
					SettingBluetooth::~SettingBluetooth() {
 | 
				
			||||||
  lv_obj_clean(lv_scr_act());
 | 
					  lv_obj_clean(lv_scr_act());
 | 
				
			||||||
 | 
					  // Pushing the message in the OnValueChanged function causes a freeze?
 | 
				
			||||||
 | 
					  app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,6 @@ namespace Pinetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      private:
 | 
					      private:
 | 
				
			||||||
        DisplayApp* app;
 | 
					        DisplayApp* app;
 | 
				
			||||||
        Pinetime::Controllers::Settings& settings;
 | 
					 | 
				
			||||||
        CheckboxList checkboxList;
 | 
					        CheckboxList checkboxList;
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,70 +0,0 @@
 | 
				
			|||||||
#include "displayapp/screens/settings/SettingLocation.h"
 | 
					 | 
				
			||||||
#include <lvgl/lvgl.h>
 | 
					 | 
				
			||||||
#include <nrf_log.h>
 | 
					 | 
				
			||||||
#include "displayapp/DisplayApp.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Symbols.h"
 | 
					 | 
				
			||||||
#include "components/settings/Settings.h"
 | 
					 | 
				
			||||||
#include "displayapp/InfiniTimeTheme.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace Pinetime::Applications::Screens;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace {
 | 
					 | 
				
			||||||
  constexpr int16_t POS_X_LAT = -80;
 | 
					 | 
				
			||||||
  constexpr int16_t POS_X_LONG = 0;
 | 
					 | 
				
			||||||
  constexpr int16_t POS_X_TZ = 80;
 | 
					 | 
				
			||||||
  constexpr int16_t POS_Y_TEXT = 25;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void ValueChangedHandler(void* userData) {
 | 
					 | 
				
			||||||
    auto* screen = static_cast<SettingLocation*>(userData);
 | 
					 | 
				
			||||||
    screen->UpdateScreen();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SettingLocation::SettingLocation(Pinetime::Controllers::Settings& settingsController)
 | 
					 | 
				
			||||||
  : settingsController {settingsController} {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_label_set_text_static(title, "Set location\n(lat/long/tz)");
 | 
					 | 
				
			||||||
  lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
 | 
					 | 
				
			||||||
  lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 15, 15);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr);
 | 
					 | 
				
			||||||
  lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lv_label_set_text_static(icon, Symbols::map);
 | 
					 | 
				
			||||||
  lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
 | 
					 | 
				
			||||||
  lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Controllers::Settings::Location loc = settingsController.GetLocation();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  latCounter.Create();
 | 
					 | 
				
			||||||
  latCounter.SetValue(loc.latitude);
 | 
					 | 
				
			||||||
  lv_obj_align(latCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LAT, POS_Y_TEXT);
 | 
					 | 
				
			||||||
  latCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  longCounter.Create();
 | 
					 | 
				
			||||||
  longCounter.SetValue(loc.longitude);
 | 
					 | 
				
			||||||
  lv_obj_align(longCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LONG, POS_Y_TEXT);
 | 
					 | 
				
			||||||
  longCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  tzCounter.Create();
 | 
					 | 
				
			||||||
  tzCounter.SetValue(loc.tzOffset);
 | 
					 | 
				
			||||||
  lv_obj_align(tzCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_TZ, POS_Y_TEXT);
 | 
					 | 
				
			||||||
  tzCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  UpdateScreen();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SettingLocation::~SettingLocation() {
 | 
					 | 
				
			||||||
  lv_obj_clean(lv_scr_act());
 | 
					 | 
				
			||||||
  settingsController.SaveSettings();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void SettingLocation::UpdateScreen() {
 | 
					 | 
				
			||||||
  Controllers::Settings::Location loc = {
 | 
					 | 
				
			||||||
  latitude: (int16_t)latCounter.GetValue(),
 | 
					 | 
				
			||||||
  longitude: (int16_t)longCounter.GetValue(),
 | 
					 | 
				
			||||||
  tzOffset: (int8_t)tzCounter.GetValue(),
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  settingsController.SetLocation(loc);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,31 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <cstdint>
 | 
					 | 
				
			||||||
#include <lvgl/lvgl.h>
 | 
					 | 
				
			||||||
#include "components/datetime/DateTimeController.h"
 | 
					 | 
				
			||||||
#include "components/settings/Settings.h"
 | 
					 | 
				
			||||||
#include "displayapp/widgets/Counter.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/Screen.h"
 | 
					 | 
				
			||||||
#include "displayapp/widgets/DotIndicator.h"
 | 
					 | 
				
			||||||
#include "displayapp/screens/settings/SettingSetDateTime.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pinetime {
 | 
					 | 
				
			||||||
  namespace Applications {
 | 
					 | 
				
			||||||
    namespace Screens {
 | 
					 | 
				
			||||||
      class SettingLocation : public Screen {
 | 
					 | 
				
			||||||
      public:
 | 
					 | 
				
			||||||
        SettingLocation(Pinetime::Controllers::Settings& settingsController);
 | 
					 | 
				
			||||||
        ~SettingLocation() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        void UpdateScreen();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      private:
 | 
					 | 
				
			||||||
        Controllers::Settings& settingsController;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Widgets::Counter latCounter = Widgets::Counter(-90, 90, jetbrains_mono_bold_20);
 | 
					 | 
				
			||||||
        Widgets::Counter longCounter = Widgets::Counter(-180, 180, jetbrains_mono_bold_20);
 | 
					 | 
				
			||||||
        Widgets::Counter tzCounter = Widgets::Counter(-12, 12, jetbrains_mono_bold_20);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -13,9 +13,10 @@ namespace {
 | 
				
			|||||||
    const char* name;
 | 
					    const char* name;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constexpr std::array<Option, 2> options = {{
 | 
					  constexpr std::array<Option, 3> options = {{
 | 
				
			||||||
    {Pinetime::Controllers::Settings::ClockType::H12, "12-hour"},
 | 
					    {Pinetime::Controllers::Settings::ClockType::H12, "12-hour"},
 | 
				
			||||||
    {Pinetime::Controllers::Settings::ClockType::H24, "24-hour"},
 | 
					    {Pinetime::Controllers::Settings::ClockType::H24, "24-hour"},
 | 
				
			||||||
 | 
					    {Pinetime::Controllers::Settings::ClockType::Fuzzy, "Fuzzy"},
 | 
				
			||||||
  }};
 | 
					  }};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
 | 
					  std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,37 +9,6 @@ using namespace Pinetime::Applications::Screens;
 | 
				
			|||||||
constexpr const char* SettingWatchFace::title;
 | 
					constexpr const char* SettingWatchFace::title;
 | 
				
			||||||
constexpr const char* SettingWatchFace::symbol;
 | 
					constexpr const char* SettingWatchFace::symbol;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace {
 | 
					 | 
				
			||||||
  uint32_t IndexOf(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
 | 
					 | 
				
			||||||
                                    Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
 | 
					 | 
				
			||||||
                   Pinetime::Applications::WatchFace watchface) {
 | 
					 | 
				
			||||||
    size_t index = 0;
 | 
					 | 
				
			||||||
    auto found = std::find_if(watchfaces.begin(),
 | 
					 | 
				
			||||||
                              watchfaces.end(),
 | 
					 | 
				
			||||||
                              [&index, &watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) {
 | 
					 | 
				
			||||||
                                const bool result = item.watchface == watchface;
 | 
					 | 
				
			||||||
                                if (!result) {
 | 
					 | 
				
			||||||
                                  index++;
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                return result;
 | 
					 | 
				
			||||||
                              });
 | 
					 | 
				
			||||||
    if (found == watchfaces.end()) {
 | 
					 | 
				
			||||||
      index = 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return index;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Pinetime::Applications::WatchFace IndexToWatchFace(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
 | 
					 | 
				
			||||||
                                                                      Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
 | 
					 | 
				
			||||||
                                                     size_t index) {
 | 
					 | 
				
			||||||
    if (index >= watchfaces.size()) {
 | 
					 | 
				
			||||||
      return watchfaces[0].watchface;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return watchfaces[index].watchface;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
auto SettingWatchFace::CreateScreenList() const {
 | 
					auto SettingWatchFace::CreateScreenList() const {
 | 
				
			||||||
  std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
 | 
					  std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
 | 
				
			||||||
  for (size_t i = 0; i < screens.size(); i++) {
 | 
					  for (size_t i = 0; i < screens.size(); i++) {
 | 
				
			||||||
@ -51,7 +20,7 @@ auto SettingWatchFace::CreateScreenList() const {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
 | 
					SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
 | 
				
			||||||
                                   std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
 | 
					                                   std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
 | 
				
			||||||
                                   Pinetime::Controllers::Settings& settingsController,
 | 
					                                   Pinetime::Controllers::Settings& settingsController,
 | 
				
			||||||
                                   Pinetime::Controllers::FS& filesystem)
 | 
					                                   Pinetime::Controllers::FS& filesystem)
 | 
				
			||||||
  : app {app},
 | 
					  : app {app},
 | 
				
			||||||
@ -75,8 +44,7 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
 | 
				
			|||||||
    if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
 | 
					    if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
 | 
				
			||||||
      watchfacesOnThisScreen[i] = {"", false};
 | 
					      watchfacesOnThisScreen[i] = {"", false};
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      auto& item = watchfaceItems[i + (screenNum * settingsPerScreen)];
 | 
					      watchfacesOnThisScreen[i] = watchfaceItems[i + (screenNum * settingsPerScreen)];
 | 
				
			||||||
      watchfacesOnThisScreen[i] = Screens::CheckboxList::Item {item.name, item.enabled};
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -85,9 +53,9 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
 | 
				
			|||||||
    nScreens,
 | 
					    nScreens,
 | 
				
			||||||
    title,
 | 
					    title,
 | 
				
			||||||
    symbol,
 | 
					    symbol,
 | 
				
			||||||
    static_cast<uint32_t>(IndexOf(watchfaceItems, settingsController.GetWatchFace())),
 | 
					    static_cast<uint32_t>(settingsController.GetWatchFace()),
 | 
				
			||||||
    [this, &settings = settingsController](uint32_t index) {
 | 
					    [&settings = settingsController](uint32_t index) {
 | 
				
			||||||
      settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index));
 | 
					      settings.SetWatchFace(static_cast<WatchFace>(index));
 | 
				
			||||||
      settings.SaveSettings();
 | 
					      settings.SaveSettings();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    watchfacesOnThisScreen);
 | 
					    watchfacesOnThisScreen);
 | 
				
			||||||
 | 
				
			|||||||
@ -9,8 +9,8 @@
 | 
				
			|||||||
#include "displayapp/screens/Screen.h"
 | 
					#include "displayapp/screens/Screen.h"
 | 
				
			||||||
#include "displayapp/screens/Symbols.h"
 | 
					#include "displayapp/screens/Symbols.h"
 | 
				
			||||||
#include "displayapp/screens/CheckboxList.h"
 | 
					#include "displayapp/screens/CheckboxList.h"
 | 
				
			||||||
// #include "displayapp/screens/WatchFaceInfineat.h"
 | 
					#include "displayapp/screens/WatchFaceInfineat.h"
 | 
				
			||||||
// #include "displayapp/screens/WatchFaceCasioStyleG7710.h"
 | 
					#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pinetime {
 | 
					namespace Pinetime {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,14 +19,8 @@ namespace Pinetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      class SettingWatchFace : public Screen {
 | 
					      class SettingWatchFace : public Screen {
 | 
				
			||||||
      public:
 | 
					      public:
 | 
				
			||||||
        struct Item {
 | 
					 | 
				
			||||||
          const char* name;
 | 
					 | 
				
			||||||
          WatchFace watchface;
 | 
					 | 
				
			||||||
          bool enabled;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        SettingWatchFace(DisplayApp* app,
 | 
					        SettingWatchFace(DisplayApp* app,
 | 
				
			||||||
                         std::array<Item, UserWatchFaceTypes::Count>&& watchfaceItems,
 | 
					                         std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
 | 
				
			||||||
                         Pinetime::Controllers::Settings& settingsController,
 | 
					                         Pinetime::Controllers::Settings& settingsController,
 | 
				
			||||||
                         Pinetime::Controllers::FS& filesystem);
 | 
					                         Pinetime::Controllers::FS& filesystem);
 | 
				
			||||||
        ~SettingWatchFace() override;
 | 
					        ~SettingWatchFace() override;
 | 
				
			||||||
@ -39,7 +33,7 @@ namespace Pinetime {
 | 
				
			|||||||
        std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
 | 
					        std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static constexpr int settingsPerScreen = 4;
 | 
					        static constexpr int settingsPerScreen = 4;
 | 
				
			||||||
        std::array<Item, UserWatchFaceTypes::Count> watchfaceItems;
 | 
					        std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> watchfaceItems;
 | 
				
			||||||
        static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;
 | 
					        static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Controllers::Settings& settingsController;
 | 
					        Controllers::Settings& settingsController;
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,6 @@ namespace Pinetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          {Symbols::shoe, "Steps", Apps::SettingSteps},
 | 
					          {Symbols::shoe, "Steps", Apps::SettingSteps},
 | 
				
			||||||
          {Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
 | 
					          {Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
 | 
				
			||||||
          {Symbols::map, "Location", Apps::SettingLocation},
 | 
					 | 
				
			||||||
          {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
 | 
					          {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
 | 
				
			||||||
          {Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
 | 
					          {Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -42,8 +42,6 @@ namespace Pinetime {
 | 
				
			|||||||
        int max;
 | 
					        int max;
 | 
				
			||||||
        int value;
 | 
					        int value;
 | 
				
			||||||
        const int leadingZeroCount;
 | 
					        const int leadingZeroCount;
 | 
				
			||||||
        uint8_t containerHeight;
 | 
					 | 
				
			||||||
        static constexpr uint8_t btnHeight = 50;
 | 
					 | 
				
			||||||
        bool twelveHourMode = false;
 | 
					        bool twelveHourMode = false;
 | 
				
			||||||
        bool monthMode = false;
 | 
					        bool monthMode = false;
 | 
				
			||||||
        lv_font_t& font;
 | 
					        lv_font_t& font;
 | 
				
			||||||
 | 
				
			|||||||
@ -22,16 +22,6 @@ namespace {
 | 
				
			|||||||
  void user_delay(uint32_t period_us, void* /*intf_ptr*/) {
 | 
					  void user_delay(uint32_t period_us, void* /*intf_ptr*/) {
 | 
				
			||||||
    nrf_delay_us(period_us);
 | 
					    nrf_delay_us(period_us);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Scale factors to convert accelerometer counts to milli-g
 | 
					 | 
				
			||||||
  // from datasheet: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf
 | 
					 | 
				
			||||||
  // The array index to use is stored in accel_conf.range
 | 
					 | 
				
			||||||
  constexpr int16_t accelScaleFactors[] = {
 | 
					 | 
				
			||||||
    [BMA4_ACCEL_RANGE_2G] = 1024, // LSB/g +/- 2g range
 | 
					 | 
				
			||||||
    [BMA4_ACCEL_RANGE_4G] = 512,  // LSB/g +/- 4g range
 | 
					 | 
				
			||||||
    [BMA4_ACCEL_RANGE_8G] = 256,  // LSB/g +/- 8g range
 | 
					 | 
				
			||||||
    [BMA4_ACCEL_RANGE_16G] = 128  // LSB/g +/- 16g range
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} {
 | 
					Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} {
 | 
				
			||||||
@ -84,6 +74,7 @@ void Bma421::Init() {
 | 
				
			|||||||
  if (ret != BMA4_OK)
 | 
					  if (ret != BMA4_OK)
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct bma4_accel_config accel_conf;
 | 
				
			||||||
  accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
 | 
					  accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
 | 
				
			||||||
  accel_conf.range = BMA4_ACCEL_RANGE_2G;
 | 
					  accel_conf.range = BMA4_ACCEL_RANGE_2G;
 | 
				
			||||||
  accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
 | 
					  accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
 | 
				
			||||||
@ -111,21 +102,19 @@ void Bma421::Write(uint8_t registerAddress, const uint8_t* data, size_t size) {
 | 
				
			|||||||
Bma421::Values Bma421::Process() {
 | 
					Bma421::Values Bma421::Process() {
 | 
				
			||||||
  if (not isOk)
 | 
					  if (not isOk)
 | 
				
			||||||
    return {};
 | 
					    return {};
 | 
				
			||||||
  struct bma4_accel rawData;
 | 
					 | 
				
			||||||
  struct bma4_accel data;
 | 
					  struct bma4_accel data;
 | 
				
			||||||
  bma4_read_accel_xyz(&rawData, &bma);
 | 
					  bma4_read_accel_xyz(&data, &bma);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Scale the measured ADC counts to units of 'binary milli-g'
 | 
					 | 
				
			||||||
  // where 1g = 1024 'binary milli-g' units.
 | 
					 | 
				
			||||||
  // See https://github.com/InfiniTimeOrg/InfiniTime/pull/1950 for
 | 
					 | 
				
			||||||
  // discussion of why we opted for scaling to 1024 rather than 1000.
 | 
					 | 
				
			||||||
  data.x = 1024 * rawData.x / accelScaleFactors[accel_conf.range];
 | 
					 | 
				
			||||||
  data.y = 1024 * rawData.y / accelScaleFactors[accel_conf.range];
 | 
					 | 
				
			||||||
  data.z = 1024 * rawData.z / accelScaleFactors[accel_conf.range];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint32_t steps = 0;
 | 
					  uint32_t steps = 0;
 | 
				
			||||||
  bma423_step_counter_output(&steps, &bma);
 | 
					  bma423_step_counter_output(&steps, &bma);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int32_t temperature;
 | 
				
			||||||
 | 
					  bma4_get_temperature(&temperature, BMA4_DEG, &bma);
 | 
				
			||||||
 | 
					  temperature = temperature / 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t activity = 0;
 | 
				
			||||||
 | 
					  bma423_activity_output(&activity, &bma);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // X and Y axis are swapped because of the way the sensor is mounted in the PineTime
 | 
					  // X and Y axis are swapped because of the way the sensor is mounted in the PineTime
 | 
				
			||||||
  return {steps, data.y, data.x, data.z};
 | 
					  return {steps, data.y, data.x, data.z};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -41,7 +41,6 @@ namespace Pinetime {
 | 
				
			|||||||
      TwiMaster& twiMaster;
 | 
					      TwiMaster& twiMaster;
 | 
				
			||||||
      uint8_t deviceAddress = 0x18;
 | 
					      uint8_t deviceAddress = 0x18;
 | 
				
			||||||
      struct bma4_dev bma;
 | 
					      struct bma4_dev bma;
 | 
				
			||||||
      struct bma4_accel_config accel_conf; // Store the device configuration for later reference.
 | 
					 | 
				
			||||||
      bool isOk = false;
 | 
					      bool isOk = false;
 | 
				
			||||||
      bool isResetOk = false;
 | 
					      bool isResetOk = false;
 | 
				
			||||||
      DeviceTypes deviceType = DeviceTypes::Unknown;
 | 
					      DeviceTypes deviceType = DeviceTypes::Unknown;
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ namespace {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Driver for the HRS3300 heart rate sensor.
 | 
					/** Driver for the HRS3300 heart rate sensor.
 | 
				
			||||||
 * Original implementation from wasp-os : https://github.com/wasp-os/wasp-os/blob/master/wasp/drivers/hrs3300.py
 | 
					 * Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/drivers/hrs3300.py
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Experimentaly derived changes to improve signal/noise (see comments below) - Ceimour
 | 
					 * Experimentaly derived changes to improve signal/noise (see comments below) - Ceimour
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
				
			|||||||
@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn {
 | 
				
			|||||||
  nrf_gpio_pin_set(pinCsn);
 | 
					  nrf_gpio_pin_set(pinCsn);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Spi::Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
 | 
					bool Spi::Write(const uint8_t* data, size_t size) {
 | 
				
			||||||
  return spiMaster.Write(pinCsn, data, size, preTransactionHook);
 | 
					  return spiMaster.Write(pinCsn, data, size);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
 | 
					bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
#include <cstddef>
 | 
					#include <cstddef>
 | 
				
			||||||
#include <functional>
 | 
					 | 
				
			||||||
#include "drivers/SpiMaster.h"
 | 
					#include "drivers/SpiMaster.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pinetime {
 | 
					namespace Pinetime {
 | 
				
			||||||
@ -15,7 +14,7 @@ namespace Pinetime {
 | 
				
			|||||||
      Spi& operator=(Spi&&) = delete;
 | 
					      Spi& operator=(Spi&&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      bool Init();
 | 
					      bool Init();
 | 
				
			||||||
      bool Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
 | 
					      bool Write(const uint8_t* data, size_t size);
 | 
				
			||||||
      bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
 | 
					      bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
 | 
				
			||||||
      bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
 | 
					      bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
 | 
				
			||||||
      void Sleep();
 | 
					      void Sleep();
 | 
				
			||||||
 | 
				
			|||||||
@ -94,45 +94,32 @@ bool SpiMaster::Init() {
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SpiMaster::SetupWorkaroundForErratum58() {
 | 
					void SpiMaster::SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
 | 
				
			||||||
  nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
 | 
					  // Create an event when SCK toggles.
 | 
				
			||||||
  nrfx_gpiote_in_config_t gpioteCfg = {.sense = NRF_GPIOTE_POLARITY_TOGGLE,
 | 
					  NRF_GPIOTE->CONFIG[gpiote_channel] = (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | (spim->PSEL.SCK << GPIOTE_CONFIG_PSEL_Pos) |
 | 
				
			||||||
                                       .pull = NRF_GPIO_PIN_NOPULL,
 | 
					                                       (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
 | 
				
			||||||
                                       .is_watcher = false,
 | 
					 | 
				
			||||||
                                       .hi_accuracy = true,
 | 
					 | 
				
			||||||
                                       .skip_gpio_setup = true};
 | 
					 | 
				
			||||||
  if (!workaroundActive) {
 | 
					 | 
				
			||||||
    // Create an event when SCK toggles.
 | 
					 | 
				
			||||||
    APP_ERROR_CHECK(nrfx_gpiote_in_init(pin, &gpioteCfg, NULL));
 | 
					 | 
				
			||||||
    nrfx_gpiote_in_event_enable(pin, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Stop the spim instance when SCK toggles.
 | 
					 | 
				
			||||||
    nrf_ppi_channel_endpoint_setup(workaroundPpi, nrfx_gpiote_in_event_addr_get(pin), spiBaseAddress->TASKS_STOP);
 | 
					 | 
				
			||||||
    nrf_ppi_channel_enable(workaroundPpi);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Stop the spim instance when SCK toggles.
 | 
				
			||||||
 | 
					  NRF_PPI->CH[ppi_channel].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel];
 | 
				
			||||||
 | 
					  NRF_PPI->CH[ppi_channel].TEP = (uint32_t) &spim->TASKS_STOP;
 | 
				
			||||||
 | 
					  NRF_PPI->CHENSET = 1U << ppi_channel;
 | 
				
			||||||
  spiBaseAddress->EVENTS_END = 0;
 | 
					  spiBaseAddress->EVENTS_END = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Disable IRQ
 | 
					  // Disable IRQ
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 6);
 | 
					  spim->INTENCLR = (1 << 6);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 1);
 | 
					  spim->INTENCLR = (1 << 1);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 19);
 | 
					  spim->INTENCLR = (1 << 19);
 | 
				
			||||||
  workaroundActive = true;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SpiMaster::DisableWorkaroundForErratum58() {
 | 
					void SpiMaster::DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
 | 
				
			||||||
  nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
 | 
					  NRF_GPIOTE->CONFIG[gpiote_channel] = 0;
 | 
				
			||||||
  if (workaroundActive) {
 | 
					  NRF_PPI->CH[ppi_channel].EEP = 0;
 | 
				
			||||||
    nrfx_gpiote_in_uninit(pin);
 | 
					  NRF_PPI->CH[ppi_channel].TEP = 0;
 | 
				
			||||||
    nrf_ppi_channel_disable(workaroundPpi);
 | 
					  NRF_PPI->CHENSET = ppi_channel;
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  spiBaseAddress->EVENTS_END = 0;
 | 
					  spiBaseAddress->EVENTS_END = 0;
 | 
				
			||||||
 | 
					  spim->INTENSET = (1 << 6);
 | 
				
			||||||
  // Enable IRQ
 | 
					  spim->INTENSET = (1 << 1);
 | 
				
			||||||
  spiBaseAddress->INTENSET = (1 << 6);
 | 
					  spim->INTENSET = (1 << 19);
 | 
				
			||||||
  spiBaseAddress->INTENSET = (1 << 1);
 | 
					 | 
				
			||||||
  spiBaseAddress->INTENSET = (1 << 19);
 | 
					 | 
				
			||||||
  workaroundActive = false;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SpiMaster::OnEndEvent() {
 | 
					void SpiMaster::OnEndEvent() {
 | 
				
			||||||
@ -149,11 +136,17 @@ void SpiMaster::OnEndEvent() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    spiBaseAddress->TASKS_START = 1;
 | 
					    spiBaseAddress->TASKS_START = 1;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
 | 
					    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
 | 
					    if (taskToNotify != nullptr) {
 | 
				
			||||||
 | 
					      vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    nrf_gpio_pin_set(this->pinCsn);
 | 
					    nrf_gpio_pin_set(this->pinCsn);
 | 
				
			||||||
    currentBufferAddr = 0;
 | 
					    currentBufferAddr = 0;
 | 
				
			||||||
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
					    BaseType_t xHigherPriorityTaskWoken2 = pdFALSE;
 | 
				
			||||||
    xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken);
 | 
					    xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2);
 | 
				
			||||||
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
					    portYIELD_FROM_ISR(xHigherPriorityTaskWoken | xHigherPriorityTaskWoken2);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -180,23 +173,21 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
 | 
				
			|||||||
  spiBaseAddress->EVENTS_END = 0;
 | 
					  spiBaseAddress->EVENTS_END = 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
 | 
					bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
 | 
				
			||||||
  if (data == nullptr)
 | 
					  if (data == nullptr)
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
 | 
					  auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
 | 
				
			||||||
  ASSERT(ok == true);
 | 
					  ASSERT(ok == true);
 | 
				
			||||||
 | 
					  taskToNotify = xTaskGetCurrentTaskHandle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->pinCsn = pinCsn;
 | 
					  this->pinCsn = pinCsn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (size == 1) {
 | 
					  if (size == 1) {
 | 
				
			||||||
    SetupWorkaroundForErratum58();
 | 
					    SetupWorkaroundForFtpan58(spiBaseAddress, 0, 0);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    DisableWorkaroundForErratum58();
 | 
					    DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (preTransactionHook != nullptr) {
 | 
					 | 
				
			||||||
    preTransactionHook();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  nrf_gpio_pin_clear(this->pinCsn);
 | 
					  nrf_gpio_pin_clear(this->pinCsn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  currentBufferAddr = (uint32_t) data;
 | 
					  currentBufferAddr = (uint32_t) data;
 | 
				
			||||||
@ -214,7 +205,7 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const st
 | 
				
			|||||||
    nrf_gpio_pin_set(this->pinCsn);
 | 
					    nrf_gpio_pin_set(this->pinCsn);
 | 
				
			||||||
    currentBufferAddr = 0;
 | 
					    currentBufferAddr = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    DisableWorkaroundForErratum58();
 | 
					    DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    xSemaphoreGive(mutex);
 | 
					    xSemaphoreGive(mutex);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -225,8 +216,10 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const st
 | 
				
			|||||||
bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
 | 
					bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
 | 
				
			||||||
  xSemaphoreTake(mutex, portMAX_DELAY);
 | 
					  xSemaphoreTake(mutex, portMAX_DELAY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  taskToNotify = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->pinCsn = pinCsn;
 | 
					  this->pinCsn = pinCsn;
 | 
				
			||||||
  DisableWorkaroundForErratum58();
 | 
					  DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 6);
 | 
					  spiBaseAddress->INTENCLR = (1 << 6);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 1);
 | 
					  spiBaseAddress->INTENCLR = (1 << 1);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 19);
 | 
					  spiBaseAddress->INTENCLR = (1 << 19);
 | 
				
			||||||
@ -272,8 +265,10 @@ void SpiMaster::Wakeup() {
 | 
				
			|||||||
bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) {
 | 
					bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) {
 | 
				
			||||||
  xSemaphoreTake(mutex, portMAX_DELAY);
 | 
					  xSemaphoreTake(mutex, portMAX_DELAY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  taskToNotify = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->pinCsn = pinCsn;
 | 
					  this->pinCsn = pinCsn;
 | 
				
			||||||
  DisableWorkaroundForErratum58();
 | 
					  DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 6);
 | 
					  spiBaseAddress->INTENCLR = (1 << 6);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 1);
 | 
					  spiBaseAddress->INTENCLR = (1 << 1);
 | 
				
			||||||
  spiBaseAddress->INTENCLR = (1 << 19);
 | 
					  spiBaseAddress->INTENCLR = (1 << 19);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,10 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
#include <cstddef>
 | 
					#include <cstddef>
 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
#include <functional>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <FreeRTOS.h>
 | 
					#include <FreeRTOS.h>
 | 
				
			||||||
#include <semphr.h>
 | 
					#include <semphr.h>
 | 
				
			||||||
#include <task.h>
 | 
					#include <task.h>
 | 
				
			||||||
#include "nrfx_gpiote.h"
 | 
					 | 
				
			||||||
#include "nrf_ppi.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pinetime {
 | 
					namespace Pinetime {
 | 
				
			||||||
  namespace Drivers {
 | 
					  namespace Drivers {
 | 
				
			||||||
@ -34,7 +31,7 @@ namespace Pinetime {
 | 
				
			|||||||
      SpiMaster& operator=(SpiMaster&&) = delete;
 | 
					      SpiMaster& operator=(SpiMaster&&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      bool Init();
 | 
					      bool Init();
 | 
				
			||||||
      bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
 | 
					      bool Write(uint8_t pinCsn, const uint8_t* data, size_t size);
 | 
				
			||||||
      bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
 | 
					      bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
 | 
					      bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
 | 
				
			||||||
@ -46,8 +43,8 @@ namespace Pinetime {
 | 
				
			|||||||
      void Wakeup();
 | 
					      void Wakeup();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
      void SetupWorkaroundForErratum58();
 | 
					      void SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
 | 
				
			||||||
      void DisableWorkaroundForErratum58();
 | 
					      void DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
 | 
				
			||||||
      void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
 | 
					      void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
 | 
				
			||||||
      void PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size);
 | 
					      void PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -59,9 +56,8 @@ namespace Pinetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      volatile uint32_t currentBufferAddr = 0;
 | 
					      volatile uint32_t currentBufferAddr = 0;
 | 
				
			||||||
      volatile size_t currentBufferSize = 0;
 | 
					      volatile size_t currentBufferSize = 0;
 | 
				
			||||||
 | 
					      volatile TaskHandle_t taskToNotify;
 | 
				
			||||||
      SemaphoreHandle_t mutex = nullptr;
 | 
					      SemaphoreHandle_t mutex = nullptr;
 | 
				
			||||||
      static constexpr nrf_ppi_channel_t workaroundPpi = NRF_PPI_CHANNEL0;
 | 
					 | 
				
			||||||
      bool workaroundActive = false;
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ void SpiNorFlash::Uninit() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void SpiNorFlash::Sleep() {
 | 
					void SpiNorFlash::Sleep() {
 | 
				
			||||||
  auto cmd = static_cast<uint8_t>(Commands::DeepPowerDown);
 | 
					  auto cmd = static_cast<uint8_t>(Commands::DeepPowerDown);
 | 
				
			||||||
  spi.Write(&cmd, sizeof(uint8_t), nullptr);
 | 
					  spi.Write(&cmd, sizeof(uint8_t));
 | 
				
			||||||
  NRF_LOG_INFO("[SpiNorFlash] Sleep")
 | 
					  NRF_LOG_INFO("[SpiNorFlash] Sleep")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
#include "drivers/St7789.h"
 | 
					#include "drivers/St7789.h"
 | 
				
			||||||
#include <hal/nrf_gpio.h>
 | 
					#include <hal/nrf_gpio.h>
 | 
				
			||||||
 | 
					#include <libraries/delay/nrf_delay.h>
 | 
				
			||||||
#include <nrfx_log.h>
 | 
					#include <nrfx_log.h>
 | 
				
			||||||
#include "drivers/Spi.h"
 | 
					#include "drivers/Spi.h"
 | 
				
			||||||
#include "task.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace Pinetime::Drivers;
 | 
					using namespace Pinetime::Drivers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,77 +29,37 @@ void St7789::Init() {
 | 
				
			|||||||
  DisplayOn();
 | 
					  DisplayOn();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void St7789::WriteCommand(uint8_t cmd) {
 | 
				
			||||||
 | 
					  nrf_gpio_pin_clear(pinDataCommand);
 | 
				
			||||||
 | 
					  WriteSpi(&cmd, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::WriteData(uint8_t data) {
 | 
					void St7789::WriteData(uint8_t data) {
 | 
				
			||||||
  WriteData(&data, 1);
 | 
					  nrf_gpio_pin_set(pinDataCommand);
 | 
				
			||||||
 | 
					  WriteSpi(&data, 1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::WriteData(const uint8_t* data, size_t size) {
 | 
					void St7789::WriteSpi(const uint8_t* data, size_t size) {
 | 
				
			||||||
  WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
 | 
					  spi.Write(data, size);
 | 
				
			||||||
    nrf_gpio_pin_set(pinDataCommand);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void St7789::WriteCommand(uint8_t data) {
 | 
					 | 
				
			||||||
  WriteCommand(&data, 1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void St7789::WriteCommand(const uint8_t* data, size_t size) {
 | 
					 | 
				
			||||||
  WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
 | 
					 | 
				
			||||||
    nrf_gpio_pin_clear(pinDataCommand);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
 | 
					 | 
				
			||||||
  spi.Write(data, size, preTransactionHook);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::SoftwareReset() {
 | 
					void St7789::SoftwareReset() {
 | 
				
			||||||
  EnsureSleepOutPostDelay();
 | 
					 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::SoftwareReset));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::SoftwareReset));
 | 
				
			||||||
  // If sleep in: must wait 120ms before sleep out can sent (see driver datasheet)
 | 
					  nrf_delay_ms(150);
 | 
				
			||||||
  // Unconditionally wait as software reset doesn't need to be performant
 | 
					 | 
				
			||||||
  sleepIn = true;
 | 
					 | 
				
			||||||
  lastSleepExit = xTaskGetTickCount();
 | 
					 | 
				
			||||||
  vTaskDelay(pdMS_TO_TICKS(125));
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::SleepOut() {
 | 
					void St7789::SleepOut() {
 | 
				
			||||||
  if (!sleepIn) {
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::SleepOut));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::SleepOut));
 | 
				
			||||||
  // Wait 5ms for clocks to stabilise
 | 
					 | 
				
			||||||
  // pdMS rounds down => 6 used here
 | 
					 | 
				
			||||||
  vTaskDelay(pdMS_TO_TICKS(6));
 | 
					 | 
				
			||||||
  // Cannot send sleep in or software reset for 120ms
 | 
					 | 
				
			||||||
  lastSleepExit = xTaskGetTickCount();
 | 
					 | 
				
			||||||
  sleepIn = false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void St7789::EnsureSleepOutPostDelay() {
 | 
					 | 
				
			||||||
  TickType_t delta = xTaskGetTickCount() - lastSleepExit;
 | 
					 | 
				
			||||||
  // Due to timer wraparound, there is a chance of delaying when not necessary
 | 
					 | 
				
			||||||
  // It is very low (pdMS_TO_TICKS(125)/2^32) and waiting an extra 125ms isn't too bad
 | 
					 | 
				
			||||||
  if (delta < pdMS_TO_TICKS(125)) {
 | 
					 | 
				
			||||||
    vTaskDelay(pdMS_TO_TICKS(125) - delta);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::SleepIn() {
 | 
					void St7789::SleepIn() {
 | 
				
			||||||
  if (sleepIn) {
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  EnsureSleepOutPostDelay();
 | 
					 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::SleepIn));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::SleepIn));
 | 
				
			||||||
  // Wait 5ms for clocks to stabilise
 | 
					 | 
				
			||||||
  // pdMS rounds down => 6 used here
 | 
					 | 
				
			||||||
  vTaskDelay(pdMS_TO_TICKS(6));
 | 
					 | 
				
			||||||
  sleepIn = true;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::ColMod() {
 | 
					void St7789::ColMod() {
 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::ColMod));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::ColMod));
 | 
				
			||||||
  WriteData(0x55);
 | 
					  WriteData(0x55);
 | 
				
			||||||
 | 
					  nrf_delay_ms(10);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::MemoryDataAccessControl() {
 | 
					void St7789::MemoryDataAccessControl() {
 | 
				
			||||||
@ -136,10 +96,12 @@ void St7789::RowAddressSet() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void St7789::DisplayInversionOn() {
 | 
					void St7789::DisplayInversionOn() {
 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
 | 
				
			||||||
 | 
					  nrf_delay_ms(10);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::NormalModeOn() {
 | 
					void St7789::NormalModeOn() {
 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
 | 
				
			||||||
 | 
					  nrf_delay_ms(10);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::DisplayOn() {
 | 
					void St7789::DisplayOn() {
 | 
				
			||||||
@ -158,11 +120,12 @@ void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
 | 
				
			|||||||
  WriteData(y0 & 0xff);
 | 
					  WriteData(y0 & 0xff);
 | 
				
			||||||
  WriteData(y1 >> 8);
 | 
					  WriteData(y1 >> 8);
 | 
				
			||||||
  WriteData(y1 & 0xff);
 | 
					  WriteData(y1 & 0xff);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  WriteToRam();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::WriteToRam(const uint8_t* data, size_t size) {
 | 
					void St7789::WriteToRam() {
 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::WriteToRam));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::WriteToRam));
 | 
				
			||||||
  WriteData(data, size);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::SetVdv() {
 | 
					void St7789::SetVdv() {
 | 
				
			||||||
@ -174,6 +137,17 @@ void St7789::SetVdv() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void St7789::DisplayOff() {
 | 
					void St7789::DisplayOff() {
 | 
				
			||||||
  WriteCommand(static_cast<uint8_t>(Commands::DisplayOff));
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::DisplayOff));
 | 
				
			||||||
 | 
					  nrf_delay_ms(500);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void St7789::VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
 | 
				
			||||||
 | 
					  WriteCommand(static_cast<uint8_t>(Commands::VerticalScrollDefinition));
 | 
				
			||||||
 | 
					  WriteData(topFixedLines >> 8u);
 | 
				
			||||||
 | 
					  WriteData(topFixedLines & 0x00ffu);
 | 
				
			||||||
 | 
					  WriteData(scrollLines >> 8u);
 | 
				
			||||||
 | 
					  WriteData(scrollLines & 0x00ffu);
 | 
				
			||||||
 | 
					  WriteData(bottomFixedLines >> 8u);
 | 
				
			||||||
 | 
					  WriteData(bottomFixedLines & 0x00ffu);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::VerticalScrollStartAddress(uint16_t line) {
 | 
					void St7789::VerticalScrollStartAddress(uint16_t line) {
 | 
				
			||||||
@ -186,20 +160,27 @@ void St7789::VerticalScrollStartAddress(uint16_t line) {
 | 
				
			|||||||
void St7789::Uninit() {
 | 
					void St7789::Uninit() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void St7789::DrawPixel(uint16_t x, uint16_t y, uint32_t color) {
 | 
				
			||||||
 | 
					  if (x >= Width || y >= Height) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SetAddrWindow(x, y, x + 1, y + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  nrf_gpio_pin_set(pinDataCommand);
 | 
				
			||||||
 | 
					  WriteSpi(reinterpret_cast<const uint8_t*>(&color), 2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) {
 | 
					void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) {
 | 
				
			||||||
  SetAddrWindow(x, y, x + width - 1, y + height - 1);
 | 
					  SetAddrWindow(x, y, x + width - 1, y + height - 1);
 | 
				
			||||||
  WriteToRam(data, size);
 | 
					  nrf_gpio_pin_set(pinDataCommand);
 | 
				
			||||||
 | 
					  WriteSpi(data, size);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::HardwareReset() {
 | 
					void St7789::HardwareReset() {
 | 
				
			||||||
  nrf_gpio_pin_clear(pinReset);
 | 
					  nrf_gpio_pin_clear(pinReset);
 | 
				
			||||||
  vTaskDelay(pdMS_TO_TICKS(1));
 | 
					  nrf_delay_ms(10);
 | 
				
			||||||
  nrf_gpio_pin_set(pinReset);
 | 
					  nrf_gpio_pin_set(pinReset);
 | 
				
			||||||
  // If hardware reset started while sleep out, reset time may be up to 120ms
 | 
					 | 
				
			||||||
  // Unconditionally wait as hardware reset doesn't need to be performant
 | 
					 | 
				
			||||||
  sleepIn = true;
 | 
					 | 
				
			||||||
  lastSleepExit = xTaskGetTickCount();
 | 
					 | 
				
			||||||
  vTaskDelay(pdMS_TO_TICKS(125));
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void St7789::Sleep() {
 | 
					void St7789::Sleep() {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,6 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
#include <cstddef>
 | 
					#include <cstddef>
 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
#include <functional>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <FreeRTOS.h>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pinetime {
 | 
					namespace Pinetime {
 | 
				
			||||||
  namespace Drivers {
 | 
					  namespace Drivers {
 | 
				
			||||||
@ -19,7 +16,9 @@ namespace Pinetime {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      void Init();
 | 
					      void Init();
 | 
				
			||||||
      void Uninit();
 | 
					      void Uninit();
 | 
				
			||||||
 | 
					      void DrawPixel(uint16_t x, uint16_t y, uint32_t color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      void VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
 | 
				
			||||||
      void VerticalScrollStartAddress(uint16_t line);
 | 
					      void VerticalScrollStartAddress(uint16_t line);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
 | 
					      void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
 | 
				
			||||||
@ -32,27 +31,23 @@ namespace Pinetime {
 | 
				
			|||||||
      uint8_t pinDataCommand;
 | 
					      uint8_t pinDataCommand;
 | 
				
			||||||
      uint8_t pinReset;
 | 
					      uint8_t pinReset;
 | 
				
			||||||
      uint8_t verticalScrollingStartAddress = 0;
 | 
					      uint8_t verticalScrollingStartAddress = 0;
 | 
				
			||||||
      bool sleepIn;
 | 
					 | 
				
			||||||
      TickType_t lastSleepExit;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      void HardwareReset();
 | 
					      void HardwareReset();
 | 
				
			||||||
      void SoftwareReset();
 | 
					      void SoftwareReset();
 | 
				
			||||||
      void SleepOut();
 | 
					      void SleepOut();
 | 
				
			||||||
      void EnsureSleepOutPostDelay();
 | 
					 | 
				
			||||||
      void SleepIn();
 | 
					      void SleepIn();
 | 
				
			||||||
      void ColMod();
 | 
					      void ColMod();
 | 
				
			||||||
      void MemoryDataAccessControl();
 | 
					      void MemoryDataAccessControl();
 | 
				
			||||||
      void DisplayInversionOn();
 | 
					      void DisplayInversionOn();
 | 
				
			||||||
      void NormalModeOn();
 | 
					      void NormalModeOn();
 | 
				
			||||||
      void WriteToRam(const uint8_t* data, size_t size);
 | 
					      void WriteToRam();
 | 
				
			||||||
      void DisplayOn();
 | 
					      void DisplayOn();
 | 
				
			||||||
      void DisplayOff();
 | 
					      void DisplayOff();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
 | 
					      void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
 | 
				
			||||||
      void SetVdv();
 | 
					      void SetVdv();
 | 
				
			||||||
      void WriteCommand(uint8_t cmd);
 | 
					      void WriteCommand(uint8_t cmd);
 | 
				
			||||||
      void WriteCommand(const uint8_t* data, size_t size);
 | 
					      void WriteSpi(const uint8_t* data, size_t size);
 | 
				
			||||||
      void WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      enum class Commands : uint8_t {
 | 
					      enum class Commands : uint8_t {
 | 
				
			||||||
        SoftwareReset = 0x01,
 | 
					        SoftwareReset = 0x01,
 | 
				
			||||||
@ -72,7 +67,6 @@ namespace Pinetime {
 | 
				
			|||||||
        VdvSet = 0xc4,
 | 
					        VdvSet = 0xc4,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      void WriteData(uint8_t data);
 | 
					      void WriteData(uint8_t data);
 | 
				
			||||||
      void WriteData(const uint8_t* data, size_t size);
 | 
					 | 
				
			||||||
      void ColumnAddressSet();
 | 
					      void ColumnAddressSet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      static constexpr uint16_t Width = 240;
 | 
					      static constexpr uint16_t Width = 240;
 | 
				
			||||||
 | 
				
			|||||||
@ -103,7 +103,10 @@ void HeartRateTask::Work() {
 | 
				
			|||||||
void HeartRateTask::PushMessage(HeartRateTask::Messages msg) {
 | 
					void HeartRateTask::PushMessage(HeartRateTask::Messages msg) {
 | 
				
			||||||
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
					  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
  xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken);
 | 
					  xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken);
 | 
				
			||||||
  portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
					  if (xHigherPriorityTaskWoken) {
 | 
				
			||||||
 | 
					    /* Actual macro used here is port specific. */
 | 
				
			||||||
 | 
					    // TODO : should I do something here?
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HeartRateTask::StartMeasurement() {
 | 
					void HeartRateTask::StartMeasurement() {
 | 
				
			||||||
 | 
				
			|||||||
@ -729,9 +729,7 @@ typedef void* lv_obj_user_data_t;
 | 
				
			|||||||
#define LV_USE_TABLE    1
 | 
					#define LV_USE_TABLE    1
 | 
				
			||||||
#if LV_USE_TABLE
 | 
					#if LV_USE_TABLE
 | 
				
			||||||
#define LV_TABLE_COL_MAX    12
 | 
					#define LV_TABLE_COL_MAX    12
 | 
				
			||||||
#define LV_TABLE_CELL_STYLE_CNT 6
 | 
					#define LV_TABLE_CELL_STYLE_CNT 5
 | 
				
			||||||
#define LV_TABLE_PART_CELL5 5
 | 
					 | 
				
			||||||
#define LV_TABLE_PART_CELL6 6
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +0,0 @@
 | 
				
			|||||||
Subproject commit 4b08734eb8856154d7226bfdced91a571fb6a64b
 | 
					 | 
				
			||||||
@ -10,6 +10,7 @@
 | 
				
			|||||||
#include <libraries/gpiote/app_gpiote.h>
 | 
					#include <libraries/gpiote/app_gpiote.h>
 | 
				
			||||||
#include <hal/nrf_wdt.h>
 | 
					#include <hal/nrf_wdt.h>
 | 
				
			||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					#include <components/gfx/Gfx.h>
 | 
				
			||||||
#include <drivers/St7789.h>
 | 
					#include <drivers/St7789.h>
 | 
				
			||||||
#include <components/brightness/BrightnessController.h>
 | 
					#include <components/brightness/BrightnessController.h>
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
@ -47,6 +48,7 @@ Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi};
 | 
				
			|||||||
Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn};
 | 
					Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn};
 | 
				
			||||||
Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset};
 | 
					Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pinetime::Components::Gfx gfx {lcd};
 | 
				
			||||||
Pinetime::Controllers::BrightnessController brightnessController;
 | 
					Pinetime::Controllers::BrightnessController brightnessController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DisplayProgressBar(uint8_t percent, uint16_t color);
 | 
					void DisplayProgressBar(uint8_t percent, uint16_t color);
 | 
				
			||||||
@ -90,6 +92,7 @@ void Process(void* /*instance*/) {
 | 
				
			|||||||
  spiNorFlash.Wakeup();
 | 
					  spiNorFlash.Wakeup();
 | 
				
			||||||
  brightnessController.Init();
 | 
					  brightnessController.Init();
 | 
				
			||||||
  lcd.Init();
 | 
					  lcd.Init();
 | 
				
			||||||
 | 
					  gfx.Init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  NRF_LOG_INFO("Display logo")
 | 
					  NRF_LOG_INFO("Display logo")
 | 
				
			||||||
  DisplayLogo();
 | 
					  DisplayLogo();
 | 
				
			||||||
@ -121,6 +124,7 @@ void DisplayLogo() {
 | 
				
			|||||||
  Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb));
 | 
					  Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb));
 | 
				
			||||||
  for (int i = 0; i < displayWidth; i++) {
 | 
					  for (int i = 0; i < displayWidth; i++) {
 | 
				
			||||||
    rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
 | 
					    rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
 | 
				
			||||||
 | 
					    ulTaskNotifyTake(pdTRUE, 500);
 | 
				
			||||||
    lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
 | 
					    lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -129,6 +133,7 @@ void DisplayProgressBar(uint8_t percent, uint16_t color) {
 | 
				
			|||||||
  static constexpr uint8_t barHeight = 20;
 | 
					  static constexpr uint8_t barHeight = 20;
 | 
				
			||||||
  std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
 | 
					  std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
 | 
				
			||||||
  for (int i = 0; i < barHeight; i++) {
 | 
					  for (int i = 0; i < barHeight; i++) {
 | 
				
			||||||
 | 
					    ulTaskNotifyTake(pdTRUE, 500);
 | 
				
			||||||
    uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
 | 
					    uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
 | 
				
			||||||
    lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
 | 
					    lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -101,13 +101,6 @@ def main():
 | 
				
			|||||||
    img = Image.open(img_path)
 | 
					    img = Image.open(img_path)
 | 
				
			||||||
    img_height = img.height
 | 
					    img_height = img.height
 | 
				
			||||||
    img_width = img.width
 | 
					    img_width = img.width
 | 
				
			||||||
    if args.color_format == "CF_TRUE_COLOR_ALPHA" and img.mode != "RGBA":
 | 
					 | 
				
			||||||
        # support pictures stored in other formats like with a color palette 'P'
 | 
					 | 
				
			||||||
        # see: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes
 | 
					 | 
				
			||||||
        img = img.convert(mode="RGBA")
 | 
					 | 
				
			||||||
    elif args.color_format == "CF_INDEXED_1_BIT" and img.mode != "L":
 | 
					 | 
				
			||||||
        # for CF_INDEXED_1_BIT we need just a grayscale value per pixel
 | 
					 | 
				
			||||||
        img = img.convert(mode="L")
 | 
					 | 
				
			||||||
    if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
 | 
					    if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
 | 
				
			||||||
        buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
 | 
					        buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
 | 
				
			||||||
        for y in range(img_height):
 | 
					        for y in range(img_height):
 | 
				
			||||||
@ -147,7 +140,7 @@ def main():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        for y in range(img_height):
 | 
					        for y in range(img_height):
 | 
				
			||||||
            for x in range(img_width):
 | 
					            for x in range(img_width):
 | 
				
			||||||
                c = img.getpixel((x,y))
 | 
					                c, a = img.getpixel((x,y))
 | 
				
			||||||
                p = w * y + (x >> 3) + 8  # +8 for the palette
 | 
					                p = w * y + (x >> 3) + 8  # +8 for the palette
 | 
				
			||||||
                buf[p] |= (c & 0x1) << (7 - (x & 0x7))
 | 
					                buf[p] |= (c & 0x1) << (7 - (x & 0x7))
 | 
				
			||||||
        # write palette information, for indexed-1-bit we need palette with two values
 | 
					        # write palette information, for indexed-1-bit we need palette with two values
 | 
				
			||||||
 | 
				
			|||||||
@ -342,12 +342,8 @@ void SystemTask::Work() {
 | 
				
			|||||||
          if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
 | 
					          if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
 | 
				
			||||||
              settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
 | 
					              settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
 | 
				
			||||||
              alarmController.State() != AlarmController::AlarmState::Alerting) {
 | 
					              alarmController.State() != AlarmController::AlarmState::Alerting) {
 | 
				
			||||||
            // if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
 | 
					 | 
				
			||||||
            // request running first and repush the chime message
 | 
					 | 
				
			||||||
            if (state == SystemTaskState::Sleeping) {
 | 
					            if (state == SystemTaskState::Sleeping) {
 | 
				
			||||||
              GoToRunning();
 | 
					              GoToRunning();
 | 
				
			||||||
              PushMessage(msg);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
 | 
					              displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -357,12 +353,8 @@ void SystemTask::Work() {
 | 
				
			|||||||
          if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
 | 
					          if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
 | 
				
			||||||
              settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
 | 
					              settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
 | 
				
			||||||
              alarmController.State() != AlarmController::AlarmState::Alerting) {
 | 
					              alarmController.State() != AlarmController::AlarmState::Alerting) {
 | 
				
			||||||
            // if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
 | 
					 | 
				
			||||||
            // request running first and repush the chime message
 | 
					 | 
				
			||||||
            if (state == SystemTaskState::Sleeping) {
 | 
					            if (state == SystemTaskState::Sleeping) {
 | 
				
			||||||
              GoToRunning();
 | 
					              GoToRunning();
 | 
				
			||||||
              PushMessage(msg);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
 | 
					              displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -426,8 +418,7 @@ void SystemTask::UpdateMotion() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
 | 
					  if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
 | 
				
			||||||
                                              settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
 | 
					                                              settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake))) {
 | 
				
			||||||
                                              motionController.GetService()->IsMotionNotificationSubscribed())) {
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -512,7 +503,10 @@ void SystemTask::PushMessage(System::Messages msg) {
 | 
				
			|||||||
  if (in_isr()) {
 | 
					  if (in_isr()) {
 | 
				
			||||||
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
					    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 | 
				
			||||||
    xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
 | 
					    xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
 | 
				
			||||||
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
					    if (xHigherPriorityTaskWoken == pdTRUE) {
 | 
				
			||||||
 | 
					      /* Actual macro used here is port specific. */
 | 
				
			||||||
 | 
					      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY);
 | 
					    xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user