Compare commits

...

42 commits

Author SHA1 Message Date
D. Scott Boggs 423dd489ef Bump version 2024-10-19 16:06:12 -04:00
D. Scott Boggs d93d809255 Merge branch 'timerv' into v1.15.3 2024-10-19 16:04:58 -04:00
D. Scott Boggs 6e57b178e8 Merge branch 'heartrate-measurements-in-background' into v1.15.3 2024-10-19 07:00:30 -04:00
D. Scott Boggs a2a2b6a64d bump settings version 2024-10-19 06:31:11 -04:00
Dom Rodriguez c613bc7afd
feat: Introduce Flake for development and builds
This PR introduces a Nix flake, allowing for InfiniTime to be built as a
Flake, including a FHS development environment.

It's derived from #1850 and
c57c57f3c6.

We also introduce `flake-compat`, allowing for non-Flake Nix mahcines to
use the project as-is, both for building (`default.nix`), and
development (`shell.nix`).

Additionally, we introduce `.envrc`, meaning that with `direnv`, the Nix
Flake is activated automatically.

Fixes #1850.

Signed-off-by: Dom Rodriguez <shymega@shymega.org.uk>
2024-10-13 16:13:37 +01:00
NeroBurner 8598142c27
Remove unused submodule QCBOR (#2138)
The submodule isn't used anymore. Remove the submodule reference
completely.
2024-10-09 20:26:08 +02:00
NeroBurner a2356f2f4a
MusicService: add missing includes for TickType_t and xTaskGetTickCount (#2130)
Add `FreeRTOS.h` include for the directly used data type `TickType_t` in the header
and the function `xTaskGetTickCount` from FreeRTOS's `task.h`
2024-09-29 21:10:32 +02:00
NeroBurner 3db4e012ce
Remove unused pointer to DisplayApp member variables (#2125)
In the screens that use `DisplayApp *app` and pass it to a child item,
or use the reference just in the constructor. Afterwards the `app`
member is not used. So remove it from the private member variables.

Completely remove `app` parameter from `SettingDisplay` constructor as
it is unused.
2024-09-29 19:39:14 +02:00
NeroBurner a0cd439efc
Alarm persist to flash (#1367)
* AlarmController: Add saving alarm time to file

Save the set alarm time to the SPI NOR flash, so it does not reset to
the default value when the watch resets, e.g. due to watchdog timeout
or reflashing of a new version of InfiniTime.

Just like the `Settings.h` `LoadSettingsFromFile()` the previous alarm
at boot (if available) and `SaveSettingsToFile()` the current alarm when
the `Alarm.h` screen is closed (only if the settings have changed).

The alarm-settings file is stored in `.system/alarm.dat`. The `.system`
folder is created if it doesn't yet exist.

Fixes: https://github.com/InfiniTimeOrg/InfiniTime/issues/1330

* alarmController: close .system dir after usage

Close the `lfs_dir` object for the `.system` dir after usage. Otherwise
on the second changed alarm the system will lockup because the `.system`
dir is already open and was never closed.

---------

Co-authored-by: Galdor Takacs <g@ldor.de>
2024-09-28 08:14:08 +02:00
Patric Gruber 462ea11bd4 use interval as interval, instead of wait time 2024-09-22 20:55:45 +02:00
Reinhold Gschweicher 997e4cee8c Hrs3300: fix includes for std::begin/std::end
Fix for Hrs3300 PR about Atomic HRS reads:
https://github.com/InfiniTimeOrg/InfiniTime/pull/1845

We use `std::begin` and `std::end`, but we don't include one of the
headers that define those functions.
See https://en.cppreference.com/w/cpp/iterator/begin for a list of
headers that define `std::begin` and `std::end`.

Starting with GCC 14 this leads to a compilation error presumably
because they cleaned up their headers.

Fix code by inlcuding `<iterator>`
2024-09-22 16:15:48 +02:00
mark9064 ad3bf49c7b
Atomic HRS reads (#1845)
- Combine the reading of all `HRS3300` registers into one I2C read so data is not partial
- Downsizes both HRS and ALS to 16bit as the sensor does not generate larger than
  16bit values in its current configuration
  - Increasing the resolution by 1 bit doubles the sensor acquisition time,
    since we are already at 10Hz we are never going to use a higher resolution
  - The PPG algorithm buffers for ALS/HRS are already 16bit anyway
- Remove functions for setting gain / drive that are unused throughout the codebase
- Calculate constants with constexpr
2024-09-22 00:29:15 +02:00
mark9064 7ca0418c82 Refactor doNotGoToSleep to a wakelock counter 2024-09-21 22:45:57 +02:00
mark9064 c3d05901a0 Refactor SystemTask state handling for resilience
State transitions now happen immediately where possible
This simplifies state management in general,
and prevents bugs such as the chime issue from occurring in the first place
2024-09-21 21:08:07 +02:00
mark9064 b3756e45fa Remove unused method declarations 2024-09-21 21:08:07 +02:00
Eli Tan a266202831
notifications: Dismiss to watchface when empty (#1716)
Set `running` to false to flag end of watchface when there are no more
notifications left to display.

I found it slightly annoying that dismissing all notifications leaves me with
a "No notification to display" message. Instead of dismissing to a relatively
useless message, dismiss to watchface.
2024-09-18 22:31:15 +02:00
mark9064 c8236afbef Restrict debugging monitor to debug builds 2024-09-14 12:10:55 +02:00
mark9064 5040733a97 Clean unused DisplayApp messages 2024-09-14 12:10:14 +02:00
mark9064 fd019c7aad Use DirtyValue for timer 2024-09-14 11:20:47 +02:00
mark9064 975bfc5420 Size optimise NRF SDK build 2024-09-14 11:19:34 +02:00
Victor Kareh 2625ed39e5 DisplayApp: Go to clock on sleep if no app loaded
When turning off the screen, if there is no actual app loaded (i.e. we
are still in the Launcher, Notifications, QuickSettings, or Settings
screens) we should just reload the Clock app directly.
2024-08-22 17:34:25 +02:00
Derry Tutt a3dbcd62f6
Documentation improvements (#2091)
Add documentation about watch faces and applications.
Update getting started documentation.

Co-authored-by: tituscmd <154823939+tituscmd@users.noreply.github.com>
2024-08-18 16:18:15 +02:00
mark9064 4fddf93114 Advertise HR service 2024-08-18 12:00:13 +02:00
Derry Tutt 83922fb3de Remove space before colon 2024-08-18 11:58:56 +02:00
Felipe Martínez 95917c65a5 Update main.yml 2024-08-18 11:55:03 +02:00
Jean-François Milants 3a0d673df4 Display the SPI flash JEDEC IDs in SystemInformation.
This is needed since a new memory chip will be used in future batches of PineTimes.
2024-08-05 20:34:41 +02:00
mark9064 53dc9dafe7 aod: simplify AOD disablement based on notification status 2024-08-05 20:32:43 +02:00
mark9064 a407902b06 aod: avoid spinning DisplayApp under high LVGL load 2024-08-05 20:32:43 +02:00
mark9064 3e8accde69 aod: run LVGL task handler until all work finished 2024-08-05 20:32:43 +02:00
mark9064 2bb611db8e aod: constant frequency idle frames 2024-08-05 20:32:43 +02:00
mark9064 ef88e8165c aod: porch control: 2Hz idle + 75Hz on 2024-08-05 20:32:43 +02:00
mark9064 da9ab4a7b4 aod: lower lcd voltage 2024-08-05 20:32:43 +02:00
John Crawford 0bcd7e0009 aod: lower voltage going to the display 2024-08-05 20:32:43 +02:00
mark9064 bf69e0dcc5 aod: fix flashlight brightness restore 2024-08-05 20:32:43 +02:00
mark9064 947c4f5067 aod: fix brightness getting stuck high 2024-08-05 20:32:43 +02:00
John Crawford 0960d67001 aod: lower refresh rate when always on 2024-08-05 20:32:43 +02:00
John Crawford 5385f7e275 aod: switch to 8 colors when always on 2024-08-05 20:32:43 +02:00
John Crawford e884b053d3 aod: disable while in notification sleep 2024-08-05 20:32:43 +02:00
John Crawford 85a2181b64 aod: integrate with display timeout 2024-08-05 20:32:43 +02:00
mark9064 3dca742b65 aod: PPI/RTC-based backlight brightness 2024-08-05 20:32:43 +02:00
KaffeinatedKat 20ac7e8df3 feat: always on display 2024-08-05 20:32:43 +02:00
Davis Mosenkovs 98455aa54d Improve timer vibration 2024-06-30 22:37:01 +03:00
100 changed files with 1211 additions and 373 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

View file

@ -56,6 +56,11 @@ jobs:
with: with:
name: InfiniTime MCUBoot image ${{ github.head_ref }} name: InfiniTime MCUBoot image ${{ github.head_ref }}
path: ./build/output/pinetime-mcuboot-app-image-*.bin path: ./build/output/pinetime-mcuboot-app-image-*.bin
- name: Upload standalone ELF artifacts
uses: actions/upload-artifact@v3
with:
name: InfiniTime image ${{ github.head_ref }}
path: ./build/output/src/pinetime-app-*.out
- name: Upload resources artifacts - name: Upload resources artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:

3
.gitmodules vendored
View file

@ -4,9 +4,6 @@
[submodule "src/libs/littlefs"] [submodule "src/libs/littlefs"]
path = src/libs/littlefs path = src/libs/littlefs
url = https://github.com/littlefs-project/littlefs.git 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"] [submodule "src/libs/arduinoFFT"]
path = src/libs/arduinoFFT path = src/libs/arduinoFFT
url = https://github.com/kosme/arduinoFFT.git url = https://github.com/kosme/arduinoFFT.git

View file

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release") set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
project(pinetime VERSION 1.14.0 LANGUAGES C CXX ASM) project(pinetime VERSION 1.15.3 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)

View file

@ -9,6 +9,8 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic
- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
- [Updating the software](doc/gettingStarted/updating-software.md) - [Updating the software](doc/gettingStarted/updating-software.md)
- [About the firmware and bootloader](doc/gettingStarted/about-software.md) - [About the firmware and bootloader](doc/gettingStarted/about-software.md)
- [Available apps](doc/gettingStarted/Applications.md)
- [Available watch faces](/doc/gettingStarted/Watchfaces.md)
- [PineTimeStyle Watch face](https://pine64.org/documentation/PineTime/Watchfaces/PineTimeStyle) - [PineTimeStyle Watch face](https://pine64.org/documentation/PineTime/Watchfaces/PineTimeStyle)
- [Weather integration](https://pine64.org/documentation/PineTime/Software/InfiniTime_weather/) - [Weather integration](https://pine64.org/documentation/PineTime/Software/InfiniTime_weather/)
@ -35,7 +37,7 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic
### Contributing ### Contributing
- [How to contribute?](CONTRIBUTING.md) - [How to contribute](CONTRIBUTING.md)
- [Coding conventions](doc/coding-convention.md) - [Coding conventions](doc/coding-convention.md)
### Build, flash and debug ### Build, flash and debug

10
default.nix Normal file
View file

@ -0,0 +1,10 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{ src = ./.; }
).defaultNix

View file

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 256 KiB

View file

@ -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) - 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 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) 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) ![Settings](ui/settings.jpg)
- Swipe **up** to display the application menus. Apps (stopwatch, music, step, games,...) can be started from this menu. - 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 - Swipe **right** to display the Quick Actions menu. This menu allows you to
- Set the brightness of the display - Set the brightness of the display
- Start the **flashlight** app - Start the **flashlight** app

View file

@ -1,29 +1,35 @@
# Connecting to Gadgetbridge # 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) ![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) ![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: Tap on it. Gadgdetbridge will pair and connect to your device:
![Gadgetbridge 2](gadgetbridge2.jpg) ![Gadgetbridge 2](gadgetbridge2.jpg)
# Updating with Gadgetbridge # 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) ![Gadgetbridge 3](gadgetbridge3.jpg)
Read the warning carefully and tap **Install**: Now press the "File Installer" button:
![Gadgetbridge 4](gadgetbridge4.jpg) ![Gadgetbridge 4](gadgetbridge4.jpg)
Wait for the transfer to finish. Your PineTime should reset and reboot with the new version of InfiniTime! Select the firmware you downloaded (`pinetime-mcuboot-app-dfu-x.x.x.zip`) from the [Releases tab](https://github.com/InfiniTimeOrg/InfiniTime/releases/latest):
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.
![Gadgetbridge 5](gadgetbridge5.jpg) ![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)

View file

@ -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. 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) 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). 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. 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 ## Amazfish
Use the `Download file` functionality of Amazfish. Use the `Download file` functionality of Amazfish.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

42
flake.lock Normal file
View file

@ -0,0 +1,42 @@
{
"nodes": {
"flake-compat": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1728492678,
"narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

102
flake.nix Normal file
View file

@ -0,0 +1,102 @@
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
};
outputs = { self, ... }@inputs:
let
forAllSystems = function:
inputs.nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
]
(system: function (import inputs.nixpkgs {
inherit system;
config.allowUnfree = true;
}));
in
{
packages = forAllSystems (pkgs:
let
infinitime-nrf5-sdk = pkgs.nrf5-sdk.overrideAttrs (old: {
version = "15.3.0";
src = pkgs.fetchzip {
url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip";
sha256 = "sha256-pfmhbpgVv5x2ju489XcivguwpnofHbgVA7bFUJRTj08=";
};
});
in
with pkgs; {
default = stdenv.mkDerivation rec {
name = "infinitime";
version = "1.14.1";
src = fetchFromGitHub {
owner = "InfiniTimeOrg";
repo = "InfiniTime";
rev = "refs/tags/${version}";
hash = "sha256-IrsN+9LgEjgfoRR6H7FhsdLMK+GLxc41IBnSbdpwv/E=";
fetchSubmodules = true;
};
nativeBuildInputs = [
adafruit-nrfutil
nodePackages.lv_font_conv
patch
python3
python3.pkgs.cbor
python3.pkgs.click
python3.pkgs.cryptography
python3.pkgs.intelhex
python3.pkgs.pillow
];
postPatch = ''
# /usr/bin/env is not available in the build sandbox
substituteInPlace src/displayapp/fonts/generate.py --replace "'/usr/bin/env', 'patch'" "'patch'"
substituteInPlace tools/mcuboot/imgtool.py --replace "/usr/bin/env python3" "${python3}/bin/python3"
'';
cmakeFlags = [
''-DARM_NONE_EABI_TOOLCHAIN_PATH=${gcc-arm-embedded-10}''
''-DNRF5_SDK_PATH=${infinitime-nrf5-sdk}/share/nRF5_SDK''
''-DBUILD_DFU=1''
''-DBUILD_RESOURCES=1''
''-DCMAKE_SOURCE_DIR=${src}''
];
installPhase = ''
SOURCES_DIR=${src} BUILD_DIR=. OUTPUT_DIR=$out ./post_build.sh
'';
};
});
devShells = forAllSystems (pkgs:
let
infinitime-nrf5-sdk = pkgs.nrf5-sdk.overrideAttrs (old: {
version = "15.3.0";
src = pkgs.fetchzip {
url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip";
sha256 = "sha256-pfmhbpgVv5x2ju489XcivguwpnofHbgVA7bFUJRTj08=";
};
});
in
with pkgs; {
default =
mkShell {
packages = [
(writeShellScriptBin "cmake_infinitime" ''
${cmake}/bin/cmake -DARM_NONE_EABI_TOOLCHAIN_PATH="${gcc-arm-embedded-10}" \
-DNRF5_SDK_PATH="${infinitime-nrf5-sdk}/share/nRF5_SDK" \
"$@"
'')
ninja
] ++ self.packages.${pkgs.system}.default.nativeBuildInputs;
};
});
};
}

10
shell.nix Normal file
View file

@ -0,0 +1,10 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{ src = ./.; }
).shellNix

View file

@ -479,6 +479,7 @@ list(APPEND SOURCE_FILES
systemtask/SystemTask.cpp systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp drivers/TwiMaster.cpp
heartratetask/HeartRateTask.cpp heartratetask/HeartRateTask.cpp
@ -543,6 +544,7 @@ list(APPEND RECOVERY_SOURCE_FILES
systemtask/SystemTask.cpp systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp drivers/TwiMaster.cpp
components/rle/RleDecoder.cpp components/rle/RleDecoder.cpp
components/heartrate/HeartRateController.cpp components/heartrate/HeartRateController.cpp
@ -661,6 +663,7 @@ set(INCLUDE_FILES
displayapp/InfiniTimeTheme.h displayapp/InfiniTimeTheme.h
systemtask/SystemTask.h systemtask/SystemTask.h
systemtask/SystemMonitor.h systemtask/SystemMonitor.h
systemtask/WakeLock.h
displayapp/screens/Symbols.h displayapp/screens/Symbols.h
drivers/TwiMaster.h drivers/TwiMaster.h
heartratetask/HeartRateTask.h heartratetask/HeartRateTask.h
@ -861,7 +864,6 @@ target_compile_options(nrf-sdk PRIVATE
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}> $<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}> $<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}> $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
-O3
) )
# NimBLE # NimBLE

View file

@ -19,11 +19,13 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "task.h" #include "task.h"
#include <chrono> #include <chrono>
#include <libraries/log/nrf_log.h>
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
using namespace std::chrono_literals; 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 { namespace {
@ -36,11 +38,28 @@ namespace {
void AlarmController::Init(System::SystemTask* systemTask) { void AlarmController::Init(System::SystemTask* systemTask) {
this->systemTask = systemTask; this->systemTask = systemTask;
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm); 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) { void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
hours = alarmHr; if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
minutes = alarmMin; return;
}
alarm.hours = alarmHr;
alarm.minutes = alarmMin;
alarmChanged = true;
} }
void AlarmController::ScheduleAlarm() { void AlarmController::ScheduleAlarm() {
@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() {
tm* tmAlarmTime = std::localtime(&ttAlarmTime); tm* tmAlarmTime = std::localtime(&ttAlarmTime);
// If the time being set has already passed today,the alarm should be set for tomorrow // 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; tmAlarmTime->tm_mday += 1;
// tm_wday doesn't update automatically // tm_wday doesn't update automatically
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7; tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
} }
tmAlarmTime->tm_hour = hours; tmAlarmTime->tm_hour = alarm.hours;
tmAlarmTime->tm_min = minutes; tmAlarmTime->tm_min = alarm.minutes;
tmAlarmTime->tm_sec = 0; tmAlarmTime->tm_sec = 0;
// if alarm is in weekday-only mode, make sure it shifts to the next weekday // 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 if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
tmAlarmTime->tm_mday += 1; tmAlarmTime->tm_mday += 1;
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days } else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() {
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0); xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
xTimerStart(alarmTimer, 0); xTimerStart(alarmTimer, 0);
state = AlarmState::Set; if (!alarm.isEnabled) {
alarm.isEnabled = true;
alarmChanged = true;
}
} }
uint32_t AlarmController::SecondsToAlarm() const { uint32_t AlarmController::SecondsToAlarm() const {
@ -88,20 +111,72 @@ uint32_t AlarmController::SecondsToAlarm() const {
void AlarmController::DisableAlarm() { void AlarmController::DisableAlarm() {
xTimerStop(alarmTimer, 0); xTimerStop(alarmTimer, 0);
state = AlarmState::Not_Set; isAlerting = false;
if (alarm.isEnabled) {
alarm.isEnabled = false;
alarmChanged = true;
}
} }
void AlarmController::SetOffAlarmNow() { void AlarmController::SetOffAlarmNow() {
state = AlarmState::Alerting; isAlerting = true;
systemTask->PushMessage(System::Messages::SetOffAlarm); systemTask->PushMessage(System::Messages::SetOffAlarm);
} }
void AlarmController::StopAlerting() { void AlarmController::StopAlerting() {
// Alarm state is off unless this is a recurring alarm isAlerting = false;
if (recurrence == RecurType::None) { // Disable alarm unless it is recurring
state = AlarmState::Not_Set; if (alarm.recurrence == RecurType::None) {
alarm.isEnabled = false;
alarmChanged = true;
} else { } else {
// set next instance // set next instance
ScheduleAlarm(); 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<uint8_t*>(&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<const uint8_t*>(&alarm), sizeof(alarm));
fs.FileClose(&alarmFile);
NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version);
}

View file

@ -30,47 +30,65 @@ namespace Pinetime {
namespace Controllers { namespace Controllers {
class AlarmController { class AlarmController {
public: public:
AlarmController(Controllers::DateTime& dateTimeController); AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);
void Init(System::SystemTask* systemTask); void Init(System::SystemTask* systemTask);
void SaveAlarm();
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin); void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
void ScheduleAlarm(); void ScheduleAlarm();
void DisableAlarm(); void DisableAlarm();
void SetOffAlarmNow(); void SetOffAlarmNow();
uint32_t SecondsToAlarm() const; uint32_t SecondsToAlarm() const;
void StopAlerting(); void StopAlerting();
enum class AlarmState { Not_Set, Set, Alerting };
enum class RecurType { None, Daily, Weekdays }; enum class RecurType { None, Daily, Weekdays };
uint8_t Hours() const { uint8_t Hours() const {
return hours; return alarm.hours;
} }
uint8_t Minutes() const { uint8_t Minutes() const {
return minutes; return alarm.minutes;
} }
AlarmState State() const { bool IsAlerting() const {
return state; return isAlerting;
}
bool IsEnabled() const {
return alarm.isEnabled;
} }
RecurType Recurrence() const { RecurType Recurrence() const {
return recurrence; return alarm.recurrence;
} }
void SetRecurrence(RecurType recurType) { void SetRecurrence(RecurType recurrence);
recurrence = recurType;
}
private: private:
Controllers::DateTime& dateTimeController; // Versions 255 is reserved for now, so the version field can be made
System::SystemTask* systemTask = nullptr; // bigger, should it ever be needed.
TimerHandle_t alarmTimer; static constexpr uint8_t alarmFormatVersion = 1;
struct AlarmSettings {
uint8_t version = alarmFormatVersion;
uint8_t hours = 7; uint8_t hours = 7;
uint8_t minutes = 0; uint8_t minutes = 0;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
AlarmState state = AlarmState::Not_Set;
RecurType recurrence = RecurType::None; 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;
AlarmSettings alarm;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
void LoadSettingsFromFile();
void SaveSettingsToFile() const;
}; };
} }
} }

View file

@ -124,9 +124,11 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) {
bootloaderSize, bootloaderSize,
applicationSize); applicationSize);
// wait until SystemTask has finished waking up all devices // Wait until SystemTask has disabled sleeping
while (systemTask.IsSleeping()) { // This isn't quite correct, as we don't actually know
vTaskDelay(50); // 50ms // if BleFirmwareUpdateStarted has been received yet
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
} }
dfuImage.Erase(); dfuImage.Erase();

