diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 00000000..95a27dac --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,32 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp +{ + "build": { + "dockerfile": "docker/Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + // Set *default* container specific settings.json values on container create. + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/bin/bash" + } + }, + "terminal.integrated.defaultProfile.linux": "bash", + "editor.formatOnSave": true, + // FIXME: This and the Dockerfile might get out of sync + "clang-format.executable": "clang-format-14" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "marus25.cortex-debug", + "notskm.clang-tidy", + "mjohns.clang-format" + ] + } + }, + "remoteUser": "infinitime" +} \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index e4ad5c4f..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -FROM ubuntu:latest - -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update -qq \ - && apt-get install -y \ -# x86_64 / generic packages - bash \ - build-essential \ - cmake \ - git \ - make \ - python3 \ - python3-pip \ - python3-pil \ - tar \ - unzip \ - wget \ - curl \ - dos2unix \ - clang-format-12 \ - clang-tidy \ - locales \ - libncurses5 \ -# aarch64 packages - libffi-dev \ - libssl-dev \ - python3-dev \ - rustc \ - && rm -rf /var/cache/apt/* /var/lib/apt/lists/*; - -#SET LOCALE -RUN locale-gen en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -RUN pip3 install adafruit-nrfutil -# required for McuBoot -RUN pip3 install setuptools_rust - -WORKDIR /opt/ -# build.sh knows how to compile but it problimatic on Win10 -COPY build.sh . -RUN chmod +x build.sh -# create_build_openocd.sh uses cmake to crate to build directory -COPY create_build_openocd.sh . -RUN chmod +x create_build_openocd.sh -# Lets get each in a separate docker layer for better downloads -# GCC -# RUN bash -c "source /opt/build.sh; GetGcc;" -RUN wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -O - | tar -xj -C /opt -# NrfSdk -# RUN bash -c "source /opt/build.sh; GetNrfSdk;" -RUN wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip" -O /tmp/nRF5_SDK_15.3.0_59ac345 -RUN unzip -q /tmp/nRF5_SDK_15.3.0_59ac345 -d /opt -RUN rm /tmp/nRF5_SDK_15.3.0_59ac345 -# McuBoot -# RUN bash -c "source /opt/build.sh; GetMcuBoot;" -RUN git clone https://github.com/mcu-tools/mcuboot.git -RUN pip3 install -r ./mcuboot/scripts/requirements.txt - -RUN adduser infinitime - -ENV NRF5_SDK_PATH /opt/nRF5_SDK_15.3.0_59ac345 -ENV ARM_NONE_EABI_TOOLCHAIN_PATH /opt/gcc-arm-none-eabi-9-2020-q2-update -ENV SOURCES_DIR /workspaces/InfiniTime diff --git a/.devcontainer/build.sh b/.devcontainer/build.sh deleted file mode 100644 index b4f080dd..00000000 --- a/.devcontainer/build.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash -(return 0 2>/dev/null) && SOURCED="true" || SOURCED="false" -export LC_ALL=C.UTF-8 -export LANG=C.UTF-8 -set -x -set -e - -# Default locations if the var isn't already set -export TOOLS_DIR="${TOOLS_DIR:=/opt}" -export SOURCES_DIR="${SOURCES_DIR:=/sources}" -export BUILD_DIR="${BUILD_DIR:=$SOURCES_DIR/build}" -export OUTPUT_DIR="${OUTPUT_DIR:=$BUILD_DIR/output}" - -export BUILD_TYPE=${BUILD_TYPE:=Release} -export GCC_ARM_VER=${GCC_ARM_VER:="gcc-arm-none-eabi-9-2020-q2-update"} -export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"} - -MACHINE="$(uname -m)" -[[ "$MACHINE" == "arm64" ]] && MACHINE="aarch64" - -main() { - local target="$1" - - mkdir -p "$TOOLS_DIR" - - [[ ! -d "$TOOLS_DIR/$GCC_ARM_VER" ]] && GetGcc - [[ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]] && GetNrfSdk - [[ ! -d "$TOOLS_DIR/mcuboot" ]] && GetMcuBoot - - mkdir -p "$BUILD_DIR" - - CmakeGenerate - CmakeBuild $target - BUILD_RESULT=$? - if [ "$DISABLE_POSTBUILD" != "true" -a "$BUILD_RESULT" == 0 ]; then - source "$BUILD_DIR/post_build.sh" - fi - # assuming post_build.sh will never fail on a successful build - return $BUILD_RESULT -} - -GetGcc() { - GCC_SRC="$GCC_ARM_VER-$MACHINE-linux.tar.bz" - wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/$GCC_SRC -O - | tar -xj -C $TOOLS_DIR/ -} - -GetMcuBoot() { - git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot" - pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt" -} - -GetNrfSdk() { - wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER - unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/" - rm /tmp/$NRF_SDK_VER -} - -CmakeGenerate() { - # We can swap the CD and trailing SOURCES_DIR for -B and -S respectively - # once we go to newer CMake (Ubuntu 18.10 gives us CMake 3.10) - cd "$BUILD_DIR" - - cmake -G "Unix Makefiles" \ - -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ - -DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \ - -DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \ - "$SOURCES_DIR" - cmake -L -N . -} - -CmakeBuild() { - local target="$1" - [[ -n "$target" ]] && target="--target $target" - if cmake --build "$BUILD_DIR" --config $BUILD_TYPE $target -- -j$(nproc) - then return 0; else return 1; - fi -} - -if [[ $SOURCED == "false" ]]; then - # It is important to return exit code of main - # To be future-proof, this is handled explicitely - main "$@" - BUILD_RESULT=$? - exit $BUILD_RESULT -else - echo "Sourced!" -fi diff --git a/.devcontainer/build_app.sh b/.devcontainer/build_app.sh deleted file mode 100644 index 0f578cc6..00000000 --- a/.devcontainer/build_app.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -cmake --build /workspaces/Pinetime/build --config Release -- -j6 pinetime-app \ No newline at end of file diff --git a/.devcontainer/create_build_openocd.sh b/.devcontainer/create_build_openocd.sh deleted file mode 100644 index c5bff5c8..00000000 --- a/.devcontainer/create_build_openocd.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -rm -rf build/ -cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 -S . -Bbuild \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 1bb315f7..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,38 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp -{ - // "name": "Pinetime", - // "image": "feabhas/pinetime-dev" - "build": { - "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick an Debian / Ubuntu OS version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04 - // "args": { "VARIANT": "ubuntu-20.04" } - }, - "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"], - - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "editor.formatOnSave": true, - "clang-format.executable": "clang-format-12" - }, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools", - "marus25.cortex-debug", - "notskm.clang-tidy", - "mjohns.clang-format" - ], - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "bash /opt/create_build_openocd.sh", - - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode" - "remoteUser": "infinitime" -} diff --git a/.devcontainer/make_build_dir.sh b/.devcontainer/make_build_dir.sh deleted file mode 100644 index 76240037..00000000 --- a/.devcontainer/make_build_dir.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 ${SOURCES_DIR} diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 392f4151..c5f88a82 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,4 +1,9 @@ { + "env": { + // TODO: This is a duplication of the configuration set in /docker/build.sh! + "TOOLS_DIR": "/opt", + "GCC_ARM_PATH": "gcc-arm-none-eabi-10.3-2021.10" + }, "configurations": [ { "name": "nrfCC", @@ -14,7 +19,22 @@ "intelliSenseMode": "linux-gcc-arm", "configurationProvider": "ms-vscode.cpp-tools", "compileCommands": "${workspaceFolder}/build/compile_commands.json" + }, + { + "name": "nrfCC Devcontainer", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/src/**", + "${workspaceFolder}/src" + ], + "defines": [], + "compilerPath": "${TOOLS_DIR}/${GCC_ARM_PATH}/bin/arm-none-eabi-gcc", + "cStandard": "c99", + "cppStandard": "c++20", + "intelliSenseMode": "linux-gcc-arm", + "configurationProvider": "ms-vscode.cpp-tools", + "compileCommands": "${workspaceFolder}/build/compile_commands.json" } ], "version": 4 -} \ No newline at end of file +} diff --git a/.vscode/cmake-kits.json b/.vscode/cmake-kits.json new file mode 100644 index 00000000..95bb600b --- /dev/null +++ b/.vscode/cmake-kits.json @@ -0,0 +1,6 @@ +[ + { + "name": "InfiniTime Compiler", + "environmentSetupScript": "${workspaceFolder}/docker/build.sh" + } +] diff --git a/.vscode/launch.json b/.vscode/launch.json index a50270d2..7d3f17a1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,20 +1,18 @@ - { +{ "version": "0.1.0", "configurations": [ { "name": "Debug - Openocd docker Remote", - "type":"cortex-debug", - "cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin", + "type": "cortex-debug", "cwd": "${workspaceRoot}", "executable": "${command:cmake.launchTargetPath}", "request": "launch", "servertype": "external", - // This may need to be arm-none-eabi-gdb depending on your system - "gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", + "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", // Connect to an already running OpenOCD instance "gdbTarget": "host.docker.internal:3333", "svdFile": "${workspaceRoot}/nrf52.svd", - "runToMain": true, + "runToEntryPoint": "main", // Work around for stopping at main on restart "postRestartCommands": [ "break main", @@ -23,18 +21,16 @@ }, { "name": "Debug - Openocd Local", - "type":"cortex-debug", - "cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin", + "type": "cortex-debug", "cwd": "${workspaceRoot}", "executable": "${command:cmake.launchTargetPath}", "request": "launch", "servertype": "openocd", - // This may need to be arm-none-eabi-gdb depending on your system - "gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", + "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb", // Connect to an already running OpenOCD instance "gdbTarget": "localhost:3333", "svdFile": "${workspaceRoot}/nrf52.svd", - "runToMain": true, + "runToEntryPoint": "main", // Work around for stopping at main on restart "postRestartCommands": [ "break main", @@ -51,6 +47,11 @@ "showDevDebugOutput": false, "servertype": "openocd", "runToMain": true, + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main", + "continue" + ], // Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb) "armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin", "svdFile": "${workspaceRoot}/nrf52.svd", @@ -58,7 +59,25 @@ "interface/stlink.cfg", "target/nrf52.cfg" ], - } - + }, + { + "name": "Debug - Openocd Devcontainer", + "type": "cortex-debug", + "cwd": "${workspaceRoot}", + "executable": "${command:cmake.launchTargetPath}", + "request": "launch", + "servertype": "external", + // FIXME: This is hardcoded. I have no idea how to use the values set in build.sh here + "gdbPath": "/opt/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gdb", + // Connect to an already running OpenOCD instance + "gdbTarget": "host.docker.internal:3333", + "svdFile": "${workspaceRoot}/nrf52.svd", + "runToEntryPoint": "main", + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main", + "continue" + ] + }, ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f1cc3a81..a7b04eea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,20 @@ { "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "cmake.configureArgs": [ - "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}", - "-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}", + "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}", + "-DNRF5_SDK_PATH=${env:TOOLS_DIR}/${env:NRF_SDK_VER}", ], + "cmake.statusbar.advanced": { + "launch": { + "visibility": "hidden" + }, + "launchTarget": { + "visibility": "hidden" + }, + "debug": { + "visibility": "hidden" + } + }, "cmake.generator": "Unix Makefiles", "clang-tidy.buildPath": "build/compile_commands.json", "files.associations": { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 17f51f5e..06a08bfc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,20 +1,6 @@ { "version": "2.0.0", "tasks": [ - { - "label": "create openocd build", - "type": "shell", - "command": "/opt/create_build_openocd.sh", - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, { "label": "update submodules", "type": "shell", @@ -31,14 +17,6 @@ "panel": "shared" }, "problemMatcher": [] - }, - { - "label": "BuildInit", - "dependsOn": [ - "update submodules", - "create openocd build" - ], - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/README.md b/README.md index 8d612360..e4f6707f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo") -Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++. +Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++. ## New to InfiniTime? diff --git a/bootloader/ota-dfu-python/README.md b/bootloader/ota-dfu-python/README.md index c38e597a..c18da8bb 100644 --- a/bootloader/ota-dfu-python/README.md +++ b/bootloader/ota-dfu-python/README.md @@ -7,9 +7,9 @@ My own contribution is little more than a brute force conversion to python3. It is sparsely tested so there are likely to be a few remaining bytes versus string bugs remaining in the places I didn't test . I used it primarily as part of -[wasp-os](https://github.com/daniel-thompson/wasp-os) as a way to +[wasp-os](https://github.com/wasp-os/wasp-os) as a way to deliver OTA updates to nRF52-based smart watches, especially the -[Pine64 PineTime](https://www.pine64.org/pinetime/). +[Pine64 PineTime](https://pine64.org/devices/pinetime/). ## What does it do? diff --git a/doc/MotionService.md b/doc/MotionService.md index 429794ac..58c7e836 100644 --- a/doc/MotionService.md +++ b/doc/MotionService.md @@ -21,3 +21,5 @@ The current raw motion values. This is a 3 `int16_t` array: - [0] : X - [1] : Y - [2] : Z + +The three motion values are in units of "binary milli-g", where 1g is represented by a value of 1024. diff --git a/doc/buildWithVScode.md b/doc/buildWithVScode.md index 9d0a5bdf..5f872482 100644 --- a/doc/buildWithVScode.md +++ b/doc/buildWithVScode.md @@ -32,7 +32,7 @@ The .devcontainer folder contains the configuration and scripts for using a Dock Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up. -More documentation is available in the [readme in .devcontainer](../.devcontainer/README.md) +More documentation is available in the [readme in .devcontainer](usingDevcontainers.md) ### DevContainer on Ubuntu diff --git a/doc/code/Apps.md b/doc/code/Apps.md index b325fe98..44c4ed65 100644 --- a/doc/code/Apps.md +++ b/doc/code/Apps.md @@ -140,10 +140,10 @@ namespace Pinetime { } template <> - struct AppTraits { + struct AppTraits { static constexpr Apps app = Apps::MyApp; - static constexpr const char* icon = Screens::Symbol::myApp; - static Screens::Screens* Create(AppController& controllers) { + static constexpr const char* icon = Screens::Symbols::myApp; + static Screens::Screen* Create(AppControllers& controllers) { return new Screens::MyApp(); } }; @@ -176,7 +176,7 @@ Now we have our very own app, but InfiniTime does not know about it yet. The first step is to include your `MyApp.cpp` (or any new cpp files for that matter) in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt). The next step to making it launch-able is to give your app an id. -To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)). +To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/apps/Apps.h](/src/displayapp/apps/Apps.h.in)). Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp). diff --git a/.devcontainer/README.md b/doc/usingDevcontainers.md similarity index 100% rename from .devcontainer/README.md rename to doc/usingDevcontainers.md diff --git a/docker/Dockerfile b/docker/Dockerfile index 22bf7bd7..bb5d5f65 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -37,6 +37,13 @@ RUN apt-get update -qq \ libpangocairo-1.0-0 \ && rm -rf /var/cache/apt/* /var/lib/apt/lists/*; +# Add the necessary apt-gets for the devcontainer +RUN apt-get update -qq \ + && apt-get install -y \ + clang-format-14 \ + clang-tidy \ + libncurses5 + # Git needed for PROJECT_GIT_COMMIT_HASH variable setting RUN pip3 install adafruit-nrfutil @@ -55,5 +62,8 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;" # McuBoot RUN bash -c "source /opt/build.sh; GetMcuBoot;" +# Add the infinitime user for connecting devcontainer +RUN adduser infinitime + ENV SOURCES_DIR /sources CMD ["/opt/build.sh"] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 65a72ac2..78b27917 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -379,6 +379,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Navigation.cpp displayapp/screens/Metronome.cpp displayapp/screens/Motion.cpp + displayapp/screens/Weather.cpp displayapp/screens/FirmwareValidation.cpp displayapp/screens/ApplicationList.cpp displayapp/screens/Notifications.cpp @@ -543,7 +544,6 @@ list(APPEND RECOVERY_SOURCE_FILES systemtask/SystemTask.cpp systemtask/SystemMonitor.cpp drivers/TwiMaster.cpp - components/gfx/Gfx.cpp components/rle/RleDecoder.cpp components/heartrate/HeartRateController.cpp heartratetask/HeartRateTask.cpp @@ -573,7 +573,6 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES components/rle/RleDecoder.cpp - components/gfx/Gfx.cpp drivers/St7789.cpp components/brightness/BrightnessController.cpp diff --git a/src/FreeRTOSConfig.h b/src/FreeRTOSConfig.h index 15a862be..930ec764 100644 --- a/src/FreeRTOSConfig.h +++ b/src/FreeRTOSConfig.h @@ -75,6 +75,7 @@ #define configUSE_TIME_SLICING 0 #define configUSE_NEWLIB_REENTRANT 0 #define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configUSE_TASK_NOTIFICATIONS 0 /* Hook function related definitions. */ #define configUSE_IDLE_HOOK 0 diff --git a/src/components/ble/DfuService.cpp b/src/components/ble/DfuService.cpp index 1f06b69e..b3f2ff10 100644 --- a/src/components/ble/DfuService.cpp +++ b/src/components/ble/DfuService.cpp @@ -357,6 +357,8 @@ void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t exp this->totalSize = totalSize; this->expectedCrc = expectedCrc; this->ready = true; + totalWriteIndex = 0; + bufferWriteIndex = 0; } void DfuService::DfuImage::Append(uint8_t* data, size_t size) { diff --git a/src/components/ble/HeartRateService.h b/src/components/ble/HeartRateService.h index 72632c96..3f32fd09 100644 --- a/src/components/ble/HeartRateService.h +++ b/src/components/ble/HeartRateService.h @@ -2,9 +2,9 @@ #define min // workaround: nimble's min/max macros conflict with libstdc++ #define max #include -#include #undef max #undef min +#include namespace Pinetime { namespace Controllers { diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp index d545d45b..146152f8 100644 --- a/src/components/ble/SimpleWeatherService.cpp +++ b/src/components/ble/SimpleWeatherService.cpp @@ -158,3 +158,16 @@ bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature && std::strcmp(this->location.data(), other.location.data()) == 0; } + +bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const { + return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature; +} + +bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const { + for (int i = 0; i < this->nbDays; i++) { + if (this->days[i] != other.days[i]) { + return false; + } + } + return this->timestamp == other.timestamp && this->nbDays == other.nbDays; +} diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h index cec2bb8b..4bbefcfc 100644 --- a/src/components/ble/SimpleWeatherService.h +++ b/src/components/ble/SimpleWeatherService.h @@ -96,9 +96,13 @@ namespace Pinetime { int16_t minTemperature; int16_t maxTemperature; Icons iconId; + + bool operator==(const Day& other) const; }; std::array days; + + bool operator==(const Forecast& other) const; }; std::optional Current() const; diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp index 8d4a834e..f0ccb5e5 100644 --- a/src/components/datetime/DateTimeController.cpp +++ b/src/components/datetime/DateTimeController.cpp @@ -115,8 +115,8 @@ const char* DateTime::MonthShortToStringLow(Months month) { return MonthsStringLow[static_cast(month)]; } -const char* DateTime::DayOfWeekShortToStringLow() const { - return DaysStringShortLow[static_cast(DayOfWeek())]; +const char* DateTime::DayOfWeekShortToStringLow(Days day) { + return DaysStringShortLow[static_cast(day)]; } void DateTime::Register(Pinetime::System::SystemTask* systemTask) { diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h index 0bf6ac2a..f719df7d 100644 --- a/src/components/datetime/DateTimeController.h +++ b/src/components/datetime/DateTimeController.h @@ -122,7 +122,7 @@ namespace Pinetime { const char* MonthShortToString() const; const char* DayOfWeekShortToString() const; static const char* MonthShortToStringLow(Months month); - const char* DayOfWeekShortToStringLow() const; + static const char* DayOfWeekShortToStringLow(Days day); std::chrono::time_point CurrentDateTime() const { return currentDateTime; diff --git a/src/components/gfx/Gfx.cpp b/src/components/gfx/Gfx.cpp deleted file mode 100644 index baa6486a..00000000 --- a/src/components/gfx/Gfx.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#include "components/gfx/Gfx.h" -#include "drivers/St7789.h" -using namespace Pinetime::Components; - -Gfx::Gfx(Pinetime::Drivers::St7789& lcd) : lcd {lcd} { -} - -void Gfx::Init() { -} - -void Gfx::ClearScreen() { - SetBackgroundColor(0x0000); - - state.remainingIterations = 240 + 1; - state.currentIteration = 0; - state.busy = true; - state.action = Action::FillRectangle; - state.taskToNotify = xTaskGetCurrentTaskHandle(); - - lcd.DrawBuffer(0, 0, width, height, reinterpret_cast(buffer), width * 2); - WaitTransferFinished(); -} - -void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) { - SetBackgroundColor(color); - - state.remainingIterations = h; - state.currentIteration = 0; - state.busy = true; - state.action = Action::FillRectangle; - state.color = color; - state.taskToNotify = xTaskGetCurrentTaskHandle(); - - lcd.DrawBuffer(x, y, w, h, reinterpret_cast(buffer), width * 2); - - WaitTransferFinished(); -} - -void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) { - state.remainingIterations = h; - state.currentIteration = 0; - state.busy = true; - state.action = Action::FillRectangle; - state.color = 0x00; - state.taskToNotify = xTaskGetCurrentTaskHandle(); - - lcd.DrawBuffer(x, y, w, h, reinterpret_cast(b), width * 2); - - WaitTransferFinished(); -} - -void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap) { - if (y > (height - p_font->height)) { - // Not enough space to write even single char. - return; - } - - uint8_t current_x = x; - uint8_t current_y = y; - - for (size_t i = 0; text[i] != '\0'; i++) { - if (text[i] == '\n') { - current_x = x; - current_y += p_font->height + p_font->height / 10; - } else { - DrawChar(p_font, (uint8_t) text[i], ¤t_x, current_y, color); - } - - uint8_t char_idx = text[i] - p_font->startChar; - uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits; - - if (current_x > (width - char_width)) { - if (wrap) { - current_x = x; - current_y += p_font->height + p_font->height / 10; - } else { - break; - } - - if (y > (height - p_font->height)) { - break; - } - } - } -} - -void Gfx::DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color) { - uint8_t char_idx = c - font->startChar; - uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8); - uint16_t bg = 0x0000; - - if (c == ' ') { - *x += font->height / 2; - return; - } - - // Build first line - for (uint16_t j = 0; j < bytes_in_line; j++) { - for (uint8_t k = 0; k < 8; k++) { - if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) { - buffer[(j * 8) + k] = color; - } else { - buffer[(j * 8) + k] = bg; - } - } - } - - state.remainingIterations = font->height + 0; - state.currentIteration = 0; - state.busy = true; - state.action = Action::DrawChar; - state.font = const_cast(font); - state.character = c; - state.color = color; - state.taskToNotify = xTaskGetCurrentTaskHandle(); - - lcd.DrawBuffer(*x, y, bytes_in_line * 8, font->height, reinterpret_cast(&buffer), bytes_in_line * 8 * 2); - WaitTransferFinished(); - - *x += font->charInfo[char_idx].widthBits + font->spacePixels; -} - -void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) { - lcd.DrawPixel(x, y, color); -} - -void Gfx::Sleep() { - lcd.Sleep(); -} - -void Gfx::Wakeup() { - lcd.Wakeup(); -} - -void Gfx::SetBackgroundColor(uint16_t color) { - for (int i = 0; i < width; i++) { - buffer[i] = color; - } -} - -bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) { - if (!state.busy) - return false; - state.remainingIterations = state.remainingIterations - 1; - if (state.remainingIterations == 0) { - state.busy = false; - NotifyEndOfTransfer(state.taskToNotify); - return false; - } - - if (state.action == Action::FillRectangle) { - *data = reinterpret_cast(buffer); - size = width * 2; - } else if (state.action == Action::DrawChar) { - uint16_t bg = 0x0000; - uint8_t char_idx = state.character - state.font->startChar; - uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8); - - for (uint16_t j = 0; j < bytes_in_line; j++) { - for (uint8_t k = 0; k < 8; k++) { - if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration + 1) * bytes_in_line) + j]) { - buffer[(j * 8) + k] = state.color; - } else { - buffer[(j * 8) + k] = bg; - } - } - } - - *data = reinterpret_cast(buffer); - size = bytes_in_line * 8 * 2; - } - - state.currentIteration = state.currentIteration + 1; - - return true; -} - -void Gfx::NotifyEndOfTransfer(TaskHandle_t task) { - if (task != nullptr) { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } -} - -void Gfx::WaitTransferFinished() const { - ulTaskNotifyTake(pdTRUE, 500); -} - -void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) { - lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines); -} - -void Gfx::SetScrollStartLine(uint16_t line) { - lcd.VerticalScrollStartAddress(line); -} diff --git a/src/components/gfx/Gfx.h b/src/components/gfx/Gfx.h deleted file mode 100644 index 17c248f7..00000000 --- a/src/components/gfx/Gfx.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include "drivers/BufferProvider.h" - -namespace Pinetime { - namespace Drivers { - class St7789; - } - - namespace Components { - class Gfx : public Pinetime::Drivers::BufferProvider { - public: - explicit Gfx(Drivers::St7789& lcd); - void Init(); - void ClearScreen(); - void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap); - void DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color); - void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color); - void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b); - void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines); - void SetScrollStartLine(uint16_t line); - - void Sleep(); - void Wakeup(); - bool GetNextBuffer(uint8_t** buffer, size_t& size) override; - void pixel_draw(uint8_t x, uint8_t y, uint16_t color); - - private: - static constexpr uint8_t width = 240; - static constexpr uint8_t height = 240; - - enum class Action { None, FillRectangle, DrawChar }; - - struct State { - State() : busy {false}, action {Action::None}, remainingIterations {0}, currentIteration {0} { - } - - volatile bool busy; - volatile Action action; - volatile uint16_t remainingIterations; - volatile uint16_t currentIteration; - volatile FONT_INFO* font; - volatile uint16_t color; - volatile uint8_t character; - volatile TaskHandle_t taskToNotify = nullptr; - }; - - volatile State state; - - uint16_t buffer[width]; // 1 line buffer - Drivers::St7789& lcd; - - void SetBackgroundColor(uint16_t color); - void WaitTransferFinished() const; - void NotifyEndOfTransfer(TaskHandle_t task); - }; - } -} diff --git a/src/components/motion/MotionController.cpp b/src/components/motion/MotionController.cpp index d28378d5..72507ac5 100644 --- a/src/components/motion/MotionController.cpp +++ b/src/components/motion/MotionController.cpp @@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps) service->OnNewStepCountValue(nbSteps); } - if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) { + if (service != nullptr && (xHistory[0] != x || yHistory[0] != y || zHistory[0] != z)) { service->OnNewMotionValues(x, y, z); } lastTime = time; time = xTaskGetTickCount(); - lastX = this->x; - this->x = x; + xHistory++; + xHistory[0] = x; yHistory++; yHistory[0] = y; zHistory++; @@ -67,20 +67,26 @@ MotionController::AccelStats MotionController::GetAccelStats() const { AccelStats stats; for (uint8_t i = 0; i < AccelStats::numHistory; i++) { + stats.xMean += xHistory[histSize - i]; stats.yMean += yHistory[histSize - i]; stats.zMean += zHistory[histSize - i]; + stats.prevXMean += xHistory[1 + i]; stats.prevYMean += yHistory[1 + i]; stats.prevZMean += zHistory[1 + i]; } + stats.xMean /= AccelStats::numHistory; stats.yMean /= AccelStats::numHistory; stats.zMean /= AccelStats::numHistory; + stats.prevXMean /= AccelStats::numHistory; stats.prevYMean /= AccelStats::numHistory; stats.prevZMean /= AccelStats::numHistory; for (uint8_t i = 0; i < AccelStats::numHistory; i++) { + stats.xVariance += (xHistory[histSize - i] - stats.xMean) * (xHistory[histSize - i] - stats.xMean); stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean); stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean); } + stats.xVariance /= AccelStats::numHistory; stats.yVariance /= AccelStats::numHistory; stats.zVariance /= AccelStats::numHistory; @@ -93,7 +99,7 @@ bool MotionController::ShouldRaiseWake() const { constexpr int16_t yThresh = -64; constexpr int16_t rollDegreesThresh = -45; - if (x < -xThresh || x > xThresh) { + if (std::abs(stats.xMean) > xThresh) { return false; } @@ -107,8 +113,9 @@ bool MotionController::ShouldRaiseWake() const { bool MotionController::ShouldShakeWake(uint16_t thresh) { /* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */ - int32_t speed = - std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime); + int32_t speed = std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + + (xHistory[0] - xHistory[histSize - 1]) / 4) * + 100 / (time - lastTime); // (.2 * speed) + ((1 - .2) * accumulatedSpeed); accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5; @@ -116,6 +123,11 @@ bool MotionController::ShouldShakeWake(uint16_t thresh) { } bool MotionController::ShouldLowerSleep() const { + if ((stats.xMean > 887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) > 30) || + (stats.xMean < -887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) < -30)) { + return true; + } + if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) { return false; } diff --git a/src/components/motion/MotionController.h b/src/components/motion/MotionController.h index b2e7e7fe..be0241d3 100644 --- a/src/components/motion/MotionController.h +++ b/src/components/motion/MotionController.h @@ -21,7 +21,7 @@ namespace Pinetime { void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps); int16_t X() const { - return x; + return xHistory[0]; } int16_t Y() const { @@ -76,11 +76,14 @@ namespace Pinetime { struct AccelStats { static constexpr uint8_t numHistory = 2; + int16_t xMean = 0; int16_t yMean = 0; int16_t zMean = 0; + int16_t prevXMean = 0; int16_t prevYMean = 0; int16_t prevZMean = 0; + uint32_t xVariance = 0; uint32_t yVariance = 0; uint32_t zVariance = 0; }; @@ -89,9 +92,8 @@ namespace Pinetime { AccelStats stats = {}; - int16_t lastX = 0; - int16_t x = 0; static constexpr uint8_t histSize = 8; + Utility::CircularBuffer xHistory = {}; Utility::CircularBuffer yHistory = {}; Utility::CircularBuffer zHistory = {}; int32_t accumulatedSpeed = 0; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 0f0be986..5f80c92b 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -27,6 +27,7 @@ #include "displayapp/screens/BatteryInfo.h" #include "displayapp/screens/Steps.h" #include "displayapp/screens/Dice.h" +#include "displayapp/screens/Weather.h" #include "displayapp/screens/PassKey.h" #include "displayapp/screens/Error.h" #include "displayapp/screens/Symbols.h" @@ -143,9 +144,6 @@ void DisplayApp::Process(void* instance) { NRF_LOG_INFO("displayapp task started!"); app->InitHw(); - // Send a dummy notification to unlock the lvgl display driver for the first iteration - xTaskNotifyGive(xTaskGetCurrentTaskHandle()); - while (true) { app->Refresh(); } @@ -453,6 +451,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio else { currentScreen.reset(userWatchFaces[0].create(controllers)); } + settingsController.SetAppMenu(0); } break; case Apps::Error: currentScreen = std::make_unique(bootError); @@ -568,9 +567,7 @@ void DisplayApp::PushMessage(Messages msg) { if (in_isr()) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } else { TickType_t timeout = portMAX_DELAY; // Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index c4bd5766..28892723 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -38,9 +38,6 @@ void DisplayApp::Process(void* instance) { auto* app = static_cast(instance); NRF_LOG_INFO("displayapp task started!"); - // Send a dummy notification to unlock the lvgl display driver for the first iteration - xTaskNotifyGive(xTaskGetCurrentTaskHandle()); - app->InitHw(); while (true) { app->Refresh(); @@ -94,7 +91,6 @@ void DisplayApp::DisplayLogo(uint16_t color) { Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack); for (int i = 0; i < displayWidth; i++) { rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel); - ulTaskNotifyTake(pdTRUE, 500); lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast(displayBuffer), displayWidth * bytesPerPixel); } } @@ -103,20 +99,15 @@ void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) { const uint8_t barHeight = 20; std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color); for (int i = 0; i < barHeight; i++) { - ulTaskNotifyTake(pdTRUE, 500); uint16_t barWidth = std::min(static_cast(percent) * 2.4f, static_cast(displayWidth)); lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast(displayBuffer), barWidth * bytesPerPixel); } } void DisplayApp::PushMessage(Display::Messages msg) { - BaseType_t xHigherPriorityTaskWoken; - xHigherPriorityTaskWoken = pdFALSE; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { - /* Actual macro used here is port specific. */ - // TODO : should I do something here? - } + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) { diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index 3a5c78d9..c1bf6243 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -5,7 +5,6 @@ #include #include #include -#include "components/gfx/Gfx.h" #include "drivers/Cst816s.h" #include #include diff --git a/src/displayapp/LittleVgl.cpp b/src/displayapp/LittleVgl.cpp index 89893cf7..c70a0856 100644 --- a/src/displayapp/LittleVgl.cpp +++ b/src/displayapp/LittleVgl.cpp @@ -152,10 +152,6 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) { void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) { uint16_t y1, y2, width, height = 0; - ulTaskNotifyTake(pdTRUE, 200); - // Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin - // which cannot be set/clear during a transfer. - if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) { writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines; } else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) { @@ -219,7 +215,6 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) { if (height > 0) { lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2); - ulTaskNotifyTake(pdTRUE, 100); } uint16_t pixOffset = width * height; diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 77d3b366..2104a267 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -28,6 +28,7 @@ namespace Pinetime { Motion, Steps, Dice, + Weather, PassKey, QuickSettings, Settings, @@ -41,8 +42,7 @@ namespace Pinetime { SettingChimes, SettingShakeThreshold, SettingBluetooth, - Error, - Weather + Error }; enum class WatchFace : uint8_t { diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 51c08595..d7858760 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -13,7 +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::Weather") + 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") endif () diff --git a/src/displayapp/fonts/README.md b/src/displayapp/fonts/README.md index bcc2d49c..20fb4a43 100644 --- a/src/displayapp/fonts/README.md +++ b/src/displayapp/fonts/README.md @@ -16,7 +16,7 @@ - Define the new symbols in `src/displayapp/screens/Symbols.h`: ``` -static constexpr const char* newSymbol = "\xEF\x86\x85"; +static constexpr const char* newSymbol = "\xEF\x99\x81"; ``` ### the config file format: diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 4e415f75..59afed0f 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, 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" } ], "bpp": 1, @@ -18,7 +18,7 @@ "sources": [ { "file": "JetBrainsMono-Regular.ttf", - "range": "0x25, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74" + "range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0" } ], "bpp": 1, @@ -28,7 +28,7 @@ "sources": [ { "file": "JetBrainsMono-Light.ttf", - "range": "0x25, 0x2D, 0x2F, 0x30-0x3a" + "range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0" } ], "bpp": 1, diff --git a/src/displayapp/screens/Alarm.h b/src/displayapp/screens/Alarm.h index 993d65d1..444102cb 100644 --- a/src/displayapp/screens/Alarm.h +++ b/src/displayapp/screens/Alarm.h @@ -69,7 +69,7 @@ namespace Pinetime { template <> struct AppTraits { static constexpr Apps app = Apps::Alarm; - static constexpr const char* icon = Screens::Symbols::clock; + static constexpr const char* icon = Screens::Symbols::bell; static Screens::Screen* Create(AppControllers& controllers) { return new Screens::Alarm(controllers.alarmController, diff --git a/src/displayapp/screens/HeartRate.cpp b/src/displayapp/screens/HeartRate.cpp index f611fa26..9677be3b 100644 --- a/src/displayapp/screens/HeartRate.cpp +++ b/src/displayapp/screens/HeartRate.cpp @@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); } - lv_label_set_text_static(label_hr, "000"); + lv_label_set_text_static(label_hr, "---"); lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40); label_bpm = lv_label_create(lv_scr_act(), nullptr); @@ -82,10 +82,14 @@ void HeartRate::Refresh() { case Controllers::HeartRateController::States::NoTouch: case Controllers::HeartRateController::States::NotEnoughData: // case Controllers::HeartRateController::States::Stopped: - lv_label_set_text_static(label_hr, "000"); + lv_label_set_text_static(label_hr, "---"); break; default: - lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate()); + if (heartRateController.HeartRate() == 0) { + lv_label_set_text_static(label_hr, "---"); + } else { + lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate()); + } } lv_label_set_text_static(label_status, ToString(state)); diff --git a/src/displayapp/screens/Motion.cpp b/src/displayapp/screens/Motion.cpp index 87c55eea..ecbed317 100644 --- a/src/displayapp/screens/Motion.cpp +++ b/src/displayapp/screens/Motion.cpp @@ -53,9 +53,9 @@ void Motion::Refresh() { lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps()); lv_label_set_text_fmt(label, - "X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d#", - motionController.X() / 0x10, - motionController.Y() / 0x10, - motionController.Z() / 0x10); + "X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# mg", + motionController.X(), + motionController.Y(), + motionController.Z()); lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10); } diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 8e59e550..323fc73a 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -11,6 +11,7 @@ namespace Pinetime { static constexpr const char* plug = "\xEF\x87\xA6"; static constexpr const char* shoe = "\xEF\x95\x8B"; static constexpr const char* clock = "\xEF\x80\x97"; + static constexpr const char* bell = "\xEF\x83\xB3"; static constexpr const char* info = "\xEF\x84\xA9"; static constexpr const char* list = "\xEF\x80\xBA"; static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/displayapp/screens/WatchFaceAnalog.cpp b/src/displayapp/screens/WatchFaceAnalog.cpp index 2b27ad64..80a1c8b9 100644 --- a/src/displayapp/screens/WatchFaceAnalog.cpp +++ b/src/displayapp/screens/WatchFaceAnalog.cpp @@ -256,7 +256,7 @@ void WatchFaceAnalog::Refresh() { if (currentDateTime.IsUpdated()) { UpdateClock(); - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); if (currentDate.IsUpdated()) { lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day()); } diff --git a/src/displayapp/screens/WatchFaceAnalog.h b/src/displayapp/screens/WatchFaceAnalog.h index 89ad4e13..2eee657e 100644 --- a/src/displayapp/screens/WatchFaceAnalog.h +++ b/src/displayapp/screens/WatchFaceAnalog.h @@ -43,8 +43,7 @@ namespace Pinetime { Utility::DirtyValue bleState {}; Utility::DirtyValue> currentDateTime; Utility::DirtyValue notificationState {false}; - using days = std::chrono::duration>; // TODO: days is standard in c++20 - Utility::DirtyValue> currentDate; + Utility::DirtyValue> currentDate; lv_obj_t* minor_scales; lv_obj_t* major_scales; diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp index 72bfaaa3..c695f852 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.cpp @@ -244,7 +244,7 @@ void WatchFaceCasioStyleG7710::Refresh() { } lv_obj_realign(label_time); - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); if (currentDate.IsUpdated()) { const char* weekNumberFormat = "%V"; diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.h b/src/displayapp/screens/WatchFaceCasioStyleG7710.h index f10e931c..0f46a692 100644 --- a/src/displayapp/screens/WatchFaceCasioStyleG7710.h +++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.h @@ -51,8 +51,7 @@ namespace Pinetime { Utility::DirtyValue heartbeat {}; Utility::DirtyValue heartbeatRunning {}; Utility::DirtyValue notificationState {}; - using days = std::chrono::duration>; // TODO: days is standard in c++20 - Utility::DirtyValue> currentDate; + Utility::DirtyValue> currentDate; lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}}; lv_point_t line_day_of_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}}; diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index 0a7da2fd..2e00ee98 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -40,16 +40,16 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); weatherIcon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); lv_label_set_text(weatherIcon, ""); - lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 0); + lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50); lv_obj_set_auto_realign(weatherIcon, true); temperature = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999)); lv_label_set_text(temperature, ""); - lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 0); + lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50); label_date = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60); @@ -125,7 +125,7 @@ void WatchFaceDigital::Refresh() { lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); } - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); if (currentDate.IsUpdated()) { uint16_t year = dateTimeController.Year(); uint8_t day = dateTimeController.Day(); diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index 3ff78c8a..7bb713cb 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -43,10 +43,6 @@ namespace Pinetime { uint8_t displayedHour = -1; uint8_t displayedMinute = -1; - Utility::DirtyValue batteryPercentRemaining {}; - Utility::DirtyValue powerPresent {}; - Utility::DirtyValue bleState {}; - Utility::DirtyValue bleRadioEnabled {}; Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue heartbeat {}; @@ -54,8 +50,7 @@ namespace Pinetime { Utility::DirtyValue notificationState {}; Utility::DirtyValue> currentWeather {}; - using days = std::chrono::duration>; // TODO: days is standard in c++20 - Utility::DirtyValue> currentDate; + Utility::DirtyValue> currentDate; lv_obj_t* label_time; lv_obj_t* label_time_ampm; diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 3308303d..4c6fc196 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -423,10 +423,11 @@ void WatchFaceInfineat::Refresh() { lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0); } - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); if (currentDate.IsUpdated()) { uint8_t day = dateTimeController.Day(); - lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(), day); + Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek(); + lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day); lv_obj_realign(labelDate); } } diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 32c08f18..55c43f98 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -55,8 +55,7 @@ namespace Pinetime { Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; - using days = std::chrono::duration>; // TODO: days is standard in c++20 - Utility::DirtyValue> currentDate; + Utility::DirtyValue> currentDate; // Lines making up the side cover lv_obj_t* lineBattery; diff --git a/src/displayapp/screens/WatchFaceTerminal.cpp b/src/displayapp/screens/WatchFaceTerminal.cpp index 72383729..96d77741 100644 --- a/src/displayapp/screens/WatchFaceTerminal.cpp +++ b/src/displayapp/screens/WatchFaceTerminal.cpp @@ -125,7 +125,7 @@ void WatchFaceTerminal::Refresh() { lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second); } - currentDate = std::chrono::time_point_cast(currentDateTime.Get()); + currentDate = std::chrono::time_point_cast(currentDateTime.Get()); if (currentDate.IsUpdated()) { uint16_t year = dateTimeController.Year(); Controllers::DateTime::Months month = dateTimeController.Month(); diff --git a/src/displayapp/screens/WatchFaceTerminal.h b/src/displayapp/screens/WatchFaceTerminal.h index ce22005f..bf460866 100644 --- a/src/displayapp/screens/WatchFaceTerminal.h +++ b/src/displayapp/screens/WatchFaceTerminal.h @@ -45,8 +45,7 @@ namespace Pinetime { Utility::DirtyValue heartbeat {}; Utility::DirtyValue heartbeatRunning {}; Utility::DirtyValue notificationState {}; - using days = std::chrono::duration>; // TODO: days is standard in c++20 - Utility::DirtyValue> currentDate; + Utility::DirtyValue> currentDate; lv_obj_t* label_time; lv_obj_t* label_date; diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp new file mode 100644 index 00000000..5321b7cc --- /dev/null +++ b/src/displayapp/screens/Weather.cpp @@ -0,0 +1,198 @@ +#include "displayapp/screens/Weather.h" +#include +#include "components/ble/SimpleWeatherService.h" +#include "components/datetime/DateTimeController.h" +#include "components/settings/Settings.h" +#include "displayapp/DisplayApp.h" +#include "displayapp/screens/WeatherSymbols.h" +#include "displayapp/InfiniTimeTheme.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + lv_color_t TemperatureColor(int16_t temperature) { + if (temperature <= 0) { // freezing + return Colors::blue; + } else if (temperature <= 400) { // ice + return LV_COLOR_CYAN; + } else if (temperature >= 2700) { // hot + return Colors::deepOrange; + } + return Colors::orange; // normal + } + + uint8_t TemperatureStyle(int16_t temperature) { + if (temperature <= 0) { // freezing + return LV_TABLE_PART_CELL3; + } else if (temperature <= 400) { // ice + return LV_TABLE_PART_CELL4; + } else if (temperature >= 2700) { // hot + return LV_TABLE_PART_CELL6; + } + return LV_TABLE_PART_CELL5; // normal + } + + int16_t RoundTemperature(int16_t temp) { + return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0); + } +} + +Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService) + : settingsController {settingsController}, weatherService {weatherService} { + + temperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); + lv_label_set_text(temperature, "---"); + lv_obj_align(temperature, nullptr, LV_ALIGN_CENTER, 0, -30); + lv_obj_set_auto_realign(temperature, true); + + minTemperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(minTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg); + lv_label_set_text(minTemperature, ""); + lv_obj_align(minTemperature, temperature, LV_ALIGN_OUT_LEFT_MID, -10, 0); + lv_obj_set_auto_realign(minTemperature, true); + + maxTemperature = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(maxTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg); + lv_label_set_text(maxTemperature, ""); + lv_obj_align(maxTemperature, temperature, LV_ALIGN_OUT_RIGHT_MID, 10, 0); + lv_obj_set_auto_realign(maxTemperature, true); + + condition = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(condition, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text(condition, ""); + lv_obj_align(condition, temperature, LV_ALIGN_OUT_TOP_MID, 0, -10); + lv_obj_set_auto_realign(condition, true); + + icon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text(icon, ""); + lv_obj_align(icon, condition, LV_ALIGN_OUT_TOP_MID, 0, 0); + lv_obj_set_auto_realign(icon, true); + + forecast = lv_table_create(lv_scr_act(), nullptr); + lv_table_set_col_cnt(forecast, Controllers::SimpleWeatherService::MaxNbForecastDays); + lv_table_set_row_cnt(forecast, 4); + // LV_TABLE_PART_CELL1: Default table style + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, Colors::lightGray); + // LV_TABLE_PART_CELL2: Condition icon + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, &fontawesome_weathericons); + // LV_TABLE_PART_CELL3: Freezing + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, Colors::blue); + // LV_TABLE_PART_CELL4: Ice + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_CYAN); + // LV_TABLE_PART_CELL5: Normal + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, Colors::orange); + // LV_TABLE_PART_CELL6: Hot + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, Colors::deepOrange); + + lv_obj_align(forecast, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + + for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { + lv_table_set_col_width(forecast, i, 48); + lv_table_set_cell_type(forecast, 1, i, LV_TABLE_PART_CELL2); + lv_table_set_cell_align(forecast, 0, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_CENTER); + } + + taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this); + Refresh(); +} + +Weather::~Weather() { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); +} + +void Weather::Refresh() { + currentWeather = weatherService.Current(); + if (currentWeather.IsUpdated()) { + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + int16_t temp = optCurrentWeather->temperature; + int16_t minTemp = optCurrentWeather->minTemperature; + int16_t maxTemp = optCurrentWeather->maxTemperature; + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, TemperatureColor(temp)); + char tempUnit = 'C'; + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp); + minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp); + maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp); + tempUnit = 'F'; + } + lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId)); + lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit); + lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp)); + lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp)); + } else { + lv_label_set_text(icon, ""); + lv_label_set_text(condition, ""); + lv_label_set_text(temperature, "---"); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_label_set_text(minTemperature, ""); + lv_label_set_text(maxTemperature, ""); + } + } + + currentForecast = weatherService.GetForecast(); + if (currentForecast.IsUpdated()) { + auto optCurrentForecast = currentForecast.Get(); + if (optCurrentForecast) { + std::tm localTime = *std::localtime(reinterpret_cast(&optCurrentForecast->timestamp)); + + for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { + int16_t maxTemp = optCurrentForecast->days[i].maxTemperature; + int16_t minTemp = optCurrentForecast->days[i].minTemperature; + lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp)); + lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp)); + if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { + maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp); + minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp); + } + uint8_t wday = localTime.tm_wday + i + 1; + if (wday > 7) { + wday -= 7; + } + maxTemp = RoundTemperature(maxTemp); + minTemp = RoundTemperature(minTemp); + const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast(wday)); + lv_table_set_cell_value(forecast, 0, i, dayOfWeek); + lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId)); + // Pad cells based on the largest number of digits on each column + char maxPadding[3] = " "; + char minPadding[3] = " "; + int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp); + if (diff <= 0) { + maxPadding[-diff] = '\0'; + minPadding[0] = '\0'; + } else { + maxPadding[0] = '\0'; + minPadding[diff] = '\0'; + } + lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp); + lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp); + } + } else { + for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { + lv_table_set_cell_value(forecast, 0, i, ""); + lv_table_set_cell_value(forecast, 1, i, ""); + lv_table_set_cell_value(forecast, 2, i, ""); + lv_table_set_cell_value(forecast, 3, i, ""); + lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL1); + lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL1); + } + } + } +} diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h new file mode 100644 index 00000000..6975311e --- /dev/null +++ b/src/displayapp/screens/Weather.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include "displayapp/screens/Screen.h" +#include "components/ble/SimpleWeatherService.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" +#include "utility/DirtyValue.h" + +namespace Pinetime { + + namespace Controllers { + class Settings; + } + + namespace Applications { + namespace Screens { + + class Weather : public Screen { + public: + Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService); + ~Weather() override; + + void Refresh() override; + + private: + Controllers::Settings& settingsController; + Controllers::SimpleWeatherService& weatherService; + + Utility::DirtyValue> currentWeather {}; + Utility::DirtyValue> currentForecast {}; + + lv_obj_t* icon; + lv_obj_t* condition; + lv_obj_t* temperature; + lv_obj_t* minTemperature; + lv_obj_t* maxTemperature; + lv_obj_t* forecast; + + lv_task_t* taskRefresh; + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::Weather; + static constexpr const char* icon = Screens::Symbols::cloudSunRain; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Weather(controllers.settingsController, *controllers.weatherController); + }; + }; + } +} diff --git a/src/displayapp/screens/WeatherSymbols.cpp b/src/displayapp/screens/WeatherSymbols.cpp index a7749541..de66312f 100644 --- a/src/displayapp/screens/WeatherSymbols.cpp +++ b/src/displayapp/screens/WeatherSymbols.cpp @@ -34,3 +34,28 @@ const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime:: break; } } + +const char* Pinetime::Applications::Screens::Symbols::GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) { + switch (icon) { + case Pinetime::Controllers::SimpleWeatherService::Icons::Sun: + return "Clear sky"; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun: + return "Few clouds"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds: + return "Scattered clouds"; + case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds: + return "Broken clouds"; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy: + return "Shower rain"; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain: + return "Rain"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm: + return "Thunderstorm"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Snow: + return "Snow"; + case Pinetime::Controllers::SimpleWeatherService::Icons::Smog: + return "Mist"; + default: + return ""; + } +} diff --git a/src/displayapp/screens/WeatherSymbols.h b/src/displayapp/screens/WeatherSymbols.h index 93453b4e..f3eeed55 100644 --- a/src/displayapp/screens/WeatherSymbols.h +++ b/src/displayapp/screens/WeatherSymbols.h @@ -7,6 +7,7 @@ namespace Pinetime { namespace Screens { namespace Symbols { const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon); + const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon); } } } diff --git a/src/displayapp/screens/settings/SettingBluetooth.cpp b/src/displayapp/screens/settings/SettingBluetooth.cpp index 82c3dee1..e4dc695c 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.cpp +++ b/src/displayapp/screens/settings/SettingBluetooth.cpp @@ -36,17 +36,19 @@ namespace { SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) : app {app}, + settings {settingsController}, checkboxList( 0, 1, "Bluetooth", Symbols::bluetooth, settingsController.GetBleRadioEnabled() ? 0 : 1, - [&settings = settingsController](uint32_t index) { + [this](uint32_t index) { const bool priorMode = settings.GetBleRadioEnabled(); const bool newMode = options[index].radioEnabled; if (newMode != priorMode) { settings.SetBleRadioEnabled(newMode); + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); } }, CreateOptionArray()) { @@ -54,6 +56,4 @@ SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pine SettingBluetooth::~SettingBluetooth() { lv_obj_clean(lv_scr_act()); - // Pushing the message in the OnValueChanged function causes a freeze? - app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); } diff --git a/src/displayapp/screens/settings/SettingBluetooth.h b/src/displayapp/screens/settings/SettingBluetooth.h index 1e3f9b81..0cf014f5 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.h +++ b/src/displayapp/screens/settings/SettingBluetooth.h @@ -20,6 +20,7 @@ namespace Pinetime { private: DisplayApp* app; + Pinetime::Controllers::Settings& settings; CheckboxList checkboxList; }; } diff --git a/src/drivers/Bma421.cpp b/src/drivers/Bma421.cpp index 84d76ab3..aff62b8d 100644 --- a/src/drivers/Bma421.cpp +++ b/src/drivers/Bma421.cpp @@ -22,6 +22,16 @@ namespace { void user_delay(uint32_t period_us, void* /*intf_ptr*/) { nrf_delay_us(period_us); } + + // Scale factors to convert accelerometer counts to milli-g + // from datasheet: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf + // The array index to use is stored in accel_conf.range + constexpr int16_t accelScaleFactors[] = { + [BMA4_ACCEL_RANGE_2G] = 1024, // LSB/g +/- 2g range + [BMA4_ACCEL_RANGE_4G] = 512, // LSB/g +/- 4g range + [BMA4_ACCEL_RANGE_8G] = 256, // LSB/g +/- 8g range + [BMA4_ACCEL_RANGE_16G] = 128 // LSB/g +/- 16g range + }; } Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} { @@ -74,7 +84,6 @@ void Bma421::Init() { if (ret != BMA4_OK) return; - struct bma4_accel_config accel_conf; accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ; accel_conf.range = BMA4_ACCEL_RANGE_2G; accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4; @@ -102,8 +111,17 @@ void Bma421::Write(uint8_t registerAddress, const uint8_t* data, size_t size) { Bma421::Values Bma421::Process() { if (not isOk) return {}; + struct bma4_accel rawData; struct bma4_accel data; - bma4_read_accel_xyz(&data, &bma); + bma4_read_accel_xyz(&rawData, &bma); + + // Scale the measured ADC counts to units of 'binary milli-g' + // where 1g = 1024 'binary milli-g' units. + // See https://github.com/InfiniTimeOrg/InfiniTime/pull/1950 for + // discussion of why we opted for scaling to 1024 rather than 1000. + data.x = 1024 * rawData.x / accelScaleFactors[accel_conf.range]; + data.y = 1024 * rawData.y / accelScaleFactors[accel_conf.range]; + data.z = 1024 * rawData.z / accelScaleFactors[accel_conf.range]; uint32_t steps = 0; bma423_step_counter_output(&steps, &bma); diff --git a/src/drivers/Bma421.h b/src/drivers/Bma421.h index fb832514..5269f62b 100644 --- a/src/drivers/Bma421.h +++ b/src/drivers/Bma421.h @@ -41,6 +41,7 @@ namespace Pinetime { TwiMaster& twiMaster; uint8_t deviceAddress = 0x18; struct bma4_dev bma; + struct bma4_accel_config accel_conf; // Store the device configuration for later reference. bool isOk = false; bool isResetOk = false; DeviceTypes deviceType = DeviceTypes::Unknown; diff --git a/src/drivers/Hrs3300.cpp b/src/drivers/Hrs3300.cpp index 7dfd301c..33889b6f 100644 --- a/src/drivers/Hrs3300.cpp +++ b/src/drivers/Hrs3300.cpp @@ -19,7 +19,7 @@ namespace { } /** Driver for the HRS3300 heart rate sensor. - * Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/drivers/hrs3300.py + * Original implementation from wasp-os : https://github.com/wasp-os/wasp-os/blob/master/wasp/drivers/hrs3300.py * * Experimentaly derived changes to improve signal/noise (see comments below) - Ceimour */ diff --git a/src/drivers/Spi.cpp b/src/drivers/Spi.cpp index c85b90c1..a95a7eae 100644 --- a/src/drivers/Spi.cpp +++ b/src/drivers/Spi.cpp @@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn { nrf_gpio_pin_set(pinCsn); } -bool Spi::Write(const uint8_t* data, size_t size) { - return spiMaster.Write(pinCsn, data, size); +bool Spi::Write(const uint8_t* data, size_t size, const std::function& preTransactionHook) { + return spiMaster.Write(pinCsn, data, size, preTransactionHook); } bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) { diff --git a/src/drivers/Spi.h b/src/drivers/Spi.h index 9b6a30f4..0c5edf08 100644 --- a/src/drivers/Spi.h +++ b/src/drivers/Spi.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "drivers/SpiMaster.h" namespace Pinetime { @@ -14,7 +15,7 @@ namespace Pinetime { Spi& operator=(Spi&&) = delete; bool Init(); - bool Write(const uint8_t* data, size_t size); + bool Write(const uint8_t* data, size_t size, const std::function& preTransactionHook); bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); void Sleep(); diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp index 3446d639..690a3226 100644 --- a/src/drivers/SpiMaster.cpp +++ b/src/drivers/SpiMaster.cpp @@ -136,17 +136,11 @@ void SpiMaster::OnEndEvent() { spiBaseAddress->TASKS_START = 1; } else { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - if (taskToNotify != nullptr) { - vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } - nrf_gpio_pin_set(this->pinCsn); currentBufferAddr = 0; - BaseType_t xHigherPriorityTaskWoken2 = pdFALSE; - xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken | xHigherPriorityTaskWoken2); + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } @@ -173,12 +167,11 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) { spiBaseAddress->EVENTS_END = 0; } -bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) { +bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& preTransactionHook) { if (data == nullptr) return false; auto ok = xSemaphoreTake(mutex, portMAX_DELAY); ASSERT(ok == true); - taskToNotify = xTaskGetCurrentTaskHandle(); this->pinCsn = pinCsn; @@ -188,6 +181,9 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) { DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); } + if (preTransactionHook != nullptr) { + preTransactionHook(); + } nrf_gpio_pin_clear(this->pinCsn); currentBufferAddr = (uint32_t) data; @@ -216,8 +212,6 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) { bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) { xSemaphoreTake(mutex, portMAX_DELAY); - taskToNotify = nullptr; - this->pinCsn = pinCsn; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); spiBaseAddress->INTENCLR = (1 << 6); @@ -265,8 +259,6 @@ void SpiMaster::Wakeup() { bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) { xSemaphoreTake(mutex, portMAX_DELAY); - taskToNotify = nullptr; - this->pinCsn = pinCsn; DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); spiBaseAddress->INTENCLR = (1 << 6); diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h index 8b698c57..af38e87b 100644 --- a/src/drivers/SpiMaster.h +++ b/src/drivers/SpiMaster.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include @@ -31,7 +32,7 @@ namespace Pinetime { SpiMaster& operator=(SpiMaster&&) = delete; bool Init(); - bool Write(uint8_t pinCsn, const uint8_t* data, size_t size); + bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function& preTransactionHook); bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize); bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize); @@ -56,7 +57,6 @@ namespace Pinetime { volatile uint32_t currentBufferAddr = 0; volatile size_t currentBufferSize = 0; - volatile TaskHandle_t taskToNotify; SemaphoreHandle_t mutex = nullptr; }; } diff --git a/src/drivers/SpiNorFlash.cpp b/src/drivers/SpiNorFlash.cpp index 28f82fe6..56a8aabd 100644 --- a/src/drivers/SpiNorFlash.cpp +++ b/src/drivers/SpiNorFlash.cpp @@ -22,7 +22,7 @@ void SpiNorFlash::Uninit() { void SpiNorFlash::Sleep() { auto cmd = static_cast(Commands::DeepPowerDown); - spi.Write(&cmd, sizeof(uint8_t)); + spi.Write(&cmd, sizeof(uint8_t), nullptr); NRF_LOG_INFO("[SpiNorFlash] Sleep") } diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 17d14ce6..12e95a41 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -1,8 +1,8 @@ #include "drivers/St7789.h" #include -#include #include #include "drivers/Spi.h" +#include "task.h" using namespace Pinetime::Drivers; @@ -29,37 +29,77 @@ void St7789::Init() { DisplayOn(); } -void St7789::WriteCommand(uint8_t cmd) { - nrf_gpio_pin_clear(pinDataCommand); - WriteSpi(&cmd, 1); -} - void St7789::WriteData(uint8_t data) { - nrf_gpio_pin_set(pinDataCommand); - WriteSpi(&data, 1); + WriteData(&data, 1); } -void St7789::WriteSpi(const uint8_t* data, size_t size) { - spi.Write(data, size); +void St7789::WriteData(const uint8_t* data, size_t size) { + WriteSpi(data, size, [pinDataCommand = pinDataCommand]() { + nrf_gpio_pin_set(pinDataCommand); + }); +} + +void St7789::WriteCommand(uint8_t data) { + WriteCommand(&data, 1); +} + +void St7789::WriteCommand(const uint8_t* data, size_t size) { + WriteSpi(data, size, [pinDataCommand = pinDataCommand]() { + nrf_gpio_pin_clear(pinDataCommand); + }); +} + +void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function& preTransactionHook) { + spi.Write(data, size, preTransactionHook); } void St7789::SoftwareReset() { + EnsureSleepOutPostDelay(); WriteCommand(static_cast(Commands::SoftwareReset)); - nrf_delay_ms(150); + // If sleep in: must wait 120ms before sleep out can sent (see driver datasheet) + // Unconditionally wait as software reset doesn't need to be performant + sleepIn = true; + lastSleepExit = xTaskGetTickCount(); + vTaskDelay(pdMS_TO_TICKS(125)); } void St7789::SleepOut() { + if (!sleepIn) { + return; + } WriteCommand(static_cast(Commands::SleepOut)); + // Wait 5ms for clocks to stabilise + // pdMS rounds down => 6 used here + vTaskDelay(pdMS_TO_TICKS(6)); + // Cannot send sleep in or software reset for 120ms + lastSleepExit = xTaskGetTickCount(); + sleepIn = false; +} + +void St7789::EnsureSleepOutPostDelay() { + TickType_t delta = xTaskGetTickCount() - lastSleepExit; + // Due to timer wraparound, there is a chance of delaying when not necessary + // It is very low (pdMS_TO_TICKS(125)/2^32) and waiting an extra 125ms isn't too bad + if (delta < pdMS_TO_TICKS(125)) { + vTaskDelay(pdMS_TO_TICKS(125) - delta); + } } void St7789::SleepIn() { + if (sleepIn) { + return; + } + EnsureSleepOutPostDelay(); WriteCommand(static_cast(Commands::SleepIn)); + // Wait 5ms for clocks to stabilise + // pdMS rounds down => 6 used here + vTaskDelay(pdMS_TO_TICKS(6)); + sleepIn = true; } void St7789::ColMod() { WriteCommand(static_cast(Commands::ColMod)); WriteData(0x55); - nrf_delay_ms(10); } void St7789::MemoryDataAccessControl() { @@ -96,12 +136,10 @@ void St7789::RowAddressSet() { void St7789::DisplayInversionOn() { WriteCommand(static_cast(Commands::DisplayInversionOn)); - nrf_delay_ms(10); } void St7789::NormalModeOn() { WriteCommand(static_cast(Commands::NormalModeOn)); - nrf_delay_ms(10); } void St7789::DisplayOn() { @@ -120,12 +158,11 @@ void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { WriteData(y0 & 0xff); WriteData(y1 >> 8); WriteData(y1 & 0xff); - - WriteToRam(); } -void St7789::WriteToRam() { +void St7789::WriteToRam(const uint8_t* data, size_t size) { WriteCommand(static_cast(Commands::WriteToRam)); + WriteData(data, size); } void St7789::SetVdv() { @@ -137,17 +174,6 @@ void St7789::SetVdv() { void St7789::DisplayOff() { WriteCommand(static_cast(Commands::DisplayOff)); - nrf_delay_ms(500); -} - -void St7789::VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) { - WriteCommand(static_cast(Commands::VerticalScrollDefinition)); - WriteData(topFixedLines >> 8u); - WriteData(topFixedLines & 0x00ffu); - WriteData(scrollLines >> 8u); - WriteData(scrollLines & 0x00ffu); - WriteData(bottomFixedLines >> 8u); - WriteData(bottomFixedLines & 0x00ffu); } void St7789::VerticalScrollStartAddress(uint16_t line) { @@ -160,27 +186,20 @@ void St7789::VerticalScrollStartAddress(uint16_t line) { void St7789::Uninit() { } -void St7789::DrawPixel(uint16_t x, uint16_t y, uint32_t color) { - if (x >= Width || y >= Height) { - return; - } - - SetAddrWindow(x, y, x + 1, y + 1); - - nrf_gpio_pin_set(pinDataCommand); - WriteSpi(reinterpret_cast(&color), 2); -} - void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) { SetAddrWindow(x, y, x + width - 1, y + height - 1); - nrf_gpio_pin_set(pinDataCommand); - WriteSpi(data, size); + WriteToRam(data, size); } void St7789::HardwareReset() { nrf_gpio_pin_clear(pinReset); - nrf_delay_ms(10); + vTaskDelay(pdMS_TO_TICKS(1)); nrf_gpio_pin_set(pinReset); + // If hardware reset started while sleep out, reset time may be up to 120ms + // Unconditionally wait as hardware reset doesn't need to be performant + sleepIn = true; + lastSleepExit = xTaskGetTickCount(); + vTaskDelay(pdMS_TO_TICKS(125)); } void St7789::Sleep() { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 68e1da44..45d4b56d 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -1,6 +1,9 @@ #pragma once #include #include +#include + +#include namespace Pinetime { namespace Drivers { @@ -16,9 +19,7 @@ namespace Pinetime { void Init(); void Uninit(); - void DrawPixel(uint16_t x, uint16_t y, uint32_t color); - void VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines); void VerticalScrollStartAddress(uint16_t line); void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size); @@ -31,23 +32,27 @@ namespace Pinetime { uint8_t pinDataCommand; uint8_t pinReset; uint8_t verticalScrollingStartAddress = 0; + bool sleepIn; + TickType_t lastSleepExit; void HardwareReset(); void SoftwareReset(); void SleepOut(); + void EnsureSleepOutPostDelay(); void SleepIn(); void ColMod(); void MemoryDataAccessControl(); void DisplayInversionOn(); void NormalModeOn(); - void WriteToRam(); + void WriteToRam(const uint8_t* data, size_t size); void DisplayOn(); void DisplayOff(); void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetVdv(); void WriteCommand(uint8_t cmd); - void WriteSpi(const uint8_t* data, size_t size); + void WriteCommand(const uint8_t* data, size_t size); + void WriteSpi(const uint8_t* data, size_t size, const std::function& preTransactionHook); enum class Commands : uint8_t { SoftwareReset = 0x01, @@ -67,6 +72,7 @@ namespace Pinetime { VdvSet = 0xc4, }; void WriteData(uint8_t data); + void WriteData(const uint8_t* data, size_t size); void ColumnAddressSet(); static constexpr uint16_t Width = 240; diff --git a/src/heartratetask/HeartRateTask.cpp b/src/heartratetask/HeartRateTask.cpp index 414cdf2c..9d82d11e 100644 --- a/src/heartratetask/HeartRateTask.cpp +++ b/src/heartratetask/HeartRateTask.cpp @@ -103,10 +103,7 @@ void HeartRateTask::Work() { void HeartRateTask::PushMessage(HeartRateTask::Messages msg) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { - /* Actual macro used here is port specific. */ - // TODO : should I do something here? - } + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } void HeartRateTask::StartMeasurement() { diff --git a/src/libs/lv_conf.h b/src/libs/lv_conf.h index bdb74d9d..9bf21fb8 100644 --- a/src/libs/lv_conf.h +++ b/src/libs/lv_conf.h @@ -730,7 +730,9 @@ typedef void* lv_obj_user_data_t; #define LV_USE_TABLE 1 #if LV_USE_TABLE #define LV_TABLE_COL_MAX 12 -#define LV_TABLE_CELL_STYLE_CNT 5 +#define LV_TABLE_CELL_STYLE_CNT 6 +#define LV_TABLE_PART_CELL5 5 +#define LV_TABLE_PART_CELL6 6 #endif diff --git a/src/recoveryLoader.cpp b/src/recoveryLoader.cpp index 723977e3..fc9ab76c 100644 --- a/src/recoveryLoader.cpp +++ b/src/recoveryLoader.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -48,7 +47,6 @@ Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn}; Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset}; -Pinetime::Components::Gfx gfx {lcd}; Pinetime::Controllers::BrightnessController brightnessController; void DisplayProgressBar(uint8_t percent, uint16_t color); @@ -92,7 +90,6 @@ void Process(void* /*instance*/) { spiNorFlash.Wakeup(); brightnessController.Init(); lcd.Init(); - gfx.Init(); NRF_LOG_INFO("Display logo") DisplayLogo(); @@ -124,7 +121,6 @@ void DisplayLogo() { Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb)); for (int i = 0; i < displayWidth; i++) { rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel); - ulTaskNotifyTake(pdTRUE, 500); lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast(displayBuffer), displayWidth * bytesPerPixel); } } @@ -133,7 +129,6 @@ void DisplayProgressBar(uint8_t percent, uint16_t color) { static constexpr uint8_t barHeight = 20; std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color); for (int i = 0; i < barHeight; i++) { - ulTaskNotifyTake(pdTRUE, 500); uint16_t barWidth = std::min(static_cast(percent) * 2.4f, static_cast(displayWidth)); lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast(displayBuffer), barWidth * bytesPerPixel); } diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 57c8d0ce..82b6e610 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -370,9 +370,13 @@ void SystemTask::Work() { 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) { touchHandler.SetWokenBy(Pinetime::Controllers::TouchHandler::WokenBy::Other, false); GoToRunning(); + PushMessage(msg); + } else { displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); } } @@ -382,9 +386,13 @@ void SystemTask::Work() { 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) { touchHandler.SetWokenBy(Pinetime::Controllers::TouchHandler::WokenBy::Other, false); GoToRunning(); + PushMessage(msg); + } else { displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); } } @@ -539,10 +547,7 @@ void SystemTask::PushMessage(System::Messages msg) { if (in_isr()) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) { - /* Actual macro used here is port specific. */ - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } else { xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY); }