diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af48bf49..f9d43494 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,20 +46,30 @@ jobs: # 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 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 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 + with: + name: InfiniTime image ${{ env.REF_NAME }} + path: ./build/output/src/pinetime-app-*.out - name: Upload resources artifacts uses: actions/upload-artifact@v3 with: - name: InfiniTime resources ${{ github.head_ref }} + name: InfiniTime resources ${{ env.REF_NAME }} path: ./build/output/infinitime-resources-*.zip build-simulator: @@ -109,7 +119,7 @@ jobs: - name: Upload simulator executable uses: actions/upload-artifact@v3 with: - name: infinisim-${{ github.head_ref }} + name: infinisim-${{ env.REF_NAME }} path: build_lv_sim/infinisim get-base-ref-size: diff --git a/.gitmodules b/.gitmodules index e629301e..cbd28c02 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/CMakeLists.txt b/CMakeLists.txt index 9b5669b0..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.14.0 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 97fe94b9..4e49b19b 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++.* + +
+ +
## Wadokei fork @@ -19,19 +35,26 @@ See https://github.com/Sudrien/watchy_wadokei/blob/main/Reckoning.md for more id - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) - [Updating the software](doc/gettingStarted/updating-software.md) - [About the firmware and bootloader](doc/gettingStarted/about-software.md) -- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle) - - [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather) +- [Available apps](doc/gettingStarted/Applications.md) +- [Available watch faces](/doc/gettingStarted/Watchfaces.md) +- [PineTimeStyle Watch face](https://pine64.org/documentation/PineTime/Watchfaces/PineTimeStyle) + - [Weather integration](https://pine64.org/documentation/PineTime/Software/InfiniTime_weather/) ### 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 @@ -45,7 +68,7 @@ See https://github.com/Sudrien/watchy_wadokei/blob/main/Reckoning.md for more id ### Contributing -- [How to contribute?](CONTRIBUTING.md) +- [How to contribute](CONTRIBUTING.md) - [Coding conventions](doc/coding-convention.md) ### Build, flash and debug diff --git a/doc/gettingStarted/Applications.md b/doc/gettingStarted/Applications.md new file mode 100644 index 00000000..8ca2b252 --- /dev/null +++ b/doc/gettingStarted/Applications.md @@ -0,0 +1,99 @@ +# Applications + +InfiniTime has 13 apps on the `main` branch at the time of writing. + +## List of apps +- Stopwatch +- Alarm +- Timer +- Steps +- Heartrate +- Music +- InfiniPaint +- Paddle +- 2 +- InfiniDice +- Metronome +- Maps +- Weather + +### Stopwatch +![Stopwatch UI](/doc/gettingStarted/AppsScreenshots/stopwatch.png) +- Press the Start button (bottom right) to start or stop the timer. + - You can also press the side button while the timer is running to pause the timer. +- Press the Flag button (bottom left) to add a lap. +- The stopwatch will not yet continue counting time while the app is closed. + +### Alarm +![Alarm UI](/doc/gettingStarted/AppsScreenshots/alarm.png) +- Ajust the time with the time picker. +- Press the Info button in the top middle to see time remaning. +- Use the toggle in the bottom left to turn the alarm on/off. +- Use the button in the bottom right to change the alarm frequency. + - You can choose between once, daily, or Monday - Friday. + +### Timer +![Timer UI](/doc/gettingStarted/AppsScreenshots/timer.png) +- Ajust how long the timer should go for with the time picker. +- Press the Start button at the bottom to start/stop the timer. + +### Steps +![Steps UI](/doc/gettingStarted/AppsScreenshots/steps.png) +- The total count of steps for the current display will show in the middle of the screen. +- The Reset button in the bottom middle resets the Trip counter. (Total of all steps taken.) +- The progress circle shows the percentage of your daily goal completed. + +### Heartrate +![Heartrate UI](/doc/gettingStarted/AppsScreenshots/Heartrate.png) +- Press Start to start measuring your heartrate. + - It may take a bit to get the first measurement. + +### Music +![Music UI](/doc/gettingStarted/AppsScreenshots/Music.png) +- This app shows currently playing music. + - Please note that this app is not very useful without a device connected. +- Press the button in the center to play/pause, and the buttons on the left and right to go to the previous and next tracks, respectively. +- Swipe up to get to volume controls. + +### InfiniPaint +![InfiniPaint UI](/doc/gettingStarted/AppsScreenshots/Paint.png) +- This app does not allow you to swipe from the top to exit, use the side button instead. +- Draw on the screen to add lines. +- Hold down in one spot to change paint colors. + +### Paddle +![Paddle UI](/doc/gettingStarted/AppsScreenshots/Pong.png) +- This app does not allow you to swipe from the top to exit, use the side button instead. +- Drag your finger to move the paddle. +- Goal: Don't let the ball go off the left side of the screen. + +### 2 +![2 UI](/doc/gettingStarted/AppsScreenshots/2048.png) +- This app does not allow you to swipe from the top to exit, use the side button instead. +- Play a game of 2048. +- Swipe up, down, left, or right tomove the tiles. +- When two tiles with the same number run into each other, they will add together. +- Goal: Don't let the screen fill up with tiles, and get to the 2048 tile to win. + +### InfiniDice +![InfiniDice UI](/doc/gettingStarted/AppsScreenshots/Dice.png) +- Ajust the count to change the number of dice. +- Ajust the sides to change the number of sides. +- Press the button at the bottom to roll. +- The result will be on the right side of the screen. + +### Metronome +![Metronome UI](/doc/gettingStarted/AppsScreenshots/Metronome.png) +- Ajust the BPM with the circular slider. + - A bug currently makes it always snap to 98 BPM. +- Use the button in the bottom left to start the metronome. + +### Maps +![Maps UI](/doc/gettingStarted/AppsScreenshots/Maps.png) +- This app shows info from a navigation app. + - Please note that this app is not very useful without a device connected. + +### Weather +![Weather UI](/doc/gettingStarted/AppsScreenshots/Weather.png) +- This app shows weather info. + - Please note that this app is not very useful without a device connected. diff --git a/doc/gettingStarted/AppsScreenshots/2048.png b/doc/gettingStarted/AppsScreenshots/2048.png new file mode 100644 index 00000000..472e4fce Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/2048.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Dice.png b/doc/gettingStarted/AppsScreenshots/Dice.png new file mode 100644 index 00000000..f5421c83 Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Dice.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Heartrate.png b/doc/gettingStarted/AppsScreenshots/Heartrate.png new file mode 100644 index 00000000..ed0470dc Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Heartrate.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Maps.png b/doc/gettingStarted/AppsScreenshots/Maps.png new file mode 100644 index 00000000..84e3ebef Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Maps.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Metronome.png b/doc/gettingStarted/AppsScreenshots/Metronome.png new file mode 100644 index 00000000..989fbfda Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Metronome.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Music.png b/doc/gettingStarted/AppsScreenshots/Music.png new file mode 100644 index 00000000..063603e7 Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Music.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Paint.png b/doc/gettingStarted/AppsScreenshots/Paint.png new file mode 100644 index 00000000..147487f9 Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Paint.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Pong.png b/doc/gettingStarted/AppsScreenshots/Pong.png new file mode 100644 index 00000000..08f739ef Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Pong.png differ diff --git a/doc/gettingStarted/AppsScreenshots/Weather.png b/doc/gettingStarted/AppsScreenshots/Weather.png new file mode 100644 index 00000000..89714aa4 Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/Weather.png differ diff --git a/doc/gettingStarted/AppsScreenshots/alarm.png b/doc/gettingStarted/AppsScreenshots/alarm.png new file mode 100644 index 00000000..5d840e80 Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/alarm.png differ diff --git a/doc/gettingStarted/AppsScreenshots/steps.png b/doc/gettingStarted/AppsScreenshots/steps.png new file mode 100644 index 00000000..1d7f5608 Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/steps.png differ diff --git a/doc/gettingStarted/AppsScreenshots/stopwatch.png b/doc/gettingStarted/AppsScreenshots/stopwatch.png new file mode 100644 index 00000000..19b34a5a Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/stopwatch.png differ diff --git a/doc/gettingStarted/AppsScreenshots/timer.png b/doc/gettingStarted/AppsScreenshots/timer.png new file mode 100644 index 00000000..a0d3babf Binary files /dev/null and b/doc/gettingStarted/AppsScreenshots/timer.png differ diff --git a/doc/gettingStarted/Watchfaces.md b/doc/gettingStarted/Watchfaces.md new file mode 100644 index 00000000..9edff0bb --- /dev/null +++ b/doc/gettingStarted/Watchfaces.md @@ -0,0 +1,31 @@ +# Watchfaces + +InfiniTime has 6 apps on the `main` branch at the time of writing. + +## List of apps +- Digital +- Analog +- PineTimeStyle +- Terminal +- Infinineat +- Casio G7710 + +### Digital +![Digital face](/doc/gettingStarted/Watchfaces/Digital.png) + +### Analog +![Analog face](/doc/gettingStarted/Watchfaces/Analog.png) + +### PineTimeStyle +![PineTimeStyle face](/doc/gettingStarted/Watchfaces/PineTimeStyle.png) + - You can long-press on the display to change colors, step style, and weather. + +### Terminal +![Terminal face](/doc/gettingStarted/Watchfaces/Terminal.png) + +### Infinineat +![Infinineat face](/doc/gettingStarted/Watchfaces/Infinineat.png) + - You can long-press on the display to change colors. + +### Casio G7710 +![Casio G7710 face](/doc/gettingStarted/Watchfaces/CasioG7710.png) diff --git a/doc/gettingStarted/Watchfaces/Analog.png b/doc/gettingStarted/Watchfaces/Analog.png new file mode 100644 index 00000000..9e64e5aa Binary files /dev/null and b/doc/gettingStarted/Watchfaces/Analog.png differ diff --git a/doc/gettingStarted/Watchfaces/CasioG7710.png b/doc/gettingStarted/Watchfaces/CasioG7710.png new file mode 100644 index 00000000..b8a55cd5 Binary files /dev/null and b/doc/gettingStarted/Watchfaces/CasioG7710.png differ diff --git a/doc/gettingStarted/Watchfaces/Digital.png b/doc/gettingStarted/Watchfaces/Digital.png new file mode 100644 index 00000000..242c9d5b Binary files /dev/null and b/doc/gettingStarted/Watchfaces/Digital.png differ diff --git a/doc/gettingStarted/Watchfaces/Infinineat.png b/doc/gettingStarted/Watchfaces/Infinineat.png new file mode 100644 index 00000000..6b8cd614 Binary files /dev/null and b/doc/gettingStarted/Watchfaces/Infinineat.png differ diff --git a/doc/gettingStarted/Watchfaces/PineTimeStyle.png b/doc/gettingStarted/Watchfaces/PineTimeStyle.png new file mode 100644 index 00000000..46f0b42b Binary files /dev/null and b/doc/gettingStarted/Watchfaces/PineTimeStyle.png differ diff --git a/doc/gettingStarted/Watchfaces/Terminal.png b/doc/gettingStarted/Watchfaces/Terminal.png new file mode 100644 index 00000000..3f148c5f Binary files /dev/null and b/doc/gettingStarted/Watchfaces/Terminal.png differ diff --git a/doc/gettingStarted/gadgetbridge0.jpg b/doc/gettingStarted/gadgetbridge0.jpg index 7ea2e3cd..2a0a295e 100644 Binary files a/doc/gettingStarted/gadgetbridge0.jpg and b/doc/gettingStarted/gadgetbridge0.jpg differ diff --git a/doc/gettingStarted/gadgetbridge1.jpg b/doc/gettingStarted/gadgetbridge1.jpg index 470b0b3c..20fa7d5a 100644 Binary files a/doc/gettingStarted/gadgetbridge1.jpg and b/doc/gettingStarted/gadgetbridge1.jpg differ diff --git a/doc/gettingStarted/gadgetbridge2.jpg b/doc/gettingStarted/gadgetbridge2.jpg index 8f75b232..a03b071a 100644 Binary files a/doc/gettingStarted/gadgetbridge2.jpg and b/doc/gettingStarted/gadgetbridge2.jpg differ diff --git a/doc/gettingStarted/gadgetbridge3.jpg b/doc/gettingStarted/gadgetbridge3.jpg index cfb83f52..2e9bea34 100644 Binary files a/doc/gettingStarted/gadgetbridge3.jpg and b/doc/gettingStarted/gadgetbridge3.jpg differ diff --git a/doc/gettingStarted/gadgetbridge4.jpg b/doc/gettingStarted/gadgetbridge4.jpg index e41aff04..1b4210ca 100644 Binary files a/doc/gettingStarted/gadgetbridge4.jpg and b/doc/gettingStarted/gadgetbridge4.jpg differ diff --git a/doc/gettingStarted/gadgetbridge5.jpg b/doc/gettingStarted/gadgetbridge5.jpg index 1ef9f779..15efb4d4 100644 Binary files a/doc/gettingStarted/gadgetbridge5.jpg and b/doc/gettingStarted/gadgetbridge5.jpg differ diff --git a/doc/gettingStarted/gettingStarted-1.0.md b/doc/gettingStarted/gettingStarted-1.0.md index 5c607444..2c35f1c1 100644 --- a/doc/gettingStarted/gettingStarted-1.0.md +++ b/doc/gettingStarted/gettingStarted-1.0.md @@ -14,7 +14,7 @@ You can sync the time using companion apps. - Gadgetbridge automatically synchronizes the time when you connect it to your watch. More information on Gadgetbridge [here](/doc/gettingStarted/ota-gadgetbridge.md) - [Sync the time with NRFConnect](/doc/gettingStarted/time-nrfconnect.md) -- Sync the time with your browser https://hubmartin.github.io/WebBLEWatch/ +- [Sync the time with your browser](https://hubmartin.github.io/WebBLEWatch/) You can also set the time in the settings without a companion app. (version >1.7.0) @@ -46,7 +46,7 @@ On the bottom right, you can see how many steps you have taken today. ![Settings](ui/settings.jpg) - Swipe **up** to display the application menus. Apps (stopwatch, music, step, games,...) can be started from this menu. -- Swipe **down** to display the notification panel. Notification sent by your companion app will be displayed here. +- Swipe **down** to display the notification panel. Notifications sent by your companion app will be displayed here. - Swipe **right** to display the Quick Actions menu. This menu allows you to - Set the brightness of the display - Start the **flashlight** app diff --git a/doc/gettingStarted/ota-gadgetbridge.md b/doc/gettingStarted/ota-gadgetbridge.md index fe26c03b..76f99145 100644 --- a/doc/gettingStarted/ota-gadgetbridge.md +++ b/doc/gettingStarted/ota-gadgetbridge.md @@ -1,29 +1,35 @@ # Connecting to Gadgetbridge -Launch Gadgetbridge and tap on the **"+"** button on the bottom right to add a new device: +Launch Gadgetbridge and tap on the menu button in the top left: ![Gadgetbridge 0](gadgetbridge0.jpg) -Wait for the scan to complete, your PineTime should be detected: +Press the "Connect new device" button: ![Gadgetbridge 1](gadgetbridge1.jpg) +Your PineTime should appear on the list. Tap on it. + Tap on it. Gadgdetbridge will pair and connect to your device: ![Gadgetbridge 2](gadgetbridge2.jpg) # Updating with Gadgetbridge -Now that Gadgetbridge is connected to your PineTime, use a file browser application and find the DFU file (`pinetime-mcuboot-app-dfu-x.x.x.zip`) you downloaded previously. Tap on it and open it using the Gadgetbridge application/firmware installer: +Now that Gadgetbridge is connected to your PineTime, press the three dots on the device card: ![Gadgetbridge 3](gadgetbridge3.jpg) -Read the warning carefully and tap **Install**: +Now press the "File Installer" button: ![Gadgetbridge 4](gadgetbridge4.jpg) -Wait for the transfer to finish. Your PineTime should reset and reboot with the new version of InfiniTime! - -Don't forget to **validate** your firmware. In the InfiniTime go to the settings (swipe right, select gear icon) and Firmware option and click **validate**. Otherwise after reboot the previous firmware will be used. +Select the firmware you downloaded (`pinetime-mcuboot-app-dfu-x.x.x.zip`) from the [Releases tab](https://github.com/InfiniTimeOrg/InfiniTime/releases/latest): ![Gadgetbridge 5](gadgetbridge5.jpg) + +Wait for the transfer to finish. There will be a progress bar on both the watch and the phone. Your PineTime should reboot with the new version of InfiniTime! + +Don't forget to **validate** your firmware. In the InfiniTime go to the settings (swipe right, select gear icon) and scroll to the Firmware option and click **validate**. Otherwise, after reboot the previous firmware will be used. + +![Validate](validate.png) diff --git a/doc/gettingStarted/updating-software.md b/doc/gettingStarted/updating-software.md index 2b5d5d92..95149975 100644 --- a/doc/gettingStarted/updating-software.md +++ b/doc/gettingStarted/updating-software.md @@ -6,7 +6,7 @@ If you just want to flash or upgrade InfiniTime on your PineTime, this page is f You can check the InfiniTime version by first swiping right on the watch face to open quick settings, tapping the cogwheel to open settings, swipe up until you find an entry named "About" and tap on it. -![InfiniTime 1.0 version](version-1.0.jpg) +![InfiniTime 1.14 version](version.png) PineTimes shipped after June 2021 will ship with the latest version of [the bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader/releases/tag/1.0.0) and [recovery firmware](https://github.com/InfiniTimeOrg/InfiniTime/releases/tag/0.14.1) @@ -49,7 +49,7 @@ Since those resources are not part of the firmware, they need to be flashed and Resources are packaged into a single .zip file named `infinitime-resources-x.y.z.zip` (where `x`, `y` and `z` are the version numbers of InfiniTime). You can use the companion app of your choice to flash the resources. -**Note : at the time of writing this page, [Amazfish](https://github.com/piggz/harbour-amazfish) and [ITD](https://gitea.arsenm.dev/Arsen6331/itd) have already integrated this functionality. Other companion apps will hopefully implement it soon!* +**Note: at the time of writing this page, [Amazfish](https://github.com/piggz/harbour-amazfish) and [ITD](https://gitea.arsenm.dev/Arsen6331/itd) have already integrated this functionality. Other companion apps will hopefully implement it soon!* ## Amazfish Use the `Download file` functionality of Amazfish. diff --git a/doc/gettingStarted/validate.png b/doc/gettingStarted/validate.png new file mode 100644 index 00000000..a6f3286b Binary files /dev/null and b/doc/gettingStarted/validate.png differ diff --git a/doc/gettingStarted/version.png b/doc/gettingStarted/version.png new file mode 100644 index 00000000..89f505a3 Binary files /dev/null and b/doc/gettingStarted/version.png differ 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..e6d92aae 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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/src/CMakeLists.txt b/src/CMakeLists.txt index 551993d8..2ceed44c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -480,6 +480,7 @@ list(APPEND SOURCE_FILES systemtask/SystemTask.cpp systemtask/SystemMonitor.cpp + systemtask/WakeLock.cpp drivers/TwiMaster.cpp heartratetask/HeartRateTask.cpp @@ -544,6 +545,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 @@ -656,6 +658,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 @@ -856,7 +859,6 @@ target_compile_options(nrf-sdk PRIVATE $<$: ${DEBUG_FLAGS}> $<$: ${CXX_FLAGS}> $<$: ${ASM_FLAGS}> - -O3 ) # NimBLE diff --git a/src/components/alarm/AlarmController.cpp b/src/components/alarm/AlarmController.cpp index c4eb8ed0..7dbfb0e3 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,72 @@ uint32_t AlarmController::SecondsToAlarm() const { void AlarmController::DisableAlarm() { xTimerStop(alarmTimer, 0); - state = AlarmState::Not_Set; + isAlerting = false; + 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..2427513d 100644 --- a/src/components/ble/DfuService.cpp +++ b/src/components/ble/DfuService.cpp @@ -124,9 +124,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 b56911b9..6652cdc1 100644 --- a/src/components/ble/DfuService.h +++ b/src/components/ble/DfuService.h @@ -77,6 +77,10 @@ namespace Pinetime { uint16_t ComputeCrc(uint8_t const* p_data, uint32_t size, uint16_t const* p_crc); }; + static constexpr ble_uuid128_t serviceUuid { + .u {.type = BLE_UUID_TYPE_128}, + .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}}; + private: Pinetime::System::SystemTask& systemTask; Pinetime::Controllers::Ble& bleController; @@ -90,10 +94,6 @@ namespace Pinetime { uint16_t revision {0x0008}; - static constexpr ble_uuid128_t serviceUuid { - .u {.type = BLE_UUID_TYPE_128}, - .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}}; - static constexpr ble_uuid128_t packetCharacteristicUuid { .u {.type = BLE_UUID_TYPE_128}, .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x32, 0x15, 0x00, 0x00}}; diff --git a/src/components/ble/HeartRateService.h b/src/components/ble/HeartRateService.h index 3f32fd09..ca8f10fb 100644 --- a/src/components/ble/HeartRateService.h +++ b/src/components/ble/HeartRateService.h @@ -21,14 +21,14 @@ namespace Pinetime { void SubscribeNotification(uint16_t attributeHandle); void UnsubscribeNotification(uint16_t attributeHandle); + static constexpr uint16_t heartRateServiceId {0x180D}; + static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId}; + private: NimbleController& nimble; Controllers::HeartRateController& heartRateController; - static constexpr uint16_t heartRateServiceId {0x180D}; static constexpr uint16_t heartRateMeasurementId {0x2A37}; - static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId}; - static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId}; struct ble_gatt_chr_def characteristicDefinition[2]; 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..93d94a34 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 { diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp index 2e7f8003..5059007a 100644 --- a/src/components/ble/NimbleController.cpp +++ b/src/components/ble/NimbleController.cpp @@ -158,7 +158,10 @@ void NimbleController::StartAdvertising() { } fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; - fields.uuids128 = &dfuServiceUuid; + fields.uuids16 = &HeartRateService::heartRateServiceUuid; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + fields.uuids128 = &DfuService::serviceUuid; fields.num_uuids128 = 1; fields.uuids128_is_complete = 1; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; @@ -451,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/NimbleController.h b/src/components/ble/NimbleController.h index 29a395ea..597ef0cc 100644 --- a/src/components/ble/NimbleController.h +++ b/src/components/ble/NimbleController.h @@ -112,10 +112,6 @@ namespace Pinetime { uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE; uint8_t fastAdvCount = 0; uint8_t bondId[16] = {0}; - - ble_uuid128_t dfuServiceUuid { - .u {.type = BLE_UUID_TYPE_128}, - .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}}; }; static NimbleController* nptr; diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp index 146152f8..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}; @@ -80,7 +80,7 @@ int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble return static_cast(arg)->OnCommand(ctxt); } -SimpleWeatherService::SimpleWeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) { +SimpleWeatherService::SimpleWeatherService(DateTime& dateTimeController) : dateTimeController(dateTimeController) { } void SimpleWeatherService::Init() { @@ -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 4bbefcfc..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++ @@ -40,7 +40,7 @@ namespace Pinetime { class SimpleWeatherService { public: - explicit SimpleWeatherService(const DateTime& dateTimeController); + explicit SimpleWeatherService(DateTime& dateTimeController); void Init(); @@ -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() { @@ -140,7 +165,7 @@ namespace Pinetime { uint16_t eventHandle {}; - const Pinetime::Controllers::DateTime& dateTimeController; + Pinetime::Controllers::DateTime& dateTimeController; std::optional currentWeather; std::optional forecast; diff --git a/src/components/brightness/BrightnessController.cpp b/src/components/brightness/BrightnessController.cpp index 0392158c..4d1eba6a 100644 --- a/src/components/brightness/BrightnessController.cpp +++ b/src/components/brightness/BrightnessController.cpp @@ -2,38 +2,138 @@ #include #include "displayapp/screens/Symbols.h" #include "drivers/PinMap.h" +#include using namespace Pinetime::Controllers; +namespace { + // reinterpret_cast is not constexpr so this is the best we can do + static NRF_RTC_Type* const RTC = reinterpret_cast(NRF_RTC2_BASE); +} + void BrightnessController::Init() { nrf_gpio_cfg_output(PinMap::LcdBacklightLow); nrf_gpio_cfg_output(PinMap::LcdBacklightMedium); nrf_gpio_cfg_output(PinMap::LcdBacklightHigh); + + nrf_gpio_pin_clear(PinMap::LcdBacklightLow); + nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); + nrf_gpio_pin_clear(PinMap::LcdBacklightHigh); + + static_assert(timerFrequency == 32768, "Change the prescaler below"); + RTC->PRESCALER = 0; + // CC1 switches the backlight on (pin transitions from high to low) and resets the counter to 0 + RTC->CC[1] = timerPeriod; + // Enable compare events for CC0,CC1 + RTC->EVTEN = 0b0000'0000'0000'0011'0000'0000'0000'0000; + // Disable all interrupts + RTC->INTENCLR = 0b0000'0000'0000'1111'0000'0000'0000'0011; Set(level); } +void BrightnessController::ApplyBrightness(uint16_t rawBrightness) { + // The classic off, low, medium, high brightnesses are at {0, timerPeriod, timerPeriod*2, timerPeriod*3} + // These brightness levels do not use PWM: they only set/clear the corresponding pins + // Any brightness level between the above levels is achieved with efficient RTC based PWM on the next pin up + // E.g 2.5*timerPeriod corresponds to medium brightness with 50% PWM on the high pin + // Note: Raw brightness does not necessarily correspond to a linear perceived brightness + + uint8_t pin; + if (rawBrightness > 2 * timerPeriod) { + rawBrightness -= 2 * timerPeriod; + pin = PinMap::LcdBacklightHigh; + } else if (rawBrightness > timerPeriod) { + rawBrightness -= timerPeriod; + pin = PinMap::LcdBacklightMedium; + } else { + pin = PinMap::LcdBacklightLow; + } + if (rawBrightness == timerPeriod || rawBrightness == 0) { + if (lastPin != UNSET) { + RTC->TASKS_STOP = 1; + nrf_delay_us(rtcStopTime); + nrf_ppi_channel_disable(ppiBacklightOff); + nrf_ppi_channel_disable(ppiBacklightOn); + nrfx_gpiote_out_uninit(lastPin); + nrf_gpio_cfg_output(lastPin); + } + lastPin = UNSET; + if (rawBrightness == 0) { + nrf_gpio_pin_set(pin); + } else { + nrf_gpio_pin_clear(pin); + } + } else { + // If the pin on which we are doing PWM is changing + // Disable old PWM channel (if exists) and set up new one + if (lastPin != pin) { + if (lastPin != UNSET) { + RTC->TASKS_STOP = 1; + nrf_delay_us(rtcStopTime); + nrf_ppi_channel_disable(ppiBacklightOff); + nrf_ppi_channel_disable(ppiBacklightOn); + nrfx_gpiote_out_uninit(lastPin); + nrf_gpio_cfg_output(lastPin); + } + nrfx_gpiote_out_config_t gpioteCfg = {.action = NRF_GPIOTE_POLARITY_TOGGLE, + .init_state = NRF_GPIOTE_INITIAL_VALUE_LOW, + .task_pin = true}; + APP_ERROR_CHECK(nrfx_gpiote_out_init(pin, &gpioteCfg)); + nrfx_gpiote_out_task_enable(pin); + nrf_ppi_channel_endpoint_setup(ppiBacklightOff, + reinterpret_cast(&RTC->EVENTS_COMPARE[0]), + nrfx_gpiote_out_task_addr_get(pin)); + nrf_ppi_channel_endpoint_setup(ppiBacklightOn, + reinterpret_cast(&RTC->EVENTS_COMPARE[1]), + nrfx_gpiote_out_task_addr_get(pin)); + nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast(&RTC->TASKS_CLEAR)); + nrf_ppi_channel_enable(ppiBacklightOff); + nrf_ppi_channel_enable(ppiBacklightOn); + } else { + // If the pin used for PWM isn't changing, we only need to set the pin state to the initial value (low) + RTC->TASKS_STOP = 1; + nrf_delay_us(rtcStopTime); + // Due to errata 20,179 and the intricacies of RTC timing, keep it simple: override the pin state + nrfx_gpiote_out_task_force(pin, false); + } + // CC0 switches the backlight off (pin transitions from low to high) + RTC->CC[0] = rawBrightness; + RTC->TASKS_CLEAR = 1; + RTC->TASKS_START = 1; + lastPin = pin; + } + switch (pin) { + case PinMap::LcdBacklightHigh: + nrf_gpio_pin_clear(PinMap::LcdBacklightLow); + nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); + break; + case PinMap::LcdBacklightMedium: + nrf_gpio_pin_clear(PinMap::LcdBacklightLow); + nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + break; + case PinMap::LcdBacklightLow: + nrf_gpio_pin_set(PinMap::LcdBacklightMedium); + nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + } +} + void BrightnessController::Set(BrightnessController::Levels level) { this->level = level; switch (level) { default: case Levels::High: - nrf_gpio_pin_clear(PinMap::LcdBacklightLow); - nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); - nrf_gpio_pin_clear(PinMap::LcdBacklightHigh); + ApplyBrightness(3 * timerPeriod); break; case Levels::Medium: - nrf_gpio_pin_clear(PinMap::LcdBacklightLow); - nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); - nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + ApplyBrightness(2 * timerPeriod); break; case Levels::Low: - nrf_gpio_pin_clear(PinMap::LcdBacklightLow); - nrf_gpio_pin_set(PinMap::LcdBacklightMedium); - nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + ApplyBrightness(timerPeriod); + break; + case Levels::AlwaysOn: + ApplyBrightness(timerPeriod / 10); break; case Levels::Off: - nrf_gpio_pin_set(PinMap::LcdBacklightLow); - nrf_gpio_pin_set(PinMap::LcdBacklightMedium); - nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + ApplyBrightness(0); break; } } diff --git a/src/components/brightness/BrightnessController.h b/src/components/brightness/BrightnessController.h index 7f86759a..650749a8 100644 --- a/src/components/brightness/BrightnessController.h +++ b/src/components/brightness/BrightnessController.h @@ -2,11 +2,14 @@ #include +#include "nrf_ppi.h" +#include "nrfx_gpiote.h" + namespace Pinetime { namespace Controllers { class BrightnessController { public: - enum class Levels { Off, Low, Medium, High }; + enum class Levels { Off, AlwaysOn, Low, Medium, High }; void Init(); void Set(Levels level); @@ -20,6 +23,25 @@ namespace Pinetime { private: Levels level = Levels::High; + static constexpr uint8_t UNSET = UINT8_MAX; + uint8_t lastPin = UNSET; + // Maximum time (μs) it takes for the RTC to fully stop + static constexpr uint8_t rtcStopTime = 46; + // Frequency of timer used for PWM (Hz) + static constexpr uint16_t timerFrequency = 32768; + // Backlight PWM frequency (Hz) + static constexpr uint16_t pwmFreq = 1000; + // Wraparound point in timer ticks + // Defines the number of brightness levels between each pin + static constexpr uint16_t timerPeriod = timerFrequency / pwmFreq; + // Warning: nimble reserves some PPIs + // https://github.com/InfiniTimeOrg/InfiniTime/blob/034d83fe6baf1ab3875a34f8cee387e24410a824/src/libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c#L53 + // SpiMaster uses PPI 0 for an erratum workaround + // Channel 1, 2 should be free to use + static constexpr nrf_ppi_channel_t ppiBacklightOn = NRF_PPI_CHANNEL1; + static constexpr nrf_ppi_channel_t ppiBacklightOff = NRF_PPI_CHANNEL2; + + void ApplyBrightness(uint16_t val); }; } } diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp index 5335a398..773ffcfc 100644 --- a/src/components/datetime/DateTimeController.cpp +++ b/src/components/datetime/DateTimeController.cpp @@ -1,22 +1,42 @@ #include "components/datetime/DateTimeController.h" #include #include +#include +#include "nrf_assert.h" 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 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) { + xSemaphoreTake(mutex, portMAX_DELAY); this->currentDateTime = t; - UpdateTime(previousSystickCounter); // Update internal state without updating the time + UpdateTime(previousSystickCounter, true); // Update internal state without updating the time + xSemaphoreGive(mutex); } void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { @@ -29,15 +49,19 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, /* .tm_year = */ year - 1900, }; - tm.tm_isdst = -1; // Use DST value from local time zone - currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm)); - NRF_LOG_INFO("%d %d %d ", day, month, year); NRF_LOG_INFO("%d %d %d ", hour, minute, second); - UpdateTime(previousSystickCounter); + tm.tm_isdst = -1; // Use DST value from local time zone - systemTask->PushMessage(System::Messages::OnNewTime); + xSemaphoreTake(mutex, portMAX_DELAY); + currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm)); + UpdateTime(previousSystickCounter, true); + xSemaphoreGive(mutex); + + if (systemTask != nullptr) { + systemTask->PushMessage(System::Messages::OnNewTime); + } } void DateTime::SetTimeZone(int8_t timezone, int8_t dst) { @@ -45,25 +69,34 @@ void DateTime::SetTimeZone(int8_t timezone, int8_t dst) { dstOffset = dst; } -void DateTime::UpdateTime(uint32_t systickCounter) { +std::chrono::time_point DateTime::CurrentDateTime() { + xSemaphoreTake(mutex, portMAX_DELAY); + UpdateTime(nrf_rtc_counter_get(portNRF_RTC_REG), false); + xSemaphoreGive(mutex); + return currentDateTime; +} + +void DateTime::UpdateTime(uint32_t systickCounter, bool forceUpdate) { // Handle systick counter overflow uint32_t systickDelta = 0; if (systickCounter < previousSystickCounter) { - systickDelta = 0xffffff - previousSystickCounter; + systickDelta = static_cast(portNRF_RTC_MAXTICKS) - previousSystickCounter; systickDelta += systickCounter + 1; } else { systickDelta = systickCounter - previousSystickCounter; } - /* - * 1000 ms = 1024 ticks - */ - auto correctedDelta = systickDelta / 1024; - auto rest = systickDelta % 1024; + auto correctedDelta = systickDelta / configTICK_RATE_HZ; + // If a second hasn't passed, there is nothing to do + // If the time has been changed, set forceUpdate to trigger internal state updates + if (correctedDelta == 0 && !forceUpdate) { + return; + } + auto rest = systickDelta % configTICK_RATE_HZ; if (systickCounter >= rest) { previousSystickCounter = systickCounter - rest; } else { - previousSystickCounter = 0xffffff - (rest - systickCounter); + previousSystickCounter = static_cast(portNRF_RTC_MAXTICKS) - (rest - systickCounter - 1); } currentDateTime += std::chrono::seconds(correctedDelta); diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h index f719df7d..a005f9ac 100644 --- a/src/components/datetime/DateTimeController.h +++ b/src/components/datetime/DateTimeController.h @@ -5,6 +5,8 @@ #include #include #include "components/settings/Settings.h" +#include +#include namespace Pinetime { namespace System { @@ -39,14 +41,12 @@ 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. */ void SetTimeZone(int8_t timezone, int8_t dst); - void UpdateTime(uint32_t systickCounter); - uint16_t Year() const { return 1900 + localTime.tm_year; } @@ -124,12 +124,10 @@ namespace Pinetime { static const char* MonthShortToStringLow(Months month); static const char* DayOfWeekShortToStringLow(Days day); - std::chrono::time_point CurrentDateTime() const { - return currentDateTime; - } + std::chrono::time_point CurrentDateTime(); - std::chrono::time_point UTCDateTime() const { - return currentDateTime - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60); + std::chrono::time_point UTCDateTime() { + return CurrentDateTime() - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60); } std::chrono::seconds Uptime() const { @@ -141,10 +139,14 @@ namespace Pinetime { std::string FormattedTime(); private: + void UpdateTime(uint32_t systickCounter, bool forceUpdate); + std::tm localTime; int8_t tzOffset = 0; int8_t dstOffset = 0; + SemaphoreHandle_t mutex = nullptr; + uint32_t previousSystickCounter = 0; std::chrono::time_point currentDateTime; std::chrono::seconds uptime {0}; diff --git a/src/components/datetime/TODO.md b/src/components/datetime/TODO.md new file mode 100644 index 00000000..e9590898 --- /dev/null +++ b/src/components/datetime/TODO.md @@ -0,0 +1,41 @@ +# Refactoring needed + +## Context + +The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) highlighted some +limitations in the design of DateTimeController: the granularity of the time returned by `DateTime::CurrentDateTime()` +is limited by the frequency at which SystemTask calls `DateTime::UpdateTime()`, which is currently set to 100ms. + +@mark9064 provided more details +in [this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041#issuecomment-2048528967). + +The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) provided some changes +to `DateTime` controller that improves the granularity of the time returned by `DateTime::CurrentDateTime()`. + +However, the review showed that `DateTime` cannot be `const` anymore, even when it's only used to get the current time, +since `DateTime::CurrentDateTime()` changes the internal state of the instance. + +We tried to identify alternative implementation that would have maintained the "const correctness" but we eventually +figured that this would lead to a re-design of `DateTime` which was out of scope of the initial PR (Continuous time +updates and always on display). + +So we decided to merge this PR #2041 and agree to fix/improve `DateTime` later on. + +## What needs to be done? + +Improve/redesign `DateTime` so that it + +* provides a very granular (ideally down to the millisecond) date and time via `CurrentDateTime()`. +* can be declared/passed as `const` when it's only used to **get** the time. +* limits the use of mutex as much as possible (an ideal implementation would not use any mutex, but this might not be + possible). +* improves the design of `DateTime::Seconds()`, `DateTime::Minutes()`, `DateTime::Hours()`, etc as + explained [in this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054#pullrequestreview-2037033105). + +Once this redesign is implemented, all instances/references to `DateTime` should be reviewed and updated to use `const` +where appropriate. + +Please check the following PR to get more context about this redesign: + +* [#2041 - Continuous time updates by @mark9064](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) +* [#2054 - Continuous time update - Alternative implementation to #2041 by @JF002](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054) \ No newline at end of file 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.h b/src/components/settings/Settings.h index b42b0f46..160f6e6c 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -220,6 +220,21 @@ namespace Pinetime { return settings.screenTimeOut; }; + bool GetAlwaysOnDisplay() const { + return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep; + }; + + void SetAlwaysOnDisplaySetting(bool state) { + if (state != settings.alwaysOnDisplay) { + settingsChanged = true; + } + settings.alwaysOnDisplay = state; + } + + bool GetAlwaysOnDisplaySetting() const { + return settings.alwaysOnDisplay; + } + void SetShakeThreshold(uint16_t thresh) { if (settings.shakeWakeThreshold != thresh) { settings.shakeWakeThreshold = thresh; @@ -307,14 +322,16 @@ namespace Pinetime { private: Pinetime::Controllers::FS& fs; - static constexpr uint32_t settingsVersion = 0x0007; + static constexpr uint32_t settingsVersion = 0x0008; struct SettingsData { uint32_t version = settingsVersion; uint32_t stepsGoal = 10000; uint32_t screenTimeOut = 15000; - ClockType clockType = ClockType::FUZZY; + bool alwaysOnDisplay = false; + + ClockType clockType = ClockType::H24; WeatherFormat weatherFormat = WeatherFormat::Metric; Notification notificationStatus = Notification::On; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index b421cab3..158849db 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -83,7 +83,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::FS& filesystem) + Pinetime::Controllers::FS& filesystem, + Pinetime::Drivers::SpiNorFlash& spiNorFlash) : lcd {lcd}, touchPanel {touchPanel}, batteryController {batteryController}, @@ -99,6 +100,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, brightnessController {brightnessController}, touchHandler {touchHandler}, filesystem {filesystem}, + spiNorFlash {spiNorFlash}, lvgl {lcd, filesystem}, timer(this, TimerCallback), controllers {batteryController, @@ -156,6 +158,44 @@ void DisplayApp::InitHw() { lcd.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 (=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 + + // Tick rate is multiple of 8 + // Needed for division trick above + static_assert(configTICK_RATE_HZ % 8 == 0); + + // Frame count must always wraparound more often than the system tick count does + // Always on overflow time (ms) < system tick overflow time (ms) + // Using 64bit ints here to avoid overflow + static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ); + + if (alwaysOnFrameCount == overflowFrameCount) { + alwaysOnFrameCount = 0; + alwaysOnStartTime = xTaskGetTickCount(); + } + if (targetRenderTick > ticksElapsed) { + return targetRenderTick - ticksElapsed; + } else { + return 0; + } +} + void DisplayApp::Refresh() { auto LoadPreviousScreen = [this]() { FullRefreshDirections returnDirection; @@ -179,21 +219,6 @@ void DisplayApp::Refresh() { LoadScreen(returnAppStack.Pop(), returnDirection); }; - auto DimScreen = [this]() { - if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { - isDimmed = true; - brightnessController.Set(Controllers::BrightnessController::Levels::Low); - } - }; - - auto RestoreBrightness = [this]() { - if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { - isDimmed = false; - lv_disp_trig_activity(nullptr); - ApplyBrightness(); - } - }; - auto IsPastDimTime = [this]() -> bool { return lv_disp_get_inactive_time(nullptr) >= pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000); }; @@ -207,6 +232,27 @@ void DisplayApp::Refresh() { case States::Idle: 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(); + } + } + } + break; case States::Running: if (!currentScreen->IsRunning()) { LoadPreviousScreen(); @@ -215,14 +261,27 @@ void DisplayApp::Refresh() { if (!systemTask->IsSleepDisabled() && IsPastDimTime()) { if (!isDimmed) { - DimScreen(); + 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) { - RestoreBrightness(); + isDimmed = false; + ApplyBrightness(); } break; default: @@ -238,30 +297,73 @@ void DisplayApp::Refresh() { uint8_t myChimes = 9-(((int)floor(myHour/2))%6); switch (msg) { - case Messages::DimScreen: - DimScreen(); - break; - case Messages::RestoreBrightness: - RestoreBrightness(); - break; case Messages::GoToSleep: - while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { + 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); } - lcd.Sleep(); - PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); - state = States::Idle; + // Turn brightness down (or set to AlwaysOn mode) + if (msg == Messages::GoToAOD) { + brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn); + } else { + brightnessController.Set(Controllers::BrightnessController::Levels::Off); + } + // Since the active screen is not really an app, go back to Clock. + if (currentApp == Apps::Launcher || currentApp == Apps::Notifications || currentApp == Apps::QuickSettings || + currentApp == Apps::Settings) { + LoadScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); + // Wait for the clock app to load before moving on. + while (!lv_task_handler()) { + }; + } + // 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 + alwaysOnFrameCount = 0; + alwaysOnStartTime = xTaskGetTickCount(); + PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD); + state = States::AOD; + } else { + lcd.Sleep(); + PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); + state = States::Idle; + } + break; + case Messages::NotifyDeviceActivity: + lv_disp_trig_activity(nullptr); break; case Messages::GoToRunning: - lcd.Wakeup(); + // 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(); + } lv_disp_trig_activity(nullptr); ApplyBrightness(); state = States::Running; break; case Messages::UpdateBleConnection: - // clockScreen.SetBleConnectionState(bleController.IsConnected() ? Screens::Clock::BleConnectionStates::Connected : - // Screens::Clock::BleConnectionStates::NotConnected); + // Only used for recovery firmware break; case Messages::NewNotification: LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down); @@ -376,10 +478,6 @@ void DisplayApp::Refresh() { case Messages::BleRadioEnableToggle: PushMessageToSystemTask(System::Messages::BleRadioEnableToggle); break; - case Messages::UpdateDateTime: - // Added to remove warning - // What should happen here? - break; case Messages::Chime: LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); @@ -417,13 +515,12 @@ void DisplayApp::Refresh() { } break; case Messages::OnChargingEvent: - RestoreBrightness(); motorController.RunForDuration(15); break; } } - if (touchHandler.IsTouching()) { + if (state == States::Running && touchHandler.IsTouching()) { currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY()); } @@ -546,7 +643,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio 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); @@ -577,7 +674,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio bleController, watchdog, motionController, - touchPanel); + touchPanel, + spiNorFlash); break; case Apps::FlashLight: currentScreen = std::make_unique(*systemTask, brightnessController); diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 96bce4dd..2f276eaf 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, @@ -66,7 +66,8 @@ namespace Pinetime { Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::FS& filesystem); + Pinetime::Controllers::FS& filesystem, + Pinetime::Drivers::SpiNorFlash& spiNorFlash); void Start(System::BootErrors error); void PushMessage(Display::Messages msg); @@ -96,6 +97,7 @@ namespace Pinetime { Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::FS& filesystem; + Pinetime::Drivers::SpiNorFlash& spiNorFlash; Pinetime::Controllers::FirmwareValidator validator; Pinetime::Components::LittleVgl lvgl; @@ -135,6 +137,13 @@ namespace Pinetime { Utility::StaticStack appStackDirections; bool isDimmed = false; + + TickType_t CalculateSleepTime(); + 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 + static constexpr uint32_t alwaysOnRefreshPeriod = 500; }; } } diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index 28892723..bcb8db0e 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::AlarmController& /*alarmController*/, Pinetime::Controllers::BrightnessController& /*brightnessController*/, Pinetime::Controllers::TouchHandler& /*touchHandler*/, - Pinetime::Controllers::FS& /*filesystem*/) + Pinetime::Controllers::FS& /*filesystem*/, + Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/) : lcd {lcd}, bleController {bleController} { } diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index c1bf6243..162ff257 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -18,6 +18,7 @@ namespace Pinetime { class St7789; class Cst816S; class Watchdog; + class SpiNorFlash; } namespace Controllers { @@ -59,7 +60,8 @@ namespace Pinetime { Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::FS& filesystem); + Pinetime::Controllers::FS& filesystem, + Pinetime::Drivers::SpiNorFlash& spiNorFlash); void Start(); void Start(Pinetime::System::BootErrors) { 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 dada3088..d2abc8e5 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -6,8 +6,8 @@ namespace Pinetime { namespace Display { enum class Messages : uint8_t { GoToSleep, + GoToAOD, GoToRunning, - UpdateDateTime, UpdateBleConnection, TouchEvent, ButtonPushed, @@ -17,8 +17,9 @@ namespace Pinetime { NewNotification, TimerDone, BleFirmwareUpdateStarted, - DimScreen, - RestoreBrightness, + // Resets the screen timeout timer when awake + // Does nothing when asleep + NotifyDeviceActivity, ShowPairingKey, AlarmTriggered, Chime, diff --git a/src/displayapp/fonts/CMakeLists.txt b/src/displayapp/fonts/CMakeLists.txt index a85e5f35..4b3f48a4 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/screens/Alarm.cpp b/src/displayapp/screens/Alarm.cpp index cbc702f3..b1e67363 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); @@ -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() { @@ -205,7 +206,7 @@ void Alarm::SetAlerting() { 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 +217,16 @@ void Alarm::StopAlerting() { lv_task_del(taskStopAlarm); taskStopAlarm = nullptr; } - systemTask.PushMessage(System::Messages::EnableSleeping); + wakeLock.Release(); lv_obj_set_hidden(enableSwitch, false); lv_obj_set_hidden(btnStop, true); } 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 +243,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..a875b275 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; diff --git a/src/displayapp/screens/FirmwareValidation.cpp b/src/displayapp/screens/FirmwareValidation.cpp index 4a1b3d9f..2a9919d5 100644 --- a/src/displayapp/screens/FirmwareValidation.cpp +++ b/src/displayapp/screens/FirmwareValidation.cpp @@ -17,8 +17,8 @@ namespace { FirmwareValidation::FirmwareValidation(Pinetime::Controllers::FirmwareValidator& validator) : validator {validator} { labelVersion = lv_label_create(lv_scr_act(), nullptr); lv_label_set_text_fmt(labelVersion, - "Version : %lu.%lu.%lu\n" - "ShortRef : %s", + "Version: %lu.%lu.%lu\n" + "ShortRef: %s", Version::Major(), Version::Minor(), Version::Patch(), diff --git a/src/displayapp/screens/FlashLight.cpp b/src/displayapp/screens/FlashLight.cpp index 1b7cf39c..7e0caff1 100644 --- a/src/displayapp/screens/FlashLight.cpp +++ b/src/displayapp/screens/FlashLight.cpp @@ -15,8 +15,9 @@ 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); flashLight = lv_label_create(lv_scr_act(), nullptr); @@ -46,13 +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); - systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); + brightnessController.Set(previousBrightnessLevel); } void FlashLight::SetColors() { diff --git a/src/displayapp/screens/FlashLight.h b/src/displayapp/screens/FlashLight.h index 2b710ed5..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,10 +24,11 @@ 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; + Controllers::BrightnessController::Levels previousBrightnessLevel; lv_obj_t* flashLight; lv_obj_t* backgroundAction; 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..88b4918c 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; 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..fab7ff87 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; diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp index 037c43a7..45f72f2e 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; 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/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..55a178dc 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; diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp index 18fb7ad2..d265fddd 100644 --- a/src/displayapp/screens/SystemInfo.cpp +++ b/src/displayapp/screens/SystemInfo.cpp @@ -38,15 +38,16 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app, const Pinetime::Controllers::Ble& bleController, const Pinetime::Drivers::Watchdog& watchdog, Pinetime::Controllers::MotionController& motionController, - const Pinetime::Drivers::Cst816S& touchPanel) - : app {app}, - dateTimeController {dateTimeController}, + const Pinetime::Drivers::Cst816S& touchPanel, + const Pinetime::Drivers::SpiNorFlash& spiNorFlash) + : dateTimeController {dateTimeController}, batteryController {batteryController}, brightnessController {brightnessController}, bleController {bleController}, watchdog {watchdog}, motionController {motionController}, touchPanel {touchPanel}, + spiNorFlash {spiNorFlash}, screens {app, 0, {[this]() -> std::unique_ptr { @@ -186,10 +187,12 @@ std::unique_ptr SystemInfo::CreateScreen3() { lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); lv_label_set_recolor(label, true); const auto& bleAddr = bleController.Address(); + auto spiFlashId = spiNorFlash.GetIdentification(); lv_label_set_text_fmt(label, "#808080 BLE MAC#\n" - " %02x:%02x:%02x:%02x:%02x:%02x" + " %02x:%02x:%02x:%02x:%02x:%02x\n" "\n" + "#808080 SPI Flash# %02x-%02x-%02x\n" "\n" "#808080 Memory heap#\n" " #808080 Free# %d\n" @@ -202,6 +205,9 @@ std::unique_ptr SystemInfo::CreateScreen3() { bleAddr[2], bleAddr[1], bleAddr[0], + spiFlashId.manufacturer, + spiFlashId.type, + spiFlashId.density, xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize(), mallocFailedCount, diff --git a/src/displayapp/screens/SystemInfo.h b/src/displayapp/screens/SystemInfo.h index 199af51e..301662dc 100644 --- a/src/displayapp/screens/SystemInfo.h +++ b/src/displayapp/screens/SystemInfo.h @@ -29,12 +29,12 @@ namespace Pinetime { const Pinetime::Controllers::Ble& bleController, const Pinetime::Drivers::Watchdog& watchdog, Pinetime::Controllers::MotionController& motionController, - const Pinetime::Drivers::Cst816S& touchPanel); + const Pinetime::Drivers::Cst816S& touchPanel, + const Pinetime::Drivers::SpiNorFlash& spiNorFlash); ~SystemInfo() override; bool OnTouchEvent(TouchEvents event) override; private: - DisplayApp* app; Pinetime::Controllers::DateTime& dateTimeController; const Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::BrightnessController& brightnessController; @@ -42,6 +42,7 @@ namespace Pinetime { const Pinetime::Drivers::Watchdog& watchdog; Pinetime::Controllers::MotionController& motionController; const Pinetime::Drivers::Cst816S& touchPanel; + const Pinetime::Drivers::SpiNorFlash& spiNorFlash; ScreenList<5> screens; diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp index a1ede6be..f6d5e73b 100644 --- a/src/displayapp/screens/Timer.cpp +++ b/src/displayapp/screens/Timer.cpp @@ -104,9 +104,7 @@ void Timer::UpdateMask() { void Timer::Refresh() { if (timer.IsRunning()) { - auto secondsRemaining = std::chrono::duration_cast(timer.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); + DisplayTime(); } else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { lv_label_set_text_static(txtPlayPause, "Reset"); maskPosition += 15; @@ -119,6 +117,14 @@ void Timer::Refresh() { } } +void Timer::DisplayTime() { + displaySeconds = std::chrono::duration_cast(timer.GetTimeRemaining()); + if (displaySeconds.IsUpdated()) { + minuteCounter.SetValue(displaySeconds.Get().count() / 60); + secondCounter.SetValue(displaySeconds.Get().count() % 60); + } +} + void Timer::SetTimerRunning() { minuteCounter.HideControls(); secondCounter.HideControls(); @@ -133,9 +139,7 @@ void Timer::SetTimerStopped() { void Timer::ToggleRunning() { if (timer.IsRunning()) { - auto secondsRemaining = std::chrono::duration_cast(timer.GetTimeRemaining()); - minuteCounter.SetValue(secondsRemaining.count() / 60); - secondCounter.SetValue(secondsRemaining.count() % 60); + DisplayTime(); timer.StopTimer(); SetTimerStopped(); } else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { @@ -147,7 +151,6 @@ void Timer::ToggleRunning() { } void Timer::Reset() { - minuteCounter.SetValue(0); - secondCounter.SetValue(0); + DisplayTime(); SetTimerStopped(); } diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h index 409cae1c..a07c729b 100644 --- a/src/displayapp/screens/Timer.h +++ b/src/displayapp/screens/Timer.h @@ -1,10 +1,10 @@ #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" +#include "utility/DirtyValue.h" #include #include "components/timer/Timer.h" @@ -26,6 +26,7 @@ namespace Pinetime::Applications { void SetTimerRunning(); void SetTimerStopped(); void UpdateMask(); + void DisplayTime(); Pinetime::Controllers::Timer& timer; lv_obj_t* btnPlayPause; @@ -43,6 +44,7 @@ namespace Pinetime::Applications { bool buttonPressing = false; lv_coord_t maskPosition = 0; TickType_t pressTime = 0; + Utility::DirtyValue displaySeconds; }; } diff --git a/src/displayapp/screens/WatchFaceAnalog.h b/src/displayapp/screens/WatchFaceAnalog.h index 143eb4aa..73c5d6e6 100644 --- a/src/displayapp/screens/WatchFaceAnalog.h +++ b/src/displayapp/screens/WatchFaceAnalog.h @@ -88,7 +88,7 @@ namespace Pinetime { int16_t minutesNighttime; int16_t minutesBeforeSunset; - const Controllers::DateTime& dateTimeController; + Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; Controllers::NotificationManager& notificationManager; diff --git a/src/displayapp/screens/WatchFaceFuzzy.cpp b/src/displayapp/screens/WatchFaceFuzzy.cpp index ff75de80..78a8857b 100644 --- a/src/displayapp/screens/WatchFaceFuzzy.cpp +++ b/src/displayapp/screens/WatchFaceFuzzy.cpp @@ -2,6 +2,7 @@ #include #include + #include "displayapp/screens/NotificationIcon.h" #include "displayapp/screens/Symbols.h" #include "displayapp/screens/WeatherSymbols.h" @@ -137,13 +138,12 @@ void WatchFaceFuzzy::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/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index e56031f7..22ccefc7 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" @@ -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 { diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 5321b7cc..d3ee78ef 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 minTemp = optCurrentForecast->days[i]->minTemperature.Celsius(); + int16_t maxTemp = optCurrentForecast->days[i]->maxTemperature.Celsius(); if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { - maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp); - minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp); + minTemp = optCurrentForecast->days[i]->maxTemperature.Fahrenheit(); + maxTemp = 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/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp index bd533e67..bbc188a9 100644 --- a/src/displayapp/screens/settings/SettingDisplay.cpp +++ b/src/displayapp/screens/settings/SettingDisplay.cpp @@ -9,16 +9,22 @@ using namespace Pinetime::Applications::Screens; namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { + void TimeoutEventHandler(lv_obj_t* obj, lv_event_t event) { auto* screen = static_cast(obj->user_data); screen->UpdateSelected(obj, event); } + + void AlwaysOnEventHandler(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_VALUE_CHANGED) { + auto* screen = static_cast(obj->user_data); + screen->ToggleAlwaysOn(); + } + } } 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); @@ -49,13 +55,20 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000); lv_checkbox_set_text(cbOption[i], buffer); cbOption[i]->user_data = this; - lv_obj_set_event_cb(cbOption[i], event_handler); + lv_obj_set_event_cb(cbOption[i], TimeoutEventHandler); SetRadioButtonStyle(cbOption[i]); if (settingsController.GetScreenTimeOut() == options[i]) { lv_checkbox_set_checked(cbOption[i], true); } } + + alwaysOnCheckbox = lv_checkbox_create(container1, nullptr); + lv_checkbox_set_text(alwaysOnCheckbox, "Always On"); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting()); + lv_obj_add_state(alwaysOnCheckbox, LV_STATE_DEFAULT); + alwaysOnCheckbox->user_data = this; + lv_obj_set_event_cb(alwaysOnCheckbox, AlwaysOnEventHandler); } SettingDisplay::~SettingDisplay() { @@ -63,6 +76,11 @@ SettingDisplay::~SettingDisplay() { settingsController.SaveSettings(); } +void SettingDisplay::ToggleAlwaysOn() { + settingsController.SetAlwaysOnDisplaySetting(!settingsController.GetAlwaysOnDisplaySetting()); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting()); +} + void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { if (event == LV_EVENT_CLICKED) { for (unsigned int i = 0; i < options.size(); i++) { diff --git a/src/displayapp/screens/settings/SettingDisplay.h b/src/displayapp/screens/settings/SettingDisplay.h index 64212c02..3bd10a62 100644 --- a/src/displayapp/screens/settings/SettingDisplay.h +++ b/src/displayapp/screens/settings/SettingDisplay.h @@ -14,17 +14,18 @@ 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; lv_obj_t* cbOption[options.size()]; + lv_obj_t* alwaysOnCheckbox; }; } } 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/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 2dee4b82..cefaa280 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 4e131938..147429e7 100644 --- a/src/displayapp/screens/settings/Settings.h +++ b/src/displayapp/screens/settings/Settings.h @@ -38,7 +38,7 @@ namespace Pinetime { {Symbols::home, "Watch face", Apps::SettingWatchFace}, {Symbols::shoe, "Steps", Apps::SettingSteps}, - {Symbols::clock, "Date&Time", Apps::SettingSetDateTime}, + {Symbols::clock, "Date & Time", Apps::SettingSetDateTime}, {Symbols::map, "Location", Apps::SettingLocation}, {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat}, {Symbols::batteryHalf, "Battery", Apps::BatteryInfo}, 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/SpiNorFlash.cpp b/src/drivers/SpiNorFlash.cpp index 56a8aabd..3bc45cca 100644 --- a/src/drivers/SpiNorFlash.cpp +++ b/src/drivers/SpiNorFlash.cpp @@ -10,7 +10,7 @@ SpiNorFlash::SpiNorFlash(Spi& spi) : spi {spi} { } void SpiNorFlash::Init() { - device_id = ReadIdentificaion(); + device_id = ReadIdentification(); NRF_LOG_INFO("[SpiNorFlash] Manufacturer : %d, Memory type : %d, memory density : %d", device_id.manufacturer, device_id.type, @@ -32,7 +32,7 @@ void SpiNorFlash::Wakeup() { uint8_t cmd[cmdSize] = {static_cast(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03}; uint8_t id = 0; spi.Read(reinterpret_cast(&cmd), cmdSize, &id, 1); - auto devId = device_id = ReadIdentificaion(); + auto devId = device_id = ReadIdentification(); if (devId.type != device_id.type) { NRF_LOG_INFO("[SpiNorFlash] ID on Wakeup: Failed"); } else { @@ -41,7 +41,7 @@ void SpiNorFlash::Wakeup() { NRF_LOG_INFO("[SpiNorFlash] Wakeup") } -SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() { +SpiNorFlash::Identification SpiNorFlash::ReadIdentification() { auto cmd = static_cast(Commands::ReadIdentification); Identification identification; spi.Read(&cmd, 1, reinterpret_cast(&identification), sizeof(Identification)); @@ -145,3 +145,7 @@ void SpiNorFlash::Write(uint32_t address, const uint8_t* buffer, size_t size) { len -= toWrite; } } + +SpiNorFlash::Identification SpiNorFlash::GetIdentification() const { + return device_id; +} diff --git a/src/drivers/SpiNorFlash.h b/src/drivers/SpiNorFlash.h index 8a063fea..028f92b4 100644 --- a/src/drivers/SpiNorFlash.h +++ b/src/drivers/SpiNorFlash.h @@ -20,7 +20,6 @@ namespace Pinetime { uint8_t density = 0; }; - Identification ReadIdentificaion(); uint8_t ReadStatusRegister(); bool WriteInProgress(); bool WriteEnabled(); @@ -33,6 +32,8 @@ namespace Pinetime { bool ProgramFailed(); bool EraseFailed(); + Identification GetIdentification() const; + void Init(); void Uninit(); @@ -40,6 +41,8 @@ namespace Pinetime { void Wakeup(); private: + Identification ReadIdentification(); + enum class Commands : uint8_t { PageProgram = 0x02, Read = 0x03, diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 12e95a41..482fbad6 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -1,3 +1,4 @@ +#include #include "drivers/St7789.h" #include #include @@ -15,17 +16,22 @@ void St7789::Init() { nrf_gpio_pin_set(pinReset); HardwareReset(); SoftwareReset(); + Command2Enable(); SleepOut(); - ColMod(); + PixelFormat(); MemoryDataAccessControl(); - ColumnAddressSet(); - RowAddressSet(); + SetAddrWindow(0, 0, Width, Height); // P8B Mirrored version does not need display inversion. #ifndef DRIVER_DISPLAY_MIRROR DisplayInversionOn(); #endif + PorchSet(); + FrameRateNormalSet(); + IdleFrameRateOff(); NormalModeOn(); SetVdv(); + PowerControl(); + GateControl(); DisplayOn(); } @@ -63,6 +69,17 @@ void St7789::SoftwareReset() { vTaskDelay(pdMS_TO_TICKS(125)); } +void St7789::Command2Enable() { + WriteCommand(static_cast(Commands::Command2Enable)); + constexpr uint8_t args[] = { + 0x5a, // Constant + 0x69, // Constant + 0x02, // Constant + 0x01, // Enable + }; + WriteData(args, sizeof(args)); +} + void St7789::SleepOut() { if (!sleepIn) { return; @@ -97,8 +114,9 @@ void St7789::SleepIn() { sleepIn = true; } -void St7789::ColMod() { - WriteCommand(static_cast(Commands::ColMod)); +void St7789::PixelFormat() { + WriteCommand(static_cast(Commands::PixelFormat)); + // 65K colours, 16-bit per pixel WriteData(0x55); } @@ -118,22 +136,6 @@ void St7789::MemoryDataAccessControl() { #endif } -void St7789::ColumnAddressSet() { - WriteCommand(static_cast(Commands::ColumnAddressSet)); - WriteData(0x00); - WriteData(0x00); - WriteData(Width >> 8u); - WriteData(Width & 0xffu); -} - -void St7789::RowAddressSet() { - WriteCommand(static_cast(Commands::RowAddressSet)); - WriteData(0x00); - WriteData(0x00); - WriteData(320u >> 8u); - WriteData(320u & 0xffu); -} - void St7789::DisplayInversionOn() { WriteCommand(static_cast(Commands::DisplayInversionOn)); } @@ -142,22 +144,97 @@ void St7789::NormalModeOn() { WriteCommand(static_cast(Commands::NormalModeOn)); } +void St7789::IdleModeOn() { + WriteCommand(static_cast(Commands::IdleModeOn)); +} + +void St7789::IdleModeOff() { + WriteCommand(static_cast(Commands::IdleModeOff)); +} + +void St7789::PorchSet() { + WriteCommand(static_cast(Commands::Porch)); + constexpr uint8_t args[] = { + 0x02, // Normal mode front porch + 0x03, // Normal mode back porch + 0x01, // Porch control enable + 0xed, // Idle mode front:back porch + 0xed, // Partial mode front:back porch (partial mode unused but set anyway) + }; + WriteData(args, sizeof(args)); +} + +void St7789::FrameRateNormalSet() { + WriteCommand(static_cast(Commands::FrameRateNormal)); + // Note that the datasheet table is imprecise - see formula below table + WriteData(0x0a); +} + +void St7789::IdleFrameRateOn() { + WriteCommand(static_cast(Commands::FrameRateIdle)); + // 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 + constexpr uint8_t args[] = { + 0x12, // Enable frame rate control for partial/idle mode, 4x frame divider + 0x1e, // Idle mode frame rate + 0x1e, // Partial mode frame rate (unused) + }; + WriteData(args, sizeof(args)); +} + +void St7789::IdleFrameRateOff() { + WriteCommand(static_cast(Commands::FrameRateIdle)); + constexpr uint8_t args[] = { + 0x00, // Disable frame rate control and divider + 0x0a, // Idle mode frame rate (normal) + 0x0a, // Partial mode frame rate (normal, unused) + }; + WriteData(args, sizeof(args)); +} + void St7789::DisplayOn() { WriteCommand(static_cast(Commands::DisplayOn)); } +void St7789::PowerControl() { + WriteCommand(static_cast(Commands::PowerControl1)); + constexpr uint8_t args[] = { + 0xa4, // Constant + 0x00, // Lowest possible voltages + }; + WriteData(args, sizeof(args)); + + WriteCommand(static_cast(Commands::PowerControl2)); + // Lowest possible boost circuit clocks + WriteData(0xb3); +} + +void St7789::GateControl() { + WriteCommand(static_cast(Commands::GateControl)); + // Lowest possible VGL/VGH + WriteData(0x00); +} + void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { WriteCommand(static_cast(Commands::ColumnAddressSet)); - WriteData(x0 >> 8); - WriteData(x0 & 0xff); - WriteData(x1 >> 8); - WriteData(x1 & 0xff); + uint8_t colArgs[] = { + static_cast(x0 >> 8), // x start MSB + static_cast(x0), // x start LSB + static_cast(x1 >> 8), // x end MSB + static_cast(x1) // x end LSB + }; + WriteData(colArgs, sizeof(colArgs)); WriteCommand(static_cast(Commands::RowAddressSet)); - WriteData(y0 >> 8); - WriteData(y0 & 0xff); - WriteData(y1 >> 8); - WriteData(y1 & 0xff); + uint8_t rowArgs[] = { + static_cast(y0 >> 8), // y start MSB + static_cast(y0), // y start LSB + static_cast(y1 >> 8), // y end MSB + static_cast(y1) // y end LSB + }; + memcpy(addrWindowArgs, rowArgs, sizeof(rowArgs)); + WriteData(addrWindowArgs, sizeof(addrWindowArgs)); } void St7789::WriteToRam(const uint8_t* data, size_t size) { @@ -179,8 +256,12 @@ void St7789::DisplayOff() { void St7789::VerticalScrollStartAddress(uint16_t line) { verticalScrollingStartAddress = line; WriteCommand(static_cast(Commands::VerticalScrollStartAddress)); - WriteData(line >> 8u); - WriteData(line & 0x00ffu); + uint8_t args[] = { + static_cast(line >> 8), // Frame memory line pointer MSB + static_cast(line) // Frame memory line pointer LSB + }; + memcpy(verticalScrollArgs, args, sizeof(args)); + WriteData(verticalScrollArgs, sizeof(verticalScrollArgs)); } void St7789::Uninit() { @@ -202,6 +283,18 @@ void St7789::HardwareReset() { vTaskDelay(pdMS_TO_TICKS(125)); } +void St7789::LowPowerOn() { + IdleModeOn(); + IdleFrameRateOn(); + NRF_LOG_INFO("[LCD] Low power mode"); +} + +void St7789::LowPowerOff() { + IdleModeOff(); + IdleFrameRateOff(); + NRF_LOG_INFO("[LCD] Normal power mode"); +} + void St7789::Sleep() { SleepIn(); nrf_gpio_cfg_default(pinDataCommand); diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 45d4b56d..9c778905 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -24,6 +24,8 @@ namespace Pinetime { void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size); + void LowPowerOn(); + void LowPowerOff(); void Sleep(); void Wakeup(); @@ -37,16 +39,25 @@ namespace Pinetime { void HardwareReset(); void SoftwareReset(); + void Command2Enable(); void SleepOut(); void EnsureSleepOutPostDelay(); void SleepIn(); - void ColMod(); + void PixelFormat(); void MemoryDataAccessControl(); void DisplayInversionOn(); void NormalModeOn(); void WriteToRam(const uint8_t* data, size_t size); + void IdleModeOn(); + void IdleModeOff(); + void FrameRateNormalSet(); + void IdleFrameRateOff(); + void IdleFrameRateOn(); void DisplayOn(); void DisplayOff(); + void PowerControl(); + void GateControl(); + void PorchSet(); void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetVdv(); @@ -68,16 +79,26 @@ namespace Pinetime { MemoryDataAccessControl = 0x36, VerticalScrollDefinition = 0x33, VerticalScrollStartAddress = 0x37, - ColMod = 0x3a, + IdleModeOff = 0x38, + IdleModeOn = 0x39, + PixelFormat = 0x3a, + FrameRateIdle = 0xb3, + FrameRateNormal = 0xc6, VdvSet = 0xc4, + Command2Enable = 0xdf, + PowerControl1 = 0xd0, + PowerControl2 = 0xe8, + GateControl = 0xb7, + Porch = 0xb2, }; void WriteData(uint8_t data); void WriteData(const uint8_t* data, size_t size); - void ColumnAddressSet(); static constexpr uint16_t Width = 240; static constexpr uint16_t Height = 320; - void RowAddressSet(); + + uint8_t addrWindowArgs[4]; + uint8_t verticalScrollArgs[2]; }; } } diff --git a/src/heartratetask/HeartRateTask.cpp b/src/heartratetask/HeartRateTask.cpp index 9d82d11e..8a5a871b 100644 --- a/src/heartratetask/HeartRateTask.cpp +++ b/src/heartratetask/HeartRateTask.cpp @@ -70,7 +70,8 @@ void HeartRateTask::Work() { } if (measurementStarted) { - int8_t ambient = ppg.Preprocess(heartRateSensor.ReadHrs(), heartRateSensor.ReadAls()); + 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) 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/main.cpp b/src/main.cpp index ee6a6d3d..24f13cad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -104,7 +104,7 @@ 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 {}; @@ -123,7 +123,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd, alarmController, brightnessController, touchHandler, - fs); + fs, + spiNorFlash); Pinetime::System::SystemTask systemTask(spi, spiNorFlash, @@ -167,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/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/SystemMonitor.cpp b/src/systemtask/SystemMonitor.cpp index 2edee7bf..8696c8ce 100644 --- a/src/systemtask/SystemMonitor.cpp +++ b/src/systemtask/SystemMonitor.cpp @@ -1,5 +1,5 @@ #include "systemtask/SystemTask.h" -#if configUSE_TRACE_FACILITY == 1 +#if NRF_LOG_ENABLED // FreeRtosMonitor #include #include diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index e3d40d35..eb013d6d 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -102,7 +102,9 @@ void SystemTask::Work() { watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause); watchdog.Start(); NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason())); - APP_GPIOTE_INIT(2); + if (!nrfx_gpiote_is_init()) { + nrfx_gpiote_init(); + } spi.Init(); spiNorFlash.Init(); @@ -187,37 +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; - } - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); + wakeLocksHeld--; break; case Messages::DisableSleeping: - doNotGoToSleep = true; + GoToRunning(); + wakeLocksHeld++; break; case Messages::GoToRunning: - 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 && @@ -229,75 +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: - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); - displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime); - 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(); - } else { - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); - } - 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::RestoreBrightness); - 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,19 +285,35 @@ 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. spiNorFlash.Sleep(); } - spi.Sleep(); + + // Must keep SPI awake when still updating the display for always on + if (msg == Messages::OnDisplayTaskSleeping) { + spi.Sleep(); + } // Double Tap needs the touch screen to be in normal mode if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { 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. @@ -340,39 +323,23 @@ 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(); + GoToRunning(); displayApp.PushMessage(Applications::Display::Messages::OnChargingEvent); - if (state == SystemTaskState::Sleeping) { - GoToRunning(); - } break; case Messages::MeasureBatteryTimerExpired: batteryController.MeasureVoltage(); @@ -381,9 +348,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: @@ -410,8 +375,6 @@ void SystemTask::Work() { } monitor.Process(); - uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG); - dateTimeController.UpdateTime(systick_counter); NoInit_BackUpTime = dateTimeController.CurrentDateTime(); if (nrf_gpio_pin_read(PinMap::Button) == 0) { watchdog.Reload(); @@ -420,11 +383,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())) { @@ -450,7 +457,7 @@ void SystemTask::UpdateMotion() { } if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running && motionController.ShouldLowerSleep()) { - PushMessage(Messages::GoToSleep); + GoToSleep(); } } @@ -459,14 +466,14 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) { return; } - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); using Actions = Controllers::ButtonActions; 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; @@ -486,29 +493,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..0060e360 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,13 +77,8 @@ 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() { @@ -91,7 +86,7 @@ namespace Pinetime { }; bool IsSleeping() const { - return state == SystemTaskState::Sleeping || state == SystemTaskState::WakingUp; + return state != SystemTaskState::Running; } private: @@ -127,13 +122,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; + }; + } +}