View file

@ -77,6 +77,10 @@ namespace Pinetime {
uint16_t ComputeCrc(uint8_t const* p_data, uint32_t size, uint16_t const* p_crc); 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: private:
Pinetime::System::SystemTask& systemTask; Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::Ble& bleController; Pinetime::Controllers::Ble& bleController;
@ -90,10 +94,6 @@ namespace Pinetime {
uint16_t revision {0x0008}; 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 { static constexpr ble_uuid128_t packetCharacteristicUuid {
.u {.type = BLE_UUID_TYPE_128}, .u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x32, 0x15, 0x00, 0x00}}; .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x32, 0x15, 0x00, 0x00}};

View file

@ -21,14 +21,14 @@ namespace Pinetime {
void SubscribeNotification(uint16_t attributeHandle); void SubscribeNotification(uint16_t attributeHandle);
void UnsubscribeNotification(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: private:
NimbleController& nimble; NimbleController& nimble;
Controllers::HeartRateController& heartRateController; Controllers::HeartRateController& heartRateController;
static constexpr uint16_t heartRateServiceId {0x180D};
static constexpr uint16_t heartRateMeasurementId {0x2A37}; 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}; static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId};
struct ble_gatt_chr_def characteristicDefinition[2]; struct ble_gatt_chr_def characteristicDefinition[2];

View file

