diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30f..00000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.gitattributes b/.gitattributes index 2257cac4..d625a244 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,9 @@ # Set the default behavior, in case people don't have core.autocrlf set. -* text=auto +# Prevent build errors on non lf systems (like Windows), we need files with lf as newlines. +* text=auto eol=lf # Explicitly declare text files you want to always be normalized and converted -# to native line endings on checkout. +# to lf line endings on checkout. *.c text *.cpp text *.h text @@ -13,3 +14,6 @@ *.bin binary *.jpg binary *.jpeg binary + +# Declare (Unix-style) files that will always have LF line endings on checkout. +*.sh text eol=lf diff --git a/.gitconfig b/.gitconfig deleted file mode 100644 index b2274475..00000000 --- a/.gitconfig +++ /dev/null @@ -1,5 +0,0 @@ -[core] - whitespace = blank-at-eol,blank-at-eof,space-before-tab - autocrlf = input -[apply] - whitespace = fix diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 71c05fa2..ceb37d5b 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -29,7 +29,7 @@ jobs: run: tests/test-format.sh - name: Upload patches - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: Patches diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2eb8a959..00ea9382 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,6 +22,8 @@ jobs: text_size: ${{ steps.output-sizes.outputs.text_size }} data_size: ${{ steps.output-sizes.outputs.data_size }} bss_size: ${{ steps.output-sizes.outputs.bss_size }} + firmware_artifact: ${{ steps.upload-firmware.outputs.artifact-id }} + resources_artifact: ${{ steps.upload-resources.outputs.artifact-id }} env: # InfiniTime sources are downloaded to the current directory. # Override SOURCES_DIR in build.sh @@ -37,34 +39,44 @@ jobs: apt-get -y install --no-install-recommends python3-pil - name: Build shell: bash - run: /opt/build.sh all + run: | + git config --global --add safe.directory /__w/InfiniTime/InfiniTime + /opt/build.sh all - name: Output build size id: output-sizes + shell: bash run: | . /opt/build.sh .github/workflows/getSize.sh "$BUILD_DIR"/src/pinetime-app-*.out >> $GITHUB_OUTPUT # Unzip the package because Upload Artifact will zip up the files - name: Unzip DFU package run: unzip ./build/output/pinetime-mcuboot-app-dfu-*.zip -d ./build/output/pinetime-mcuboot-app-dfu + - name: Set ref_name, but replace slashes with dashes. + shell: bash + env: + ref_name: ${{ github.head_ref || github.ref_name }} + run: echo "REF_NAME=${ref_name//\//-}" >> $GITHUB_ENV - name: Upload DFU artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: InfiniTime DFU ${{ github.head_ref }} + name: InfiniTime DFU ${{ env.REF_NAME }} path: ./build/output/pinetime-mcuboot-app-dfu/* - name: Upload MCUBoot image artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: InfiniTime MCUBoot image ${{ github.head_ref }} + name: InfiniTime MCUBoot image ${{ env.REF_NAME }} path: ./build/output/pinetime-mcuboot-app-image-*.bin - name: Upload standalone ELF artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 + id: upload-firmware with: - name: InfiniTime image ${{ github.head_ref }} + name: InfiniTime image ${{ env.REF_NAME }} path: ./build/output/src/pinetime-app-*.out - name: Upload resources artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 + id: upload-resources with: - name: InfiniTime resources ${{ github.head_ref }} + name: InfiniTime resources ${{ env.REF_NAME }} path: ./build/output/infinitime-resources-*.zip build-simulator: @@ -103,9 +115,9 @@ jobs: cmake --build build_lv_sim - name: Upload simulator executable - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: infinisim-${{ github.head_ref }} + name: infinisim-${{ env.REF_NAME }} path: build_lv_sim/infinisim get-base-ref-size: @@ -150,6 +162,7 @@ jobs: - name: Output build size id: output-sizes + shell: bash run: | . /opt/build.sh .github/workflows/getSize.sh "$BUILD_DIR"/src/pinetime-app-*.out >> $GITHUB_OUTPUT @@ -200,10 +213,12 @@ jobs: | text | ${{ needs.build-firmware.outputs.text_size }}B | ${{ steps.output-sizes-diff.outputs.text_diff }}B | | data | ${{ needs.build-firmware.outputs.data_size }}B | ${{ steps.output-sizes-diff.outputs.data_diff }}B | | bss | ${{ needs.build-firmware.outputs.bss_size }}B | ${{ steps.output-sizes-diff.outputs.bss_diff }}B | + + [Run in InfiniEmu](https://infiniemu.pipe01.net/?firmware=artifact://${{ github.repository }}/${{ needs.build-firmware.outputs.firmware_artifact }}&resources=artifact://${{ github.repository }}/${{ needs.build-firmware.outputs.resources_artifact }}) EOF - name: Upload comment - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: comment path: comment diff --git a/.gitignore b/.gitignore index 81e49ae0..d6f917cf 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,8 @@ src/arm-none-eabi # clangd .cache/ + +# npm files +node_modules +package.json +package-lock.json diff --git a/.gitmodules b/.gitmodules index 7a4e307b..a6b4d5fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "src/libs/littlefs"] path = src/libs/littlefs url = https://github.com/littlefs-project/littlefs.git -[submodule "src/libs/QCBOR"] - path = src/libs/QCBOR - url = https://github.com/laurencelundblade/QCBOR.git [submodule "src/libs/arduinoFFT"] path = src/libs/arduinoFFT url = https://github.com/kosme/arduinoFFT.git diff --git a/.vscode/settings.json b/.vscode/settings.json index ad8decfe..a7b04eea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -66,8 +66,5 @@ "streambuf": "cpp", "cinttypes": "cpp", "typeinfo": "cpp" - }, - "cSpell.words": [ - "Pinetime" - ] + } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 84e3914e..3250982d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release") -project(pinetime VERSION 1.15.1 LANGUAGES C CXX ASM) +project(pinetime VERSION 1.15.0 LANGUAGES C CXX ASM) set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 20) diff --git a/README.md b/README.md index 765d5d4b..68d8779d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,24 @@ -# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) +
-![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo") +![Header Image](doc/logo/watchface_collage.png) -Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++. +
+ +[![GitHub tag](https://img.shields.io/github/tag/InfiniTimeOrg/InfiniTime?include_prereleases=&sort=semver&color=blue)](https://github.com/InfiniTimeOrg/InfiniTime/releases) +[![GitHub License](https://img.shields.io/github/license/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniLink/blob/main/LICENSE) +[![Issues - InfiniTime](https://img.shields.io/github/issues/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/issues) +[![Pull Requests - InfiniTime](https://img.shields.io/github/issues-pr/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/pulls) +[![Downloads - InfiniTime](https://img.shields.io/github/downloads/InfiniTimeOrg/InfiniTime/total)](https://github.com/InfiniTimeOrg/InfiniTime) +[![Stars - InfiniTime](https://img.shields.io/github/stars/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/stargazers) +[![Forks - InfiniTime](https://img.shields.io/github/forks/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/network/members) + +# InfiniTime + +*Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.* + +
+ +
## New to InfiniTime? @@ -17,13 +33,18 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic ### Companion apps - [Gadgetbridge](https://gadgetbridge.org/) (Android) -- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS) +- [Amazfish](https://github.com/piggz/harbour-amazfish/) ([SailfishOS](https://sailfishos-chum.github.io/apps/harbour-amazfish/), [Ubuntu Touch](https://open-store.io/app/uk.co.piggz.amazfish), [Flatpak](https://flathub.org/apps/uk.co.piggz.amazfish)) - [Siglo](https://github.com/alexr4535/siglo) (Linux) - [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) - [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux) - [WatchMate](https://github.com/azymohliad/watchmate) (Linux) +- [InfiniTimeExplorer](https://infinitimeexplorer.netlify.app) (Web) -***Note**: We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).* +
+ +> *InfiniTimeExplorer is only compatible with web browsers that support Web BLE. Current fully supported browsers include Chrome and Microsoft Edge.* +> +> *We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).* ## Development diff --git a/default.nix b/default.nix deleted file mode 100644 index f620865d..00000000 --- a/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -(import - ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } - ) - { src = ./.; } -).defaultNix diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md index 69e4bfd8..5d3af46e 100644 --- a/doc/buildAndProgram.md +++ b/doc/buildAndProgram.md @@ -5,7 +5,7 @@ To build this project, you'll need: - A cross-compiler : [ARM-GCC (10.3-2021.10)](https://developer.arm.com/downloads/-/gnu-rm) -- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip) +- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip) - The Python 3 modules `cbor`, `intelhex`, `click` and `cryptography` modules for the `mcuboot` tool (see [requirements.txt](../tools/mcuboot/requirements.txt)) - To keep the system clean, you can install python modules into a python virtual environment (`venv`) ```sh @@ -66,7 +66,7 @@ DFU files are the files you'll need to install your build of InfiniTime using OT #### CMake command ``` -cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... +cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -S .. ``` ### Build the project diff --git a/doc/code/Apps.md b/doc/code/Apps.md index 44c4ed65..5c8a4acb 100644 --- a/doc/code/Apps.md +++ b/doc/code/Apps.md @@ -124,7 +124,7 @@ MyApp.h: ```cpp #pragma once -#include "displayapp/Apps.h" +#include "displayapp/apps/Apps.h" #include "displayapp/screens/Screen.h" #include "displayapp/Controllers.h" #include "Symbols.h" diff --git a/doc/logo/watchface_collage.png b/doc/logo/watchface_collage.png new file mode 100644 index 00000000..caeb7456 Binary files /dev/null and b/doc/logo/watchface_collage.png differ diff --git a/docker/Dockerfile b/docker/Dockerfile index bb5d5f65..bd940900 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -33,7 +33,7 @@ RUN apt-get update -qq \ libpixman-1-dev \ libcairo2-dev \ libpango-1.0-0 \ - ibpango1.0-dev \ + libpango1.0-dev \ libpangocairo-1.0-0 \ && rm -rf /var/cache/apt/* /var/lib/apt/lists/*; @@ -64,6 +64,9 @@ RUN bash -c "source /opt/build.sh; GetMcuBoot;" # Add the infinitime user for connecting devcontainer RUN adduser infinitime - + +# Configure Git to accept the /sources directory as safe +RUN git config --global --add safe.directory /sources + ENV SOURCES_DIR /sources CMD ["/opt/build.sh"] diff --git a/docker/build.sh b/docker/build.sh index 3a467b9f..b7637f49 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -18,6 +18,10 @@ export npm_config_cache="${NPM_DIR}" export BUILD_TYPE=${BUILD_TYPE:=Release} export GCC_ARM_VER=${GCC_ARM_VER:="10.3-2021.10"} export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"} +# convert to lower case and remove _ and . character +# the download URL uses the SLUG, but the extracted folder is named like the original value +NRF_SDK_VER_SLUG=${NRF_SDK_VER,,} +export NRF_SDK_VER_SLUG=${NRF_SDK_VER_SLUG//[_.]/} MACHINE="$(uname -m)" [ "$MACHINE" = "arm64" ] && MACHINE="aarch64" @@ -47,17 +51,29 @@ main() { GetGcc() { wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/$GCC_ARM_VER/$GCC_ARM_PATH-$MACHINE-linux.tar.bz2 -O - | tar -xj -C $TOOLS_DIR/ + if [ ! -d "$TOOLS_DIR/$GCC_ARM_PATH" ]; then + echo "missing GCC path: $TOOLS_DIR/$GCC_ARM_PATH" + return 1 + fi } GetMcuBoot() { git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot" pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt" + if [ ! -d "$TOOLS_DIR/mcuboot" ]; then + echo "missing mcuboot path: $TOOLS_DIR/mcuboot" + return 1 + fi } GetNrfSdk() { - wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER + wget -q "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/$NRF_SDK_VER_SLUG.zip" -O /tmp/$NRF_SDK_VER unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/" rm /tmp/$NRF_SDK_VER + if [ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]; then + echo "missing NRF_SDK path: $TOOLS_DIR/$NRF_SDK_VER" + return 1 + fi } CmakeGenerate() { diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 742baac7..00000000 --- a/flake.lock +++ /dev/null @@ -1,42 +0,0 @@ -{ - "nodes": { - "flake-compat": { - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1725634671, - "narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-compat": "flake-compat", - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index f9dd94c0..00000000 --- a/flake.nix +++ /dev/null @@ -1,114 +0,0 @@ -{ - description = "A very basic flake"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"; - }; - - outputs = { self, ... }@inputs: - let - forAllSystems = function: - inputs.nixpkgs.lib.genAttrs [ - "x86_64-linux" - "aarch64-linux" - ] - (system: function (import inputs.nixpkgs { - inherit system; - config.allowUnfree = true; - })); - in - { - packages = forAllSystems (pkgs: - let - infinitime-nrf5-sdk = pkgs.nrf5-sdk.overrideAttrs (old: { - version = "15.3.0"; - src = pkgs.fetchzip { - url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip"; - sha256 = "sha256-pfmhbpgVv5x2ju489XcivguwpnofHbgVA7bFUJRTj08="; - }; - }); - in - with pkgs; { - default = stdenv.mkDerivation rec { - name = "infinitime"; - - src = fetchFromGitea { - domain = "git.techwork.zone"; - owner = "scott"; - repo = "InfiniTime"; - rev = "1.15.1"; - hash = "sha256-9cy7qYRZrg7qZZwQzYeMIZivGLZ5uGUoTyPgHEvdnZ8="; - fetchSubmodules = true; - }; - - nativeBuildInputs = [ - cmake - nodePackages.lv_font_conv - python3 - python3.pkgs.cbor - python3.pkgs.click - python3.pkgs.cryptography - python3.pkgs.intelhex - python3.pkgs.pillow - adafruit-nrfutil - patch - git - ]; - - postPatch = '' - # /usr/bin/env is not available in the build sandbox - substituteInPlace src/displayapp/fonts/generate.py --replace "'/usr/bin/env', 'patch'" "'patch'" - substituteInPlace tools/mcuboot/imgtool.py --replace "/usr/bin/env python3" "${python3}/bin/python3" - ''; - - cmakeFlags = [ - ''-DARM_NONE_EABI_TOOLCHAIN_PATH=${gcc-arm-embedded-10}'' - ''-DNRF5_SDK_PATH=${infinitime-nrf5-sdk}/share/nRF5_SDK'' - ''-DBUILD_DFU=1'' - ''-DBUILD_RESOURCES=1'' - ''-DCMAKE_SOURCE_DIR=${src}'' - ]; - - installPhase = '' - SOURCES_DIR=${src} BUILD_DIR=. OUTPUT_DIR=$out ./post_build.sh - ''; - }; - }); - - devShells = forAllSystems (pkgs: - let - infinitime-nrf5-sdk = pkgs.nrf5-sdk.overrideAttrs (old: { - version = "15.3.0"; - src = pkgs.fetchzip { - url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip"; - sha256 = "sha256-pfmhbpgVv5x2ju489XcivguwpnofHbgVA7bFUJRTj08="; - }; - }); - in - { - default = - pkgs.buildFHSUserEnv { - name = "infinitime-devenv"; - # build tools - targetPkgs = pkgs: (with pkgs; [ - cmake - nodePackages.lv_font_conv - python3 - python3.pkgs.cbor - python3.pkgs.click - python3.pkgs.cryptography - python3.pkgs.intelhex - adafruit-nrfutil - - (pkgs.writeShellScriptBin "cmake_infinitime" '' - cmake -DARM_NONE_EABI_TOOLCHAIN_PATH="${pkgs.gcc-arm-embedded-10}" \ - -DNRF5_SDK_PATH="${infinitime-nrf5-sdk}/share/nRF5_SDK" \ - "$@" - '') - ]); - }; - }); - }; -} - diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 942ce016..00000000 --- a/shell.nix +++ /dev/null @@ -1,10 +0,0 @@ -(import - ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } - ) - { src = ./.; } -).shellNix diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1d4facf1..78ff2d6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -380,6 +380,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Metronome.cpp displayapp/screens/Motion.cpp displayapp/screens/Weather.cpp + displayapp/screens/Calculator.cpp displayapp/screens/FirmwareValidation.cpp displayapp/screens/ApplicationList.cpp displayapp/screens/Notifications.cpp @@ -411,7 +412,6 @@ list(APPEND SOURCE_FILES displayapp/screens/settings/SettingWeatherFormat.cpp displayapp/screens/settings/SettingWakeUp.cpp displayapp/screens/settings/SettingDisplay.cpp - displayapp/screens/settings/SettingHeartRate.cpp displayapp/screens/settings/SettingSteps.cpp displayapp/screens/settings/SettingSetDateTime.cpp displayapp/screens/settings/SettingSetDate.cpp @@ -419,6 +419,7 @@ list(APPEND SOURCE_FILES displayapp/screens/settings/SettingChimes.cpp displayapp/screens/settings/SettingShakeThreshold.cpp displayapp/screens/settings/SettingBluetooth.cpp + displayapp/screens/settings/SettingOTA.cpp ## Watch faces displayapp/screens/WatchFaceAnalog.cpp @@ -427,6 +428,7 @@ list(APPEND SOURCE_FILES displayapp/screens/WatchFaceTerminal.cpp displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFaceCasioStyleG7710.cpp + displayapp/screens/WatchFacePrideFlag.cpp ## @@ -479,6 +481,7 @@ list(APPEND SOURCE_FILES systemtask/SystemTask.cpp systemtask/SystemMonitor.cpp + systemtask/WakeLock.cpp drivers/TwiMaster.cpp heartratetask/HeartRateTask.cpp @@ -543,6 +546,7 @@ list(APPEND RECOVERY_SOURCE_FILES systemtask/SystemTask.cpp systemtask/SystemMonitor.cpp + systemtask/WakeLock.cpp drivers/TwiMaster.cpp components/rle/RleDecoder.cpp components/heartrate/HeartRateController.cpp @@ -661,6 +665,7 @@ set(INCLUDE_FILES displayapp/InfiniTimeTheme.h systemtask/SystemTask.h systemtask/SystemMonitor.h + systemtask/WakeLock.h displayapp/screens/Symbols.h drivers/TwiMaster.h heartratetask/HeartRateTask.h @@ -786,6 +791,10 @@ add_definitions(-DFREERTOS) add_definitions(-D__STACK_SIZE=1024) add_definitions(-D__HEAP_SIZE=0) add_definitions(-DMYNEWT_VAL_BLE_LL_RFMGMT_ENABLE_TIME=1500) +add_definitions(-DLFS_CONFIG=libs/lfs_config.h) + +# _sbrk is purposefully not implemented so that builds fail when it is used +add_link_options(-Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=calloc -Wl,-wrap=realloc -Wl,-wrap=_malloc_r -Wl,-wrap=_sbrk) # Note: Only use this for debugging # Derive the low frequency clock from the main clock (SYNT) diff --git a/src/FreeRTOS/heap_4_infinitime.c b/src/FreeRTOS/heap_4_infinitime.c index 14708471..bf649254 100644 --- a/src/FreeRTOS/heap_4_infinitime.c +++ b/src/FreeRTOS/heap_4_infinitime.c @@ -60,15 +60,6 @@ task.h is included from an application file. */ /* Assumes 8bit bytes! */ #define heapBITS_PER_BYTE ( ( size_t ) 8 ) -/* Allocate the memory for the heap. */ -#if( configAPPLICATION_ALLOCATED_HEAP == 1 ) -/* The application writer has already defined the array used for the RTOS -heap - probably so it can be placed in a special segment or address. */ -extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; -#else -static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; -#endif /* configAPPLICATION_ALLOCATED_HEAP */ - /* Define the linked list structure. This is used to link free blocks in order of their memory address. */ typedef struct A_BLOCK_LINK @@ -113,6 +104,8 @@ application. When the bit is free the block is still part of the free heap space. */ static size_t xBlockAllocatedBit = 0; +static size_t xHeapSize = 0; + /*-----------------------------------------------------------*/ void *pvPortMalloc( size_t xWantedSize ) @@ -332,27 +325,38 @@ size_t xPortGetMinimumEverFreeHeapSize( void ) } /*-----------------------------------------------------------*/ +size_t xPortGetHeapSize( void ) +{ + return xHeapSize; +} +/*-----------------------------------------------------------*/ + void vPortInitialiseBlocks( void ) { /* This just exists to keep the linker quiet. */ } /*-----------------------------------------------------------*/ +extern uint8_t *__HeapLimit; // Defined by nrf_common.ld + static void prvHeapInit( void ) { BlockLink_t *pxFirstFreeBlock; uint8_t *pucAlignedHeap; size_t uxAddress; - size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; + size_t xTotalHeapSize = ( size_t ) &__StackLimit - ( size_t ) &__HeapLimit; + uint8_t *pucHeap = ( uint8_t * ) &__HeapLimit; + + xHeapSize = xTotalHeapSize; /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = ( size_t ) ucHeap; + uxAddress = ( size_t ) pucHeap; if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) { uxAddress += ( portBYTE_ALIGNMENT - 1 ); uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK ); - xTotalHeapSize -= uxAddress - ( size_t ) ucHeap; + xTotalHeapSize -= uxAddress - ( size_t ) pucHeap; } pucAlignedHeap = ( uint8_t * ) uxAddress; diff --git a/src/FreeRTOS/portmacro_cmsis.h b/src/FreeRTOS/portmacro_cmsis.h index e6e09158..d165d171 100644 --- a/src/FreeRTOS/portmacro_cmsis.h +++ b/src/FreeRTOS/portmacro_cmsis.h @@ -180,6 +180,7 @@ __STATIC_INLINE uint32_t ulPortRaiseBASEPRI( void ) /*-----------------------------------------------------------*/ +size_t xPortGetHeapSize(void); #ifdef __cplusplus } diff --git a/src/FreeRTOSConfig.h b/src/FreeRTOSConfig.h index 67c33a34..fce50054 100644 --- a/src/FreeRTOSConfig.h +++ b/src/FreeRTOSConfig.h @@ -62,7 +62,6 @@ #define configTICK_RATE_HZ 1024 #define configMAX_PRIORITIES (3) #define configMINIMAL_STACK_SIZE (120) -#define configTOTAL_HEAP_SIZE (1024 * 40) #define configMAX_TASK_NAME_LEN (4) #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 @@ -74,7 +73,7 @@ #define configUSE_QUEUE_SETS 0 #define configUSE_TIME_SLICING 0 #define configUSE_NEWLIB_REENTRANT 0 -#define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configENABLE_BACKWARD_COMPATIBILITY 0 #define configUSE_TASK_NOTIFICATIONS 0 /* Hook function related definitions. */ diff --git a/src/components/alarm/AlarmController.cpp b/src/components/alarm/AlarmController.cpp index c4eb8ed0..4ae42c08 100644 --- a/src/components/alarm/AlarmController.cpp +++ b/src/components/alarm/AlarmController.cpp @@ -19,11 +19,13 @@ #include "systemtask/SystemTask.h" #include "task.h" #include +#include using namespace Pinetime::Controllers; using namespace std::chrono_literals; -AlarmController::AlarmController(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} { +AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs) + : dateTimeController {dateTimeController}, fs {fs} { } namespace { @@ -36,11 +38,28 @@ namespace { void AlarmController::Init(System::SystemTask* systemTask) { this->systemTask = systemTask; alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm); + LoadSettingsFromFile(); + if (alarm.isEnabled) { + NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling"); + ScheduleAlarm(); + } +} + +void AlarmController::SaveAlarm() { + // verify if it is necessary to save + if (alarmChanged) { + SaveSettingsToFile(); + } + alarmChanged = false; } void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) { - hours = alarmHr; - minutes = alarmMin; + if (alarm.hours == alarmHr && alarm.minutes == alarmMin) { + return; + } + alarm.hours = alarmHr; + alarm.minutes = alarmMin; + alarmChanged = true; } void AlarmController::ScheduleAlarm() { @@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() { tm* tmAlarmTime = std::localtime(&ttAlarmTime); // If the time being set has already passed today,the alarm should be set for tomorrow - if (hours < dateTimeController.Hours() || (hours == dateTimeController.Hours() && minutes <= dateTimeController.Minutes())) { + if (alarm.hours < dateTimeController.Hours() || + (alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) { tmAlarmTime->tm_mday += 1; // tm_wday doesn't update automatically tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7; } - tmAlarmTime->tm_hour = hours; - tmAlarmTime->tm_min = minutes; + tmAlarmTime->tm_hour = alarm.hours; + tmAlarmTime->tm_min = alarm.minutes; tmAlarmTime->tm_sec = 0; // if alarm is in weekday-only mode, make sure it shifts to the next weekday - if (recurrence == RecurType::Weekdays) { + if (alarm.recurrence == RecurType::Weekdays) { if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day tmAlarmTime->tm_mday += 1; } else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days @@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() { xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0); xTimerStart(alarmTimer, 0); - state = AlarmState::Set; + if (!alarm.isEnabled) { + alarm.isEnabled = true; + alarmChanged = true; + } } uint32_t AlarmController::SecondsToAlarm() const { @@ -88,20 +111,71 @@ uint32_t AlarmController::SecondsToAlarm() const { void AlarmController::DisableAlarm() { xTimerStop(alarmTimer, 0); - state = AlarmState::Not_Set; + if (alarm.isEnabled) { + alarm.isEnabled = false; + alarmChanged = true; + } } void AlarmController::SetOffAlarmNow() { - state = AlarmState::Alerting; + isAlerting = true; systemTask->PushMessage(System::Messages::SetOffAlarm); } void AlarmController::StopAlerting() { - // Alarm state is off unless this is a recurring alarm - if (recurrence == RecurType::None) { - state = AlarmState::Not_Set; + isAlerting = false; + // Disable alarm unless it is recurring + if (alarm.recurrence == RecurType::None) { + alarm.isEnabled = false; + alarmChanged = true; } else { // set next instance ScheduleAlarm(); } } + +void AlarmController::SetRecurrence(RecurType recurrence) { + if (alarm.recurrence != recurrence) { + alarm.recurrence = recurrence; + alarmChanged = true; + } +} + +void AlarmController::LoadSettingsFromFile() { + lfs_file_t alarmFile; + AlarmSettings alarmBuffer; + + if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) { + NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file"); + return; + } + + fs.FileRead(&alarmFile, reinterpret_cast(&alarmBuffer), sizeof(alarmBuffer)); + fs.FileClose(&alarmFile); + if (alarmBuffer.version != alarmFormatVersion) { + NRF_LOG_WARNING("[AlarmController] Loaded alarm settings has version %u instead of %u, discarding", + alarmBuffer.version, + alarmFormatVersion); + return; + } + + alarm = alarmBuffer; + NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file"); +} + +void AlarmController::SaveSettingsToFile() const { + lfs_dir systemDir; + if (fs.DirOpen("/.system", &systemDir) != LFS_ERR_OK) { + fs.DirCreate("/.system"); + } + fs.DirClose(&systemDir); + lfs_file_t alarmFile; + if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) { + NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file for saving"); + return; + } + + fs.FileWrite(&alarmFile, reinterpret_cast(&alarm), sizeof(alarm)); + fs.FileClose(&alarmFile); + NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version); +} diff --git a/src/components/alarm/AlarmController.h b/src/components/alarm/AlarmController.h index 8ac0de9a..278e9cdb 100644 --- a/src/components/alarm/AlarmController.h +++ b/src/components/alarm/AlarmController.h @@ -30,47 +30,65 @@ namespace Pinetime { namespace Controllers { class AlarmController { public: - AlarmController(Controllers::DateTime& dateTimeController); + AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs); void Init(System::SystemTask* systemTask); + void SaveAlarm(); void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin); void ScheduleAlarm(); void DisableAlarm(); void SetOffAlarmNow(); uint32_t SecondsToAlarm() const; void StopAlerting(); - enum class AlarmState { Not_Set, Set, Alerting }; enum class RecurType { None, Daily, Weekdays }; uint8_t Hours() const { - return hours; + return alarm.hours; } uint8_t Minutes() const { - return minutes; + return alarm.minutes; } - AlarmState State() const { - return state; + bool IsAlerting() const { + return isAlerting; + } + + bool IsEnabled() const { + return alarm.isEnabled; } RecurType Recurrence() const { - return recurrence; + return alarm.recurrence; } - void SetRecurrence(RecurType recurType) { - recurrence = recurType; - } + void SetRecurrence(RecurType recurrence); private: + // Versions 255 is reserved for now, so the version field can be made + // bigger, should it ever be needed. + static constexpr uint8_t alarmFormatVersion = 1; + + struct AlarmSettings { + uint8_t version = alarmFormatVersion; + uint8_t hours = 7; + uint8_t minutes = 0; + RecurType recurrence = RecurType::None; + bool isEnabled = false; + }; + + bool isAlerting = false; + bool alarmChanged = false; + Controllers::DateTime& dateTimeController; + Controllers::FS& fs; System::SystemTask* systemTask = nullptr; TimerHandle_t alarmTimer; - uint8_t hours = 7; - uint8_t minutes = 0; + AlarmSettings alarm; std::chrono::time_point alarmTime; - AlarmState state = AlarmState::Not_Set; - RecurType recurrence = RecurType::None; + + void LoadSettingsFromFile(); + void SaveSettingsToFile() const; }; } } diff --git a/src/components/ble/DfuService.cpp b/src/components/ble/DfuService.cpp index b3f2ff10..ad9c99e9 100644 --- a/src/components/ble/DfuService.cpp +++ b/src/components/ble/DfuService.cpp @@ -1,6 +1,8 @@ #include "components/ble/DfuService.h" #include #include "components/ble/BleController.h" +#include "components/ble/NotificationManager.h" +#include "components/settings/Settings.h" #include "drivers/SpiNorFlash.h" #include "systemtask/SystemTask.h" #include @@ -78,6 +80,18 @@ void DfuService::Init() { } int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) { +#ifndef PINETIME_IS_RECOVERY + if (systemTask.GetSettings().GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::Disabled) { + Pinetime::Controllers::NotificationManager::Notification notif; + memcpy(notif.message.data(), denyAlert, denyAlertLength); + notif.size = denyAlertLength; + notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert; + systemTask.GetNotificationManager().Push(std::move(notif)); + systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification); + return BLE_ATT_ERR_INSUFFICIENT_AUTHOR; + } +#endif + if (bleController.IsFirmwareUpdating()) { xTimerStart(timeoutTimer, 0); } @@ -124,9 +138,11 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) { bootloaderSize, applicationSize); - // wait until SystemTask has finished waking up all devices - while (systemTask.IsSleeping()) { - vTaskDelay(50); // 50ms + // Wait until SystemTask has disabled sleeping + // This isn't quite correct, as we don't actually know + // if BleFirmwareUpdateStarted has been received yet + while (!systemTask.IsSleepDisabled()) { + vTaskDelay(pdMS_TO_TICKS(5)); } dfuImage.Erase(); diff --git a/src/components/ble/DfuService.h b/src/components/ble/DfuService.h index 6652cdc1..99be27b9 100644 --- a/src/components/ble/DfuService.h +++ b/src/components/ble/DfuService.h @@ -20,6 +20,8 @@ namespace Pinetime { namespace Controllers { class Ble; + class Settings; + class NotificationManager; class DfuService { public: @@ -87,6 +89,9 @@ namespace Pinetime { DfuImage dfuImage; NotificationManager notificationManager; + static constexpr const char denyAlert[] = "InfiniTime\0Firmware update attempted, but disabled in settings."; + static constexpr const uint8_t denyAlertLength = sizeof(denyAlert); // for this to work denyAlert MUST be array + static constexpr uint16_t dfuServiceId {0x1530}; static constexpr uint16_t packetCharacteristicId {0x1532}; static constexpr uint16_t controlPointCharacteristicId {0x1531}; diff --git a/src/components/ble/FSService.cpp b/src/components/ble/FSService.cpp index fda6b392..721ed297 100644 --- a/src/components/ble/FSService.cpp +++ b/src/components/ble/FSService.cpp @@ -1,6 +1,8 @@ #include #include "FSService.h" #include "components/ble/BleController.h" +#include "components/ble/NotificationManager.h" +#include "components/settings/Settings.h" #include "systemtask/SystemTask.h" using namespace Pinetime::Controllers; @@ -49,6 +51,18 @@ void FSService::Init() { } int FSService::OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) { +#ifndef PINETIME_IS_RECOVERY + if (systemTask.GetSettings().GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::Disabled) { + Pinetime::Controllers::NotificationManager::Notification notif; + memcpy(notif.message.data(), denyAlert, denyAlertLength); + notif.size = denyAlertLength; + notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert; + systemTask.GetNotificationManager().Push(std::move(notif)); + systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification); + return BLE_ATT_ERR_INSUFFICIENT_AUTHOR; + } +#endif + if (attributeHandle == versionCharacteristicHandle) { NRF_LOG_INFO("FS_S : handle = %d", versionCharacteristicHandle); int res = os_mbuf_append(context->om, &fsVersion, sizeof(fsVersion)); diff --git a/src/components/ble/FSService.h b/src/components/ble/FSService.h index b2299623..b43bc9e4 100644 --- a/src/components/ble/FSService.h +++ b/src/components/ble/FSService.h @@ -14,6 +14,8 @@ namespace Pinetime { namespace Controllers { class Ble; + class Settings; + class NotificationManager; class FSService { public: @@ -26,6 +28,10 @@ namespace Pinetime { private: Pinetime::System::SystemTask& systemTask; Pinetime::Controllers::FS& fs; + + static constexpr const char denyAlert[] = "InfiniTime\0File access attempted, but disabled in settings."; + static constexpr const uint8_t denyAlertLength = sizeof(denyAlert); // for this to work denyAlert MUST be array + static constexpr uint16_t FSServiceId {0xFEBB}; static constexpr uint16_t fsVersionId {0x0100}; static constexpr uint16_t fsTransferId {0x0200}; diff --git a/src/components/ble/MusicService.cpp b/src/components/ble/MusicService.cpp index 94d97f46..43cbec70 100644 --- a/src/components/ble/MusicService.cpp +++ b/src/components/ble/MusicService.cpp @@ -18,6 +18,8 @@ #include "components/ble/MusicService.h" #include "components/ble/NimbleController.h" #include +#include +#include namespace { // 0000yyxx-78fc-48fe-8e23-433b3a1942d0 diff --git a/src/components/ble/MusicService.h b/src/components/ble/MusicService.h index f4b902fe..e32980b4 100644 --- a/src/components/ble/MusicService.h +++ b/src/components/ble/MusicService.h @@ -25,6 +25,7 @@ #include #undef max #undef min +#include namespace Pinetime { namespace Controllers { @@ -70,9 +71,9 @@ namespace Pinetime { uint16_t eventHandle {}; - std::string artistName {"Waiting for"}; - std::string albumName {}; - std::string trackName {"track information.."}; + std::string trackName; + std::string albumName; + std::string artistName {"Not Playing"}; bool playing {false}; diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp index f1411a3e..5059007a 100644 --- a/src/components/ble/NimbleController.cpp +++ b/src/components/ble/NimbleController.cpp @@ -454,9 +454,15 @@ void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) { /* Wakeup Spi and SpiNorFlash before accessing the file system * This should be fixed in the FS driver */ - systemTask.PushMessage(Pinetime::System::Messages::GoToRunning); systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); - vTaskDelay(10); + + // This isn't quite correct + // SystemTask could receive EnableSleeping right after passing this check + // We need some guarantee that the SystemTask has processed the above message + // before we can continue + while (!systemTask.IsSleepDisabled()) { + vTaskDelay(pdMS_TO_TICKS(5)); + } lfs_file_t file_p; diff --git a/src/components/ble/NotificationManager.h b/src/components/ble/NotificationManager.h index 189a7e13..57a9c715 100644 --- a/src/components/ble/NotificationManager.h +++ b/src/components/ble/NotificationManager.h @@ -28,7 +28,7 @@ namespace Pinetime { using Id = uint8_t; using Idx = uint8_t; - std::array message; + std::array message{}; uint8_t size; Categories category = Categories::Unknown; Id id = 0; diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp index 504cad14..51baf543 100644 --- a/src/components/ble/SimpleWeatherService.cpp +++ b/src/components/ble/SimpleWeatherService.cpp @@ -42,9 +42,9 @@ namespace { std::memcpy(cityName.data(), &dataBuffer[16], 32); cityName[32] = '\0'; return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]), - ToInt16(&dataBuffer[10]), - ToInt16(&dataBuffer[12]), - ToInt16(&dataBuffer[14]), + SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])), + SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])), + SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])), SimpleWeatherService::Icons {dataBuffer[16 + 32]}, std::move(cityName)); } @@ -52,12 +52,12 @@ namespace { SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) { auto timestamp = static_cast(ToUInt64(&dataBuffer[2])); - std::array days; + std::array, SimpleWeatherService::MaxNbForecastDays> days; const uint8_t nbDaysInBuffer = dataBuffer[10]; const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer); for (int i = 0; i < nbDays; i++) { - days[i] = SimpleWeatherService::Forecast::Day {ToInt16(&dataBuffer[11 + (i * 5)]), - ToInt16(&dataBuffer[13 + (i * 5)]), + days[i] = SimpleWeatherService::Forecast::Day {SimpleWeatherService::Temperature(ToInt16(&dataBuffer[11 + (i * 5)])), + SimpleWeatherService::Temperature(ToInt16(&dataBuffer[13 + (i * 5)])), SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}}; } return SimpleWeatherService::Forecast {timestamp, nbDays, days}; @@ -98,9 +98,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { currentWeather = CreateCurrentWeather(dataBuffer); NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s", currentWeather->timestamp, - currentWeather->temperature, - currentWeather->minTemperature, - currentWeather->maxTemperature, + currentWeather->temperature.PreciseCelsius(), + currentWeather->minTemperature.PreciseCelsius(), + currentWeather->maxTemperature.PreciseCelsius(), currentWeather->iconId, currentWeather->location.data()); } @@ -112,9 +112,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { for (int i = 0; i < 5; i++) { NRF_LOG_INFO("\t[%d] Min: %d - Max : %d - Icon : %d", i, - forecast->days[i].minTemperature, - forecast->days[i].maxTemperature, - forecast->days[i].iconId); + forecast->days[i]->minTemperature.PreciseCelsius(), + forecast->days[i]->maxTemperature.PreciseCelsius(), + forecast->days[i]->iconId); } } break; diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h index 03d2f6ff..0f8c181b 100644 --- a/src/components/ble/SimpleWeatherService.h +++ b/src/components/ble/SimpleWeatherService.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include #define min // workaround: nimble's min/max macros conflict with libstdc++ @@ -61,13 +61,42 @@ namespace Pinetime { Unknown = 255 }; + class Temperature { + public: + explicit Temperature(int16_t raw) : raw {raw} { + } + + [[nodiscard]] int16_t PreciseCelsius() const { + return raw; + } + + [[nodiscard]] int16_t PreciseFahrenheit() const { + return raw * 9 / 5 + 3200; + } + + [[nodiscard]] int16_t Celsius() const { + return (PreciseCelsius() + 50) / 100; + } + + [[nodiscard]] int16_t Fahrenheit() const { + return (PreciseFahrenheit() + 50) / 100; + } + + bool operator==(const Temperature& other) const { + return raw == other.raw; + } + + private: + int16_t raw; + }; + using Location = std::array; // 32 char + \0 (end of string) struct CurrentWeather { CurrentWeather(uint64_t timestamp, - int16_t temperature, - int16_t minTemperature, - int16_t maxTemperature, + Temperature temperature, + Temperature minTemperature, + Temperature maxTemperature, Icons iconId, Location&& location) : timestamp {timestamp}, @@ -79,9 +108,9 @@ namespace Pinetime { } uint64_t timestamp; - int16_t temperature; - int16_t minTemperature; - int16_t maxTemperature; + Temperature temperature; + Temperature minTemperature; + Temperature maxTemperature; Icons iconId; Location location; @@ -93,14 +122,14 @@ namespace Pinetime { uint8_t nbDays; struct Day { - int16_t minTemperature; - int16_t maxTemperature; + Temperature minTemperature; + Temperature maxTemperature; Icons iconId; bool operator==(const Day& other) const; }; - std::array days; + std::array, MaxNbForecastDays> days; bool operator==(const Forecast& other) const; }; @@ -108,10 +137,6 @@ namespace Pinetime { std::optional Current() const; std::optional GetForecast() const; - static int16_t CelsiusToFahrenheit(int16_t celsius) { - return celsius * 9 / 5 + 3200; - } - private: // 00050000-78fc-48fe-8e23-433b3a1942d0 static constexpr ble_uuid128_t BaseUuid() { diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp index 7f58c9b3..2ef0ef22 100644 --- a/src/components/datetime/DateTimeController.cpp +++ b/src/components/datetime/DateTimeController.cpp @@ -7,16 +7,31 @@ using namespace Pinetime::Controllers; namespace { - char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"}; - char const* DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; - char const* MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; - char const* MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + constexpr const char* const DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"}; + constexpr const char* const DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; + constexpr const char* const DaysString[] = {"--", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"}; + constexpr const char* const DaysStringLow[] = {"--", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; + constexpr const char* const MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; + constexpr const char* const MonthsStringLow[] = + {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + constexpr int compileTimeAtoi(const char* str) { + int result = 0; + while (*str >= '0' && *str <= '9') { + result = result * 10 + *str - '0'; + str++; + } + return result; + } } DateTime::DateTime(Controllers::Settings& settingsController) : settingsController {settingsController} { mutex = xSemaphoreCreateMutex(); ASSERT(mutex != nullptr); xSemaphoreGive(mutex); + + // __DATE__ is a string of the format "MMM DD YYYY", so an offset of 7 gives the start of the year + SetTime(compileTimeAtoi(&__DATE__[7]), 1, 1, 0, 0, 0); } void DateTime::SetCurrentTime(std::chrono::time_point t) { @@ -46,7 +61,9 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, UpdateTime(previousSystickCounter, true); xSemaphoreGive(mutex); - systemTask->PushMessage(System::Messages::OnNewTime); + if (systemTask != nullptr) { + systemTask->PushMessage(System::Messages::OnNewTime); + } } void DateTime::SetTimeZone(int8_t timezone, int8_t dst) { @@ -129,6 +146,10 @@ const char* DateTime::DayOfWeekShortToString() const { return DaysStringShort[static_cast(DayOfWeek())]; } +const char* DateTime::DayOfWeekToString() const { + return DaysString[static_cast(DayOfWeek())]; +} + const char* DateTime::MonthShortToStringLow(Months month) { return MonthsStringLow[static_cast(month)]; } @@ -137,6 +158,10 @@ const char* DateTime::DayOfWeekShortToStringLow(Days day) { return DaysStringShortLow[static_cast(day)]; } +const char* DateTime::DayOfWeekToStringLow(Days day) { + return DaysStringLow[static_cast(day)]; +} + void DateTime::Register(Pinetime::System::SystemTask* systemTask) { this->systemTask = systemTask; } diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h index 5a453f20..33fef6be 100644 --- a/src/components/datetime/DateTimeController.h +++ b/src/components/datetime/DateTimeController.h @@ -41,7 +41,7 @@ namespace Pinetime { * * used to update difference between utc and local time (see UtcOffset()) * - * parameters are in quarters of an our. Following the BLE CTS specification, + * parameters are in quarters of an hour. Following the BLE CTS specification, * timezone is expected to be constant over DST which will be reported in * dst field. */ @@ -121,8 +121,10 @@ namespace Pinetime { const char* MonthShortToString() const; const char* DayOfWeekShortToString() const; + const char* DayOfWeekToString() const; static const char* MonthShortToStringLow(Months month); static const char* DayOfWeekShortToStringLow(Days day); + static const char* DayOfWeekToStringLow(Days day); std::chrono::time_point CurrentDateTime(); diff --git a/src/components/heartrate/Ppg.cpp b/src/components/heartrate/Ppg.cpp index 3a6988ae..efbed852 100644 --- a/src/components/heartrate/Ppg.cpp +++ b/src/components/heartrate/Ppg.cpp @@ -142,7 +142,7 @@ Ppg::Ppg() { spectrum.fill(0.0f); } -int8_t Ppg::Preprocess(uint32_t hrs, uint32_t als) { +int8_t Ppg::Preprocess(uint16_t hrs, uint16_t als) { if (dataIndex < dataLength) { dataHRS[dataIndex++] = hrs; } diff --git a/src/components/heartrate/Ppg.h b/src/components/heartrate/Ppg.h index 4492b2c2..373e7985 100644 --- a/src/components/heartrate/Ppg.h +++ b/src/components/heartrate/Ppg.h @@ -14,7 +14,7 @@ namespace Pinetime { class Ppg { public: Ppg(); - int8_t Preprocess(uint32_t hrs, uint32_t als); + int8_t Preprocess(uint16_t hrs, uint16_t als); int HeartRate(); void Reset(bool resetDaqBuffer); static constexpr int deltaTms = 100; diff --git a/src/components/settings/Settings.cpp b/src/components/settings/Settings.cpp index 49073e1a..1ae00a2d 100644 --- a/src/components/settings/Settings.cpp +++ b/src/components/settings/Settings.cpp @@ -8,11 +8,13 @@ Settings::Settings(Pinetime::Controllers::FS& fs) : fs {fs} { } void Settings::Init() { + // Load default settings from Flash LoadSettingsFromFile(); } void Settings::SaveSettings() { + // verify if is necessary to save if (settingsChanged) { SaveSettingsToFile(); diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 3597e7b6..093a3ac6 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -4,6 +4,7 @@ #include "components/brightness/BrightnessController.h" #include "components/fs/FS.h" #include "displayapp/apps/Apps.h" +#include namespace Pinetime { namespace Controllers { @@ -36,6 +37,8 @@ namespace Pinetime { }; enum class PTSGaugeStyle : uint8_t { Full, Half, Numeric }; enum class PTSWeather : uint8_t { On, Off }; + enum class PrideFlag : uint8_t { Gay, Trans, Bi, Lesbian }; + enum class DfuAndFsMode : uint8_t { Disabled, Enabled, EnabledTillReboot }; struct PineTimeStyle { Colors ColorTime = Colors::Teal; @@ -50,17 +53,6 @@ namespace Pinetime { int colorIndex = 0; }; - enum class HeartRateBackgroundMeasurementInterval : uint8_t { - Off, - Continuous, - TenSeconds, - ThirtySeconds, - OneMinute, - FiveMinutes, - TenMinutes, - ThirtyMinutes, - }; - Settings(Pinetime::Controllers::FS& fs); Settings(const Settings&) = delete; @@ -165,6 +157,16 @@ namespace Pinetime { return settings.PTS.weatherEnable; }; + void SetPrideFlag(PrideFlag prideFlag) { + if (prideFlag != settings.prideFlag) + settingsChanged = true; + settings.prideFlag = prideFlag; + }; + + PrideFlag GetPrideFlag() const { + return settings.prideFlag; + }; + void SetAppMenu(uint8_t menu) { appMenu = menu; }; @@ -309,16 +311,28 @@ namespace Pinetime { return bleRadioEnabled; }; - HeartRateBackgroundMeasurementInterval GetHeartRateBackgroundMeasurementInterval() const { - return settings.heartRateBackgroundMeasurementInterval; - } - - void SetHeartRateBackgroundMeasurementInterval(HeartRateBackgroundMeasurementInterval newHeartRateBackgroundMeasurementInterval) { - if (newHeartRateBackgroundMeasurementInterval != settings.heartRateBackgroundMeasurementInterval) { + void SetDfuAndFsMode(DfuAndFsMode mode) { + if (mode == GetDfuAndFsMode()) { + return; + } + if (mode == DfuAndFsMode::Enabled || GetDfuAndFsMode() == DfuAndFsMode::Enabled) { settingsChanged = true; } - settings.heartRateBackgroundMeasurementInterval = newHeartRateBackgroundMeasurementInterval; - } + settings.dfuAndFsEnabledOnBoot = (mode == DfuAndFsMode::Enabled); + dfuAndFsEnabledTillReboot = (mode == DfuAndFsMode::EnabledTillReboot); + }; + + DfuAndFsMode GetDfuAndFsMode() { + if (dfuAndFsEnabledTillReboot) { + if (settings.dfuAndFsEnabledOnBoot) { // ensure both variables are in consistent state + settingsChanged = true; + settings.dfuAndFsEnabledOnBoot = false; + NRF_LOG_ERROR("Settings: DfuAndFsMode data corrupted"); + } + return DfuAndFsMode::EnabledTillReboot; + } + return (settings.dfuAndFsEnabledOnBoot ? DfuAndFsMode::Enabled : DfuAndFsMode::Disabled); + }; private: Pinetime::Controllers::FS& fs; @@ -341,6 +355,8 @@ namespace Pinetime { PineTimeStyle PTS; + PrideFlag prideFlag = PrideFlag::Gay; + WatchFaceInfineat watchFaceInfineat; std::bitset<5> wakeUpMode {0}; @@ -348,7 +364,7 @@ namespace Pinetime { Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium; - HeartRateBackgroundMeasurementInterval heartRateBackgroundMeasurementInterval = HeartRateBackgroundMeasurementInterval::Off; + bool dfuAndFsEnabledOnBoot = false; }; SettingsData settings; @@ -361,6 +377,7 @@ namespace Pinetime { * to off (false) on every boot because we always want ble to be enabled on startup */ bool bleRadioEnabled = true; + bool dfuAndFsEnabledTillReboot = false; void LoadSettingsFromFile(); void SaveSettingsToFile(); diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index dce60d5c..5c50e85f 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -30,6 +30,7 @@ #include "displayapp/screens/Weather.h" #include "displayapp/screens/PassKey.h" #include "displayapp/screens/Error.h" +#include "displayapp/screens/Calculator.h" #include "drivers/Cst816s.h" #include "drivers/St7789.h" @@ -47,13 +48,15 @@ #include "displayapp/screens/settings/SettingSteps.h" #include "displayapp/screens/settings/SettingSetDateTime.h" #include "displayapp/screens/settings/SettingChimes.h" -#include "displayapp/screens/settings/SettingHeartRate.h" #include "displayapp/screens/settings/SettingShakeThreshold.h" #include "displayapp/screens/settings/SettingBluetooth.h" +#include "displayapp/screens/settings/SettingOTA.h" #include "libs/lv_conf.h" #include "UserApps.h" +#include + using namespace Pinetime::Applications; using namespace Pinetime::Applications::Display; @@ -127,15 +130,6 @@ void DisplayApp::Start(System::BootErrors error) { bootError = error; - lvgl.Init(); - motorController.Init(); - - if (error == System::BootErrors::TouchController) { - LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None); - } else { - LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); - } - if (pdPASS != xTaskCreate(DisplayApp::Process, "displayapp", 800, this, 0, &taskHandle)) { APP_ERROR_HANDLER(NRF_ERROR_NO_MEM); } @@ -144,26 +138,42 @@ void DisplayApp::Start(System::BootErrors error) { void DisplayApp::Process(void* instance) { auto* app = static_cast(instance); NRF_LOG_INFO("displayapp task started!"); - app->InitHw(); + app->Init(); + + if (app->bootError == System::BootErrors::TouchController) { + app->LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None); + } else { + app->LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); + } while (true) { app->Refresh(); } } -void DisplayApp::InitHw() { +void DisplayApp::Init() { + lcd.Init(); + motorController.Init(); brightnessController.Init(); ApplyBrightness(); - lcd.Init(); + lvgl.Init(); } TickType_t DisplayApp::CalculateSleepTime() { + // Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame + // Next frame time is frame count * refresh period (ms) * tick rate + + auto RoundedDiv = [](uint32_t a, uint32_t b) { + return ((a + (b / 2)) / b); + }; + // RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max + // in this case around 9 hours (=overflow frame count / always on refresh period) + constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod); + TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime; - // Divide both the numerator and denominator by 8 to increase the number of ticks (frames) before the overflow tick is reached - TickType_t elapsedTarget = ROUNDED_DIV((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8); - // ROUNDED_DIV overflows when numerator + (denominator floordiv 2) > uint32 max - // in this case around 9 hours - constexpr TickType_t overflowTick = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod); + // Divide both the numerator and denominator by 8 (=GCD(1000,1024)) + // to increase the number of ticks (frames) before the overflow tick is reached + TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8); // Assumptions @@ -171,17 +181,17 @@ TickType_t DisplayApp::CalculateSleepTime() { // Needed for division trick above static_assert(configTICK_RATE_HZ % 8 == 0); - // Local tick count must always wraparound before the system tick count does - // As a static assert we can use 64 bit ints and therefore dodge overflows + // Frame count must always wraparound more often than the system tick count does // Always on overflow time (ms) < system tick overflow time (ms) - static_assert((uint64_t) overflowTick * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ); + // Using 64bit ints here to avoid overflow + static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ); - if (alwaysOnTickCount == overflowTick) { - alwaysOnTickCount = 0; + if (alwaysOnFrameCount == overflowFrameCount) { + alwaysOnFrameCount = 0; alwaysOnStartTime = xTaskGetTickCount(); } - if (elapsedTarget > ticksElapsed) { - return elapsedTarget - ticksElapsed; + if (targetRenderTick > ticksElapsed) { + return targetRenderTick - ticksElapsed; } else { return 0; } @@ -221,28 +231,27 @@ void DisplayApp::Refresh() { TickType_t queueTimeout; switch (state) { case States::Idle: - if (settingsController.GetAlwaysOnDisplay()) { - if (!currentScreen->IsRunning()) { - LoadPreviousScreen(); + queueTimeout = portMAX_DELAY; + break; + case States::AOD: + if (!currentScreen->IsRunning()) { + LoadPreviousScreen(); + } + // Check we've slept long enough + // Might not be true if the loop received an event + // If not true, then wait that amount of time + queueTimeout = CalculateSleepTime(); + if (queueTimeout == 0) { + // Only advance the tick count when LVGL is done + // Otherwise keep running the task handler while it still has things to draw + // Note: under high graphics load, LVGL will always have more work to do + if (lv_task_handler() > 0) { + // Drop frames that we've missed if drawing/event handling took way longer than expected + while (queueTimeout == 0) { + alwaysOnFrameCount += 1; + queueTimeout = CalculateSleepTime(); + } } - // Check we've slept long enough - // Might not be true if the loop received an event - // If not true, then wait that amount of time - queueTimeout = CalculateSleepTime(); - if (queueTimeout == 0) { - // Only advance the tick count when LVGL is done - // Otherwise keep running the task handler while it still has things to draw - // Note: under high graphics load, LVGL will always have more work to do - if (lv_task_handler() > 0) { - // Drop frames that we've missed if drawing/event handling took way longer than expected - while (queueTimeout == 0) { - alwaysOnTickCount += 1; - queueTimeout = CalculateSleepTime(); - } - }; - } - } else { - queueTimeout = portMAX_DELAY; } break; case States::Running: @@ -256,9 +265,20 @@ void DisplayApp::Refresh() { isDimmed = true; brightnessController.Set(Controllers::BrightnessController::Levels::Low); } - if (IsPastSleepTime()) { - systemTask->PushMessage(System::Messages::GoToSleep); - state = States::Idle; + if (IsPastSleepTime() && uxQueueMessagesWaiting(msgQueue) == 0) { + PushMessageToSystemTask(System::Messages::GoToSleep); + // Can't set state to Idle here, something may send + // DisableSleeping before this GoToSleep arrives + // Instead we check we have no messages queued before sending GoToSleep + // This works as the SystemTask is higher priority than DisplayApp + // As soon as we send GoToSleep, SystemTask pre-empts DisplayApp + // Whenever DisplayApp is running again, it is guaranteed that + // SystemTask has handled the message + // If it responded, we will have a GoToSleep waiting in the queue + // By checking that there are no messages in the queue, we avoid + // resending GoToSleep when we already have a response + // SystemTask is resilient to duplicate messages, this is an + // optimisation to reduce pressure on the message queues } } else if (isDimmed) { isDimmed = false; @@ -274,12 +294,22 @@ void DisplayApp::Refresh() { if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) { switch (msg) { case Messages::GoToSleep: + case Messages::GoToAOD: + // Checking if SystemTask is sleeping is purely an optimisation. + // If it's no longer sleeping since it sent GoToSleep, it has + // cancelled the sleep and transitioned directly from + // GoingToSleep->Running, so we are about to receive GoToRunning + // and can ignore this message. If it wasn't ignored, DisplayApp + // would go to sleep and then immediately re-wake + if (state != States::Running || !systemTask->IsSleeping()) { + break; + } while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) { brightnessController.Lower(); vTaskDelay(100); } // Turn brightness down (or set to AlwaysOn mode) - if (settingsController.GetAlwaysOnDisplay()) { + if (msg == Messages::GoToAOD) { brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn); } else { brightnessController.Set(Controllers::BrightnessController::Levels::Off); @@ -292,23 +322,34 @@ void DisplayApp::Refresh() { while (!lv_task_handler()) { }; } - // Turn LCD display off (or set to low power for AlwaysOn mode) - if (settingsController.GetAlwaysOnDisplay()) { + // Clear any ongoing touch pressed events + // Without this LVGL gets stuck in the pressed state and will keep refreshing the + // display activity timer causing the screen to never sleep after timeout + lvgl.ClearTouchState(); + if (msg == Messages::GoToAOD) { lcd.LowPowerOn(); // Record idle entry time - alwaysOnTickCount = 0; + alwaysOnFrameCount = 0; alwaysOnStartTime = xTaskGetTickCount(); + PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD); + state = States::AOD; } else { lcd.Sleep(); + PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); + state = States::Idle; } - PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); - state = States::Idle; break; case Messages::NotifyDeviceActivity: lv_disp_trig_activity(nullptr); break; case Messages::GoToRunning: - if (settingsController.GetAlwaysOnDisplay()) { + // If SystemTask is sleeping, the GoToRunning message is old + // and must be ignored. Otherwise DisplayApp will use SPI + // that is powered down and cause bad behaviour + if (state == States::Running || systemTask->IsSleeping()) { + break; + } + if (state == States::AOD) { lcd.LowPowerOff(); } else { lcd.Wakeup(); @@ -334,7 +375,7 @@ void DisplayApp::Refresh() { } else { LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up); } - motorController.StartRinging(); + motorController.RunForDuration(35); break; case Messages::AlarmTriggered: if (currentApp == Apps::Alarm) { @@ -437,13 +478,10 @@ void DisplayApp::Refresh() { LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); motorController.RunForDuration(35); break; - case Messages::OnChargingEvent: - motorController.RunForDuration(15); - break; } } - if (touchHandler.IsTouching()) { + if (state == States::Running && touchHandler.IsTouching()) { currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY()); } @@ -480,26 +518,25 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio switch (app) { case Apps::Launcher: { std::array apps; - int i = 0; - for (const auto& userApp : userApps) { - apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true}; - } + std::ranges::transform(userApps, apps.begin(), [this](const auto& userApp) { + return Screens::Tile::Applications {userApp.icon, userApp.app, userApp.isAvailable(controllers.filesystem)}; + }); currentScreen = std::make_unique(this, settingsController, batteryController, bleController, + alarmController, dateTimeController, filesystem, std::move(apps)); } break; case Apps::Clock: { - const auto* watchFace = - std::find_if(userWatchFaces.begin(), userWatchFaces.end(), [this](const WatchFaceDescription& watchfaceDescription) { - return watchfaceDescription.watchFace == settingsController.GetWatchFace(); - }); - if (watchFace != userWatchFaces.end()) + const auto* watchFace = std::ranges::find_if(userWatchFaces, [this](const WatchFaceDescription& watchfaceDescription) { + return watchfaceDescription.watchFace == settingsController.GetWatchFace(); + }); + if (watchFace != userWatchFaces.end()) { currentScreen.reset(watchFace->create(controllers)); - else { + } else { currentScreen.reset(userWatchFaces[0].create(controllers)); } settingsController.SetAppMenu(0); @@ -542,18 +579,19 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio brightnessController, motorController, settingsController, - bleController); + bleController, + alarmController); break; case Apps::Settings: currentScreen = std::make_unique(this, settingsController); break; case Apps::SettingWatchFace: { std::array items; - int i = 0; - for (const auto& userWatchFace : userWatchFaces) { - items[i++] = - Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)}; - } + std::ranges::transform(userWatchFaces, items.begin(), [this](const WatchFaceDescription& userWatchFace) { + return Screens::SettingWatchFace::Item {userWatchFace.name, + userWatchFace.watchFace, + userWatchFace.isAvailable(controllers.filesystem)}; + }); currentScreen = std::make_unique(this, std::move(items), settingsController, filesystem); } break; case Apps::SettingTimeFormat: @@ -565,11 +603,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio case Apps::SettingWakeUp: currentScreen = std::make_unique(settingsController); break; - case Apps::SettingHeartRate: - currentScreen = std::make_unique(settingsController); - break; case Apps::SettingDisplay: - currentScreen = std::make_unique(this, settingsController); + currentScreen = std::make_unique(settingsController); break; case Apps::SettingSteps: currentScreen = std::make_unique(settingsController); @@ -586,6 +621,9 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio case Apps::SettingBluetooth: currentScreen = std::make_unique(this, settingsController); break; + case Apps::SettingOTA: + currentScreen = std::make_unique(this, settingsController); + break; case Apps::BatteryInfo: currentScreen = std::make_unique(batteryController); break; @@ -604,7 +642,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio currentScreen = std::make_unique(*systemTask, brightnessController); break; default: { - const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) { + const auto* d = std::ranges::find_if(userApps, [app](const AppDescription& appDescription) { return appDescription.app == app; }); if (d != userApps.end()) { diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index d443b8b2..dabed99e 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -49,7 +49,7 @@ namespace Pinetime { namespace Applications { class DisplayApp { public: - enum class States { Idle, Running }; + enum class States { Idle, Running, AOD }; enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim }; DisplayApp(Drivers::St7789& lcd, @@ -121,7 +121,7 @@ namespace Pinetime { TouchEvents GetGesture(); static void Process(void* instance); - void InitHw(); + void Init(); void Refresh(); void LoadNewScreen(Apps app, DisplayApp::FullRefreshDirections direction); void LoadScreen(Apps app, DisplayApp::FullRefreshDirections direction); @@ -139,7 +139,7 @@ namespace Pinetime { bool isDimmed = false; TickType_t CalculateSleepTime(); - TickType_t alwaysOnTickCount; + TickType_t alwaysOnFrameCount; TickType_t alwaysOnStartTime; // If this is to be changed, make sure the actual always on refresh rate is changed // by configuring the LCD refresh timings diff --git a/src/displayapp/LittleVgl.cpp b/src/displayapp/LittleVgl.cpp index c70a0856..c6f6f784 100644 --- a/src/displayapp/LittleVgl.cpp +++ b/src/displayapp/LittleVgl.cpp @@ -248,6 +248,8 @@ void LittleVgl::SetNewTouchPoint(int16_t x, int16_t y, bool contact) { } } +// Cancel an ongoing tap +// Signifies that LVGL should not handle the current tap void LittleVgl::CancelTap() { if (tapped) { isCancelled = true; @@ -255,6 +257,13 @@ void LittleVgl::CancelTap() { } } +// Clear the current tapped state +// Signifies that touch input processing is suspended +void LittleVgl::ClearTouchState() { + touchPoint = {-1, -1}; + tapped = false; +} + bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) { ptr->point.x = touchPoint.x; ptr->point.y = touchPoint.y; diff --git a/src/displayapp/LittleVgl.h b/src/displayapp/LittleVgl.h index 9a15ae15..54505b36 100644 --- a/src/displayapp/LittleVgl.h +++ b/src/displayapp/LittleVgl.h @@ -26,6 +26,7 @@ namespace Pinetime { void SetFullRefresh(FullRefreshDirections direction); void SetNewTouchPoint(int16_t x, int16_t y, bool contact); void CancelTap(); + void ClearTouchState(); bool GetFullRefresh() { bool returnValue = fullRefresh; diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index dcfff4c2..1fcd72d2 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -6,6 +6,7 @@ namespace Pinetime { namespace Display { enum class Messages : uint8_t { GoToSleep, + GoToAOD, GoToRunning, UpdateBleConnection, TouchEvent, @@ -23,7 +24,6 @@ namespace Pinetime { AlarmTriggered, Chime, BleRadioEnableToggle, - OnChargingEvent, }; } } diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 67bbfa7d..25926edc 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -14,6 +14,7 @@ #include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" +#include "displayapp/screens/WatchFacePrideFlag.h" namespace Pinetime { namespace Applications { @@ -25,6 +26,7 @@ namespace Pinetime { Apps app; const char* icon; Screens::Screen* (*create)(AppControllers& controllers); + bool (*isAvailable)(Controllers::FS& fileSystem); }; struct WatchFaceDescription { @@ -36,7 +38,7 @@ namespace Pinetime { template consteval AppDescription CreateAppDescription() { - return {AppTraits::app, AppTraits::icon, &AppTraits::Create}; + return {AppTraits::app, AppTraits::icon, &AppTraits::Create, &AppTraits::IsAvailable}; } template diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index a74ca7a8..0e94d32a 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -26,6 +26,7 @@ namespace Pinetime { StopWatch, Metronome, Motion, + Calculator, Steps, Dice, Weather, @@ -35,7 +36,6 @@ namespace Pinetime { SettingWatchFace, SettingTimeFormat, SettingWeatherFormat, - SettingHeartRate, SettingDisplay, SettingWakeUp, SettingSteps, @@ -43,6 +43,7 @@ namespace Pinetime { SettingChimes, SettingShakeThreshold, SettingBluetooth, + SettingOTA, Error }; @@ -53,6 +54,7 @@ namespace Pinetime { Terminal, Infineat, CasioStyleG7710, + PrideFlag, }; template diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index d7858760..93196ed6 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -13,6 +13,7 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calculator") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware") @@ -27,6 +28,7 @@ else() set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PrideFlag") set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() diff --git a/src/displayapp/fonts/CMakeLists.txt b/src/displayapp/fonts/CMakeLists.txt index 22627efc..562f0801 100644 --- a/src/displayapp/fonts/CMakeLists.txt +++ b/src/displayapp/fonts/CMakeLists.txt @@ -11,6 +11,7 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/jetbrains_mono_bold_20.c_M.patch if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) # FindPython3 module introduces with CMake 3.12 # https://cmake.org/cmake/help/latest/module/FindPython3.html + set(Python3_FIND_STRATEGY LOCATION) # https://discourse.cmake.org/t/find-package-python3-is-not-finding-the-correct-python/10563 find_package(Python3 REQUIRED) else() set(Python3_EXECUTABLE "python") diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 41c383c0..90be1feb 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf1ec, 0xf55a, 0xf3ed" } ], "bpp": 1, diff --git a/src/displayapp/screens/Alarm.cpp b/src/displayapp/screens/Alarm.cpp index cbc702f3..4cf43921 100644 --- a/src/displayapp/screens/Alarm.cpp +++ b/src/displayapp/screens/Alarm.cpp @@ -48,7 +48,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, Controllers::Settings::ClockType clockType, System::SystemTask& systemTask, Controllers::MotorController& motorController) - : alarmController {alarmController}, systemTask {systemTask}, motorController {motorController} { + : alarmController {alarmController}, wakeLock(systemTask), motorController {motorController} { hourCounter.Create(); lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); @@ -77,7 +77,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, btnStop = lv_btn_create(lv_scr_act(), nullptr); btnStop->user_data = this; lv_obj_set_event_cb(btnStop, btnEventHandler); - lv_obj_set_size(btnStop, 115, 50); + lv_obj_set_size(btnStop, 240, 70); lv_obj_align(btnStop, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); lv_obj_set_style_local_bg_color(btnStop, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); txtStop = lv_label_create(btnStop, nullptr); @@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, UpdateAlarmTime(); - if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { SetAlerting(); } else { SetSwitchState(LV_ANIM_OFF); @@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, } Alarm::~Alarm() { - if (alarmController.State() == AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { StopAlerting(); } lv_obj_clean(lv_scr_act()); + alarmController.SaveAlarm(); } void Alarm::DisableAlarm() { - if (alarmController.State() == AlarmController::AlarmState::Set) { + if (alarmController.IsEnabled()) { alarmController.DisableAlarm(); lv_switch_off(enableSwitch, LV_ANIM_ON); } @@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() { HideInfo(); return true; } - if (alarmController.State() == AlarmController::AlarmState::Alerting) { + if (alarmController.IsAlerting()) { StopAlerting(); return true; } @@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() { bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) { // Don't allow closing the screen by swiping while the alarm is alerting - return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown; + return alarmController.IsAlerting() && event == TouchEvents::SwipeDown; } void Alarm::OnValueChanged() { @@ -202,10 +203,14 @@ void Alarm::UpdateAlarmTime() { void Alarm::SetAlerting() { lv_obj_set_hidden(enableSwitch, true); + lv_obj_set_hidden(btnRecur, true); + lv_obj_set_hidden(btnInfo, true); + hourCounter.HideControls(); + minuteCounter.HideControls(); lv_obj_set_hidden(btnStop, false); taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this); motorController.StartRinging(); - systemTask.PushMessage(System::Messages::DisableSleeping); + wakeLock.Lock(); } void Alarm::StopAlerting() { @@ -216,21 +221,20 @@ void Alarm::StopAlerting() { lv_task_del(taskStopAlarm); taskStopAlarm = nullptr; } - systemTask.PushMessage(System::Messages::EnableSleeping); - lv_obj_set_hidden(enableSwitch, false); + wakeLock.Release(); lv_obj_set_hidden(btnStop, true); + hourCounter.ShowControls(); + minuteCounter.ShowControls(); + lv_obj_set_hidden(btnInfo, false); + lv_obj_set_hidden(btnRecur, false); + lv_obj_set_hidden(enableSwitch, false); } void Alarm::SetSwitchState(lv_anim_enable_t anim) { - switch (alarmController.State()) { - case AlarmController::AlarmState::Set: - lv_switch_on(enableSwitch, anim); - break; - case AlarmController::AlarmState::Not_Set: - lv_switch_off(enableSwitch, anim); - break; - default: - break; + if (alarmController.IsEnabled()) { + lv_switch_on(enableSwitch, anim); + } else { + lv_switch_off(enableSwitch, anim); } } @@ -247,7 +251,7 @@ void Alarm::ShowInfo() { txtMessage = lv_label_create(btnMessage, nullptr); lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY); - if (alarmController.State() == AlarmController::AlarmState::Set) { + if (alarmController.IsEnabled()) { auto timeToAlarm = alarmController.SecondsToAlarm(); auto daysToAlarm = timeToAlarm / 86400; diff --git a/src/displayapp/screens/Alarm.h b/src/displayapp/screens/Alarm.h index 444102cb..2dde6e87 100644 --- a/src/displayapp/screens/Alarm.h +++ b/src/displayapp/screens/Alarm.h @@ -22,6 +22,7 @@ #include "displayapp/screens/Screen.h" #include "displayapp/widgets/Counter.h" #include "displayapp/Controllers.h" +#include "systemtask/WakeLock.h" #include "Symbols.h" namespace Pinetime { @@ -43,7 +44,7 @@ namespace Pinetime { private: Controllers::AlarmController& alarmController; - System::SystemTask& systemTask; + System::WakeLock wakeLock; Controllers::MotorController& motorController; lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch; @@ -77,6 +78,10 @@ namespace Pinetime { *controllers.systemTask, controllers.motorController); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp index 41735349..fb46b413 100644 --- a/src/displayapp/screens/ApplicationList.cpp +++ b/src/displayapp/screens/ApplicationList.cpp @@ -21,6 +21,7 @@ ApplicationList::ApplicationList(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, + const Pinetime::Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, Pinetime::Controllers::FS& filesystem, std::array&& apps) @@ -28,6 +29,7 @@ ApplicationList::ApplicationList(DisplayApp* app, settingsController {settingsController}, batteryController {batteryController}, bleController {bleController}, + alarmController {alarmController}, dateTimeController {dateTimeController}, filesystem {filesystem}, apps {std::move(apps)}, @@ -59,6 +61,7 @@ std::unique_ptr ApplicationList::CreateScreen(unsigned int screenNum) co settingsController, batteryController, bleController, + alarmController, dateTimeController, pageApps); } diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h index 41a413af..4a57d7c0 100644 --- a/src/displayapp/screens/ApplicationList.h +++ b/src/displayapp/screens/ApplicationList.h @@ -18,6 +18,7 @@ namespace Pinetime { Pinetime::Controllers::Settings& settingsController, const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, + const Pinetime::Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, Pinetime::Controllers::FS& filesystem, std::array&& apps); @@ -32,6 +33,7 @@ namespace Pinetime { Controllers::Settings& settingsController; const Pinetime::Controllers::Battery& batteryController; const Pinetime::Controllers::Ble& bleController; + const Pinetime::Controllers::AlarmController& alarmController; Controllers::DateTime& dateTimeController; Pinetime::Controllers::FS& filesystem; std::array apps; diff --git a/src/displayapp/screens/BatteryInfo.cpp b/src/displayapp/screens/BatteryInfo.cpp index ab0a2bd4..16845d53 100644 --- a/src/displayapp/screens/BatteryInfo.cpp +++ b/src/displayapp/screens/BatteryInfo.cpp @@ -10,33 +10,35 @@ BatteryInfo::BatteryInfo(const Pinetime::Controllers::Battery& batteryController batteryPercent = batteryController.PercentRemaining(); batteryVoltage = batteryController.Voltage(); - charging_bar = lv_bar_create(lv_scr_act(), nullptr); - lv_obj_set_size(charging_bar, 200, 15); - lv_bar_set_range(charging_bar, 0, 100); - lv_obj_align(charging_bar, nullptr, LV_ALIGN_CENTER, 0, 10); - lv_bar_set_anim_time(charging_bar, 1000); - lv_obj_set_style_local_radius(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt); - lv_obj_set_style_local_bg_opa(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100); - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED); - lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON); + chargingArc = lv_arc_create(lv_scr_act(), nullptr); + lv_arc_set_rotation(chargingArc, 270); + lv_arc_set_bg_angles(chargingArc, 0, 360); + lv_arc_set_adjustable(chargingArc, false); + lv_obj_set_size(chargingArc, 180, 180); + lv_obj_align(chargingArc, nullptr, LV_ALIGN_CENTER, 0, -30); + lv_arc_set_value(chargingArc, batteryPercent); + lv_obj_set_style_local_bg_opa(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, LV_OPA_0); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_border_width(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 2); + lv_obj_set_style_local_radius(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 0); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_LIME); status = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_static(status, "Reading Battery status"); lv_label_set_align(status, LV_LABEL_ALIGN_CENTER); - lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20); + lv_obj_align(status, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -17); percent = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); - lv_label_set_text_fmt(percent, "%02i%%", batteryPercent); + lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); + lv_label_set_text_fmt(percent, "%i%%", batteryPercent); lv_label_set_align(percent, LV_LABEL_ALIGN_LEFT); - lv_obj_align(percent, nullptr, LV_ALIGN_CENTER, 0, -60); + lv_obj_align(percent, chargingArc, LV_ALIGN_CENTER, 0, 0); voltage = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(voltage, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange); lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10); lv_label_set_align(voltage, LV_LABEL_ALIGN_CENTER); - lv_obj_align(voltage, nullptr, LV_ALIGN_CENTER, 0, 95); + lv_obj_align(voltage, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -7); taskRefresh = lv_task_create(RefreshTaskCallback, 5000, LV_TASK_PRIO_MID, this); Refresh(); @@ -53,22 +55,26 @@ void BatteryInfo::Refresh() { batteryVoltage = batteryController.Voltage(); if (batteryController.IsCharging()) { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_LIME); lv_label_set_text_static(status, "Charging"); } else if (batteryPercent == 100) { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE); lv_label_set_text_static(status, "Fully charged"); - } else if (batteryPercent < 10) { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_YELLOW); + } else if (batteryPercent > 15) { + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_GREEN); + lv_label_set_text_static(status, "Discharging"); + } else if (batteryPercent > 5) { + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_ORANGE); lv_label_set_text_static(status, "Battery low"); } else { - lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, Colors::highlight); - lv_label_set_text_static(status, "Discharging"); + lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, Colors::deepOrange); + lv_label_set_text_static(status, "Battery critical"); } - lv_label_set_text_fmt(percent, "%02i%%", batteryPercent); + lv_label_set_text_fmt(percent, "%i%%", batteryPercent); + lv_obj_align(percent, chargingArc, LV_ALIGN_CENTER, 0, 0); - lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20); + lv_obj_align(status, voltage, LV_ALIGN_IN_BOTTOM_MID, 0, -27); lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10); - lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON); + lv_arc_set_value(chargingArc, batteryPercent); } diff --git a/src/displayapp/screens/BatteryInfo.h b/src/displayapp/screens/BatteryInfo.h index aa01d464..27bbaa00 100644 --- a/src/displayapp/screens/BatteryInfo.h +++ b/src/displayapp/screens/BatteryInfo.h @@ -24,7 +24,7 @@ namespace Pinetime { lv_obj_t* voltage; lv_obj_t* percent; - lv_obj_t* charging_bar; + lv_obj_t* chargingArc; lv_obj_t* status; lv_task_t* taskRefresh; diff --git a/src/displayapp/screens/Calculator.cpp b/src/displayapp/screens/Calculator.cpp new file mode 100644 index 00000000..a1f09383 --- /dev/null +++ b/src/displayapp/screens/Calculator.cpp @@ -0,0 +1,375 @@ +#include +#include +#include "Calculator.h" +#include "displayapp/InfiniTimeTheme.h" +#include "Symbols.h" + +using namespace Pinetime::Applications::Screens; + +static void eventHandler(lv_obj_t* obj, lv_event_t event) { + auto app = static_cast(obj->user_data); + app->OnButtonEvent(obj, event); +} + +Calculator::~Calculator() { + lv_obj_clean(lv_scr_act()); +} + +constexpr const char* const buttonMap[] = { + "7", "8", "9", Symbols::backspace, "\n", "4", "5", "6", "+ -", "\n", "1", "2", "3", "* /", "\n", "0", ".", "(-)", "=", ""}; + +Calculator::Calculator() { + resultLabel = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(resultLabel, LV_LABEL_LONG_CROP); + lv_label_set_align(resultLabel, LV_LABEL_ALIGN_RIGHT); + lv_label_set_text_fmt(resultLabel, "%" PRId64, result); + lv_obj_set_size(resultLabel, 200, 20); + lv_obj_set_pos(resultLabel, 10, 5); + + valueLabel = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(valueLabel, LV_LABEL_LONG_CROP); + lv_label_set_align(valueLabel, LV_LABEL_ALIGN_RIGHT); + lv_label_set_text_fmt(valueLabel, "%" PRId64, value); + lv_obj_set_size(valueLabel, 200, 20); + lv_obj_set_pos(valueLabel, 10, 35); + + buttonMatrix = lv_btnmatrix_create(lv_scr_act(), nullptr); + buttonMatrix->user_data = this; + lv_obj_set_event_cb(buttonMatrix, eventHandler); + lv_btnmatrix_set_map(buttonMatrix, const_cast(buttonMap)); + lv_btnmatrix_set_one_check(buttonMatrix, true); + lv_obj_set_size(buttonMatrix, 238, 180); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_pad_inner(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_top(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_bottom(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_left(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_right(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_align(buttonMatrix, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + lv_obj_set_style_local_bg_opa(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_OPA_COVER); + lv_obj_set_style_local_bg_grad_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128); + lv_obj_set_style_local_bg_main_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128); +} + +void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { + if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) { + HandleInput(); + } +} + +void Calculator::HandleInput() { + const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix); + + if (buttonText == nullptr) { + return; + } + + if ((equalSignPressedBefore && (*buttonText != '=')) || (error != Error::None)) { + ResetInput(); + UpdateOperation(); + } + + // we only compare the first char because it is enough + switch (*buttonText) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + // *buttonText is the first char in buttonText + // "- '0'" results in the int value of the char + uint8_t digit = (*buttonText) - '0'; + int8_t sign = (value < 0) ? -1 : 1; + + // if this is true, we already pressed the . button + if (offset < FIXED_POINT_OFFSET) { + value += sign * offset * digit; + offset /= 10; + } else if (value <= MAX_VALUE / 10) { + value *= 10; + value += sign * offset * digit; + } + } break; + + // unary minus + case '(': + value = -value; + break; + + case '.': + if (offset == FIXED_POINT_OFFSET) { + offset /= 10; + } + break; + + // for every operator we: + // - eval the current operator if value > FIXED_POINT_OFFSET + // - then set the new operator + // - + and - as well as * and / cycle on the same button + case '+': + if (value != 0) { + Eval(); + ResetInput(); + } + + switch (operation) { + case '+': + operation = '-'; + break; + case '-': + operation = ' '; + break; + default: + operation = '+'; + break; + } + UpdateOperation(); + break; + + case '*': + if (value != 0) { + Eval(); + ResetInput(); + } + + switch (operation) { + case '*': + operation = '/'; + break; + case '/': + operation = ' '; + break; + default: + operation = '*'; + break; + } + UpdateOperation(); + break; + + // this is a little hacky because it matches only the first char + case Symbols::backspace[0]: + if (value != 0) { + // delete one value digit + if (offset < FIXED_POINT_OFFSET) { + if (offset == 0) { + offset = 1; + } else { + offset *= 10; + } + } else { + value /= 10; + } + if (offset < FIXED_POINT_OFFSET) { + value -= value % (10 * offset); + } else { + value -= value % offset; + } + } else if (offset < FIXED_POINT_OFFSET) { + if (offset == 0) { + offset = 1; + } else { + offset *= 10; + } + } else { + // reset the result + result = 0; + } + + if (value == 0) { + operation = ' '; + UpdateOperation(); + } + break; + + case '=': + equalSignPressedBefore = true; + Eval(); + // If the operation is ' ' then we move the value to the result. + // We reset the input after this. + // This seems more convenient. + if (operation == ' ') { + ResetInput(); + } + break; + } + + UpdateValueLabel(); + UpdateResultLabel(); +} + +void Calculator::UpdateOperation() const { + switch (operation) { + case '+': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '-': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '*': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '/': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + default: + lv_btnmatrix_clear_btn_ctrl_all(buttonMatrix, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + } +} + +void Calculator::ResetInput() { + value = 0; + offset = FIXED_POINT_OFFSET; + operation = ' '; + equalSignPressedBefore = false; + error = Error::None; +} + +void Calculator::UpdateResultLabel() const { + int64_t integer = result / FIXED_POINT_OFFSET; + int64_t remainder = result % FIXED_POINT_OFFSET; + bool negative = (remainder < 0); + + if (remainder == 0) { + lv_label_set_text_fmt(resultLabel, "%" PRId64, integer); + return; + } + + if (remainder < 0) { + remainder = -remainder; + } + + uint8_t minWidth = N_DECIMALS; + + // cut "0"-digits on the right + while ((remainder > 0) && (remainder % 10 == 0)) { + remainder /= 10; + minWidth--; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(resultLabel, "-0.%0*" PRId64, minWidth, remainder); + } else { + lv_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, remainder); + } +} + +void Calculator::UpdateValueLabel() { + switch (error) { + case Error::TooLarge: + lv_label_set_text_static(valueLabel, "too large"); + break; + case Error::ZeroDivision: + lv_label_set_text_static(valueLabel, "zero division"); + break; + case Error::None: + default: { + int64_t integer = value / FIXED_POINT_OFFSET; + int64_t remainder = value % FIXED_POINT_OFFSET; + bool negative = (remainder < 0); + + int64_t printRemainder = remainder < 0 ? -remainder : remainder; + + uint8_t minWidth = 0; + int64_t tmpOffset = offset; + + if (tmpOffset == 0) { + tmpOffset = 1; + minWidth = 1; + } + while (tmpOffset < FIXED_POINT_OFFSET) { + tmpOffset *= 10; + minWidth++; + } + minWidth--; + + for (uint8_t i = minWidth; i < N_DECIMALS; i++) { + printRemainder /= 10; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(valueLabel, "-0.%0*" PRId64, minWidth, printRemainder); + } else if (offset == FIXED_POINT_OFFSET) { + lv_label_set_text_fmt(valueLabel, "%" PRId64, integer); + } else if ((offset == (FIXED_POINT_OFFSET / 10)) && (remainder == 0)) { + lv_label_set_text_fmt(valueLabel, "%" PRId64 ".", integer); + } else { + lv_label_set_text_fmt(valueLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, printRemainder); + } + } break; + } +} + +// update the result based on value and operation +void Calculator::Eval() { + switch (operation) { + case ' ': + result = value; + break; + + case '+': + // check for overflow + if (((result > 0) && (value > (MAX_VALUE - result))) || ((result < 0) && (value < (MIN_VALUE - result)))) { + error = Error::TooLarge; + break; + } + + result += value; + break; + case '-': + // check for overflow + if (((result < 0) && (value > (MAX_VALUE + result))) || ((result > 0) && (value < (MIN_VALUE + result)))) { + error = Error::TooLarge; + break; + } + + result -= value; + break; + case '*': + // check for overflow + // while dividing we eliminate the fixed point offset + // therefore we have to multiply it again for the comparison with value + // we also assume here that MAX_VALUE == -MIN_VALUE + if ((result != 0) && (std::abs(value) > (FIXED_POINT_OFFSET * (MAX_VALUE / std::abs(result))))) { + error = Error::TooLarge; + break; + } + + result *= value; + // fixed point offset was multiplied too + result /= FIXED_POINT_OFFSET; + break; + case '/': + // check for zero division + if (value == 0) { + error = Error::ZeroDivision; + break; + } + + // fixed point offset will be divided too + result *= FIXED_POINT_OFFSET; + result /= value; + break; + + default: + break; + } +} diff --git a/src/displayapp/screens/Calculator.h b/src/displayapp/screens/Calculator.h new file mode 100644 index 00000000..25bb67a7 --- /dev/null +++ b/src/displayapp/screens/Calculator.h @@ -0,0 +1,87 @@ +#pragma once + +#include "displayapp/screens/Screen.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" + +namespace { + constexpr int64_t powi(int64_t base, uint8_t exponent) { + int64_t value = 1; + while (exponent) { + value *= base; + exponent--; + } + return value; + } +} + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Calculator : public Screen { + public: + ~Calculator() override; + + Calculator(); + + void OnButtonEvent(lv_obj_t* obj, lv_event_t event); + + private: + lv_obj_t* buttonMatrix {}; + lv_obj_t* valueLabel {}; + lv_obj_t* resultLabel {}; + + void Eval(); + void ResetInput(); + void HandleInput(); + void UpdateValueLabel(); + void UpdateResultLabel() const; + void UpdateOperation() const; + + // change this if you want to change the number of decimals + // keep in mind, that we only have 12 digits in total (see MAX_DIGITS) + // due to the fixed point implementation + static constexpr uint8_t N_DECIMALS = 3; + // this is the constant default offset + static constexpr int64_t FIXED_POINT_OFFSET = powi(10, N_DECIMALS); + // this is the current offset, may vary after pressing '.' + int64_t offset = FIXED_POINT_OFFSET; + + // the screen can show 12 chars + // but two are needed for '.' and '-' + static constexpr uint8_t MAX_DIGITS = 12; + static constexpr int64_t MAX_VALUE = powi(10, MAX_DIGITS) - 1; + // this is assumed in the multiplication overflow! + static constexpr int64_t MIN_VALUE = -MAX_VALUE; + + int64_t value = 0; + int64_t result = 0; + char operation = ' '; + bool equalSignPressedBefore = false; + + enum Error { + TooLarge, + ZeroDivision, + None, + }; + + Error error = Error::None; + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::Calculator; + static constexpr const char* icon = Screens::Symbols::calculator; + + static Screens::Screen* Create(AppControllers& /* controllers */) { + return new Screens::Calculator(); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; + }; + } +} diff --git a/src/displayapp/screens/Dice.h b/src/displayapp/screens/Dice.h index da91657d..d12848d3 100644 --- a/src/displayapp/screens/Dice.h +++ b/src/displayapp/screens/Dice.h @@ -56,6 +56,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/FirmwareUpdate.cpp b/src/displayapp/screens/FirmwareUpdate.cpp index c40240c9..7d00ef39 100644 --- a/src/displayapp/screens/FirmwareUpdate.cpp +++ b/src/displayapp/screens/FirmwareUpdate.cpp @@ -2,6 +2,7 @@ #include #include "components/ble/BleController.h" #include "displayapp/DisplayApp.h" +#include "displayapp/InfiniTimeTheme.h" using namespace Pinetime::Applications::Screens; @@ -12,6 +13,9 @@ FirmwareUpdate::FirmwareUpdate(const Pinetime::Controllers::Ble& bleController) lv_obj_align(titleLabel, nullptr, LV_ALIGN_IN_TOP_MID, 0, 50); bar1 = lv_bar_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_color(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_bg_opa(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100); + lv_obj_set_style_local_radius(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); lv_obj_set_size(bar1, 200, 30); lv_obj_align(bar1, nullptr, LV_ALIGN_CENTER, 0, 0); lv_bar_set_range(bar1, 0, 1000); @@ -75,7 +79,7 @@ void FirmwareUpdate::DisplayProgression() const { const uint32_t total = bleController.FirmwareUpdateTotalBytes(); const int16_t permille = current / (total / 1000); - lv_label_set_text_fmt(percentLabel, "%d %%", permille / 10); + lv_label_set_text_fmt(percentLabel, "%d%%", permille / 10); lv_bar_set_value(bar1, permille, LV_ANIM_OFF); } diff --git a/src/displayapp/screens/FlashLight.cpp b/src/displayapp/screens/FlashLight.cpp index f169fac1..7e0caff1 100644 --- a/src/displayapp/screens/FlashLight.cpp +++ b/src/displayapp/screens/FlashLight.cpp @@ -15,7 +15,7 @@ namespace { } FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController) - : systemTask {systemTask}, brightnessController {brightnessController} { + : wakeLock(systemTask), brightnessController {brightnessController} { previousBrightnessLevel = brightnessController.Level(); brightnessController.Set(Controllers::BrightnessController::Levels::Low); @@ -47,14 +47,13 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo backgroundAction->user_data = this; lv_obj_set_event_cb(backgroundAction, EventHandler); - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); } FlashLight::~FlashLight() { lv_obj_clean(lv_scr_act()); lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); brightnessController.Set(previousBrightnessLevel); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); } void FlashLight::SetColors() { diff --git a/src/displayapp/screens/FlashLight.h b/src/displayapp/screens/FlashLight.h index c5404e93..00ef4a7e 100644 --- a/src/displayapp/screens/FlashLight.h +++ b/src/displayapp/screens/FlashLight.h @@ -3,6 +3,7 @@ #include "displayapp/screens/Screen.h" #include "components/brightness/BrightnessController.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" #include #include @@ -23,7 +24,7 @@ namespace Pinetime { void SetIndicators(); void SetColors(); - Pinetime::System::SystemTask& systemTask; + Pinetime::System::WakeLock wakeLock; Controllers::BrightnessController& brightnessController; Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High; diff --git a/src/displayapp/screens/HeartRate.cpp b/src/displayapp/screens/HeartRate.cpp index 9677be3b..1a84d349 100644 --- a/src/displayapp/screens/HeartRate.cpp +++ b/src/displayapp/screens/HeartRate.cpp @@ -29,7 +29,7 @@ namespace { } HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, System::SystemTask& systemTask) - : heartRateController {heartRateController}, systemTask {systemTask} { + : heartRateController {heartRateController}, wakeLock(systemTask) { bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; label_hr = lv_label_create(lv_scr_act(), nullptr); @@ -63,7 +63,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst label_startStop = lv_label_create(btn_startStop, nullptr); UpdateStartStopButton(isHrRunning); if (isHrRunning) { - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); } taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this); @@ -72,7 +72,6 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst HeartRate::~HeartRate() { lv_task_del(taskRefresh); lv_obj_clean(lv_scr_act()); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); } void HeartRate::Refresh() { @@ -101,12 +100,12 @@ void HeartRate::OnStartStopEvent(lv_event_t event) { if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) { heartRateController.Start(); UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight); } else { heartRateController.Stop(); UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); + wakeLock.Release(); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); } } diff --git a/src/displayapp/screens/HeartRate.h b/src/displayapp/screens/HeartRate.h index bf39209a..69c935de 100644 --- a/src/displayapp/screens/HeartRate.h +++ b/src/displayapp/screens/HeartRate.h @@ -4,6 +4,7 @@ #include #include "displayapp/screens/Screen.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" #include "Symbols.h" #include #include @@ -27,7 +28,7 @@ namespace Pinetime { private: Controllers::HeartRateController& heartRateController; - Pinetime::System::SystemTask& systemTask; + Pinetime::System::WakeLock wakeLock; void UpdateStartStopButton(bool isRunning); lv_obj_t* label_hr; lv_obj_t* label_bpm; @@ -47,6 +48,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::HeartRate(controllers.heartRateController, *controllers.systemTask); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/InfiniPaint.h b/src/displayapp/screens/InfiniPaint.h index b1f9741a..06552abb 100644 --- a/src/displayapp/screens/InfiniPaint.h +++ b/src/displayapp/screens/InfiniPaint.h @@ -47,6 +47,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::InfiniPaint(controllers.lvgl, controllers.motorController); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/Metronome.cpp b/src/displayapp/screens/Metronome.cpp index 314fde73..6b758470 100644 --- a/src/displayapp/screens/Metronome.cpp +++ b/src/displayapp/screens/Metronome.cpp @@ -22,7 +22,7 @@ namespace { } Metronome::Metronome(Controllers::MotorController& motorController, System::SystemTask& systemTask) - : motorController {motorController}, systemTask {systemTask} { + : motorController {motorController}, wakeLock(systemTask) { bpmArc = lv_arc_create(lv_scr_act(), nullptr); bpmArc->user_data = this; @@ -72,7 +72,6 @@ Metronome::Metronome(Controllers::MotorController& motorController, System::Syst Metronome::~Metronome() { lv_task_del(taskRefresh); - systemTask.PushMessage(System::Messages::EnableSleeping); lv_obj_clean(lv_scr_act()); } @@ -128,12 +127,12 @@ void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) { metronomeStarted = !metronomeStarted; if (metronomeStarted) { lv_label_set_text_static(lblPlayPause, Symbols::pause); - systemTask.PushMessage(System::Messages::DisableSleeping); + wakeLock.Lock(); startTime = xTaskGetTickCount(); counter = 1; } else { lv_label_set_text_static(lblPlayPause, Symbols::play); - systemTask.PushMessage(System::Messages::EnableSleeping); + wakeLock.Release(); } } break; diff --git a/src/displayapp/screens/Metronome.h b/src/displayapp/screens/Metronome.h index c498048e..595b30ca 100644 --- a/src/displayapp/screens/Metronome.h +++ b/src/displayapp/screens/Metronome.h @@ -1,6 +1,7 @@ #pragma once #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" #include "components/motor/MotorController.h" #include "displayapp/screens/Screen.h" #include "Symbols.h" @@ -21,7 +22,7 @@ namespace Pinetime { TickType_t startTime = 0; TickType_t tappedTime = 0; Controllers::MotorController& motorController; - System::SystemTask& systemTask; + System::WakeLock wakeLock; int16_t bpm = 120; uint8_t bpb = 4; uint8_t counter = 1; @@ -46,6 +47,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Metronome(controllers.motorController, *controllers.systemTask); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/Motion.h b/src/displayapp/screens/Motion.h index e13e068c..7a4d156a 100644 --- a/src/displayapp/screens/Motion.h +++ b/src/displayapp/screens/Motion.h @@ -41,6 +41,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Motion(controllers.motionController); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/Music.cpp b/src/displayapp/screens/Music.cpp index 0763da0f..0bd0965f 100644 --- a/src/displayapp/screens/Music.cpp +++ b/src/displayapp/screens/Music.cpp @@ -23,6 +23,7 @@ #include "displayapp/icons/music/disc.c" #include "displayapp/icons/music/disc_f_1.c" #include "displayapp/icons/music/disc_f_2.c" +#include "displayapp/InfiniTimeTheme.h" using namespace Pinetime::Applications::Screens; @@ -52,8 +53,7 @@ Music::Music(Pinetime::Controllers::MusicService& music) : musicService(music) { lv_style_init(&btn_style); lv_style_set_radius(&btn_style, LV_STATE_DEFAULT, 20); - lv_style_set_bg_color(&btn_style, LV_STATE_DEFAULT, LV_COLOR_AQUA); - lv_style_set_bg_opa(&btn_style, LV_STATE_DEFAULT, LV_OPA_50); + lv_style_set_bg_color(&btn_style, LV_STATE_DEFAULT, Colors::bgAlt); btnVolDown = lv_btn_create(lv_scr_act(), nullptr); btnVolDown->user_data = this; @@ -114,18 +114,18 @@ Music::Music(Pinetime::Controllers::MusicService& music) : musicService(music) { constexpr int8_t MIDDLE_OFFSET = -25; txtArtist = lv_label_create(lv_scr_act(), nullptr); lv_label_set_long_mode(txtArtist, LV_LABEL_LONG_SROLL_CIRC); - lv_obj_align(txtArtist, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 1 * FONT_HEIGHT); + lv_obj_align(txtArtist, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 2 * FONT_HEIGHT + LINE_PAD); lv_label_set_align(txtArtist, LV_ALIGN_IN_LEFT_MID); lv_obj_set_width(txtArtist, LV_HOR_RES - 12); - lv_label_set_text_static(txtArtist, "Artist Name"); + lv_label_set_text_static(txtArtist, ""); + lv_obj_set_style_local_text_color(txtArtist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); txtTrack = lv_label_create(lv_scr_act(), nullptr); lv_label_set_long_mode(txtTrack, LV_LABEL_LONG_SROLL_CIRC); - lv_obj_align(txtTrack, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 2 * FONT_HEIGHT + LINE_PAD); - + lv_obj_align(txtTrack, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 1 * FONT_HEIGHT); lv_label_set_align(txtTrack, LV_ALIGN_IN_LEFT_MID); lv_obj_set_width(txtTrack, LV_HOR_RES - 12); - lv_label_set_text_static(txtTrack, "This is a very long getTrack name"); + lv_label_set_text_static(txtTrack, ""); /** Init animation */ imgDisc = lv_img_create(lv_scr_act(), nullptr); diff --git a/src/displayapp/screens/Music.h b/src/displayapp/screens/Music.h index 52253321..acf69c41 100644 --- a/src/displayapp/screens/Music.h +++ b/src/displayapp/screens/Music.h @@ -94,6 +94,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Music(*controllers.musicService); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/Navigation.h b/src/displayapp/screens/Navigation.h index 5c7a0429..95b23d71 100644 --- a/src/displayapp/screens/Navigation.h +++ b/src/displayapp/screens/Navigation.h @@ -67,6 +67,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Navigation(*controllers.navigationService); }; + + static bool IsAvailable(Pinetime::Controllers::FS& filesystem) { + return Screens::Navigation::IsAvailable(filesystem); + }; }; } } diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp index 037c43a7..837c4683 100644 --- a/src/displayapp/screens/Notifications.cpp +++ b/src/displayapp/screens/Notifications.cpp @@ -20,7 +20,7 @@ Notifications::Notifications(DisplayApp* app, notificationManager {notificationManager}, alertNotificationService {alertNotificationService}, motorController {motorController}, - systemTask {systemTask}, + wakeLock(systemTask), mode {mode} { notificationManager.ClearNewNotificationFlag(); @@ -40,7 +40,7 @@ Notifications::Notifications(DisplayApp* app, validDisplay = false; } if (mode == Modes::Preview) { - systemTask.PushMessage(System::Messages::DisableSleeping); + wakeLock.Lock(); if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) { motorController.StartRinging(); } else { @@ -65,7 +65,6 @@ Notifications::~Notifications() { lv_task_del(taskRefresh); // make sure we stop any vibrations before exiting motorController.StopRinging(); - systemTask.PushMessage(System::Messages::EnableSleeping); lv_obj_clean(lv_scr_act()); } @@ -82,7 +81,6 @@ void Notifications::Refresh() { } else if (mode == Modes::Preview && dismissingNotification) { running = false; - currentItem = std::make_unique(alertNotificationService, motorController); } else if (dismissingNotification) { dismissingNotification = false; @@ -113,15 +111,15 @@ void Notifications::Refresh() { alertNotificationService, motorController); } else { - currentItem = std::make_unique(alertNotificationService, motorController); + running = false; } } - running = currentItem->IsRunning() && running; + running = running && currentItem->IsRunning(); } void Notifications::OnPreviewInteraction() { - systemTask.PushMessage(System::Messages::EnableSleeping); + wakeLock.Release(); motorController.StopRinging(); if (timeoutLine != nullptr) { lv_obj_del(timeoutLine); @@ -173,7 +171,9 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) { } else if (nextMessage.valid) { currentId = nextMessage.id; } else { - // don't update id, won't be found be refresh and try to load latest message or no message box + // don't update id, notification manager will try to fetch + // but not find it. Refresh will try to load latest message + // or dismiss to watchface } DismissToBlack(); return true; @@ -246,8 +246,8 @@ namespace { Notifications::NotificationItem::NotificationItem(Pinetime::Controllers::AlertNotificationService& alertNotificationService, Pinetime::Controllers::MotorController& motorController) - : NotificationItem("Notification", - "No notification to display", + : NotificationItem("Notifications", + "No notifications to display", 0, Controllers::NotificationManager::Categories::Unknown, 0, diff --git a/src/displayapp/screens/Notifications.h b/src/displayapp/screens/Notifications.h index 114316b3..8488dc5b 100644 --- a/src/displayapp/screens/Notifications.h +++ b/src/displayapp/screens/Notifications.h @@ -8,6 +8,7 @@ #include "components/ble/NotificationManager.h" #include "components/motor/MotorController.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" namespace Pinetime { namespace Controllers { @@ -73,7 +74,7 @@ namespace Pinetime { Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Controllers::AlertNotificationService& alertNotificationService; Pinetime::Controllers::MotorController& motorController; - System::SystemTask& systemTask; + System::WakeLock wakeLock; Modes mode = Modes::Normal; std::unique_ptr currentItem; Pinetime::Controllers::NotificationManager::Notification::Id currentId; diff --git a/src/displayapp/screens/Paddle.h b/src/displayapp/screens/Paddle.h index 586cccf4..aa24bea0 100644 --- a/src/displayapp/screens/Paddle.h +++ b/src/displayapp/screens/Paddle.h @@ -57,6 +57,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Paddle(controllers.lvgl); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/Steps.h b/src/displayapp/screens/Steps.h index 6443582f..1a4fe647 100644 --- a/src/displayapp/screens/Steps.h +++ b/src/displayapp/screens/Steps.h @@ -51,6 +51,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Steps(controllers.motionController, controllers.settingsController); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp index f0359da4..ff852beb 100644 --- a/src/displayapp/screens/StopWatch.cpp +++ b/src/displayapp/screens/StopWatch.cpp @@ -34,7 +34,7 @@ namespace { constexpr TickType_t blinkInterval = pdMS_TO_TICKS(1000); } -StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} { +StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) { static constexpr uint8_t btnWidth = 115; static constexpr uint8_t btnHeight = 80; btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); @@ -79,7 +79,6 @@ StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} { StopWatch::~StopWatch() { lv_task_del(taskRefresh); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); lv_obj_clean(lv_scr_act()); } @@ -135,7 +134,7 @@ void StopWatch::Start() { SetInterfaceRunning(); startTime = xTaskGetTickCount(); currentState = States::Running; - systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); + wakeLock.Lock(); } void StopWatch::Pause() { @@ -145,7 +144,7 @@ void StopWatch::Pause() { oldTimeElapsed = laps[lapsDone]; blinkTime = xTaskGetTickCount() + blinkInterval; currentState = States::Halted; - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); + wakeLock.Release(); } void StopWatch::Refresh() { diff --git a/src/displayapp/screens/StopWatch.h b/src/displayapp/screens/StopWatch.h index 3386d042..7256dd5b 100644 --- a/src/displayapp/screens/StopWatch.h +++ b/src/displayapp/screens/StopWatch.h @@ -7,6 +7,7 @@ #include "portmacro_cmsis.h" #include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" #include "displayapp/apps/Apps.h" #include "displayapp/Controllers.h" #include "Symbols.h" @@ -43,7 +44,7 @@ namespace Pinetime { void Start(); void Pause(); - Pinetime::System::SystemTask& systemTask; + Pinetime::System::WakeLock wakeLock; States currentState = States::Init; TickType_t startTime; TickType_t oldTimeElapsed = 0; @@ -68,6 +69,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::StopWatch(*controllers.systemTask); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index bd958b28..fb93e80e 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -8,6 +8,7 @@ namespace Pinetime { static constexpr const char* batteryHalf = "\xEF\x89\x82"; static constexpr const char* heartBeat = "\xEF\x88\x9E"; static constexpr const char* bluetooth = "\xEF\x8A\x94"; + static constexpr const char* shieldAlt = "\xEF\x8F\xAD"; static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; static constexpr const char* clock = "\xEF\x80\x97"; @@ -39,6 +40,8 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; + static constexpr const char* calculator = "\xEF\x87\xAC"; + static constexpr const char* backspace = "\xEF\x95\x9A"; // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp index dd39f88a..2392f3be 100644 --- a/src/displayapp/screens/SystemInfo.cpp +++ b/src/displayapp/screens/SystemInfo.cpp @@ -40,8 +40,7 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::MotionController& motionController, const Pinetime::Drivers::Cst816S& touchPanel, const Pinetime::Drivers::SpiNorFlash& spiNorFlash) - : app {app}, - dateTimeController {dateTimeController}, + : dateTimeController {dateTimeController}, batteryController {batteryController}, brightnessController {brightnessController}, bleController {bleController}, @@ -196,10 +195,10 @@ std::unique_ptr SystemInfo::CreateScreen3() { "#808080 SPI Flash# %02x-%02x-%02x\n" "\n" "#808080 Memory heap#\n" - " #808080 Free# %d\n" + " #808080 Free# %d/%d\n" " #808080 Min free# %d\n" " #808080 Alloc err# %d\n" - " #808080 Ovrfl err# %d\n", + " #808080 Ovrfl err# %d", bleAddr[5], bleAddr[4], bleAddr[3], @@ -210,6 +209,7 @@ std::unique_ptr SystemInfo::CreateScreen3() { spiFlashId.type, spiFlashId.density, xPortGetFreeHeapSize(), + xPortGetHeapSize(), xPortGetMinimumEverFreeHeapSize(), mallocFailedCount, stackOverflowCount); @@ -241,7 +241,12 @@ std::unique_ptr SystemInfo::CreateScreen4() { lv_table_set_col_width(infoTask, 3, 90); auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr); +// g++ emits a spurious warning (and thus error because we compile with -Werror) +// due to the way std::sort is implemented +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" std::sort(tasksStatus, tasksStatus + nb, sortById); +#pragma GCC diagnostic pop for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) { char buffer[11] = {0}; diff --git a/src/displayapp/screens/SystemInfo.h b/src/displayapp/screens/SystemInfo.h index 3129c463..301662dc 100644 --- a/src/displayapp/screens/SystemInfo.h +++ b/src/displayapp/screens/SystemInfo.h @@ -35,7 +35,6 @@ namespace Pinetime { bool OnTouchEvent(TouchEvents event) override; private: - DisplayApp* app; Pinetime::Controllers::DateTime& dateTimeController; const Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::BrightnessController& brightnessController; diff --git a/src/displayapp/screens/Tile.cpp b/src/displayapp/screens/Tile.cpp index 7c392c59..45f715b5 100644 --- a/src/displayapp/screens/Tile.cpp +++ b/src/displayapp/screens/Tile.cpp @@ -29,9 +29,13 @@ Tile::Tile(uint8_t screenID, Controllers::Settings& settingsController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, std::array& applications) - : app {app}, dateTimeController {dateTimeController}, pageIndicator(screenID, numScreens), statusIcons(batteryController, bleController) { + : app {app}, + dateTimeController {dateTimeController}, + pageIndicator(screenID, numScreens), + statusIcons(batteryController, bleController, alarmController) { settingsController.SetAppMenu(screenID); @@ -66,8 +70,7 @@ Tile::Tile(uint8_t screenID, lv_obj_align(btnm1, nullptr, LV_ALIGN_CENTER, 0, 10); lv_obj_set_style_local_radius(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, 20); - lv_obj_set_style_local_bg_opa(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, LV_OPA_50); - lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, LV_COLOR_AQUA); + lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt); lv_obj_set_style_local_bg_opa(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, LV_OPA_50); lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, Colors::bgDark); lv_obj_set_style_local_pad_all(btnm1, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 0); diff --git a/src/displayapp/screens/Tile.h b/src/displayapp/screens/Tile.h index f1b86246..c16151d0 100644 --- a/src/displayapp/screens/Tile.h +++ b/src/displayapp/screens/Tile.h @@ -28,6 +28,7 @@ namespace Pinetime { Controllers::Settings& settingsController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, std::array& applications); diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp index f6d5e73b..31cde733 100644 --- a/src/displayapp/screens/Timer.cpp +++ b/src/displayapp/screens/Timer.cpp @@ -59,8 +59,8 @@ Timer::Timer(Controllers::Timer& timerController) : timer {timerController} { lv_obj_set_event_cb(btnPlayPause, btnEventHandler); lv_obj_set_size(btnPlayPause, LV_HOR_RES, 50); - txtPlayPause = lv_label_create(lv_scr_act(), nullptr); - lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0); + // Create the label as a child of the button so it stays centered by default + txtPlayPause = lv_label_create(btnPlayPause, nullptr); if (timer.IsRunning()) { SetTimerRunning(); diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h index 0cb2bb08..f4ceddef 100644 --- a/src/displayapp/screens/Timer.h +++ b/src/displayapp/screens/Timer.h @@ -1,7 +1,6 @@ #pragma once #include "displayapp/screens/Screen.h" -#include "components/datetime/DateTimeController.h" #include "systemtask/SystemTask.h" #include "displayapp/LittleVgl.h" #include "displayapp/widgets/Counter.h" @@ -57,5 +56,9 @@ namespace Pinetime::Applications { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Timer(controllers.timer); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } diff --git a/src/displayapp/screens/Twos.h b/src/displayapp/screens/Twos.h index 52449fd3..9ebd7f2e 100644 --- a/src/displayapp/screens/Twos.h +++ b/src/displayapp/screens/Twos.h @@ -45,6 +45,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& /*controllers*/) { return new Screens::Twos(); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/WatchFaceAnalog.cpp b/src/displayapp/screens/WatchFaceAnalog.cpp index 80a1c8b9..3a7a00fa 100644 --- a/src/displayapp/screens/WatchFaceAnalog.cpp +++ b/src/displayapp/screens/WatchFaceAnalog.cpp @@ -1,5 +1,6 @@ #include "displayapp/screens/WatchFaceAnalog.h" #include +#include #include #include "displayapp/screens/BatteryIcon.h" #include "displayapp/screens/BleIcon.h" @@ -15,8 +16,10 @@ namespace { constexpr int16_t MinuteLength = 90; constexpr int16_t SecondLength = 110; + // LVGL sin isn't constexpr (though it could be if it were C++) so fix size here + // All the types are hardcoded anyway and would need changing if the size changed // sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor - const auto LV_TRIG_SCALE = _lv_trigo_sin(90); + constexpr int16_t LV_TRIG_SCALE = std::numeric_limits::max(); // = _lv_trigo_sin(90) int16_t Cosine(int16_t angle) { return _lv_trigo_sin(angle + 90); diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index 2e00ee98..3163c6e7 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -2,6 +2,7 @@ #include #include + #include "displayapp/screens/NotificationIcon.h" #include "displayapp/screens/Symbols.h" #include "displayapp/screens/WeatherSymbols.h" @@ -18,6 +19,7 @@ using namespace Pinetime::Applications::Screens; WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, @@ -30,7 +32,7 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, heartRateController {heartRateController}, motionController {motionController}, weatherService {weatherService}, - statusIcons(batteryController, bleController) { + statusIcons(batteryController, bleController, alarmController) { statusIcons.Create(); @@ -174,13 +176,12 @@ void WatchFaceDigital::Refresh() { if (currentWeather.IsUpdated()) { auto optCurrentWeather = currentWeather.Get(); if (optCurrentWeather) { - int16_t temp = optCurrentWeather->temperature; + int16_t temp = optCurrentWeather->temperature.Celsius(); char tempUnit = 'C'; if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { - temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp); + temp = optCurrentWeather->temperature.Fahrenheit(); 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 { diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index 7bb713cb..3005cea5 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -17,6 +17,7 @@ namespace Pinetime { class Settings; class Battery; class Ble; + class AlarmController; class NotificationManager; class HeartRateController; class MotionController; @@ -30,6 +31,7 @@ namespace Pinetime { WatchFaceDigital(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, @@ -84,6 +86,7 @@ namespace Pinetime { return new Screens::WatchFaceDigital(controllers.dateTimeController, controllers.batteryController, controllers.bleController, + controllers.alarmController, controllers.notificationManager, controllers.settingsController, controllers.heartRateController, diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 4c6fc196..c7939711 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -316,7 +316,7 @@ WatchFaceInfineat::~WatchFaceInfineat() { bool WatchFaceInfineat::OnTouchEvent(Pinetime::Applications::TouchEvents event) { if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) { lv_obj_set_hidden(btnSettings, false); - savedTick = lv_tick_get(); + savedTick = xTaskGetTickCount(); return true; } // Prevent screen from sleeping when double tapping with settings on @@ -434,12 +434,15 @@ void WatchFaceInfineat::Refresh() { batteryPercentRemaining = batteryController.PercentRemaining(); isCharging = batteryController.IsCharging(); - if (batteryController.IsCharging()) { // Charging battery animation - chargingBatteryPercent += 1; + // Charging battery animation + if (batteryController.IsCharging() && (xTaskGetTickCount() - chargingAnimationTick > pdMS_TO_TICKS(150))) { + // Dividing 100 by the height gives the battery percentage required to shift the animation by 1 pixel + chargingBatteryPercent += 100 / lv_obj_get_height(logoPine); if (chargingBatteryPercent > 100) { chargingBatteryPercent = batteryPercentRemaining.Get(); } SetBatteryLevel(chargingBatteryPercent); + chargingAnimationTick = xTaskGetTickCount(); } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { chargingBatteryPercent = batteryPercentRemaining.Get(); SetBatteryLevel(chargingBatteryPercent); @@ -460,7 +463,7 @@ void WatchFaceInfineat::Refresh() { } if (!lv_obj_get_hidden(btnSettings)) { - if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { + if ((savedTick > 0) && (xTaskGetTickCount() - savedTick > pdMS_TO_TICKS(3000))) { lv_obj_set_hidden(btnSettings, true); savedTick = 0; } diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 55c43f98..c8e11039 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -45,8 +45,9 @@ namespace Pinetime { static bool IsAvailable(Pinetime::Controllers::FS& filesystem); private: - uint32_t savedTick = 0; uint8_t chargingBatteryPercent = 101; // not a mistake ;) + TickType_t savedTick = 0; + TickType_t chargingAnimationTick = 0; Utility::DirtyValue batteryPercentRemaining {}; Utility::DirtyValue isCharging {}; diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index e56031f7..ce8f8f7e 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp +++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp @@ -22,7 +22,7 @@ #include "displayapp/screens/WatchFacePineTimeStyle.h" #include #include -#include +#include "displayapp/Colors.h" #include "displayapp/screens/BatteryIcon.h" #include "displayapp/screens/BleIcon.h" #include "displayapp/screens/NotificationIcon.h" @@ -407,7 +407,7 @@ bool WatchFacePineTimeStyle::OnTouchEvent(Pinetime::Applications::TouchEvents ev if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnClose)) { lv_obj_set_hidden(btnSetColor, false); lv_obj_set_hidden(btnSetOpts, false); - savedTick = lv_tick_get(); + savedTick = xTaskGetTickCount(); return true; } if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && (lv_obj_get_hidden(btnClose) == false)) { @@ -543,11 +543,10 @@ void WatchFacePineTimeStyle::Refresh() { if (currentWeather.IsUpdated()) { auto optCurrentWeather = currentWeather.Get(); if (optCurrentWeather) { - int16_t temp = optCurrentWeather->temperature; + int16_t temp = optCurrentWeather->temperature.Celsius(); if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { - temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp); + temp = optCurrentWeather->temperature.Fahrenheit(); } - temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0); lv_label_set_text_fmt(temperature, "%d°", temp); lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); } else { @@ -559,7 +558,7 @@ void WatchFacePineTimeStyle::Refresh() { } if (!lv_obj_get_hidden(btnSetColor)) { - if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) { + if ((savedTick > 0) && (xTaskGetTickCount() - savedTick > pdMS_TO_TICKS(3000))) { lv_obj_set_hidden(btnSetColor, true); lv_obj_set_hidden(btnSetOpts, true); savedTick = 0; diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.h b/src/displayapp/screens/WatchFacePineTimeStyle.h index 72537095..e44a6128 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.h +++ b/src/displayapp/screens/WatchFacePineTimeStyle.h @@ -52,7 +52,7 @@ namespace Pinetime { Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown; Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown; uint8_t currentDay = 0; - uint32_t savedTick = 0; + TickType_t savedTick = 0; Utility::DirtyValue batteryPercentRemaining {}; Utility::DirtyValue isCharging {}; diff --git a/src/displayapp/screens/WatchFacePrideFlag.cpp b/src/displayapp/screens/WatchFacePrideFlag.cpp new file mode 100644 index 00000000..e029c076 --- /dev/null +++ b/src/displayapp/screens/WatchFacePrideFlag.cpp @@ -0,0 +1,337 @@ +#include "displayapp/screens/WatchFacePrideFlag.h" +#include +#include "components/battery/BatteryController.h" +#include "components/ble/BleController.h" +#include "displayapp/screens/Symbols.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void EventHandler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } + + Pinetime::Controllers::Settings::PrideFlag GetNext(Pinetime::Controllers::Settings::PrideFlag prideFlag) { + const auto prideFlagAsInt = static_cast(prideFlag); + Pinetime::Controllers::Settings::PrideFlag nextFlag; + if (prideFlagAsInt < 3) { + nextFlag = static_cast(prideFlagAsInt + 1); + } else { + nextFlag = static_cast(0); + } + return nextFlag; + } + + Pinetime::Controllers::Settings::PrideFlag GetPrevious(Pinetime::Controllers::Settings::PrideFlag prideFlag) { + const auto prideFlagAsInt = static_cast(prideFlag); + Pinetime::Controllers::Settings::PrideFlag prevFlag; + if (prideFlagAsInt > 0) { + prevFlag = static_cast(prideFlagAsInt - 1); + } else { + prevFlag = static_cast(3); + } + return prevFlag; + } + + template + class PrideFlagData { + public: + constexpr PrideFlagData(const std::array& sectionColours, + lv_color_t defaultTopLabelColour, + lv_color_t labelTimeColour, + lv_color_t defaultBottomLabelColour) + : sectionColours {sectionColours}, + defaultTopLabelColour {defaultTopLabelColour}, + labelTimeColour {labelTimeColour}, + defaultBottomLabelColour {defaultBottomLabelColour} { + // Space between adjacent text values calculated according to the following equation + spacing = static_cast(1.5f * static_cast(N) + 40.5f); + } + + std::array sectionColours; + lv_color_t defaultTopLabelColour; + lv_color_t labelTimeColour; + lv_color_t defaultBottomLabelColour; + uint8_t spacing; + }; + + constexpr lv_color_t lightBlue = LV_COLOR_MAKE(0x00, 0xbf, 0xf3); + constexpr lv_color_t lightPink = LV_COLOR_MAKE(0xf4, 0x9a, 0xc1); + constexpr lv_color_t hotPink = LV_COLOR_MAKE(0xd6, 0x02, 0x70); + constexpr lv_color_t grayPurple = LV_COLOR_MAKE(0x9b, 0x4f, 0x96); + constexpr lv_color_t darkBlue = LV_COLOR_MAKE(0x00, 0x38, 0xa8); + constexpr lv_color_t orange = LV_COLOR_MAKE(0xef, 0x76, 0x27); + constexpr lv_color_t lightOrange = LV_COLOR_MAKE(0xff, 0x9b, 0x55); + constexpr lv_color_t lightPurple = LV_COLOR_MAKE(0xd4, 0x61, 0xa6); + constexpr lv_color_t darkPurple = LV_COLOR_MAKE(0xb5, 0x56, 0x90); + constexpr lv_color_t magenta = LV_COLOR_MAKE(0xa5, 0x00, 0x62); + constexpr lv_color_t darkGreen = LV_COLOR_MAKE(0x07, 0x8d, 0x70); + constexpr lv_color_t cyan = LV_COLOR_MAKE(0x26, 0xce, 0xaa); + constexpr lv_color_t lightGreen = LV_COLOR_MAKE(0x98, 0xe8, 0xc1); + constexpr lv_color_t indigo = LV_COLOR_MAKE(0x50, 0x49, 0xcc); + constexpr lv_color_t steelBlue = LV_COLOR_MAKE(0x3d, 0x1a, 0x78); + constexpr std::array gayColours {darkGreen, cyan, lightGreen, LV_COLOR_WHITE, lightBlue, indigo, steelBlue}; + constexpr std::array transColours {lightBlue, lightPink, LV_COLOR_WHITE, lightPink, lightBlue}; + constexpr std::array biColours {hotPink, hotPink, grayPurple, darkBlue, darkBlue}; + constexpr std::array lesbianColours {LV_COLOR_RED, orange, lightOrange, LV_COLOR_WHITE, lightPurple, darkPurple, magenta}; + constexpr PrideFlagData gayFlagData(gayColours, LV_COLOR_BLACK, LV_COLOR_BLACK, LV_COLOR_WHITE); + constexpr PrideFlagData transFlagData(transColours, LV_COLOR_WHITE, LV_COLOR_BLACK, LV_COLOR_WHITE); + constexpr PrideFlagData biFlagData(biColours, LV_COLOR_BLACK, LV_COLOR_WHITE, LV_COLOR_BLACK); + constexpr PrideFlagData lesbianFlagData(lesbianColours, LV_COLOR_WHITE, LV_COLOR_BLACK, LV_COLOR_WHITE); +} + +WatchFacePrideFlag::WatchFacePrideFlag(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController) + : currentDateTime {{}}, + dateTimeController {dateTimeController}, + batteryController {batteryController}, + bleController {bleController}, + notificationManager {notificationManager}, + settingsController {settingsController}, + motionController {motionController} { + + bluetoothStatus = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(bluetoothStatus, ""); + lv_obj_align(bluetoothStatus, nullptr, LV_ALIGN_IN_TOP_RIGHT, -16, 0); + + batteryValue = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_recolor(batteryValue, true); + lv_label_set_align(batteryValue, LV_LABEL_ALIGN_CENTER); + lv_obj_set_auto_realign(batteryValue, true); + + notificationText = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(notificationText, nullptr, LV_ALIGN_IN_LEFT_MID, 0, -110); + lv_obj_set_style_local_text_color(notificationText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + + btnClose = lv_btn_create(lv_scr_act(), nullptr); + btnClose->user_data = this; + lv_obj_set_size(btnClose, 60, 60); + lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80); + lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); + lv_obj_t* lblClose = lv_label_create(btnClose, nullptr); + lv_label_set_text_static(lblClose, "X"); + lv_obj_set_event_cb(btnClose, EventHandler); + lv_obj_set_hidden(btnClose, true); + + btnNextFlag = lv_btn_create(lv_scr_act(), nullptr); + btnNextFlag->user_data = this; + lv_obj_set_size(btnNextFlag, 60, 60); + lv_obj_align(btnNextFlag, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 80); + lv_obj_set_style_local_bg_opa(btnNextFlag, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); + lv_obj_t* lblNextBG = lv_label_create(btnNextFlag, nullptr); + lv_label_set_text_static(lblNextBG, ">"); + lv_obj_set_event_cb(btnNextFlag, EventHandler); + lv_obj_set_hidden(btnNextFlag, true); + + btnPrevFlag = lv_btn_create(lv_scr_act(), nullptr); + btnPrevFlag->user_data = this; + lv_obj_set_size(btnPrevFlag, 60, 60); + lv_obj_align(btnPrevFlag, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 80); + lv_obj_set_style_local_bg_opa(btnPrevFlag, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50); + lv_obj_t* lblPrevFlag = lv_label_create(btnPrevFlag, nullptr); + lv_label_set_text_static(lblPrevFlag, "<"); + lv_obj_set_event_cb(btnPrevFlag, EventHandler); + lv_obj_set_hidden(btnPrevFlag, true); + + labelDate = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_recolor(labelDate, true); + lv_label_set_align(labelDate, LV_LABEL_ALIGN_CENTER); + lv_obj_set_auto_realign(labelDate, true); + + labelTime = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_recolor(labelTime, true); + lv_obj_align(labelTime, lv_scr_act(), LV_ALIGN_CENTER, 0, -1); + lv_label_set_align(labelTime, LV_LABEL_ALIGN_CENTER); + lv_obj_set_style_local_text_font(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); + lv_obj_set_auto_realign(labelTime, true); + + labelDay = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_recolor(labelDay, true); + lv_label_set_align(labelDay, LV_LABEL_ALIGN_CENTER); + lv_obj_set_auto_realign(labelDay, true); + + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_recolor(stepValue, true); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, 96); + lv_label_set_align(stepValue, LV_LABEL_ALIGN_CENTER); + lv_obj_set_auto_realign(stepValue, true); + + UpdateScreen(settingsController.GetPrideFlag()); + + taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); + Refresh(); +} + +WatchFacePrideFlag::~WatchFacePrideFlag() { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); +} + +bool WatchFacePrideFlag::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnClose)) { + lv_obj_set_hidden(btnPrevFlag, false); + lv_obj_set_hidden(btnNextFlag, false); + lv_obj_set_hidden(btnClose, false); + savedTick = xTaskGetTickCount(); + return true; + } + if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) { + return true; + } + return false; +} + +void WatchFacePrideFlag::CloseMenu() { + settingsController.SaveSettings(); + lv_obj_set_hidden(btnClose, true); + lv_obj_set_hidden(btnNextFlag, true); + lv_obj_set_hidden(btnPrevFlag, true); +} + +void WatchFacePrideFlag::Refresh() { + powerPresent = batteryController.IsPowerPresent(); + bleState = bleController.IsConnected(); + batteryPercentRemaining = batteryController.PercentRemaining(); + if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated() || themeChanged) { + lv_label_set_text_fmt(batteryValue, "%d%%", batteryPercentRemaining.Get()); + if (batteryController.IsPowerPresent()) { + lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging"); + } + } + if (bleState.IsUpdated()) { + if (bleState.Get()) { + lv_label_set_text_static(bluetoothStatus, Symbols::bluetooth); + } else { + lv_label_set_text_static(bluetoothStatus, ""); + } + } + + notificationState = notificationManager.AreNewNotificationsAvailable(); + if (notificationState.IsUpdated()) { + if (notificationState.Get()) { + lv_label_set_text_static(notificationText, "You have\nmail!"); + } else { + lv_label_set_text_static(notificationText, ""); + } + } + + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + if (currentDateTime.IsUpdated() || themeChanged) { + uint8_t hour = dateTimeController.Hours(); + const uint8_t minute = dateTimeController.Minutes(); + const uint8_t second = dateTimeController.Seconds(); + + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + if (hour == 0) { + ampmChar = "AM"; + hour = 12; + } else if (hour == 12) { + ampmChar = "PM"; + } else if (hour > 12) { + hour = hour - 12; + ampmChar = "PM"; + } + } + + lv_label_set_text_fmt(labelTime, "%02d:%02d:%02d", hour, minute, second); + + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + if (currentDate.IsUpdated() || ampmChar.IsUpdated() || themeChanged) { + const uint16_t year = dateTimeController.Year(); + const Controllers::DateTime::Months month = dateTimeController.Month(); + const uint8_t day = dateTimeController.Day(); + const Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); + lv_label_set_text_fmt(labelDate, "%02d-%02d-%04d", day, static_cast(month), year); + if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { + lv_label_set_text_fmt(labelDay, "%s %s", dateTimeController.DayOfWeekToStringLow(dayOfWeek), ampmChar); + } else { + lv_label_set_text_fmt(labelDay, "%s", dateTimeController.DayOfWeekToStringLow(dayOfWeek)); + } + } + } + + stepCount = motionController.NbSteps(); + if (stepCount.IsUpdated() || themeChanged) { + lv_label_set_text_fmt(stepValue, "%lu steps", stepCount.Get()); + } + if (themeChanged) { + themeChanged = false; + } +} + +void WatchFacePrideFlag::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + auto valueFlag = settingsController.GetPrideFlag(); + bool flagChanged = false; + if (object == btnClose) { + CloseMenu(); + } + if (object == btnNextFlag) { + valueFlag = GetNext(valueFlag); + flagChanged = true; + } + if (object == btnPrevFlag) { + valueFlag = GetPrevious(valueFlag); + flagChanged = true; + } + settingsController.SetPrideFlag(valueFlag); + if (flagChanged) { + UpdateScreen(valueFlag); + } + } +} + +bool WatchFacePrideFlag::OnButtonPushed() { + if (!lv_obj_get_hidden(btnClose)) { + CloseMenu(); + return true; + } + return false; +} + +void WatchFacePrideFlag::UpdateScreen(const Pinetime::Controllers::Settings::PrideFlag prideFlag) { + auto UseFlagData = [this](PrideFlagData flagData) { + backgroundSections.reserve(N); + for (size_t i = 0; i < N; i++) { + backgroundSections.push_back(lv_obj_create(lv_scr_act(), nullptr)); + lv_obj_set_style_local_bg_color(backgroundSections[i], LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, flagData.sectionColours[i]); + lv_obj_set_size(backgroundSections[i], LV_HOR_RES, (LV_VER_RES / N) + 1); + lv_obj_set_pos(backgroundSections[i], 0, i * LV_VER_RES / N); + lv_obj_set_style_local_radius(backgroundSections[i], LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 0); + lv_obj_move_background(backgroundSections[i]); + } + lv_obj_set_style_local_text_color(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.labelTimeColour); + lv_obj_set_style_local_text_color(batteryValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultTopLabelColour); + lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultTopLabelColour); + lv_obj_set_style_local_text_color(labelDay, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultBottomLabelColour); + lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultBottomLabelColour); + lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_CENTER, 0, -2 * flagData.spacing); + lv_obj_align(labelDate, lv_scr_act(), LV_ALIGN_CENTER, 0, -1 * flagData.spacing); + lv_obj_align(labelDay, lv_scr_act(), LV_ALIGN_CENTER, 0, flagData.spacing); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, 2 * flagData.spacing); + }; + themeChanged = true; + for (lv_obj_t* backgroundSection : backgroundSections) { + lv_obj_del(backgroundSection); + } + backgroundSections.clear(); + switch (prideFlag) { + case Pinetime::Controllers::Settings::PrideFlag::Gay: + UseFlagData(gayFlagData); + break; + case Pinetime::Controllers::Settings::PrideFlag::Trans: + UseFlagData(transFlagData); + break; + case Pinetime::Controllers::Settings::PrideFlag::Bi: + UseFlagData(biFlagData); + break; + case Pinetime::Controllers::Settings::PrideFlag::Lesbian: + UseFlagData(lesbianFlagData); + break; + } +} diff --git a/src/displayapp/screens/WatchFacePrideFlag.h b/src/displayapp/screens/WatchFacePrideFlag.h new file mode 100644 index 00000000..10a94e8b --- /dev/null +++ b/src/displayapp/screens/WatchFacePrideFlag.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "displayapp/screens/Screen.h" +#include "utility/DirtyValue.h" +#include "components/settings/Settings.h" +#include "components/battery/BatteryController.h" + +namespace Pinetime { + namespace Controllers { + class Settings; + class Battery; + class Ble; + class NotificationManager; + class MotionController; + } + + namespace Applications { + namespace Screens { + + class WatchFacePrideFlag : public Screen { + public: + WatchFacePrideFlag(Controllers::DateTime& dateTimeController, + const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + Controllers::NotificationManager& notificationManager, + Controllers::Settings& settingsController, + Controllers::MotionController& motionController); + ~WatchFacePrideFlag() override; + + bool OnTouchEvent(TouchEvents event) override; + bool OnButtonPushed() override; + + void Refresh() override; + + void UpdateSelected(lv_obj_t* object, lv_event_t event); + + private: + void UpdateScreen(Pinetime::Controllers::Settings::PrideFlag); + + Utility::DirtyValue batteryPercentRemaining; + Utility::DirtyValue powerPresent; + Utility::DirtyValue bleState; + Utility::DirtyValue> currentDateTime; + Utility::DirtyValue stepCount; + Utility::DirtyValue notificationState; + Utility::DirtyValue> currentDate; + // Must be wrapped in a dirty value, since it is displayed in the day but is updated twice a day + Utility::DirtyValue ampmChar {"AM"}; + + TickType_t savedTick = 0; + + std::vector backgroundSections; + bool themeChanged = false; + lv_obj_t* bluetoothStatus; + lv_obj_t* labelTime; + lv_obj_t* labelDate; + lv_obj_t* labelDay; + lv_obj_t* batteryValue; + lv_obj_t* stepValue; + lv_obj_t* notificationText; + lv_obj_t* btnClose; + lv_obj_t* btnNextFlag; + lv_obj_t* btnPrevFlag; + + Controllers::DateTime& dateTimeController; + const Controllers::Battery& batteryController; + const Controllers::Ble& bleController; + Controllers::NotificationManager& notificationManager; + Controllers::Settings& settingsController; + Controllers::MotionController& motionController; + + lv_task_t* taskRefresh; + void CloseMenu(); + }; + } + + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::PrideFlag; + static constexpr const char* name = "Pride Flag"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFacePrideFlag(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController, + controllers.motionController); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; + } +} diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 5321b7cc..25464c70 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -1,5 +1,7 @@ #include "displayapp/screens/Weather.h" + #include + #include "components/ble/SimpleWeatherService.h" #include "components/datetime/DateTimeController.h" #include "components/settings/Settings.h" @@ -10,31 +12,27 @@ using namespace Pinetime::Applications::Screens; namespace { - lv_color_t TemperatureColor(int16_t temperature) { - if (temperature <= 0) { // freezing + lv_color_t TemperatureColor(Pinetime::Controllers::SimpleWeatherService::Temperature temp) { + if (temp.Celsius() <= 0) { // freezing return Colors::blue; - } else if (temperature <= 400) { // ice + } else if (temp.Celsius() <= 4) { // ice return LV_COLOR_CYAN; - } else if (temperature >= 2700) { // hot + } else if (temp.Celsius() >= 27) { // hot return Colors::deepOrange; } return Colors::orange; // normal } - uint8_t TemperatureStyle(int16_t temperature) { - if (temperature <= 0) { // freezing + uint8_t TemperatureStyle(Pinetime::Controllers::SimpleWeatherService::Temperature temp) { + if (temp.Celsius() <= 0) { // freezing return LV_TABLE_PART_CELL3; - } else if (temperature <= 400) { // ice + } else if (temp.Celsius() <= 4) { // ice return LV_TABLE_PART_CELL4; - } else if (temperature >= 2700) { // hot + } else if (temp.Celsius() >= 27) { // 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) @@ -120,22 +118,25 @@ void Weather::Refresh() { 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)); + int16_t temp = optCurrentWeather->temperature.Celsius(); + int16_t minTemp = optCurrentWeather->minTemperature.Celsius(); + int16_t maxTemp = optCurrentWeather->maxTemperature.Celsius(); 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); + temp = optCurrentWeather->temperature.Fahrenheit(); + minTemp = optCurrentWeather->minTemperature.Fahrenheit(); + maxTemp = optCurrentWeather->maxTemperature.Fahrenheit(); tempUnit = 'F'; } + lv_obj_set_style_local_text_color(temperature, + LV_LABEL_PART_MAIN, + LV_STATE_DEFAULT, + TemperatureColor(optCurrentWeather->temperature)); 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)); + lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); + lv_label_set_text_fmt(minTemperature, "%d°", minTemp); + lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp); } else { lv_label_set_text(icon, ""); lv_label_set_text(condition, ""); @@ -152,24 +153,22 @@ void Weather::Refresh() { if (optCurrentForecast) { std::tm localTime = *std::localtime(reinterpret_cast(&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)); + for (int i = 0; i < optCurrentForecast->nbDays; i++) { + int16_t maxTemp = optCurrentForecast->days[i]->maxTemperature.Celsius(); + int16_t minTemp = optCurrentForecast->days[i]->minTemperature.Celsius(); if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { - maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp); - minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp); + maxTemp = optCurrentForecast->days[i]->maxTemperature.Fahrenheit(); + minTemp = optCurrentForecast->days[i]->minTemperature.Fahrenheit(); } + lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(optCurrentForecast->days[i]->maxTemperature)); + lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(optCurrentForecast->days[i]->minTemperature)); 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(wday)); lv_table_set_cell_value(forecast, 0, i, dayOfWeek); - lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId)); + 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] = " "; diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h index 6975311e..03266be1 100644 --- a/src/displayapp/screens/Weather.h +++ b/src/displayapp/screens/Weather.h @@ -51,6 +51,10 @@ namespace Pinetime { static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Weather(controllers.settingsController, *controllers.weatherController); }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; }; } } diff --git a/src/displayapp/screens/settings/QuickSettings.cpp b/src/displayapp/screens/settings/QuickSettings.cpp index 05484888..c5c3071a 100644 --- a/src/displayapp/screens/settings/QuickSettings.cpp +++ b/src/displayapp/screens/settings/QuickSettings.cpp @@ -33,13 +33,14 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app, Controllers::BrightnessController& brightness, Controllers::MotorController& motorController, Pinetime::Controllers::Settings& settingsController, - const Controllers::Ble& bleController) + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController) : app {app}, dateTimeController {dateTimeController}, brightness {brightness}, motorController {motorController}, settingsController {settingsController}, - statusIcons(batteryController, bleController) { + statusIcons(batteryController, bleController, alarmController) { statusIcons.Create(); diff --git a/src/displayapp/screens/settings/QuickSettings.h b/src/displayapp/screens/settings/QuickSettings.h index 55da6176..87c126b7 100644 --- a/src/displayapp/screens/settings/QuickSettings.h +++ b/src/displayapp/screens/settings/QuickSettings.h @@ -23,7 +23,8 @@ namespace Pinetime { Controllers::BrightnessController& brightness, Controllers::MotorController& motorController, Pinetime::Controllers::Settings& settingsController, - const Controllers::Ble& bleController); + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController); ~QuickSettings() override; diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp index 57a64d7f..bbc188a9 100644 --- a/src/displayapp/screens/settings/SettingDisplay.cpp +++ b/src/displayapp/screens/settings/SettingDisplay.cpp @@ -24,8 +24,7 @@ namespace { constexpr std::array SettingDisplay::options; -SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) - : app {app}, settingsController {settingsController} { +SettingDisplay::SettingDisplay(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} { lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); diff --git a/src/displayapp/screens/settings/SettingDisplay.h b/src/displayapp/screens/settings/SettingDisplay.h index b6d147c8..3bd10a62 100644 --- a/src/displayapp/screens/settings/SettingDisplay.h +++ b/src/displayapp/screens/settings/SettingDisplay.h @@ -14,14 +14,13 @@ namespace Pinetime { class SettingDisplay : public Screen { public: - SettingDisplay(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); + SettingDisplay(Pinetime::Controllers::Settings& settingsController); ~SettingDisplay() override; void UpdateSelected(lv_obj_t* object, lv_event_t event); void ToggleAlwaysOn(); private: - DisplayApp* app; static constexpr std::array options = {5000, 7000, 10000, 15000, 20000, 30000}; Controllers::Settings& settingsController; diff --git a/src/displayapp/screens/settings/SettingHeartRate.cpp b/src/displayapp/screens/settings/SettingHeartRate.cpp deleted file mode 100644 index fdba9af1..00000000 --- a/src/displayapp/screens/settings/SettingHeartRate.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "displayapp/screens/settings/SettingHeartRate.h" -#include -#include "displayapp/screens/Styles.h" -#include "displayapp/screens/Screen.h" -#include "displayapp/screens/Symbols.h" -#include -#include - -using namespace Pinetime::Applications::Screens; - -namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { - auto* screen = static_cast(obj->user_data); - screen->UpdateSelected(obj, event); - } -} - -constexpr std::array SettingHeartRate::options; - -SettingHeartRate::SettingHeartRate(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} { - - lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); - - lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); - lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); - lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); - lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); - - lv_obj_set_pos(container1, 10, 60); - lv_obj_set_width(container1, LV_HOR_RES - 20); - lv_obj_set_height(container1, LV_VER_RES - 50); - lv_cont_set_layout(container1, LV_LAYOUT_PRETTY_TOP); - - lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); - lv_label_set_text_static(title, "Backg. Interval"); - lv_label_set_text(title, "Backg. Interval"); - lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); - lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 10, 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_RED); - lv_label_set_text_static(icon, Symbols::heartBeat); - lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); - lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); - - for (unsigned int i = 0; i < options.size(); i++) { - cbOption[i] = lv_checkbox_create(container1, nullptr); - lv_checkbox_set_text(cbOption[i], options[i].name); - cbOption[i]->user_data = this; - lv_obj_set_event_cb(cbOption[i], event_handler); - SetRadioButtonStyle(cbOption[i]); - - if (settingsController.GetHeartRateBackgroundMeasurementInterval() == options[i].interval) { - lv_checkbox_set_checked(cbOption[i], true); - } - } -} - -SettingHeartRate::~SettingHeartRate() { - lv_obj_clean(lv_scr_act()); - settingsController.SaveSettings(); -} - -void SettingHeartRate::UpdateSelected(lv_obj_t* object, lv_event_t event) { - if (event == LV_EVENT_CLICKED) { - for (unsigned int i = 0; i < options.size(); i++) { - if (object == cbOption[i]) { - lv_checkbox_set_checked(cbOption[i], true); - settingsController.SetHeartRateBackgroundMeasurementInterval(options[i].interval); - } else { - lv_checkbox_set_checked(cbOption[i], false); - } - } - } -} diff --git a/src/displayapp/screens/settings/SettingHeartRate.h b/src/displayapp/screens/settings/SettingHeartRate.h deleted file mode 100644 index 3cb08907..00000000 --- a/src/displayapp/screens/settings/SettingHeartRate.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include - -#include "components/settings/Settings.h" -#include "displayapp/screens/ScreenList.h" -#include "displayapp/screens/Screen.h" -#include "displayapp/screens/Symbols.h" -#include "displayapp/screens/CheckboxList.h" - -namespace Pinetime { - - namespace Applications { - namespace Screens { - - struct Option { - const Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval interval; - const char* name; - }; - - class SettingHeartRate : public Screen { - public: - SettingHeartRate(Pinetime::Controllers::Settings& settings); - ~SettingHeartRate() override; - - void UpdateSelected(lv_obj_t* object, lv_event_t event); - - private: - Pinetime::Controllers::Settings& settingsController; - - static constexpr std::array options = {{ - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off, " Off"}, - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous, "Cont"}, - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds, " 10s"}, - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds, " 30s"}, - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute, " 1m"}, - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes, " 5m"}, - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenMinutes, " 10m"}, - {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtyMinutes, " 30m"}, - }}; - - lv_obj_t* cbOption[options.size()]; - }; - } - } -} diff --git a/src/displayapp/screens/settings/SettingOTA.cpp b/src/displayapp/screens/settings/SettingOTA.cpp new file mode 100644 index 00000000..ad123dec --- /dev/null +++ b/src/displayapp/screens/settings/SettingOTA.cpp @@ -0,0 +1,58 @@ +#include "displayapp/screens/settings/SettingOTA.h" +#include +#include "displayapp/DisplayApp.h" +#include "displayapp/Messages.h" +#include "displayapp/screens/Styles.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + struct Option { + const char* name; + Pinetime::Controllers::Settings::DfuAndFsMode mode; + }; + + constexpr std::array options = {{ + {"Enabled", Pinetime::Controllers::Settings::DfuAndFsMode::Enabled}, + {"Disabled", Pinetime::Controllers::Settings::DfuAndFsMode::Disabled}, + {"Till reboot", Pinetime::Controllers::Settings::DfuAndFsMode::EnabledTillReboot}, + }}; + + std::array CreateOptionArray() { + std::array optionArray; + for (size_t i = 0; i < CheckboxList::MaxItems; i++) { + if (i >= options.size()) { + optionArray[i].name = ""; + optionArray[i].enabled = false; + } else { + optionArray[i].name = options[i].name; + optionArray[i].enabled = true; + } + } + return optionArray; + }; +} + +SettingOTA::SettingOTA(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) + : app {app}, + settingsController {settingsController}, + checkboxList( + 0, + 1, + "Firmware & files", + Symbols::shieldAlt, + settingsController.GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::Enabled ? 0 + : settingsController.GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::EnabledTillReboot ? 2 + : 1, + [&settings = settingsController](uint32_t index) { + settings.SetDfuAndFsMode(options[index].mode); + }, + CreateOptionArray()) { +} + +SettingOTA::~SettingOTA() { + lv_obj_clean(lv_scr_act()); + settingsController.SaveSettings(); +} diff --git a/src/displayapp/screens/settings/SettingOTA.h b/src/displayapp/screens/settings/SettingOTA.h new file mode 100644 index 00000000..13c9a33d --- /dev/null +++ b/src/displayapp/screens/settings/SettingOTA.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include "components/settings/Settings.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/CheckboxList.h" + +namespace Pinetime { + + namespace Applications { + namespace Screens { + + class SettingOTA : public Screen { + public: + SettingOTA(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); + ~SettingOTA() override; + + private: + DisplayApp* app; + Pinetime::Controllers::Settings& settingsController; + CheckboxList checkboxList; + }; + } + } +} diff --git a/src/displayapp/screens/settings/SettingSetDateTime.cpp b/src/displayapp/screens/settings/SettingSetDateTime.cpp index cf9b0638..8926ff31 100644 --- a/src/displayapp/screens/settings/SettingSetDateTime.cpp +++ b/src/displayapp/screens/settings/SettingSetDateTime.cpp @@ -15,8 +15,7 @@ bool SettingSetDateTime::OnTouchEvent(Pinetime::Applications::TouchEvents event) SettingSetDateTime::SettingSetDateTime(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::DateTime& dateTimeController, Pinetime::Controllers::Settings& settingsController) - : app {app}, - dateTimeController {dateTimeController}, + : dateTimeController {dateTimeController}, settingsController {settingsController}, screens {app, 0, diff --git a/src/displayapp/screens/settings/SettingSetDateTime.h b/src/displayapp/screens/settings/SettingSetDateTime.h index 051b1abe..dea283f8 100644 --- a/src/displayapp/screens/settings/SettingSetDateTime.h +++ b/src/displayapp/screens/settings/SettingSetDateTime.h @@ -20,7 +20,6 @@ namespace Pinetime { void Quit(); private: - DisplayApp* app; Controllers::DateTime& dateTimeController; Controllers::Settings& settingsController; diff --git a/src/displayapp/screens/settings/SettingWakeUp.cpp b/src/displayapp/screens/settings/SettingWakeUp.cpp index 4649dc82..3413257d 100644 --- a/src/displayapp/screens/settings/SettingWakeUp.cpp +++ b/src/displayapp/screens/settings/SettingWakeUp.cpp @@ -8,8 +8,6 @@ using namespace Pinetime::Applications::Screens; -constexpr std::array SettingWakeUp::options; - namespace { void event_handler(lv_obj_t* obj, lv_event_t event) { auto* screen = static_cast(obj->user_data); @@ -45,7 +43,7 @@ SettingWakeUp::SettingWakeUp(Pinetime::Controllers::Settings& settingsController for (unsigned int i = 0; i < options.size(); i++) { cbOption[i] = lv_checkbox_create(container1, nullptr); - lv_checkbox_set_text(cbOption[i], options[i].name); + lv_checkbox_set_text_static(cbOption[i], options[i].name); if (settingsController.isWakeUpModeOn(static_cast(i))) { lv_checkbox_set_checked(cbOption[i], true); } diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp index e01e9f84..0d5168d2 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.cpp +++ b/src/displayapp/screens/settings/SettingWatchFace.cpp @@ -54,8 +54,7 @@ SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, std::array&& watchfaceItems, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem) - : app {app}, - watchfaceItems {std::move(watchfaceItems)}, + : watchfaceItems {std::move(watchfaceItems)}, settingsController {settingsController}, filesystem {filesystem}, screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} { diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 4c75b0ab..9edc1f7a 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -34,7 +34,6 @@ namespace Pinetime { bool OnTouchEvent(TouchEvents event) override; private: - DisplayApp* app; auto CreateScreenList() const; std::unique_ptr CreateScreen(unsigned int screenNum) const; diff --git a/src/displayapp/screens/settings/Settings.h b/src/displayapp/screens/settings/Settings.h index 4f1082ad..370e83f4 100644 --- a/src/displayapp/screens/settings/Settings.h +++ b/src/displayapp/screens/settings/Settings.h @@ -38,14 +38,14 @@ namespace Pinetime { {Symbols::home, "Watch face", Apps::SettingWatchFace}, {Symbols::shoe, "Steps", Apps::SettingSteps}, - {Symbols::heartBeat, "Heartrate", Apps::SettingHeartRate}, - {Symbols::clock, "Date&Time", Apps::SettingSetDateTime}, + {Symbols::clock, "Date & Time", Apps::SettingSetDateTime}, {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat}, - {Symbols::batteryHalf, "Battery", Apps::BatteryInfo}, + {Symbols::clock, "Chimes", Apps::SettingChimes}, {Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold}, {Symbols::check, "Firmware", Apps::FirmwareValidation}, + {Symbols::shieldAlt, "Over-the-air", Apps::SettingOTA}, {Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth}, {Symbols::list, "About", Apps::SysInfo}, diff --git a/src/displayapp/widgets/StatusIcons.cpp b/src/displayapp/widgets/StatusIcons.cpp index 423b53d9..777731a5 100644 --- a/src/displayapp/widgets/StatusIcons.cpp +++ b/src/displayapp/widgets/StatusIcons.cpp @@ -1,10 +1,13 @@ #include "displayapp/widgets/StatusIcons.h" #include "displayapp/screens/Symbols.h" +#include "components/alarm/AlarmController.h" using namespace Pinetime::Applications::Widgets; -StatusIcons::StatusIcons(const Controllers::Battery& batteryController, const Controllers::Ble& bleController) - : batteryIcon(true), batteryController {batteryController}, bleController {bleController} { +StatusIcons::StatusIcons(const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController) + : batteryIcon(true), batteryController {batteryController}, bleController {bleController}, alarmController {alarmController} { } void StatusIcons::Create() { @@ -20,6 +23,9 @@ void StatusIcons::Create() { batteryPlug = lv_label_create(container, nullptr); lv_label_set_text_static(batteryPlug, Screens::Symbols::plug); + alarmIcon = lv_label_create(container, nullptr); + lv_label_set_text_static(alarmIcon, Screens::Symbols::bell); + batteryIcon.Create(container); lv_obj_align(container, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); @@ -37,6 +43,11 @@ void StatusIcons::Update() { batteryIcon.SetBatteryPercentage(batteryPercent); } + alarmEnabled = alarmController.IsEnabled(); + if (alarmEnabled.IsUpdated()) { + lv_obj_set_hidden(alarmIcon, !alarmEnabled.Get()); + } + bleState = bleController.IsConnected(); bleRadioEnabled = bleController.IsRadioEnabled(); if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) { diff --git a/src/displayapp/widgets/StatusIcons.h b/src/displayapp/widgets/StatusIcons.h index 9e21d3ad..5524e996 100644 --- a/src/displayapp/widgets/StatusIcons.h +++ b/src/displayapp/widgets/StatusIcons.h @@ -5,6 +5,7 @@ #include "displayapp/screens/Screen.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" #include "displayapp/screens/BatteryIcon.h" #include "utility/DirtyValue.h" @@ -13,7 +14,9 @@ namespace Pinetime { namespace Widgets { class StatusIcons { public: - StatusIcons(const Controllers::Battery& batteryController, const Controllers::Ble& bleController); + StatusIcons(const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController); void Align(); void Create(); @@ -27,13 +30,16 @@ namespace Pinetime { Screens::BatteryIcon batteryIcon; const Pinetime::Controllers::Battery& batteryController; const Controllers::Ble& bleController; + const Controllers::AlarmController& alarmController; Utility::DirtyValue batteryPercentRemaining {}; Utility::DirtyValue powerPresent {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; + Utility::DirtyValue alarmEnabled {}; lv_obj_t* bleIcon; + lv_obj_t* alarmIcon; lv_obj_t* batteryPlug; lv_obj_t* container; }; diff --git a/src/drivers/Hrs3300.cpp b/src/drivers/Hrs3300.cpp index 33889b6f..a4b72479 100644 --- a/src/drivers/Hrs3300.cpp +++ b/src/drivers/Hrs3300.cpp @@ -6,6 +6,7 @@ #include "drivers/Hrs3300.h" #include +#include #include #include @@ -67,40 +68,37 @@ void Hrs3300::Disable() { WriteRegister(static_cast(Registers::PDriver), 0); } -uint32_t Hrs3300::ReadHrs() { - auto m = ReadRegister(static_cast(Registers::C0DataM)); - auto h = ReadRegister(static_cast(Registers::C0DataH)); - auto l = ReadRegister(static_cast(Registers::C0dataL)); - return ((l & 0x30) << 12) | (m << 8) | ((h & 0x0f) << 4) | (l & 0x0f); -} +Hrs3300::PackedHrsAls Hrs3300::ReadHrsAls() { + constexpr Registers dataRegisters[] = + {Registers::C1dataM, Registers::C0DataM, Registers::C0DataH, Registers::C1dataH, Registers::C1dataL, Registers::C0dataL}; + // Calculate smallest register address + constexpr uint8_t baseOffset = static_cast(*std::min_element(std::begin(dataRegisters), std::end(dataRegisters))); + // Calculate largest address to determine length of read needed + // Add one to largest relative index to find the length + constexpr uint8_t length = static_cast(*std::max_element(std::begin(dataRegisters), std::end(dataRegisters))) - baseOffset + 1; -uint32_t Hrs3300::ReadAls() { - auto m = ReadRegister(static_cast(Registers::C1dataM)); - auto h = ReadRegister(static_cast(Registers::C1dataH)); - auto l = ReadRegister(static_cast(Registers::C1dataL)); - return ((h & 0x3f) << 11) | (m << 3) | (l & 0x07); -} - -void Hrs3300::SetGain(uint8_t gain) { - constexpr uint8_t maxGain = 64U; - gain = std::min(gain, maxGain); - uint8_t hgain = 0; - while ((1 << hgain) < gain) { - ++hgain; + Hrs3300::PackedHrsAls res; + uint8_t buf[length]; + auto ret = twiMaster.Read(twiAddress, baseOffset, buf, length); + if (ret != TwiMaster::ErrorCodes::NoError) { + NRF_LOG_INFO("READ ERROR"); } + // hrs + uint8_t m = static_cast(Registers::C0DataM) - baseOffset; + uint8_t h = static_cast(Registers::C0DataH) - baseOffset; + uint8_t l = static_cast(Registers::C0dataL) - baseOffset; + // There are two extra bits (17 and 18) but they are not read here + // as resolutions >16bit aren't practically useful (too slow) and + // all hrs values throughout InfiniTime are 16bit + res.hrs = (buf[m] << 8) | ((buf[h] & 0x0f) << 4) | (buf[l] & 0x0f); - WriteRegister(static_cast(Registers::Hgain), hgain << 2); -} + // als + m = static_cast(Registers::C1dataM) - baseOffset; + h = static_cast(Registers::C1dataH) - baseOffset; + l = static_cast(Registers::C1dataL) - baseOffset; + res.als = ((buf[h] & 0x3f) << 11) | (buf[m] << 3) | (buf[l] & 0x07); -void Hrs3300::SetDrive(uint8_t drive) { - auto en = ReadRegister(static_cast(Registers::Enable)); - auto pd = ReadRegister(static_cast(Registers::PDriver)); - - en = (en & 0xf7) | ((drive & 2) << 2); - pd = (pd & 0xbf) | ((drive & 1) << 6); - - WriteRegister(static_cast(Registers::Enable), en); - WriteRegister(static_cast(Registers::PDriver), pd); + return res; } void Hrs3300::WriteRegister(uint8_t reg, uint8_t data) { diff --git a/src/drivers/Hrs3300.h b/src/drivers/Hrs3300.h index 8bbdc69a..6f721448 100644 --- a/src/drivers/Hrs3300.h +++ b/src/drivers/Hrs3300.h @@ -21,6 +21,11 @@ namespace Pinetime { Hgain = 0x17 }; + struct PackedHrsAls { + uint16_t hrs; + uint16_t als; + }; + Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress); Hrs3300(const Hrs3300&) = delete; Hrs3300& operator=(const Hrs3300&) = delete; @@ -30,10 +35,7 @@ namespace Pinetime { void Init(); void Enable(); void Disable(); - uint32_t ReadHrs(); - uint32_t ReadAls(); - void SetGain(uint8_t gain); - void SetDrive(uint8_t drive); + PackedHrsAls ReadHrsAls(); private: TwiMaster& twiMaster; diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 0df19b45..482fbad6 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -175,9 +175,8 @@ void St7789::IdleFrameRateOn() { // According to the datasheet, these controls should apply only to partial/idle mode // However they appear to apply to normal mode, so we have to enable/disable // every time we enter/exit always on - // In testing this divider appears to actually be 16x? constexpr uint8_t args[] = { - 0x13, // Enable frame rate control for partial/idle mode, 8x frame divider + 0x12, // Enable frame rate control for partial/idle mode, 4x frame divider 0x1e, // Idle mode frame rate 0x1e, // Partial mode frame rate (unused) }; diff --git a/src/heartratetask/HeartRateTask.cpp b/src/heartratetask/HeartRateTask.cpp index 0c082a5d..8a5a871b 100644 --- a/src/heartratetask/HeartRateTask.cpp +++ b/src/heartratetask/HeartRateTask.cpp @@ -5,23 +5,8 @@ using namespace Pinetime::Applications; -TickType_t CurrentTaskDelay(HeartRateTask::States state, TickType_t ppgDeltaTms) { - switch (state) { - case HeartRateTask::States::ScreenOnAndMeasuring: - case HeartRateTask::States::ScreenOffAndMeasuring: - return ppgDeltaTms; - case HeartRateTask::States::ScreenOffAndWaiting: - return pdMS_TO_TICKS(1000); - default: - return portMAX_DELAY; - } -} - - -HeartRateTask::HeartRateTask(Drivers::Hrs3300& heartRateSensor, - Controllers::HeartRateController& controller, - Controllers::Settings& settings) - : heartRateSensor {heartRateSensor}, controller {controller}, settings {settings} { +HeartRateTask::HeartRateTask(Drivers::Hrs3300& heartRateSensor, Controllers::HeartRateController& controller) + : heartRateSensor {heartRateSensor}, controller {controller} { } void HeartRateTask::Start() { @@ -40,40 +25,78 @@ void HeartRateTask::Process(void* instance) { void HeartRateTask::Work() { int lastBpm = 0; - while (true) { - TickType_t delay = CurrentTaskDelay(state, ppg.deltaTms); Messages msg; + uint32_t delay; + if (state == States::Running) { + if (measurementStarted) { + delay = ppg.deltaTms; + } else { + delay = 100; + } + } else { + delay = portMAX_DELAY; + } - if (xQueueReceive(messageQueue, &msg, delay) == pdTRUE) { + if (xQueueReceive(messageQueue, &msg, delay)) { switch (msg) { case Messages::GoToSleep: - HandleGoToSleep(); + StopMeasurement(); + state = States::Idle; break; case Messages::WakeUp: - HandleWakeUp(); + state = States::Running; + if (measurementStarted) { + lastBpm = 0; + StartMeasurement(); + } break; case Messages::StartMeasurement: - HandleStartMeasurement(&lastBpm); + if (measurementStarted) { + break; + } + lastBpm = 0; + StartMeasurement(); + measurementStarted = true; break; case Messages::StopMeasurement: - HandleStopMeasurement(); + if (!measurementStarted) { + break; + } + StopMeasurement(); + measurementStarted = false; break; } } - switch (state) { - case States::ScreenOffAndWaiting: - HandleBackgroundWaiting(); - break; - case States::ScreenOffAndMeasuring: - case States::ScreenOnAndMeasuring: - HandleSensorData(&lastBpm); - break; - case States::ScreenOffAndStopped: - case States::ScreenOnAndStopped: - // nothing to do -> ignore - break; + if (measurementStarted) { + auto sensorData = heartRateSensor.ReadHrsAls(); + int8_t ambient = ppg.Preprocess(sensorData.hrs, sensorData.als); + int bpm = ppg.HeartRate(); + + // If ambient light detected or a reset requested (bpm < 0) + if (ambient > 0) { + // Reset all DAQ buffers + ppg.Reset(true); + // Force state to NotEnoughData (below) + lastBpm = 0; + bpm = 0; + } else if (bpm < 0) { + // Reset all DAQ buffers except HRS buffer + ppg.Reset(false); + // Set HR to zero and update + bpm = 0; + controller.Update(Controllers::HeartRateController::States::Running, bpm); + } + + if (lastBpm == 0 && bpm == 0) { + controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm); + } + + if (bpm != 0) { + lastBpm = bpm; + controller.Update(Controllers::HeartRateController::States::Running, lastBpm); + } } } } @@ -88,7 +111,6 @@ void HeartRateTask::StartMeasurement() { heartRateSensor.Enable(); ppg.Reset(true); vTaskDelay(100); - measurementStart = xTaskGetTickCount(); } void HeartRateTask::StopMeasurement() { @@ -96,165 +118,3 @@ void HeartRateTask::StopMeasurement() { ppg.Reset(true); vTaskDelay(100); } - -void HeartRateTask::StartWaiting() { - StopMeasurement(); - backgroundWaitingStart = xTaskGetTickCount(); -} - -void HeartRateTask::HandleGoToSleep() { - switch (state) { - case States::ScreenOnAndStopped: - state = States::ScreenOffAndStopped; - break; - case States::ScreenOnAndMeasuring: - state = States::ScreenOffAndMeasuring; - break; - case States::ScreenOffAndStopped: - case States::ScreenOffAndWaiting: - case States::ScreenOffAndMeasuring: - // shouldn't happen -> ignore - break; - } -} - -void HeartRateTask::HandleWakeUp() { - switch (state) { - case States::ScreenOffAndStopped: - state = States::ScreenOnAndStopped; - break; - case States::ScreenOffAndMeasuring: - state = States::ScreenOnAndMeasuring; - break; - case States::ScreenOffAndWaiting: - state = States::ScreenOnAndMeasuring; - StartMeasurement(); - break; - case States::ScreenOnAndStopped: - case States::ScreenOnAndMeasuring: - // shouldn't happen -> ignore - break; - } -} - -void HeartRateTask::HandleStartMeasurement(int* lastBpm) { - switch (state) { - case States::ScreenOffAndStopped: - case States::ScreenOnAndStopped: - state = States::ScreenOnAndMeasuring; - *lastBpm = 0; - StartMeasurement(); - break; - case States::ScreenOnAndMeasuring: - case States::ScreenOffAndMeasuring: - case States::ScreenOffAndWaiting: - // shouldn't happen -> ignore - break; - } -} - -void HeartRateTask::HandleStopMeasurement() { - switch (state) { - case States::ScreenOnAndMeasuring: - state = States::ScreenOnAndStopped; - StopMeasurement(); - break; - case States::ScreenOffAndMeasuring: - case States::ScreenOffAndWaiting: - state = States::ScreenOffAndStopped; - StopMeasurement(); - break; - case States::ScreenOnAndStopped: - case States::ScreenOffAndStopped: - // shouldn't happen -> ignore - break; - } -} - -void HeartRateTask::HandleBackgroundWaiting() { - if (!IsBackgroundMeasurementActivated()) { - return; - } - - TickType_t ticksSinceWaitingStart = xTaskGetTickCount() - backgroundWaitingStart; - if (ticksSinceWaitingStart >= GetHeartRateBackgroundMeasurementIntervalInTicks()) { - state = States::ScreenOffAndMeasuring; - StartMeasurement(); - } -} - -void HeartRateTask::HandleSensorData(int* lastBpm) { - int8_t ambient = ppg.Preprocess(heartRateSensor.ReadHrs(), heartRateSensor.ReadAls()); - int bpm = ppg.HeartRate(); - - // If ambient light detected or a reset requested (bpm < 0) - if (ambient > 0) { - // Reset all DAQ buffers - ppg.Reset(true); - } else if (bpm < 0) { - // Reset all DAQ buffers except HRS buffer - ppg.Reset(false); - // Set HR to zero and update - bpm = 0; - } - - if (*lastBpm == 0 && bpm == 0) { - controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm); - } - - if (bpm != 0) { - *lastBpm = bpm; - controller.Update(Controllers::HeartRateController::States::Running, bpm); - if (state == States::ScreenOnAndMeasuring || IsContinuousModeActivated()) { - return; - } - if (state == States::ScreenOffAndMeasuring) { - state = States::ScreenOffAndWaiting; - StartWaiting(); - } - } - TickType_t ticksSinceMeasurementStart = xTaskGetTickCount() - measurementStart; - if (bpm == 0 && state == States::ScreenOffAndMeasuring && !IsContinuousModeActivated() && - ticksSinceMeasurementStart >= DURATION_UNTIL_BACKGROUND_MEASUREMENT_IS_STOPPED) { - state = States::ScreenOffAndWaiting; - StartWaiting(); - } -} - -TickType_t HeartRateTask::GetHeartRateBackgroundMeasurementIntervalInTicks() { - int ms; - switch (settings.GetHeartRateBackgroundMeasurementInterval()) { - case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds: - ms = 10 * 1000; - break; - case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds: - ms = 30 * 1000; - break; - case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute: - ms = 60 * 1000; - break; - case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes: - ms = 5 * 60 * 1000; - break; - case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenMinutes: - ms = 10 * 60 * 1000; - break; - case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtyMinutes: - ms = 30 * 60 * 1000; - break; - default: - ms = 0; - break; - } - return pdMS_TO_TICKS(ms); -} - -bool HeartRateTask::IsContinuousModeActivated() { - return settings.GetHeartRateBackgroundMeasurementInterval() == - Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous; -} - -bool HeartRateTask::IsBackgroundMeasurementActivated() { - return settings.GetHeartRateBackgroundMeasurementInterval() != - Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off; -} diff --git a/src/heartratetask/HeartRateTask.h b/src/heartratetask/HeartRateTask.h index f7d7e38b..5bbfb9fb 100644 --- a/src/heartratetask/HeartRateTask.h +++ b/src/heartratetask/HeartRateTask.h @@ -3,9 +3,6 @@ #include #include #include -#include "components/settings/Settings.h" - -#define DURATION_UNTIL_BACKGROUND_MEASUREMENT_IS_STOPPED pdMS_TO_TICKS(30 * 1000) namespace Pinetime { namespace Drivers { @@ -19,24 +16,10 @@ namespace Pinetime { namespace Applications { class HeartRateTask { public: - enum class Messages : uint8_t { - GoToSleep, - WakeUp, - StartMeasurement, - StopMeasurement - }; + enum class Messages : uint8_t { GoToSleep, WakeUp, StartMeasurement, StopMeasurement }; + enum class States { Idle, Running }; - enum class States { - ScreenOnAndStopped, - ScreenOnAndMeasuring, - ScreenOffAndStopped, - ScreenOffAndWaiting, - ScreenOffAndMeasuring - }; - - explicit HeartRateTask(Drivers::Hrs3300& heartRateSensor, - Controllers::HeartRateController& controller, - Controllers::Settings& settings); + explicit HeartRateTask(Drivers::Hrs3300& heartRateSensor, Controllers::HeartRateController& controller); void Start(); void Work(); void PushMessage(Messages msg); @@ -45,29 +28,14 @@ namespace Pinetime { static void Process(void* instance); void StartMeasurement(); void StopMeasurement(); - void StartWaiting(); - - void HandleGoToSleep(); - void HandleWakeUp(); - void HandleStartMeasurement(int* lastBpm); - void HandleStopMeasurement(); - - void HandleBackgroundWaiting(); - void HandleSensorData(int* lastBpm); - - TickType_t GetHeartRateBackgroundMeasurementIntervalInTicks(); - bool IsContinuousModeActivated(); - bool IsBackgroundMeasurementActivated(); TaskHandle_t taskHandle; QueueHandle_t messageQueue; - States state = States::ScreenOnAndStopped; + States state = States::Running; Drivers::Hrs3300& heartRateSensor; Controllers::HeartRateController& controller; - Controllers::Settings& settings; Controllers::Ppg ppg; - TickType_t backgroundWaitingStart = 0; - TickType_t measurementStart = 0; + bool measurementStarted = false; }; } diff --git a/src/libs/QCBOR b/src/libs/QCBOR deleted file mode 160000 index 56b17bf9..00000000 --- a/src/libs/QCBOR +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 56b17bf9f74096774944bcac0829adcd887d391e diff --git a/src/libs/lfs_config.h b/src/libs/lfs_config.h new file mode 100644 index 00000000..eaeede0e --- /dev/null +++ b/src/libs/lfs_config.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#ifndef LFS_TRACE +#ifdef LFS_YES_TRACE +#define LFS_TRACE_(fmt, ...) \ + NRF_LOG_DEBUG("[LFS] %s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") +#else +#define LFS_TRACE(...) +#endif +#endif + +#ifndef LFS_DEBUG +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG_(fmt, ...) \ + NRF_LOG_DEBUG("[LFS] %s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") +#else +#define LFS_DEBUG(...) +#endif +#endif + +#ifndef LFS_WARN +#ifndef LFS_NO_WARN +#define LFS_WARN_(fmt, ...) \ + NRF_LOG_WARNING("[LFS] %s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") +#else +#define LFS_WARN(...) +#endif +#endif + +#ifndef LFS_ERROR +#ifndef LFS_NO_ERROR +#define LFS_ERROR_(fmt, ...) \ + NRF_LOG_ERROR("[LFS] %s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") +#else +#define LFS_ERROR(...) +#endif +#endif + +// This is required in order for the CRC implementation in littlefs/lfs_util.c to be compiled +#undef LFS_CONFIG + +#undef LFS_UTIL_H +#include diff --git a/src/main.cpp b/src/main.cpp index 9db1abf0..24f13cad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,18 +93,18 @@ TimerHandle_t debounceChargeTimer; Pinetime::Controllers::Battery batteryController; Pinetime::Controllers::Ble bleController; +Pinetime::Controllers::HeartRateController heartRateController; +Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController); + Pinetime::Controllers::FS fs {spiNorFlash}; Pinetime::Controllers::Settings settingsController {fs}; Pinetime::Controllers::MotorController motorController {}; -Pinetime::Controllers::HeartRateController heartRateController; -Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController, settingsController); - Pinetime::Controllers::DateTime dateTimeController {settingsController}; Pinetime::Drivers::Watchdog watchdog; Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::MotionController motionController; -Pinetime::Controllers::AlarmController alarmController {dateTimeController}; +Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs}; Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; @@ -168,7 +168,7 @@ std::chrono::time_point NoI void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { if (pin == Pinetime::PinMap::Cst816sIrq) { - systemTask.OnTouchEvent(); + systemTask.PushMessage(Pinetime::System::Messages::OnTouchEvent); return; } diff --git a/src/resources/CMakeLists.txt b/src/resources/CMakeLists.txt index 3834e854..9181d4a6 100644 --- a/src/resources/CMakeLists.txt +++ b/src/resources/CMakeLists.txt @@ -10,6 +10,7 @@ message(STATUS "Using ${LV_IMG_CONV} to generate font files") if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) # FindPython3 module introduces with CMake 3.12 # https://cmake.org/cmake/help/latest/module/FindPython3.html + set(Python3_FIND_STRATEGY LOCATION) # https://discourse.cmake.org/t/find-package-python3-is-not-finding-the-correct-python/10563 find_package(Python3 REQUIRED) else() set(Python3_EXECUTABLE "python") diff --git a/src/stdlib.c b/src/stdlib.c index 3ad66b37..21b506a8 100644 --- a/src/stdlib.c +++ b/src/stdlib.c @@ -1,4 +1,5 @@ #include +#include #include // Override malloc() and free() to use the memory manager from FreeRTOS. @@ -10,18 +11,41 @@ void* malloc(size_t size) { return pvPortMalloc(size); } +void* __wrap_malloc(size_t size) { + return malloc(size); +} + +void* __wrap__malloc_r(struct _reent* reent, size_t size) { + (void) reent; + return malloc(size); +} + void free(void* ptr) { vPortFree(ptr); } -void* calloc(size_t num, size_t size) { - (void)(num); - (void)(size); - // Not supported - return NULL; +void __wrap_free(void* ptr) { + free(ptr); } -void *pvPortRealloc(void *ptr, size_t xWantedSize); -void* realloc( void *ptr, size_t newSize) { +void* calloc(size_t num, size_t size) { + void *ptr = malloc(num * size); + if (ptr) { + memset(ptr, 0, num * size); + } + return ptr; +} + +void* __wrap_calloc(size_t num, size_t size) { + return calloc(num, size); +} + +void* pvPortRealloc(void* ptr, size_t xWantedSize); + +void* realloc(void* ptr, size_t newSize) { return pvPortRealloc(ptr, newSize); } + +void* __wrap_realloc(void* ptr, size_t newSize) { + return realloc(ptr, newSize); +} diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index 81be9151..fee94bb7 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -6,7 +6,6 @@ namespace Pinetime { enum class Messages : uint8_t { GoToSleep, GoToRunning, - TouchWakeUp, OnNewTime, OnNewNotification, OnNewCall, @@ -17,6 +16,7 @@ namespace Pinetime { HandleButtonEvent, HandleButtonTimerEvent, OnDisplayTaskSleeping, + OnDisplayTaskAOD, EnableSleeping, DisableSleeping, OnNewDay, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 848fb54c..8e0435e3 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -189,39 +189,71 @@ void SystemTask::Work() { if (xQueueReceive(systemTasksMsgQueue, &msg, 100) == pdTRUE) { switch (msg) { case Messages::EnableSleeping: - // Make sure that exiting an app doesn't enable sleeping, - // if the exiting was caused by a firmware update - if (!bleController.IsFirmwareUpdating()) { - doNotGoToSleep = false; - } + wakeLocksHeld--; break; case Messages::DisableSleeping: - doNotGoToSleep = true; + GoToRunning(); + wakeLocksHeld++; break; case Messages::GoToRunning: - // SPI doesn't go to sleep for always on mode - if (!settingsController.GetAlwaysOnDisplay()) { - spi.Wakeup(); - } - - // Double Tap needs the touch screen to be in normal mode - if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { - touchPanel.Wakeup(); - } - - spiNorFlash.Wakeup(); - - displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning); - heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp); - - if (bleController.IsRadioEnabled() && !bleController.IsConnected()) { - nimbleController.RestartFastAdv(); - } - - state = SystemTaskState::Running; + GoToRunning(); break; - case Messages::TouchWakeUp: { - if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) { + case Messages::GoToSleep: + GoToSleep(); + break; + case Messages::OnNewTime: + if (alarmController.IsEnabled()) { + alarmController.ScheduleAlarm(); + } + break; + case Messages::OnNewNotification: + if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) { + if (IsSleeping()) { + GoToRunning(); + } + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification); + } + break; + case Messages::SetOffAlarm: + GoToRunning(); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); + break; + case Messages::BleConnected: + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); + isBleDiscoveryTimerRunning = true; + bleDiscoveryTimer = 5; + break; + case Messages::BleFirmwareUpdateStarted: + GoToRunning(); + wakeLocksHeld++; + displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted); + break; + case Messages::BleFirmwareUpdateFinished: + if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) { + NVIC_SystemReset(); + } + wakeLocksHeld--; + break; + case Messages::StartFileTransfer: + NRF_LOG_INFO("[systemtask] FS Started"); + GoToRunning(); + wakeLocksHeld++; + // TODO add intent of fs access icon or something + break; + case Messages::StopFileTransfer: + NRF_LOG_INFO("[systemtask] FS Stopped"); + wakeLocksHeld--; + // TODO add intent of fs access icon or something + break; + case Messages::OnTouchEvent: + // Finish immediately if no new events + if (!touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) { + break; + } + if (state == SystemTaskState::Running) { + displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent); + } else { + // If asleep, check for touch panel wake triggers auto gesture = touchHandler.GestureGet(); if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && gesture != Pinetime::Applications::TouchEvents::None && @@ -233,71 +265,6 @@ void SystemTask::Work() { } } break; - } - case Messages::GoToSleep: - if (doNotGoToSleep) { - break; - } - state = SystemTaskState::GoingToSleep; // Already set in PushMessage() - NRF_LOG_INFO("[systemtask] Going to sleep"); - displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep); - heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep); - break; - case Messages::OnNewTime: - if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) { - alarmController.ScheduleAlarm(); - } - break; - case Messages::OnNewNotification: - if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) { - if (state == SystemTaskState::Sleeping) { - GoToRunning(); - } - displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification); - } - break; - case Messages::SetOffAlarm: - if (state == SystemTaskState::Sleeping) { - GoToRunning(); - } - displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); - break; - case Messages::BleConnected: - displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); - isBleDiscoveryTimerRunning = true; - bleDiscoveryTimer = 5; - break; - case Messages::BleFirmwareUpdateStarted: - doNotGoToSleep = true; - if (state == SystemTaskState::Sleeping) { - GoToRunning(); - } - displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted); - break; - case Messages::BleFirmwareUpdateFinished: - if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) { - NVIC_SystemReset(); - } - doNotGoToSleep = false; - break; - case Messages::StartFileTransfer: - NRF_LOG_INFO("[systemtask] FS Started"); - doNotGoToSleep = true; - if (state == SystemTaskState::Sleeping) { - GoToRunning(); - } - // TODO add intent of fs access icon or something - break; - case Messages::StopFileTransfer: - NRF_LOG_INFO("[systemtask] FS Stopped"); - doNotGoToSleep = false; - // TODO add intent of fs access icon or something - break; - case Messages::OnTouchEvent: - if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) { - displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent); - } - break; case Messages::HandleButtonEvent: { Controllers::ButtonActions action = Controllers::ButtonActions::None; if (nrf_gpio_pin_read(Pinetime::PinMap::Button) == 0) { @@ -318,6 +285,14 @@ void SystemTask::Work() { HandleButtonAction(action); } break; case Messages::OnDisplayTaskSleeping: + case Messages::OnDisplayTaskAOD: + // The state was set to GoingToSleep when GoToSleep() was called + // If the state is no longer GoingToSleep, we have since transitioned back to Running + // In this case absorb the OnDisplayTaskSleeping/AOD + // as DisplayApp is about to receive GoToRunning + if (state != SystemTaskState::GoingToSleep) { + break; + } if (BootloaderVersion::IsValid()) { // First versions of the bootloader do not expose their version and cannot initialize the SPI NOR FLASH // if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions. @@ -325,7 +300,7 @@ void SystemTask::Work() { } // Must keep SPI awake when still updating the display for always on - if (!settingsController.GetAlwaysOnDisplay()) { + if (msg == Messages::OnDisplayTaskSleeping) { spi.Sleep(); } @@ -334,7 +309,11 @@ void SystemTask::Work() { touchPanel.Sleep(); } - state = SystemTaskState::Sleeping; + if (msg == Messages::OnDisplayTaskSleeping) { + state = SystemTaskState::Sleeping; + } else { + state = SystemTaskState::AODSleeping; + } break; case Messages::OnNewDay: // We might be sleeping (with TWI device disabled. @@ -344,39 +323,22 @@ void SystemTask::Work() { case Messages::OnNewHour: using Pinetime::Controllers::AlarmController; if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && - settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && - 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) { - GoToRunning(); - PushMessage(msg); - } else { - displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); - } + settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && !alarmController.IsAlerting()) { + GoToRunning(); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); } break; case Messages::OnNewHalfHour: using Pinetime::Controllers::AlarmController; if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && - settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && - 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) { - GoToRunning(); - PushMessage(msg); - } else { - displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); - } + settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && !alarmController.IsAlerting()) { + GoToRunning(); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); } break; case Messages::OnChargingEvent: batteryController.ReadPowerState(); - displayApp.PushMessage(Applications::Display::Messages::OnChargingEvent); - if (state == SystemTaskState::Sleeping) { - GoToRunning(); - } + GoToRunning(); break; case Messages::MeasureBatteryTimerExpired: batteryController.MeasureVoltage(); @@ -385,9 +347,7 @@ void SystemTask::Work() { nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining()); break; case Messages::OnPairing: - if (state == SystemTaskState::Sleeping) { - GoToRunning(); - } + GoToRunning(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey); break; case Messages::BleRadioEnableToggle: @@ -422,11 +382,55 @@ void SystemTask::Work() { #pragma clang diagnostic pop } -void SystemTask::UpdateMotion() { - if (state == SystemTaskState::GoingToSleep || state == SystemTaskState::WakingUp) { +void SystemTask::GoToRunning() { + if (state == SystemTaskState::Running) { return; } + if (state == SystemTaskState::Sleeping || state == SystemTaskState::AODSleeping) { + // SPI only switched off when entering Sleeping, not AOD or GoingToSleep + if (state == SystemTaskState::Sleeping) { + spi.Wakeup(); + } + // Double Tap needs the touch screen to be in normal mode + if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { + touchPanel.Wakeup(); + } + + spiNorFlash.Wakeup(); + } + + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning); + heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp); + + if (bleController.IsRadioEnabled() && !bleController.IsConnected()) { + nimbleController.RestartFastAdv(); + } + + state = SystemTaskState::Running; +}; + +void SystemTask::GoToSleep() { + if (IsSleeping()) { + return; + } + if (IsSleepDisabled()) { + return; + } + NRF_LOG_INFO("[systemtask] Going to sleep"); + if (settingsController.GetAlwaysOnDisplay()) { + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToAOD); + } else { + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep); + } + heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep); + + state = SystemTaskState::GoingToSleep; +}; + +void SystemTask::UpdateMotion() { + // Only consider disabling motion updates specifically in the Sleeping state + // AOD needs motion on to show up to date step counts if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) || settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) || motionController.GetService()->IsMotionNotificationSubscribed())) { @@ -452,7 +456,7 @@ void SystemTask::UpdateMotion() { } if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running && motionController.ShouldLowerSleep()) { - PushMessage(Messages::GoToSleep); + GoToSleep(); } } @@ -468,7 +472,7 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) { switch (action) { case Actions::Click: // If the first action after fast wakeup is a click, it should be ignored. - if (!fastWakeUpDone && state != SystemTaskState::GoingToSleep) { + if (!fastWakeUpDone) { displayApp.PushMessage(Applications::Display::Messages::ButtonPushed); } break; @@ -488,29 +492,7 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) { fastWakeUpDone = false; } -void SystemTask::GoToRunning() { - if (state == SystemTaskState::Sleeping) { - state = SystemTaskState::WakingUp; - PushMessage(Messages::GoToRunning); - } -} - -void SystemTask::OnTouchEvent() { - if (state == SystemTaskState::Running) { - PushMessage(Messages::OnTouchEvent); - } else if (state == SystemTaskState::Sleeping) { - if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or - settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { - PushMessage(Messages::TouchWakeUp); - } - } -} - void SystemTask::PushMessage(System::Messages msg) { - if (msg == Messages::GoToSleep && !doNotGoToSleep) { - state = SystemTaskState::GoingToSleep; - } - if (in_isr()) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken); diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 79f1cf44..0ee877b7 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -52,7 +52,7 @@ namespace Pinetime { namespace System { class SystemTask { public: - enum class SystemTaskState { Sleeping, Running, GoingToSleep, WakingUp }; + enum class SystemTaskState { Sleeping, Running, GoingToSleep, AODSleeping }; SystemTask(Drivers::SpiMaster& spi, Pinetime::Drivers::SpiNorFlash& spiNorFlash, Drivers::TwiMaster& twiMaster, @@ -77,21 +77,24 @@ namespace Pinetime { void Start(); void PushMessage(Messages msg); - void OnTouchEvent(); - - void OnIdle(); - void OnDim(); - bool IsSleepDisabled() { - return doNotGoToSleep; + return wakeLocksHeld > 0; } Pinetime::Controllers::NimbleController& nimble() { return nimbleController; }; + Pinetime::Controllers::NotificationManager& GetNotificationManager() { + return notificationManager; + }; + + Pinetime::Controllers::Settings& GetSettings() { + return settingsController; + }; + bool IsSleeping() const { - return state == SystemTaskState::Sleeping || state == SystemTaskState::WakingUp; + return state != SystemTaskState::Running; } private: @@ -127,13 +130,14 @@ namespace Pinetime { bool isBleDiscoveryTimerRunning = false; uint8_t bleDiscoveryTimer = 0; TimerHandle_t measureBatteryTimer; - bool doNotGoToSleep = false; + uint8_t wakeLocksHeld = 0; SystemTaskState state = SystemTaskState::Running; void HandleButtonAction(Controllers::ButtonActions action); bool fastWakeUpDone = false; void GoToRunning(); + void GoToSleep(); void UpdateMotion(); bool stepCounterMustBeReset = false; static constexpr TickType_t batteryMeasurementPeriod = pdMS_TO_TICKS(10 * 60 * 1000); diff --git a/src/systemtask/WakeLock.cpp b/src/systemtask/WakeLock.cpp new file mode 100644 index 00000000..2953f7ee --- /dev/null +++ b/src/systemtask/WakeLock.cpp @@ -0,0 +1,27 @@ +#include "systemtask/WakeLock.h" + +using namespace Pinetime::System; + +WakeLock::WakeLock(SystemTask& systemTask) : systemTask {systemTask} { + lockHeld = false; +} + +WakeLock::~WakeLock() { + Release(); +} + +void WakeLock::Lock() { + if (lockHeld) { + return; + } + systemTask.PushMessage(Messages::DisableSleeping); + lockHeld = true; +} + +void WakeLock::Release() { + if (!lockHeld) { + return; + } + systemTask.PushMessage(Messages::EnableSleeping); + lockHeld = false; +} diff --git a/src/systemtask/WakeLock.h b/src/systemtask/WakeLock.h new file mode 100644 index 00000000..5424c009 --- /dev/null +++ b/src/systemtask/WakeLock.h @@ -0,0 +1,19 @@ +#pragma once + +#include "systemtask/SystemTask.h" + +namespace Pinetime { + namespace System { + class WakeLock { + public: + WakeLock(SystemTask& systemTask); + ~WakeLock(); + void Lock(); + void Release(); + + private: + bool lockHeld; + SystemTask& systemTask; + }; + } +}