@ -18,6 +18,8 @@
#include "components/ble/MusicService.h" #include "components/ble/MusicService.h"
#include "components/ble/NimbleController.h" #include "components/ble/NimbleController.h"
#include <cstring> #include <cstring>
#include <FreeRTOS.h>
#include <task.h>
namespace { namespace {
// 0000yyxx-78fc-48fe-8e23-433b3a1942d0 // 0000yyxx-78fc-48fe-8e23-433b3a1942d0

View file

@ -25,6 +25,7 @@
#include <host/ble_uuid.h> #include <host/ble_uuid.h>
#undef max #undef max
#undef min #undef min
#include <FreeRTOS.h>
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {

View file

@ -158,7 +158,10 @@ void NimbleController::StartAdvertising() {
} }
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; 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.num_uuids128 = 1;
fields.uuids128_is_complete = 1; fields.uuids128_is_complete = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; 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 /* Wakeup Spi and SpiNorFlash before accessing the file system
* This should be fixed in the FS driver * This should be fixed in the FS driver
*/ */
systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); 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; lfs_file_t file_p;

View file

@ -112,10 +112,6 @@ namespace Pinetime {
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE; uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
uint8_t fastAdvCount = 0; uint8_t fastAdvCount = 0;
uint8_t bondId[16] = {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; static NimbleController* nptr;

View file

@ -2,38 +2,138 @@
#include <hal/nrf_gpio.h> #include <hal/nrf_gpio.h>
#include "displayapp/screens/Symbols.h" #include "displayapp/screens/Symbols.h"
#include "drivers/PinMap.h" #include "drivers/PinMap.h"
#include <libraries/delay/nrf_delay.h>
using namespace Pinetime::Controllers; 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_RTC_Type*>(NRF_RTC2_BASE);
}
void BrightnessController::Init() { void BrightnessController::Init() {
nrf_gpio_cfg_output(PinMap::LcdBacklightLow); nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
nrf_gpio_cfg_output(PinMap::LcdBacklightMedium); nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
nrf_gpio_cfg_output(PinMap::LcdBacklightHigh); 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); 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<uint32_t>(&RTC->EVENTS_COMPARE[0]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_channel_endpoint_setup(ppiBacklightOn,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[1]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast<uint32_t>(&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) { void BrightnessController::Set(BrightnessController::Levels level) {
this->level = level; this->level = level;
switch (level) { switch (level) {
default: default:
case Levels::High: case Levels::High:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow); ApplyBrightness(3 * timerPeriod);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
break; break;
case Levels::Medium: case Levels::Medium:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow); ApplyBrightness(2 * timerPeriod);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
break; break;
case Levels::Low: case Levels::Low:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow); ApplyBrightness(timerPeriod);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium); break;
nrf_gpio_pin_set(PinMap::LcdBacklightHigh); case Levels::AlwaysOn:
ApplyBrightness(timerPeriod / 10);
break; break;
case Levels::Off: case Levels::Off:
nrf_gpio_pin_set(PinMap::LcdBacklightLow); ApplyBrightness(0);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
break; break;
} }
} }

View file

@ -2,11 +2,14 @@
#include <cstdint> #include <cstdint>
#include "nrf_ppi.h"
#include "nrfx_gpiote.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
class BrightnessController { class BrightnessController {
public: public:
enum class Levels { Off, Low, Medium, High }; enum class Levels { Off, AlwaysOn, Low, Medium, High };
void Init(); void Init();
void Set(Levels level); void Set(Levels level);
@ -20,6 +23,25 @@ namespace Pinetime {
private: private:
Levels level = Levels::High; 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);
}; };
} }
} }

View file

@ -142,7 +142,7 @@ Ppg::Ppg() {
spectrum.fill(0.0f); 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) { if (dataIndex < dataLength) {
dataHRS[dataIndex++] = hrs; dataHRS[dataIndex++] = hrs;
} }

View file

@ -14,7 +14,7 @@ namespace Pinetime {
class Ppg { class Ppg {
public: public:
Ppg(); Ppg();
int8_t Preprocess(uint32_t hrs, uint32_t als); int8_t Preprocess(uint16_t hrs, uint16_t als);
int HeartRate(); int HeartRate();
void Reset(bool resetDaqBuffer); void Reset(bool resetDaqBuffer);
static constexpr int deltaTms = 100; static constexpr int deltaTms = 100;

View file

@ -53,7 +53,7 @@ namespace Pinetime {
enum class HeartRateBackgroundMeasurementInterval : uint8_t { enum class HeartRateBackgroundMeasurementInterval : uint8_t {
Off, Off,
Continuous, Continuous,
TenSeconds, FifteenSeconds,
ThirtySeconds, ThirtySeconds,
OneMinute, OneMinute,
FiveMinutes, FiveMinutes,
@ -225,6 +225,21 @@ namespace Pinetime {
return settings.screenTimeOut; 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) { void SetShakeThreshold(uint16_t thresh) {
if (settings.shakeWakeThreshold != thresh) { if (settings.shakeWakeThreshold != thresh) {
settings.shakeWakeThreshold = thresh; settings.shakeWakeThreshold = thresh;
@ -308,13 +323,15 @@ namespace Pinetime {
private: private:
Pinetime::Controllers::FS& fs; Pinetime::Controllers::FS& fs;
static constexpr uint32_t settingsVersion = 0x0008; static constexpr uint32_t settingsVersion = 0x0009;
struct SettingsData { struct SettingsData {
uint32_t version = settingsVersion; uint32_t version = settingsVersion;
uint32_t stepsGoal = 10000; uint32_t stepsGoal = 10000;
uint32_t screenTimeOut = 15000; uint32_t screenTimeOut = 15000;
bool alwaysOnDisplay = false;
ClockType clockType = ClockType::H24; ClockType clockType = ClockType::H24;
WeatherFormat weatherFormat = WeatherFormat::Metric; WeatherFormat weatherFormat = WeatherFormat::Metric;
Notification notificationStatus = Notification::On; Notification notificationStatus = Notification::On;

View file

@ -82,7 +82,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem) Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: lcd {lcd}, : lcd {lcd},
touchPanel {touchPanel}, touchPanel {touchPanel},
batteryController {batteryController}, batteryController {batteryController},
@ -98,6 +99,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
brightnessController {brightnessController}, brightnessController {brightnessController},
touchHandler {touchHandler}, touchHandler {touchHandler},
filesystem {filesystem}, filesystem {filesystem},
spiNorFlash {spiNorFlash},
lvgl {lcd, filesystem}, lvgl {lcd, filesystem},
timer(this, TimerCallback), timer(this, TimerCallback),
controllers {batteryController, controllers {batteryController,
@ -155,6 +157,36 @@ void DisplayApp::InitHw() {
lcd.Init(); lcd.Init();
} }
TickType_t DisplayApp::CalculateSleepTime() {
TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
// Divide both the numerator and denominator by 8 to increase the number of ticks (frames) before the overflow tick is reached
TickType_t elapsedTarget = ROUNDED_DIV((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8);
// ROUNDED_DIV overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours
constexpr TickType_t overflowTick = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
// Assumptions
// Tick rate is multiple of 8
// Needed for division trick above
static_assert(configTICK_RATE_HZ % 8 == 0);
// Local tick count must always wraparound before the system tick count does
// As a static assert we can use 64 bit ints and therefore dodge overflows
// Always on overflow time (ms) < system tick overflow time (ms)
static_assert((uint64_t) overflowTick * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
if (alwaysOnTickCount == overflowTick) {
alwaysOnTickCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
}
if (elapsedTarget > ticksElapsed) {
return elapsedTarget - ticksElapsed;
} else {
return 0;
}
}
void DisplayApp::Refresh() { void DisplayApp::Refresh() {
auto LoadPreviousScreen = [this]() { auto LoadPreviousScreen = [this]() {
FullRefreshDirections returnDirection; FullRefreshDirections returnDirection;
@ -178,21 +210,6 @@ void DisplayApp::Refresh() {
LoadScreen(returnAppStack.Pop(), returnDirection); 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 { auto IsPastDimTime = [this]() -> bool {
return lv_disp_get_inactive_time(nullptr) >= pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000); return lv_disp_get_inactive_time(nullptr) >= pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000);
}; };
@ -204,7 +221,29 @@ void DisplayApp::Refresh() {
TickType_t queueTimeout; TickType_t queueTimeout;
switch (state) { switch (state) {
case States::Idle: case States::Idle:
if (settingsController.GetAlwaysOnDisplay()) {
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) {
alwaysOnTickCount += 1;
queueTimeout = CalculateSleepTime();
}
};
}
} else {
queueTimeout = portMAX_DELAY; queueTimeout = portMAX_DELAY;
}
break; break;
case States::Running: case States::Running:
if (!currentScreen->IsRunning()) { if (!currentScreen->IsRunning()) {
@ -214,14 +253,27 @@ void DisplayApp::Refresh() {
if (!systemTask->IsSleepDisabled() && IsPastDimTime()) { if (!systemTask->IsSleepDisabled() && IsPastDimTime()) {
if (!isDimmed) { if (!isDimmed) {
DimScreen(); isDimmed = true;
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
} }
if (IsPastSleepTime()) { if (IsPastSleepTime() && uxQueueMessagesWaiting(msgQueue) == 0) {
systemTask->PushMessage(System::Messages::GoToSleep); PushMessageToSystemTask(System::Messages::GoToSleep);
state = States::Idle; // 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) { } else if (isDimmed) {
RestoreBrightness(); isDimmed = false;
ApplyBrightness();
} }
break; break;
default: default:
@ -232,30 +284,58 @@ void DisplayApp::Refresh() {
Messages msg; Messages msg;
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) { if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
switch (msg) { switch (msg) {
case Messages::DimScreen:
DimScreen();
break;
case Messages::RestoreBrightness:
RestoreBrightness();
break;
case Messages::GoToSleep: case Messages::GoToSleep:
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { if (state != States::Running) {
break;
}
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
brightnessController.Lower(); brightnessController.Lower();
vTaskDelay(100); vTaskDelay(100);
} }
// Turn brightness down (or set to AlwaysOn mode)
if (settingsController.GetAlwaysOnDisplay()) {
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()) {
};
}
// Turn LCD display off (or set to low power for AlwaysOn mode)
if (settingsController.GetAlwaysOnDisplay()) {
lcd.LowPowerOn();
// Record idle entry time
alwaysOnTickCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
} else {
lcd.Sleep(); lcd.Sleep();
}
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle; state = States::Idle;
break; break;
case Messages::NotifyDeviceActivity:
lv_disp_trig_activity(nullptr);
break;
case Messages::GoToRunning: case Messages::GoToRunning:
if (state == States::Running) {
break;
}
if (settingsController.GetAlwaysOnDisplay()) {
lcd.LowPowerOff();
} else {
lcd.Wakeup(); lcd.Wakeup();
}
lv_disp_trig_activity(nullptr); lv_disp_trig_activity(nullptr);
ApplyBrightness(); ApplyBrightness();
state = States::Running; state = States::Running;
break; break;
case Messages::UpdateBleConnection: case Messages::UpdateBleConnection:
// clockScreen.SetBleConnectionState(bleController.IsConnected() ? Screens::Clock::BleConnectionStates::Connected : // Only used for recovery firmware
// Screens::Clock::BleConnectionStates::NotConnected);
break; break;
case Messages::NewNotification: case Messages::NewNotification:
LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down); LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down);
@ -271,7 +351,7 @@ void DisplayApp::Refresh() {
} else { } else {
LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up); LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up);
} }
motorController.RunForDuration(35); motorController.StartRinging();
break; break;
case Messages::AlarmTriggered: case Messages::AlarmTriggered:
if (currentApp == Apps::Alarm) { if (currentApp == Apps::Alarm) {
@ -370,16 +450,11 @@ void DisplayApp::Refresh() {
case Messages::BleRadioEnableToggle: case Messages::BleRadioEnableToggle:
PushMessageToSystemTask(System::Messages::BleRadioEnableToggle); PushMessageToSystemTask(System::Messages::BleRadioEnableToggle);
break; break;
case Messages::UpdateDateTime:
// Added to remove warning
// What should happen here?
break;
case Messages::Chime: case Messages::Chime:
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
motorController.RunForDuration(35); motorController.RunForDuration(35);
break; break;
case Messages::OnChargingEvent: case Messages::OnChargingEvent:
RestoreBrightness();
motorController.RunForDuration(15); motorController.RunForDuration(15);
break; break;
} }
@ -511,7 +586,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::SettingHeartRate>(settingsController); currentScreen = std::make_unique<Screens::SettingHeartRate>(settingsController);
break; break;
case Apps::SettingDisplay: case Apps::SettingDisplay:
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController); currentScreen = std::make_unique<Screens::SettingDisplay>(settingsController);
break; break;
case Apps::SettingSteps: case Apps::SettingSteps:
currentScreen = std::make_unique<Screens::SettingSteps>(settingsController); currentScreen = std::make_unique<Screens::SettingSteps>(settingsController);
@ -539,7 +614,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
bleController, bleController,
watchdog, watchdog,
motionController, motionController,
touchPanel); touchPanel,
spiNorFlash);
break; break;
case Apps::FlashLight: case Apps::FlashLight:
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController); currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);

View file

@ -66,7 +66,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem); Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start(System::BootErrors error); void Start(System::BootErrors error);
void PushMessage(Display::Messages msg); void PushMessage(Display::Messages msg);
@ -96,6 +97,7 @@ namespace Pinetime {
Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::BrightnessController& brightnessController;
Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::TouchHandler& touchHandler;
Pinetime::Controllers::FS& filesystem; Pinetime::Controllers::FS& filesystem;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
Pinetime::Controllers::FirmwareValidator validator; Pinetime::Controllers::FirmwareValidator validator;
Pinetime::Components::LittleVgl lvgl; Pinetime::Components::LittleVgl lvgl;
@ -135,6 +137,13 @@ namespace Pinetime {
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections; Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections;
bool isDimmed = false; bool isDimmed = false;
TickType_t CalculateSleepTime();
TickType_t alwaysOnTickCount;
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;
}; };
} }
} }

View file

@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& /*alarmController*/, Pinetime::Controllers::AlarmController& /*alarmController*/,
Pinetime::Controllers::BrightnessController& /*brightnessController*/, Pinetime::Controllers::BrightnessController& /*brightnessController*/,
Pinetime::Controllers::TouchHandler& /*touchHandler*/, Pinetime::Controllers::TouchHandler& /*touchHandler*/,
Pinetime::Controllers::FS& /*filesystem*/) Pinetime::Controllers::FS& /*filesystem*/,
Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/)
: lcd {lcd}, bleController {bleController} { : lcd {lcd}, bleController {bleController} {
} }

View file

@ -18,6 +18,7 @@ namespace Pinetime {
class St7789; class St7789;
class Cst816S; class Cst816S;
class Watchdog; class Watchdog;
class SpiNorFlash;
} }
namespace Controllers { namespace Controllers {
@ -59,7 +60,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem); Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start(); void Start();
void Start(Pinetime::System::BootErrors) { void Start(Pinetime::System::BootErrors) {

View file

@ -7,7 +7,6 @@ namespace Pinetime {
enum class Messages : uint8_t { enum class Messages : uint8_t {
GoToSleep, GoToSleep,
GoToRunning, GoToRunning,
UpdateDateTime,
UpdateBleConnection, UpdateBleConnection,
TouchEvent, TouchEvent,
ButtonPushed, ButtonPushed,
@ -17,8 +16,9 @@ namespace Pinetime {
NewNotification, NewNotification,
TimerDone, TimerDone,
BleFirmwareUpdateStarted, BleFirmwareUpdateStarted,
DimScreen, // Resets the screen timeout timer when awake
RestoreBrightness, // Does nothing when asleep
NotifyDeviceActivity,
ShowPairingKey, ShowPairingKey,
AlarmTriggered, AlarmTriggered,
Chime, Chime,

View file

@ -48,7 +48,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
Controllers::Settings::ClockType clockType, Controllers::Settings::ClockType clockType,
System::SystemTask& systemTask, System::SystemTask& systemTask,
Controllers::MotorController& motorController) Controllers::MotorController& motorController)
: alarmController {alarmController}, systemTask {systemTask}, motorController {motorController} { : alarmController {alarmController}, wakeLock(systemTask), motorController {motorController} {
hourCounter.Create(); hourCounter.Create();
lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
UpdateAlarmTime(); UpdateAlarmTime();
if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
SetAlerting(); SetAlerting();
} else { } else {
SetSwitchState(LV_ANIM_OFF); SetSwitchState(LV_ANIM_OFF);
@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
} }
Alarm::~Alarm() { Alarm::~Alarm() {
if (alarmController.State() == AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
StopAlerting(); StopAlerting();
} }
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
alarmController.SaveAlarm();
} }
void Alarm::DisableAlarm() { void Alarm::DisableAlarm() {
if (alarmController.State() == AlarmController::AlarmState::Set) { if (alarmController.IsEnabled()) {
alarmController.DisableAlarm(); alarmController.DisableAlarm();
lv_switch_off(enableSwitch, LV_ANIM_ON); lv_switch_off(enableSwitch, LV_ANIM_ON);
} }
@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() {
HideInfo(); HideInfo();
return true; return true;
} }
if (alarmController.State() == AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
StopAlerting(); StopAlerting();
return true; return true;
} }
@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() {
bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) { bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
// Don't allow closing the screen by swiping while the alarm is alerting // 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() { void Alarm::OnValueChanged() {
@ -205,7 +206,7 @@ void Alarm::SetAlerting() {
lv_obj_set_hidden(btnStop, false); lv_obj_set_hidden(btnStop, false);
taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this); taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this);
motorController.StartRinging(); motorController.StartRinging();
systemTask.PushMessage(System::Messages::DisableSleeping); wakeLock.Lock();
} }
void Alarm::StopAlerting() { void Alarm::StopAlerting() {
@ -216,21 +217,16 @@ void Alarm::StopAlerting() {
lv_task_del(taskStopAlarm); lv_task_del(taskStopAlarm);
taskStopAlarm = nullptr; taskStopAlarm = nullptr;
} }
systemTask.PushMessage(System::Messages::EnableSleeping); wakeLock.Release();
lv_obj_set_hidden(enableSwitch, false); lv_obj_set_hidden(enableSwitch, false);
lv_obj_set_hidden(btnStop, true); lv_obj_set_hidden(btnStop, true);
} }
void Alarm::SetSwitchState(lv_anim_enable_t anim) { void Alarm::SetSwitchState(lv_anim_enable_t anim) {
switch (alarmController.State()) { if (alarmController.IsEnabled()) {
case AlarmController::AlarmState::Set:
lv_switch_on(enableSwitch, anim); lv_switch_on(enableSwitch, anim);
break; } else {
case AlarmController::AlarmState::Not_Set:
lv_switch_off(enableSwitch, anim); lv_switch_off(enableSwitch, anim);
break;
default:
break;
} }
} }
@ -247,7 +243,7 @@ void Alarm::ShowInfo() {
txtMessage = lv_label_create(btnMessage, nullptr); txtMessage = lv_label_create(btnMessage, nullptr);
lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY); 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 timeToAlarm = alarmController.SecondsToAlarm();
auto daysToAlarm = timeToAlarm / 86400; auto daysToAlarm = timeToAlarm / 86400;

View file

@ -22,6 +22,7 @@
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/widgets/Counter.h" #include "displayapp/widgets/Counter.h"
#include "displayapp/Controllers.h" #include "displayapp/Controllers.h"
#include "systemtask/WakeLock.h"
#include "Symbols.h" #include "Symbols.h"
namespace Pinetime { namespace Pinetime {
@ -43,7 +44,7 @@ namespace Pinetime {
private: private:
Controllers::AlarmController& alarmController; Controllers::AlarmController& alarmController;
System::SystemTask& systemTask; System::WakeLock wakeLock;
Controllers::MotorController& motorController; Controllers::MotorController& motorController;
lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch; lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch;

View file

@ -17,8 +17,8 @@ namespace {
FirmwareValidation::FirmwareValidation(Pinetime::Controllers::FirmwareValidator& validator) : validator {validator} { FirmwareValidation::FirmwareValidation(Pinetime::Controllers::FirmwareValidator& validator) : validator {validator} {
labelVersion = lv_label_create(lv_scr_act(), nullptr); labelVersion = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_fmt(labelVersion, lv_label_set_text_fmt(labelVersion,
"Version : %lu.%lu.%lu\n" "Version: %lu.%lu.%lu\n"
"ShortRef : %s", "ShortRef: %s",
Version::Major(), Version::Major(),
Version::Minor(), Version::Minor(),
Version::Patch(), Version::Patch(),

View file

@ -15,8 +15,9 @@ namespace {
} }
FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController) 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); brightnessController.Set(Controllers::BrightnessController::Levels::Low);
flashLight = lv_label_create(lv_scr_act(), nullptr); flashLight = lv_label_create(lv_scr_act(), nullptr);
@ -46,13 +47,13 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo
backgroundAction->user_data = this; backgroundAction->user_data = this;
lv_obj_set_event_cb(backgroundAction, EventHandler); lv_obj_set_event_cb(backgroundAction, EventHandler);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); wakeLock.Lock();
} }
FlashLight::~FlashLight() { FlashLight::~FlashLight() {
lv_obj_clean(lv_scr_act()); 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); 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() { void FlashLight::SetColors() {

View file

@ -3,6 +3,7 @@
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "components/brightness/BrightnessController.h" #include "components/brightness/BrightnessController.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include <cstdint> #include <cstdint>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
@ -23,10 +24,11 @@ namespace Pinetime {
void SetIndicators(); void SetIndicators();
void SetColors(); void SetColors();
Pinetime::System::SystemTask& systemTask; Pinetime::System::WakeLock wakeLock;
Controllers::BrightnessController& brightnessController; Controllers::BrightnessController& brightnessController;
Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High; Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High;
Controllers::BrightnessController::Levels previousBrightnessLevel;
lv_obj_t* flashLight; lv_obj_t* flashLight;
lv_obj_t* backgroundAction; lv_obj_t* backgroundAction;

View file

@ -29,7 +29,7 @@ namespace {
} }
HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, System::SystemTask& systemTask) HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, System::SystemTask& systemTask)
: heartRateController {heartRateController}, systemTask {systemTask} { : heartRateController {heartRateController}, wakeLock(systemTask) {
bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
label_hr = lv_label_create(lv_scr_act(), nullptr); 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); label_startStop = lv_label_create(btn_startStop, nullptr);
UpdateStartStopButton(isHrRunning); UpdateStartStopButton(isHrRunning);
if (isHrRunning) { if (isHrRunning) {
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); wakeLock.Lock();
} }
taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this); taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this);
@ -72,7 +72,6 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
HeartRate::~HeartRate() { HeartRate::~HeartRate() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
} }
void HeartRate::Refresh() { void HeartRate::Refresh() {
@ -101,12 +100,12 @@ void HeartRate::OnStartStopEvent(lv_event_t event) {
if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) { if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) {
heartRateController.Start(); heartRateController.Start();
UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); 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); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight);
} else { } else {
heartRateController.Stop(); heartRateController.Stop();
UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); 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); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
} }
} }

View file

@ -4,6 +4,7 @@
#include <chrono> #include <chrono>
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "Symbols.h" #include "Symbols.h"
#include <lvgl/src/lv_core/lv_style.h> #include <lvgl/src/lv_core/lv_style.h>
#include <lvgl/src/lv_core/lv_obj.h> #include <lvgl/src/lv_core/lv_obj.h>
@ -27,7 +28,7 @@ namespace Pinetime {
private: private:
Controllers::HeartRateController& heartRateController; Controllers::HeartRateController& heartRateController;
Pinetime::System::SystemTask& systemTask; Pinetime::System::WakeLock wakeLock;
void UpdateStartStopButton(bool isRunning); void UpdateStartStopButton(bool isRunning);
lv_obj_t* label_hr; lv_obj_t* label_hr;
lv_obj_t* label_bpm; lv_obj_t* label_bpm;

View file

@ -22,7 +22,7 @@ namespace {
} }
Metronome::Metronome(Controllers::MotorController& motorController, System::SystemTask& systemTask) 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 = lv_arc_create(lv_scr_act(), nullptr);
bpmArc->user_data = this; bpmArc->user_data = this;
@ -72,7 +72,6 @@ Metronome::Metronome(Controllers::MotorController& motorController, System::Syst
Metronome::~Metronome() { Metronome::~Metronome() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
systemTask.PushMessage(System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
@ -128,12 +127,12 @@ void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) {
metronomeStarted = !metronomeStarted; metronomeStarted = !metronomeStarted;
if (metronomeStarted) { if (metronomeStarted) {
lv_label_set_text_static(lblPlayPause, Symbols::pause); lv_label_set_text_static(lblPlayPause, Symbols::pause);
systemTask.PushMessage(System::Messages::DisableSleeping); wakeLock.Lock();
startTime = xTaskGetTickCount(); startTime = xTaskGetTickCount();
counter = 1; counter = 1;
} else { } else {
lv_label_set_text_static(lblPlayPause, Symbols::play); lv_label_set_text_static(lblPlayPause, Symbols::play);
systemTask.PushMessage(System::Messages::EnableSleeping); wakeLock.Release();
} }
} }
break; break;

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "Symbols.h" #include "Symbols.h"
@ -21,7 +22,7 @@ namespace Pinetime {
TickType_t startTime = 0; TickType_t startTime = 0;
TickType_t tappedTime = 0; TickType_t tappedTime = 0;
Controllers::MotorController& motorController; Controllers::MotorController& motorController;
System::SystemTask& systemTask; System::WakeLock wakeLock;
int16_t bpm = 120; int16_t bpm = 120;
uint8_t bpb = 4; uint8_t bpb = 4;
uint8_t counter = 1; uint8_t counter = 1;

View file

@ -20,7 +20,7 @@ Notifications::Notifications(DisplayApp* app,
notificationManager {notificationManager}, notificationManager {notificationManager},
alertNotificationService {alertNotificationService}, alertNotificationService {alertNotificationService},
motorController {motorController}, motorController {motorController},
systemTask {systemTask}, wakeLock(systemTask),
mode {mode} { mode {mode} {
notificationManager.ClearNewNotificationFlag(); notificationManager.ClearNewNotificationFlag();
@ -40,7 +40,7 @@ Notifications::Notifications(DisplayApp* app,
validDisplay = false; validDisplay = false;
} }
if (mode == Modes::Preview) { if (mode == Modes::Preview) {
systemTask.PushMessage(System::Messages::DisableSleeping); wakeLock.Lock();
if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) { if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) {
motorController.StartRinging(); motorController.StartRinging();
} else { } else {
@ -65,7 +65,6 @@ Notifications::~Notifications() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
// make sure we stop any vibrations before exiting // make sure we stop any vibrations before exiting
motorController.StopRinging(); motorController.StopRinging();
systemTask.PushMessage(System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
@ -82,7 +81,6 @@ void Notifications::Refresh() {
} else if (mode == Modes::Preview && dismissingNotification) { } else if (mode == Modes::Preview && dismissingNotification) {
running = false; running = false;
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController);
} else if (dismissingNotification) { } else if (dismissingNotification) {
dismissingNotification = false; dismissingNotification = false;
@ -113,15 +111,15 @@ void Notifications::Refresh() {
alertNotificationService, alertNotificationService,
motorController); motorController);
} else { } else {
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController); running = false;
} }
} }
running = currentItem->IsRunning() && running; running = running && currentItem->IsRunning();
} }
void Notifications::OnPreviewInteraction() { void Notifications::OnPreviewInteraction() {
systemTask.PushMessage(System::Messages::EnableSleeping); wakeLock.Release();
motorController.StopRinging(); motorController.StopRinging();
if (timeoutLine != nullptr) { if (timeoutLine != nullptr) {
lv_obj_del(timeoutLine); lv_obj_del(timeoutLine);
@ -173,7 +171,9 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
} else if (nextMessage.valid) { } else if (nextMessage.valid) {
currentId = nextMessage.id; currentId = nextMessage.id;
} else { } 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(); DismissToBlack();
return true; return true;

View file

@ -8,6 +8,7 @@
#include "components/ble/NotificationManager.h" #include "components/ble/NotificationManager.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@ -73,7 +74,7 @@ namespace Pinetime {
Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Controllers::AlertNotificationService& alertNotificationService; Pinetime::Controllers::AlertNotificationService& alertNotificationService;
Pinetime::Controllers::MotorController& motorController; Pinetime::Controllers::MotorController& motorController;
System::SystemTask& systemTask; System::WakeLock wakeLock;
Modes mode = Modes::Normal; Modes mode = Modes::Normal;
std::unique_ptr<NotificationItem> currentItem; std::unique_ptr<NotificationItem> currentItem;
Pinetime::Controllers::NotificationManager::Notification::Id currentId; Pinetime::Controllers::NotificationManager::Notification::Id currentId;

View file

@ -34,7 +34,7 @@ namespace {
constexpr TickType_t blinkInterval = pdMS_TO_TICKS(1000); 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 btnWidth = 115;
static constexpr uint8_t btnHeight = 80; static constexpr uint8_t btnHeight = 80;
btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); btnPlayPause = lv_btn_create(lv_scr_act(), nullptr);
@ -79,7 +79,6 @@ StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} {
StopWatch::~StopWatch() { StopWatch::~StopWatch() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
@ -135,7 +134,7 @@ void StopWatch::Start() {
SetInterfaceRunning(); SetInterfaceRunning();
startTime = xTaskGetTickCount(); startTime = xTaskGetTickCount();
currentState = States::Running; currentState = States::Running;
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); wakeLock.Lock();
} }
void StopWatch::Pause() { void StopWatch::Pause() {
@ -145,7 +144,7 @@ void StopWatch::Pause() {
oldTimeElapsed = laps[lapsDone]; oldTimeElapsed = laps[lapsDone];
blinkTime = xTaskGetTickCount() + blinkInterval; blinkTime = xTaskGetTickCount() + blinkInterval;
currentState = States::Halted; currentState = States::Halted;
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); wakeLock.Release();
} }
void StopWatch::Refresh() { void StopWatch::Refresh() {

View file

@ -7,6 +7,7 @@
#include "portmacro_cmsis.h" #include "portmacro_cmsis.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "displayapp/apps/Apps.h" #include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h" #include "displayapp/Controllers.h"
#include "Symbols.h" #include "Symbols.h"
@ -43,7 +44,7 @@ namespace Pinetime {
void Start(); void Start();
void Pause(); void Pause();
Pinetime::System::SystemTask& systemTask; Pinetime::System::WakeLock wakeLock;
States currentState = States::Init; States currentState = States::Init;
TickType_t startTime; TickType_t startTime;
TickType_t oldTimeElapsed = 0; TickType_t oldTimeElapsed = 0;

View file

@ -38,15 +38,16 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
const Pinetime::Controllers::Ble& bleController, const Pinetime::Controllers::Ble& bleController,
const Pinetime::Drivers::Watchdog& watchdog, const Pinetime::Drivers::Watchdog& watchdog,
Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel) const Pinetime::Drivers::Cst816S& touchPanel,
: app {app}, const Pinetime::Drivers::SpiNorFlash& spiNorFlash)
dateTimeController {dateTimeController}, : dateTimeController {dateTimeController},
batteryController {batteryController}, batteryController {batteryController},
brightnessController {brightnessController}, brightnessController {brightnessController},
bleController {bleController}, bleController {bleController},
watchdog {watchdog}, watchdog {watchdog},
motionController {motionController}, motionController {motionController},
touchPanel {touchPanel}, touchPanel {touchPanel},
spiNorFlash {spiNorFlash},
screens {app, screens {app,
0, 0,
{[this]() -> std::unique_ptr<Screen> { {[this]() -> std::unique_ptr<Screen> {
@ -186,10 +187,12 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(label, true); lv_label_set_recolor(label, true);
const auto& bleAddr = bleController.Address(); const auto& bleAddr = bleController.Address();
auto spiFlashId = spiNorFlash.GetIdentification();
lv_label_set_text_fmt(label, lv_label_set_text_fmt(label,
"#808080 BLE MAC#\n" "#808080 BLE MAC#\n"
" %02x:%02x:%02x:%02x:%02x:%02x" " %02x:%02x:%02x:%02x:%02x:%02x\n"
"\n" "\n"
"#808080 SPI Flash# %02x-%02x-%02x\n"
"\n" "\n"
"#808080 Memory heap#\n" "#808080 Memory heap#\n"
" #808080 Free# %d\n" " #808080 Free# %d\n"
@ -202,6 +205,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
bleAddr[2], bleAddr[2],
bleAddr[1], bleAddr[1],
bleAddr[0], bleAddr[0],
spiFlashId.manufacturer,
spiFlashId.type,
spiFlashId.density,
xPortGetFreeHeapSize(), xPortGetFreeHeapSize(),
xPortGetMinimumEverFreeHeapSize(), xPortGetMinimumEverFreeHeapSize(),
mallocFailedCount, mallocFailedCount,

View file

@ -29,12 +29,12 @@ namespace Pinetime {
const Pinetime::Controllers::Ble& bleController, const Pinetime::Controllers::Ble& bleController,
const Pinetime::Drivers::Watchdog& watchdog, const Pinetime::Drivers::Watchdog& watchdog,
Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel); const Pinetime::Drivers::Cst816S& touchPanel,
const Pinetime::Drivers::SpiNorFlash& spiNorFlash);
~SystemInfo() override; ~SystemInfo() override;
bool OnTouchEvent(TouchEvents event) override; bool OnTouchEvent(TouchEvents event) override;
private: private:
DisplayApp* app;
Pinetime::Controllers::DateTime& dateTimeController; Pinetime::Controllers::DateTime& dateTimeController;
const Pinetime::Controllers::Battery& batteryController; const Pinetime::Controllers::Battery& batteryController;
Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::BrightnessController& brightnessController;
@ -42,6 +42,7 @@ namespace Pinetime {
const Pinetime::Drivers::Watchdog& watchdog; const Pinetime::Drivers::Watchdog& watchdog;
Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::MotionController& motionController;
const Pinetime::Drivers::Cst816S& touchPanel; const Pinetime::Drivers::Cst816S& touchPanel;
const Pinetime::Drivers::SpiNorFlash& spiNorFlash;
ScreenList<5> screens; ScreenList<5> screens;

View file

@ -104,9 +104,7 @@ void Timer::UpdateMask() {
void Timer::Refresh() { void Timer::Refresh() {
if (timer.IsRunning()) { if (timer.IsRunning()) {
auto secondsRemaining = std::chrono::duration_cast<std::chrono::seconds>(timer.GetTimeRemaining()); DisplayTime();
minuteCounter.SetValue(secondsRemaining.count() / 60);
secondCounter.SetValue(secondsRemaining.count() % 60);
} else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { } else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) {
lv_label_set_text_static(txtPlayPause, "Reset"); lv_label_set_text_static(txtPlayPause, "Reset");
maskPosition += 15; maskPosition += 15;
@ -119,6 +117,14 @@ void Timer::Refresh() {
} }
} }
void Timer::DisplayTime() {
displaySeconds = std::chrono::duration_cast<std::chrono::seconds>(timer.GetTimeRemaining());
if (displaySeconds.IsUpdated()) {
minuteCounter.SetValue(displaySeconds.Get().count() / 60);
secondCounter.SetValue(displaySeconds.Get().count() % 60);
}
}
void Timer::SetTimerRunning() { void Timer::SetTimerRunning() {
minuteCounter.HideControls(); minuteCounter.HideControls();
secondCounter.HideControls(); secondCounter.HideControls();
@ -133,9 +139,7 @@ void Timer::SetTimerStopped() {
void Timer::ToggleRunning() { void Timer::ToggleRunning() {
if (timer.IsRunning()) { if (timer.IsRunning()) {
auto secondsRemaining = std::chrono::duration_cast<std::chrono::seconds>(timer.GetTimeRemaining()); DisplayTime();
minuteCounter.SetValue(secondsRemaining.count() / 60);
secondCounter.SetValue(secondsRemaining.count() % 60);
timer.StopTimer(); timer.StopTimer();
SetTimerStopped(); SetTimerStopped();
} else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { } else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) {
@ -147,7 +151,6 @@ void Timer::ToggleRunning() {
} }
void Timer::Reset() { void Timer::Reset() {
minuteCounter.SetValue(0); DisplayTime();
secondCounter.SetValue(0);
SetTimerStopped(); SetTimerStopped();
} }

View file

@ -5,6 +5,7 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "displayapp/LittleVgl.h" #include "displayapp/LittleVgl.h"
#include "displayapp/widgets/Counter.h" #include "displayapp/widgets/Counter.h"
#include "utility/DirtyValue.h"
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "components/timer/Timer.h" #include "components/timer/Timer.h"
@ -26,6 +27,7 @@ namespace Pinetime::Applications {
void SetTimerRunning(); void SetTimerRunning();
void SetTimerStopped(); void SetTimerStopped();
void UpdateMask(); void UpdateMask();
void DisplayTime();
Pinetime::Controllers::Timer& timer; Pinetime::Controllers::Timer& timer;
lv_obj_t* btnPlayPause; lv_obj_t* btnPlayPause;
@ -43,6 +45,7 @@ namespace Pinetime::Applications {
bool buttonPressing = false; bool buttonPressing = false;
lv_coord_t maskPosition = 0; lv_coord_t maskPosition = 0;
TickType_t pressTime = 0; TickType_t pressTime = 0;
Utility::DirtyValue<std::chrono::seconds> displaySeconds;
}; };
} }

View file

@ -9,16 +9,22 @@
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
namespace { 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<SettingDisplay*>(obj->user_data); auto* screen = static_cast<SettingDisplay*>(obj->user_data);
screen->UpdateSelected(obj, event); screen->UpdateSelected(obj, event);
} }
void AlwaysOnEventHandler(lv_obj_t* obj, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) {
auto* screen = static_cast<SettingDisplay*>(obj->user_data);
screen->ToggleAlwaysOn();
}
}
} }
constexpr std::array<uint16_t, 6> SettingDisplay::options; constexpr std::array<uint16_t, 6> SettingDisplay::options;
SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) SettingDisplay::SettingDisplay(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} {
: app {app}, settingsController {settingsController} {
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); 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); snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000);
lv_checkbox_set_text(cbOption[i], buffer); lv_checkbox_set_text(cbOption[i], buffer);
cbOption[i]->user_data = this; 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]); SetRadioButtonStyle(cbOption[i]);
if (settingsController.GetScreenTimeOut() == options[i]) { if (settingsController.GetScreenTimeOut() == options[i]) {
lv_checkbox_set_checked(cbOption[i], true); 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() { SettingDisplay::~SettingDisplay() {
@ -63,6 +76,11 @@ SettingDisplay::~SettingDisplay() {
settingsController.SaveSettings(); 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) { void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) { if (event == LV_EVENT_CLICKED) {
for (unsigned int i = 0; i < options.size(); i++) { for (unsigned int i = 0; i < options.size(); i++) {

View file

@ -14,17 +14,18 @@ namespace Pinetime {
class SettingDisplay : public Screen { class SettingDisplay : public Screen {
public: public:
SettingDisplay(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); SettingDisplay(Pinetime::Controllers::Settings& settingsController);
~SettingDisplay() override; ~SettingDisplay() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event); void UpdateSelected(lv_obj_t* object, lv_event_t event);
void ToggleAlwaysOn();
private: private:
DisplayApp* app;
static constexpr std::array<uint16_t, 6> options = {5000, 7000, 10000, 15000, 20000, 30000}; static constexpr std::array<uint16_t, 6> options = {5000, 7000, 10000, 15000, 20000, 30000};
Controllers::Settings& settingsController; Controllers::Settings& settingsController;
lv_obj_t* cbOption[options.size()]; lv_obj_t* cbOption[options.size()];
lv_obj_t* alwaysOnCheckbox;
}; };
} }
} }

View file

@ -32,7 +32,7 @@ namespace Pinetime {
static constexpr std::array<Option, 8> options = {{ static constexpr std::array<Option, 8> options = {{
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off, " Off"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off, " Off"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous, "Cont"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous, "Cont"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds, " 10s"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FifteenSeconds, " 15s"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds, " 30s"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds, " 30s"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute, " 1m"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute, " 1m"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes, " 5m"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes, " 5m"},

View file

@ -15,8 +15,7 @@ bool SettingSetDateTime::OnTouchEvent(Pinetime::Applications::TouchEvents event)
SettingSetDateTime::SettingSetDateTime(Pinetime::Applications::DisplayApp* app, SettingSetDateTime::SettingSetDateTime(Pinetime::Applications::DisplayApp* app,
Pinetime::Controllers::DateTime& dateTimeController, Pinetime::Controllers::DateTime& dateTimeController,
Pinetime::Controllers::Settings& settingsController) Pinetime::Controllers::Settings& settingsController)
: app {app}, : dateTimeController {dateTimeController},
dateTimeController {dateTimeController},
settingsController {settingsController}, settingsController {settingsController},
screens {app, screens {app,
0, 0,

View file

@ -20,7 +20,6 @@ namespace Pinetime {
void Quit(); void Quit();
private: private:
DisplayApp* app;
Controllers::DateTime& dateTimeController; Controllers::DateTime& dateTimeController;
Controllers::Settings& settingsController; Controllers::Settings& settingsController;

View file

@ -54,8 +54,7 @@ SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems, std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem) Pinetime::Controllers::FS& filesystem)
: app {app}, : watchfaceItems {std::move(watchfaceItems)},
watchfaceItems {std::move(watchfaceItems)},
settingsController {settingsController}, settingsController {settingsController},
filesystem {filesystem}, filesystem {filesystem},
screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} { screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} {

View file

@ -34,7 +34,6 @@ namespace Pinetime {
bool OnTouchEvent(TouchEvents event) override; bool OnTouchEvent(TouchEvents event) override;
private: private:
DisplayApp* app;
auto CreateScreenList() const; auto CreateScreenList() const;
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const; std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;

View file

@ -6,6 +6,7 @@
#include "drivers/Hrs3300.h" #include "drivers/Hrs3300.h"
#include <algorithm> #include <algorithm>
#include <iterator>
#include <nrf_gpio.h> #include <nrf_gpio.h>
#include <FreeRTOS.h> #include <FreeRTOS.h>
@ -67,40 +68,37 @@ void Hrs3300::Disable() {
WriteRegister(static_cast<uint8_t>(Registers::PDriver), 0); WriteRegister(static_cast<uint8_t>(Registers::PDriver), 0);
} }
uint32_t Hrs3300::ReadHrs() { Hrs3300::PackedHrsAls Hrs3300::ReadHrsAls() {
auto m = ReadRegister(static_cast<uint8_t>(Registers::C0DataM)); constexpr Registers dataRegisters[] =
auto h = ReadRegister(static_cast<uint8_t>(Registers::C0DataH)); {Registers::C1dataM, Registers::C0DataM, Registers::C0DataH, Registers::C1dataH, Registers::C1dataL, Registers::C0dataL};
auto l = ReadRegister(static_cast<uint8_t>(Registers::C0dataL)); // Calculate smallest register address
return ((l & 0x30) << 12) | (m << 8) | ((h & 0x0f) << 4) | (l & 0x0f); constexpr uint8_t baseOffset = static_cast<uint8_t>(*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<uint8_t>(*std::max_element(std::begin(dataRegisters), std::end(dataRegisters))) - baseOffset + 1;
uint32_t Hrs3300::ReadAls() { Hrs3300::PackedHrsAls res;
auto m = ReadRegister(static_cast<uint8_t>(Registers::C1dataM)); uint8_t buf[length];
auto h = ReadRegister(static_cast<uint8_t>(Registers::C1dataH)); auto ret = twiMaster.Read(twiAddress, baseOffset, buf, length);
auto l = ReadRegister(static_cast<uint8_t>(Registers::C1dataL)); if (ret != TwiMaster::ErrorCodes::NoError) {
return ((h & 0x3f) << 11) | (m << 3) | (l & 0x07); NRF_LOG_INFO("READ ERROR");
}
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;
} }
// hrs
uint8_t m = static_cast<uint8_t>(Registers::C0DataM) - baseOffset;
uint8_t h = static_cast<uint8_t>(Registers::C0DataH) - baseOffset;
uint8_t l = static_cast<uint8_t>(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<uint8_t>(Registers::Hgain), hgain << 2); // als
} m = static_cast<uint8_t>(Registers::C1dataM) - baseOffset;
h = static_cast<uint8_t>(Registers::C1dataH) - baseOffset;
l = static_cast<uint8_t>(Registers::C1dataL) - baseOffset;
res.als = ((buf[h] & 0x3f) << 11) | (buf[m] << 3) | (buf[l] & 0x07);
void Hrs3300::SetDrive(uint8_t drive) { return res;
auto en = ReadRegister(static_cast<uint8_t>(Registers::Enable));
auto pd = ReadRegister(static_cast<uint8_t>(Registers::PDriver));
en = (en & 0xf7) | ((drive & 2) << 2);
pd = (pd & 0xbf) | ((drive & 1) << 6);
WriteRegister(static_cast<uint8_t>(Registers::Enable), en);
WriteRegister(static_cast<uint8_t>(Registers::PDriver), pd);
} }
void Hrs3300::WriteRegister(uint8_t reg, uint8_t data) { void Hrs3300::WriteRegister(uint8_t reg, uint8_t data) {

View file

@ -21,6 +21,11 @@ namespace Pinetime {
Hgain = 0x17 Hgain = 0x17
}; };
struct PackedHrsAls {
uint16_t hrs;
uint16_t als;
};
Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress); Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress);
Hrs3300(const Hrs3300&) = delete; Hrs3300(const Hrs3300&) = delete;
Hrs3300& operator=(const Hrs3300&) = delete; Hrs3300& operator=(const Hrs3300&) = delete;
@ -30,10 +35,7 @@ namespace Pinetime {
void Init(); void Init();
void Enable(); void Enable();
void Disable(); void Disable();
uint32_t ReadHrs(); PackedHrsAls ReadHrsAls();
uint32_t ReadAls();
void SetGain(uint8_t gain);
void SetDrive(uint8_t drive);
private: private:
TwiMaster& twiMaster; TwiMaster& twiMaster;

View file

@ -10,7 +10,7 @@ SpiNorFlash::SpiNorFlash(Spi& spi) : spi {spi} {
} }
void SpiNorFlash::Init() { void SpiNorFlash::Init() {
device_id = ReadIdentificaion(); device_id = ReadIdentification();
NRF_LOG_INFO("[SpiNorFlash] Manufacturer : %d, Memory type : %d, memory density : %d", NRF_LOG_INFO("[SpiNorFlash] Manufacturer : %d, Memory type : %d, memory density : %d",
device_id.manufacturer, device_id.manufacturer,
device_id.type, device_id.type,
@ -32,7 +32,7 @@ void SpiNorFlash::Wakeup() {
uint8_t cmd[cmdSize] = {static_cast<uint8_t>(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03}; uint8_t cmd[cmdSize] = {static_cast<uint8_t>(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03};
uint8_t id = 0; uint8_t id = 0;
spi.Read(reinterpret_cast<uint8_t*>(&cmd), cmdSize, &id, 1); spi.Read(reinterpret_cast<uint8_t*>(&cmd), cmdSize, &id, 1);
auto devId = device_id = ReadIdentificaion(); auto devId = device_id = ReadIdentification();
if (devId.type != device_id.type) { if (devId.type != device_id.type) {
NRF_LOG_INFO("[SpiNorFlash] ID on Wakeup: Failed"); NRF_LOG_INFO("[SpiNorFlash] ID on Wakeup: Failed");
} else { } else {
@ -41,7 +41,7 @@ void SpiNorFlash::Wakeup() {
NRF_LOG_INFO("[SpiNorFlash] Wakeup") NRF_LOG_INFO("[SpiNorFlash] Wakeup")
} }
SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() { SpiNorFlash::Identification SpiNorFlash::ReadIdentification() {
auto cmd = static_cast<uint8_t>(Commands::ReadIdentification); auto cmd = static_cast<uint8_t>(Commands::ReadIdentification);
Identification identification; Identification identification;
spi.Read(&cmd, 1, reinterpret_cast<uint8_t*>(&identification), sizeof(Identification)); spi.Read(&cmd, 1, reinterpret_cast<uint8_t*>(&identification), sizeof(Identification));
@ -145,3 +145,7 @@ void SpiNorFlash::Write(uint32_t address, const uint8_t* buffer, size_t size) {
len -= toWrite; len -= toWrite;
} }
} }
SpiNorFlash::Identification SpiNorFlash::GetIdentification() const {
return device_id;
}

View file

@ -20,7 +20,6 @@ namespace Pinetime {
uint8_t density = 0; uint8_t density = 0;
}; };
Identification ReadIdentificaion();
uint8_t ReadStatusRegister(); uint8_t ReadStatusRegister();
bool WriteInProgress(); bool WriteInProgress();
bool WriteEnabled(); bool WriteEnabled();
@ -33,6 +32,8 @@ namespace Pinetime {
bool ProgramFailed(); bool ProgramFailed();
bool EraseFailed(); bool EraseFailed();
Identification GetIdentification() const;
void Init(); void Init();
void Uninit(); void Uninit();
@ -40,6 +41,8 @@ namespace Pinetime {
void Wakeup(); void Wakeup();
private: private:
Identification ReadIdentification();
enum class Commands : uint8_t { enum class Commands : uint8_t {
PageProgram = 0x02, PageProgram = 0x02,
Read = 0x03, Read = 0x03,

View file

@ -16,6 +16,7 @@ void St7789::Init() {
nrf_gpio_pin_set(pinReset); nrf_gpio_pin_set(pinReset);
HardwareReset(); HardwareReset();
SoftwareReset(); SoftwareReset();
Command2Enable();
SleepOut(); SleepOut();
PixelFormat(); PixelFormat();
MemoryDataAccessControl(); MemoryDataAccessControl();
@ -24,8 +25,13 @@ void St7789::Init() {
#ifndef DRIVER_DISPLAY_MIRROR #ifndef DRIVER_DISPLAY_MIRROR
DisplayInversionOn(); DisplayInversionOn();
#endif #endif
PorchSet();
FrameRateNormalSet();
IdleFrameRateOff();
NormalModeOn(); NormalModeOn();
SetVdv(); SetVdv();
PowerControl();
GateControl();
DisplayOn(); DisplayOn();
} }
@ -63,6 +69,17 @@ void St7789::SoftwareReset() {
vTaskDelay(pdMS_TO_TICKS(125)); vTaskDelay(pdMS_TO_TICKS(125));
} }
void St7789::Command2Enable() {
WriteCommand(static_cast<uint8_t>(Commands::Command2Enable));
constexpr uint8_t args[] = {
0x5a, // Constant
0x69, // Constant
0x02, // Constant
0x01, // Enable
};
WriteData(args, sizeof(args));
}
void St7789::SleepOut() { void St7789::SleepOut() {
if (!sleepIn) { if (!sleepIn) {
return; return;
@ -127,10 +144,79 @@ void St7789::NormalModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn)); WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
} }
void St7789::IdleModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOn));
}
void St7789::IdleModeOff() {
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOff));
}
void St7789::PorchSet() {
WriteCommand(static_cast<uint8_t>(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<uint8_t>(Commands::FrameRateNormal));
// Note that the datasheet table is imprecise - see formula below table
WriteData(0x0a);
}
void St7789::IdleFrameRateOn() {
WriteCommand(static_cast<uint8_t>(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
// In testing this divider appears to actually be 16x?
constexpr uint8_t args[] = {
0x13, // Enable frame rate control for partial/idle mode, 8x frame divider
0x1e, // Idle mode frame rate
0x1e, // Partial mode frame rate (unused)
};
WriteData(args, sizeof(args));
}
void St7789::IdleFrameRateOff() {
WriteCommand(static_cast<uint8_t>(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() { void St7789::DisplayOn() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayOn)); WriteCommand(static_cast<uint8_t>(Commands::DisplayOn));
} }
void St7789::PowerControl() {
WriteCommand(static_cast<uint8_t>(Commands::PowerControl1));
constexpr uint8_t args[] = {
0xa4, // Constant
0x00, // Lowest possible voltages
};
WriteData(args, sizeof(args));
WriteCommand(static_cast<uint8_t>(Commands::PowerControl2));
// Lowest possible boost circuit clocks
WriteData(0xb3);
}
void St7789::GateControl() {
WriteCommand(static_cast<uint8_t>(Commands::GateControl));
// Lowest possible VGL/VGH
WriteData(0x00);
}
void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet)); WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet));
uint8_t colArgs[] = { uint8_t colArgs[] = {
@ -198,6 +284,18 @@ void St7789::HardwareReset() {
vTaskDelay(pdMS_TO_TICKS(125)); 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() { void St7789::Sleep() {
SleepIn(); SleepIn();
nrf_gpio_cfg_default(pinDataCommand); nrf_gpio_cfg_default(pinDataCommand);

View file

@ -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 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 Sleep();
void Wakeup(); void Wakeup();
@ -37,6 +39,7 @@ namespace Pinetime {
void HardwareReset(); void HardwareReset();
void SoftwareReset(); void SoftwareReset();
void Command2Enable();
void SleepOut(); void SleepOut();
void EnsureSleepOutPostDelay(); void EnsureSleepOutPostDelay();
void SleepIn(); void SleepIn();
@ -45,8 +48,16 @@ namespace Pinetime {
void DisplayInversionOn(); void DisplayInversionOn();
void NormalModeOn(); void NormalModeOn();
void WriteToRam(const uint8_t* data, size_t size); void WriteToRam(const uint8_t* data, size_t size);
void IdleModeOn();
void IdleModeOff();
void FrameRateNormalSet();
void IdleFrameRateOff();
void IdleFrameRateOn();
void DisplayOn(); void DisplayOn();
void DisplayOff(); void DisplayOff();
void PowerControl();
void GateControl();
void PorchSet();
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void SetVdv(); void SetVdv();
@ -68,8 +79,17 @@ namespace Pinetime {
MemoryDataAccessControl = 0x36, MemoryDataAccessControl = 0x36,
VerticalScrollDefinition = 0x33, VerticalScrollDefinition = 0x33,
VerticalScrollStartAddress = 0x37, VerticalScrollStartAddress = 0x37,
IdleModeOff = 0x38,
IdleModeOn = 0x39,
PixelFormat = 0x3a, PixelFormat = 0x3a,
FrameRateIdle = 0xb3,
FrameRateNormal = 0xc6,
VdvSet = 0xc4, VdvSet = 0xc4,
Command2Enable = 0xdf,
PowerControl1 = 0xd0,
PowerControl2 = 0xe8,
GateControl = 0xb7,
Porch = 0xb2,
}; };
void WriteData(uint8_t data); void WriteData(uint8_t data);
void WriteData(const uint8_t* data, size_t size); void WriteData(const uint8_t* data, size_t size);

View file

@ -97,11 +97,6 @@ void HeartRateTask::StopMeasurement() {
vTaskDelay(100); vTaskDelay(100);
} }
void HeartRateTask::StartWaiting() {
StopMeasurement();
backgroundWaitingStart = xTaskGetTickCount();
}
void HeartRateTask::HandleGoToSleep() { void HeartRateTask::HandleGoToSleep() {
switch (state) { switch (state) {
case States::ScreenOnAndStopped: case States::ScreenOnAndStopped:
@ -176,8 +171,7 @@ void HeartRateTask::HandleBackgroundWaiting() {
return; return;
} }
TickType_t ticksSinceWaitingStart = xTaskGetTickCount() - backgroundWaitingStart; if (ShouldStartBackgroundMeasuring()) {
if (ticksSinceWaitingStart >= GetHeartRateBackgroundMeasurementIntervalInTicks()) {
state = States::ScreenOffAndMeasuring; state = States::ScreenOffAndMeasuring;
StartMeasurement(); StartMeasurement();
} }
@ -198,34 +192,45 @@ void HeartRateTask::HandleSensorData(int* lastBpm) {
bpm = 0; bpm = 0;
} }
if (*lastBpm == 0 && bpm == 0) { bool notEnoughData = *lastBpm == 0 && bpm == 0;
if (notEnoughData) {
controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm); controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm);
} }
if (bpm != 0) { if (bpm != 0) {
*lastBpm = bpm; *lastBpm = bpm;
controller.Update(Controllers::HeartRateController::States::Running, bpm); controller.Update(Controllers::HeartRateController::States::Running, bpm);
}
if (state == States::ScreenOnAndMeasuring || IsContinuousModeActivated()) { if (state == States::ScreenOnAndMeasuring || IsContinuousModeActivated()) {
return; return;
} }
if (state == States::ScreenOffAndMeasuring) {
// state == States::ScreenOffAndMeasuring
// (because state != ScreenOnAndMeasuring and the only state that enables measuring is ScreenOffAndMeasuring)
// !IsContinuousModeActivated()
if (ShouldStartBackgroundMeasuring()) {
// This doesn't change the state but resets the measurment timer, which basically starts the next measurment without resetting the sensor.
// This is basically a fall back to continuous mode, when measurments take too long.
measurementStart = xTaskGetTickCount();
return;
}
bool noDataWithinTimeLimit = bpm == 0 && ShoudStopTryingToGetData();
bool dataWithinTimeLimit = bpm != 0;
if (dataWithinTimeLimit || noDataWithinTimeLimit) {
state = States::ScreenOffAndWaiting; state = States::ScreenOffAndWaiting;
StartWaiting(); StopMeasurement();
}
}
TickType_t ticksSinceMeasurementStart = xTaskGetTickCount() - measurementStart;
if (bpm == 0 && state == States::ScreenOffAndMeasuring && !IsContinuousModeActivated() &&
ticksSinceMeasurementStart >= DURATION_UNTIL_BACKGROUND_MEASUREMENT_IS_STOPPED) {
state = States::ScreenOffAndWaiting;
StartWaiting();
} }
} }
TickType_t HeartRateTask::GetHeartRateBackgroundMeasurementIntervalInTicks() { TickType_t HeartRateTask::GetHeartRateBackgroundMeasurementIntervalInTicks() {
int ms; int ms;
switch (settings.GetHeartRateBackgroundMeasurementInterval()) { switch (settings.GetHeartRateBackgroundMeasurementInterval()) {
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds: case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FifteenSeconds:
ms = 10 * 1000; ms = 15 * 1000;
break; break;
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds: case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds:
ms = 30 * 1000; ms = 30 * 1000;
@ -258,3 +263,15 @@ bool HeartRateTask::IsBackgroundMeasurementActivated() {
return settings.GetHeartRateBackgroundMeasurementInterval() != return settings.GetHeartRateBackgroundMeasurementInterval() !=
Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off; Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off;
} }
TickType_t HeartRateTask::GetTicksSinceLastMeasurementStarted() {
return xTaskGetTickCount() - measurementStart;
}
bool HeartRateTask::ShoudStopTryingToGetData() {
return GetTicksSinceLastMeasurementStarted() >= DURATION_UNTIL_BACKGROUND_MEASUREMENT_IS_STOPPED;
}
bool HeartRateTask::ShouldStartBackgroundMeasuring() {
return GetTicksSinceLastMeasurementStarted() >= GetHeartRateBackgroundMeasurementIntervalInTicks();
}

View file

@ -45,7 +45,6 @@ namespace Pinetime {
static void Process(void* instance); static void Process(void* instance);
void StartMeasurement(); void StartMeasurement();
void StopMeasurement(); void StopMeasurement();
void StartWaiting();
void HandleGoToSleep(); void HandleGoToSleep();
void HandleWakeUp(); void HandleWakeUp();
@ -59,6 +58,10 @@ namespace Pinetime {
bool IsContinuousModeActivated(); bool IsContinuousModeActivated();
bool IsBackgroundMeasurementActivated(); bool IsBackgroundMeasurementActivated();
TickType_t GetTicksSinceLastMeasurementStarted();
bool ShoudStopTryingToGetData();
bool ShouldStartBackgroundMeasuring();
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
QueueHandle_t messageQueue; QueueHandle_t messageQueue;
States state = States::ScreenOnAndStopped; States state = States::ScreenOnAndStopped;
@ -66,7 +69,6 @@ namespace Pinetime {
Controllers::HeartRateController& controller; Controllers::HeartRateController& controller;
Controllers::Settings& settings; Controllers::Settings& settings;
Controllers::Ppg ppg; Controllers::Ppg ppg;
TickType_t backgroundWaitingStart = 0;
TickType_t measurementStart = 0; TickType_t measurementStart = 0;
}; };

@ -1 +0,0 @@
Subproject commit 56b17bf9f74096774944bcac0829adcd887d391e

View file

@ -104,7 +104,7 @@ Pinetime::Controllers::DateTime dateTimeController {settingsController};
Pinetime::Drivers::Watchdog watchdog; Pinetime::Drivers::Watchdog watchdog;
Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::NotificationManager notificationManager;
Pinetime::Controllers::MotionController motionController; Pinetime::Controllers::MotionController motionController;
Pinetime::Controllers::AlarmController alarmController {dateTimeController}; Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs};
Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::TouchHandler touchHandler;
Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::ButtonHandler buttonHandler;
Pinetime::Controllers::BrightnessController brightnessController {}; Pinetime::Controllers::BrightnessController brightnessController {};
@ -123,7 +123,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd,
alarmController, alarmController,
brightnessController, brightnessController,
touchHandler, touchHandler,
fs); fs,
spiNorFlash);
Pinetime::System::SystemTask systemTask(spi, Pinetime::System::SystemTask systemTask(spi,
spiNorFlash, spiNorFlash,

View file

@ -1,5 +1,5 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#if configUSE_TRACE_FACILITY == 1 #if NRF_LOG_ENABLED
// FreeRtosMonitor // FreeRtosMonitor
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include <task.h> #include <task.h>

View file

@ -102,7 +102,9 @@ void SystemTask::Work() {
watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause); watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause);
watchdog.Start(); watchdog.Start();
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason())); 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(); spi.Init();
spiNorFlash.Init(); spiNorFlash.Init();
@ -187,34 +189,14 @@ void SystemTask::Work() {
if (xQueueReceive(systemTasksMsgQueue, &msg, 100) == pdTRUE) { if (xQueueReceive(systemTasksMsgQueue, &msg, 100) == pdTRUE) {
switch (msg) { switch (msg) {
case Messages::EnableSleeping: case Messages::EnableSleeping:
// Make sure that exiting an app doesn't enable sleeping, wakeLocksHeld--;
// if the exiting was caused by a firmware update
if (!bleController.IsFirmwareUpdating()) {
doNotGoToSleep = false;
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
break; break;
case Messages::DisableSleeping: case Messages::DisableSleeping:
doNotGoToSleep = true; GoToRunning();
wakeLocksHeld++;
break; break;
case Messages::GoToRunning: case Messages::GoToRunning:
spi.Wakeup(); GoToRunning();
// 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;
break; break;
case Messages::TouchWakeUp: { case Messages::TouchWakeUp: {
if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) { if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) {
@ -231,66 +213,50 @@ void SystemTask::Work() {
break; break;
} }
case Messages::GoToSleep: case Messages::GoToSleep:
if (doNotGoToSleep) { GoToSleep();
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; break;
case Messages::OnNewTime: case Messages::OnNewTime:
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); if (alarmController.IsEnabled()) {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime);
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) {
alarmController.ScheduleAlarm(); alarmController.ScheduleAlarm();
} }
break; break;
case Messages::OnNewNotification: case Messages::OnNewNotification:
if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) { if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) {
if (state == SystemTaskState::Sleeping) { if (IsSleeping()) {
GoToRunning(); GoToRunning();
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
} }
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification); displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
} }
break; break;
case Messages::SetOffAlarm: case Messages::SetOffAlarm:
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered);
break; break;
case Messages::BleConnected: case Messages::BleConnected:
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
isBleDiscoveryTimerRunning = true; isBleDiscoveryTimerRunning = true;
bleDiscoveryTimer = 5; bleDiscoveryTimer = 5;
break; break;
case Messages::BleFirmwareUpdateStarted: case Messages::BleFirmwareUpdateStarted:
doNotGoToSleep = true;
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
} wakeLocksHeld++;
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted); displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
break; break;
case Messages::BleFirmwareUpdateFinished: case Messages::BleFirmwareUpdateFinished:
if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) { if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) {
NVIC_SystemReset(); NVIC_SystemReset();
} }
doNotGoToSleep = false; wakeLocksHeld--;
break; break;
case Messages::StartFileTransfer: case Messages::StartFileTransfer:
NRF_LOG_INFO("[systemtask] FS Started"); NRF_LOG_INFO("[systemtask] FS Started");
doNotGoToSleep = true;
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
} wakeLocksHeld++;
// TODO add intent of fs access icon or something // TODO add intent of fs access icon or something
break; break;
case Messages::StopFileTransfer: case Messages::StopFileTransfer:
NRF_LOG_INFO("[systemtask] FS Stopped"); NRF_LOG_INFO("[systemtask] FS Stopped");
doNotGoToSleep = false; wakeLocksHeld--;
// TODO add intent of fs access icon or something // TODO add intent of fs access icon or something
break; break;
case Messages::OnTouchEvent: case Messages::OnTouchEvent:
@ -318,12 +284,23 @@ void SystemTask::Work() {
HandleButtonAction(action); HandleButtonAction(action);
} break; } break;
case Messages::OnDisplayTaskSleeping: case Messages::OnDisplayTaskSleeping:
// 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
// as DisplayApp is about to receive GoToRunning
if (state != SystemTaskState::GoingToSleep) {
break;
}
if (BootloaderVersion::IsValid()) { if (BootloaderVersion::IsValid()) {
// First versions of the bootloader do not expose their version and cannot initialize the SPI NOR FLASH // 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. // if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions.
spiNorFlash.Sleep(); spiNorFlash.Sleep();
} }
// Must keep SPI awake when still updating the display for always on
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Sleep(); spi.Sleep();
}
// Double Tap needs the touch screen to be in normal mode // Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
@ -340,39 +317,23 @@ void SystemTask::Work() {
case Messages::OnNewHour: case Messages::OnNewHour:
using Pinetime::Controllers::AlarmController; using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && !alarmController.IsAlerting()) {
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(); GoToRunning();
PushMessage(msg);
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
} }
}
break; break;
case Messages::OnNewHalfHour: case Messages::OnNewHalfHour:
using Pinetime::Controllers::AlarmController; using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && !alarmController.IsAlerting()) {
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(); GoToRunning();
PushMessage(msg);
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
} }
}
break; break;
case Messages::OnChargingEvent: case Messages::OnChargingEvent:
batteryController.ReadPowerState(); batteryController.ReadPowerState();
displayApp.PushMessage(Applications::Display::Messages::OnChargingEvent);
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
} displayApp.PushMessage(Applications::Display::Messages::OnChargingEvent);
break; break;
case Messages::MeasureBatteryTimerExpired: case Messages::MeasureBatteryTimerExpired:
batteryController.MeasureVoltage(); batteryController.MeasureVoltage();
@ -381,9 +342,7 @@ void SystemTask::Work() {
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining()); nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
break; break;
case Messages::OnPairing: case Messages::OnPairing:
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey); displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey);
break; break;
case Messages::BleRadioEnableToggle: case Messages::BleRadioEnableToggle:
@ -418,12 +377,48 @@ void SystemTask::Work() {
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }
void SystemTask::UpdateMotion() { void SystemTask::GoToRunning() {
if (state == SystemTaskState::GoingToSleep || state == SystemTaskState::WakingUp) { if (state == SystemTaskState::Running) {
return; return;
} }
// SPI doesn't go to sleep for always on mode
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Wakeup();
}
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) || // 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");
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
state = SystemTaskState::GoingToSleep;
};
void SystemTask::UpdateMotion() {
if (IsSleeping() && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) || settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
motionController.GetService()->IsMotionNotificationSubscribed())) { motionController.GetService()->IsMotionNotificationSubscribed())) {
return; return;
@ -448,7 +443,7 @@ void SystemTask::UpdateMotion() {
} }
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running && if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running &&
motionController.ShouldLowerSleep()) { motionController.ShouldLowerSleep()) {
PushMessage(Messages::GoToSleep); GoToSleep();
} }
} }
@ -457,14 +452,14 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
return; return;
} }
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
using Actions = Controllers::ButtonActions; using Actions = Controllers::ButtonActions;
switch (action) { switch (action) {
case Actions::Click: case Actions::Click:
// If the first action after fast wakeup is a click, it should be ignored. // 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); displayApp.PushMessage(Applications::Display::Messages::ButtonPushed);
} }
break; break;
@ -484,17 +479,10 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
fastWakeUpDone = false; fastWakeUpDone = false;
} }
void SystemTask::GoToRunning() {
if (state == SystemTaskState::Sleeping) {
state = SystemTaskState::WakingUp;
PushMessage(Messages::GoToRunning);
}
}
void SystemTask::OnTouchEvent() { void SystemTask::OnTouchEvent() {
if (state == SystemTaskState::Running) { if (state == SystemTaskState::Running) {
PushMessage(Messages::OnTouchEvent); PushMessage(Messages::OnTouchEvent);
} else if (state == SystemTaskState::Sleeping) { } else {
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
PushMessage(Messages::TouchWakeUp); PushMessage(Messages::TouchWakeUp);
@ -503,10 +491,6 @@ void SystemTask::OnTouchEvent() {
} }
void SystemTask::PushMessage(System::Messages msg) { void SystemTask::PushMessage(System::Messages msg) {
if (msg == Messages::GoToSleep && !doNotGoToSleep) {
state = SystemTaskState::GoingToSleep;
}
if (in_isr()) { if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken); xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);

View file

@ -52,7 +52,7 @@ namespace Pinetime {
namespace System { namespace System {
class SystemTask { class SystemTask {
public: public:
enum class SystemTaskState { Sleeping, Running, GoingToSleep, WakingUp }; enum class SystemTaskState { Sleeping, Running, GoingToSleep };
SystemTask(Drivers::SpiMaster& spi, SystemTask(Drivers::SpiMaster& spi,
Pinetime::Drivers::SpiNorFlash& spiNorFlash, Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Drivers::TwiMaster& twiMaster, Drivers::TwiMaster& twiMaster,
@ -79,11 +79,8 @@ namespace Pinetime {
void OnTouchEvent(); void OnTouchEvent();
void OnIdle();
void OnDim();
bool IsSleepDisabled() { bool IsSleepDisabled() {
return doNotGoToSleep; return wakeLocksHeld > 0;
} }
Pinetime::Controllers::NimbleController& nimble() { Pinetime::Controllers::NimbleController& nimble() {
@ -91,7 +88,7 @@ namespace Pinetime {
}; };
bool IsSleeping() const { bool IsSleeping() const {
return state == SystemTaskState::Sleeping || state == SystemTaskState::WakingUp; return state != SystemTaskState::Running;
} }
private: private:
@ -127,13 +124,14 @@ namespace Pinetime {
bool isBleDiscoveryTimerRunning = false; bool isBleDiscoveryTimerRunning = false;
uint8_t bleDiscoveryTimer = 0; uint8_t bleDiscoveryTimer = 0;
TimerHandle_t measureBatteryTimer; TimerHandle_t measureBatteryTimer;
bool doNotGoToSleep = false; uint8_t wakeLocksHeld = 0;
SystemTaskState state = SystemTaskState::Running; SystemTaskState state = SystemTaskState::Running;
void HandleButtonAction(Controllers::ButtonActions action); void HandleButtonAction(Controllers::ButtonActions action);
bool fastWakeUpDone = false; bool fastWakeUpDone = false;
void GoToRunning(); void GoToRunning();
void GoToSleep();
void UpdateMotion(); void UpdateMotion();
bool stepCounterMustBeReset = false; bool stepCounterMustBeReset = false;
static constexpr TickType_t batteryMeasurementPeriod = pdMS_TO_TICKS(10 * 60 * 1000); static constexpr TickType_t batteryMeasurementPeriod = pdMS_TO_TICKS(10 * 60 * 1000);

View file

@ -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;
}

19
src/systemtask/WakeLock.h Normal file
View file

@ -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;
};
}
}