Compare commits

...

78 commits

Author SHA1 Message Date
Dāvis Mošenkovs e03414ce6d
Setting to disable DFU and FS access (#1891)
Some checks failed
Build and push Docker image / build (push) Has been cancelled
CI / build-firmware (push) Has been cancelled
CI / build-simulator (push) Has been cancelled
CI / get-base-ref-size (push) Has been cancelled
CI / Compare build size (push) Has been cancelled
* Expose SystemTask dependency controllers

Expose NotificationManager and Settings for use by the feature in next commit.

This is a memory efficient way for accessing SystemTask dependencies from
controllers that have SystemTask injected as a dependency.
Looks like each direct dependency injection uses 4 bytes RAM.
As InfiniTime is close to running out of RAM (using 16 more bytes causes build
to fail with "ld: region RAM overflowed with stack") it might be helpful to use
this approach more.

* Add setting to disable DFU and FS access
2025-09-03 20:50:13 +02:00
Heiko Stuebner 9afc23cba9 navigation: fix greying out the app icon if not enabled
Commit 0aead42fdf ("navigation: Add is available (#1847)") added the
ability to draw the app icon in grey and in a disabled state when some
prerequisits were not met. Only the Navigation app was using this mechanism
due to its icons being stored in the external memory and possibly missing.

Commit 63e0c4f4ef ("Application selection at build time") broke this by
always setting the state as true:
      for (const auto& userApp : userApps) {
        apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
      }

Fix this by creating an isAvailable() strcuture in the app classes, similar
to how the Watchfaces handle the same problem of checking availability.
2025-07-03 13:19:56 +01:00
Titus 250e7a7032
Dark App Launcher Tiles (#2294) 2025-06-29 17:39:48 +02:00
Titus c3afbc59ce
(small) Music App Redesign (#2292) 2025-06-29 16:43:41 +02:00
mark9064 c1b9967d92 Analog face constexpr fix 2025-06-28 18:30:54 +01:00
mark9064 b3f4831e54 Initialise DisplayApp components in DisplayApp context 2025-06-28 16:33:49 +01:00
mark9064 343962da5d Disable legacy FreeRTOS functions 2025-06-27 20:53:26 +01:00
Eshe 4517fb8c4b
Pride flag watchface (#2201) 2025-06-19 17:53:45 +01:00
Owen from Canada 3fc00f80db
Ignore npm files (#2313) 2025-06-18 13:17:57 +02:00
zyphlar 1340f56344
Set LF-only (\n) line endings for .sh files to fix #1905 (#2181) 2025-06-16 22:31:28 +01:00
Titus b2d0e04e42
BatteryInfo App Redesign: Fixup (#2310)
* remove leading zeros from battery percentage
* add correct colors matching the battery icon color thresholds
2025-06-12 09:13:53 +02:00
lmamane 4f426f00a8
set static text as static text (#2312)
and save a few bytes of heap memory

Co-authored-by: Lionel Elie Mamane <lionel@mamane.lu>
2025-06-12 09:12:11 +02:00
Reinhold Gschweicher 85a0542d93 DisplayApp: use std::ranges function where possible
Instead of raw for loops use `std::ranges::transform` where possible.
And also use `std::ranges::find_if` instead of `std::find_if`.
2025-05-30 22:57:12 +01:00
kon-foo 8423ed675b fixed libpango1.0-dev install 2025-05-30 19:52:23 +01:00
NeroBurner 22cb7e3388
CI: use bash for get-base-ref-size job (#2305)
Same change as done in c3295d6d2a
But for get-base-ref-size job

The variable substitution I introduced are bash features. So they don't work with sh.

Update the size job to use `bash` instead of `sh` as shell
2025-05-29 09:36:50 +02:00
NeroBurner c3295d6d2a
CI: use bash for output-sizs-job
Firmware build works. But the build size job afterwards fails.
The firmware build uses `shell: bash --noprofile --norc -e -o pipefail {0}` as shell.

The size job uses `shell: sh -e {0}`

The variable substitution I introduced are bash features. So they don't work with sh.

Update the size job to use `bash` instead of `sh` as shell
2025-05-27 23:26:43 +02:00
NeroBurner 6f2a661a36
docker: fix NRF_SDK download and subsequent build.sh (#2299)
The upstream NRF-SDK download url and zip archive filename changed,
which was fixed with https://github.com/InfiniTimeOrg/InfiniTime/pull/2270

But the archive contents stayed the same, with the "old" folder name.

After #2270 we have basically the same docker-container as before the PR,
but the `GetNrfSdk` function of the `build.sh` script is called again during
firmware build time as the expected foldername for the SDK isn't the same as
the zip filename:

```sh
[ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ] && GetNrfSdk
```

Then during the build the `buils.sh` script tries to execute `GetNrfSdk` again,
which fails as the files already exist resulting in the following error:

```
replace /opt/nRF5_SDK_15.3.0_59ac345/components/802_15_4/api/HAL/hal_atomic.h? [y]es, [n]o, [A]ll, [N]one, [r]ename:  NULL
```

Fix this by reverting the `NRF_SDK_VER` to the folder name in the zip
archive and by some character replacement generate the download URL from
the above (the download is in lower-case without the `_` and `.`
characters).

Furthermore add safeguards to check after the `GetNrfSdk` call if the
expected folder is really created. Then we have an error early during
container image creation if the contents of the zip-archive are
unexpected.
2025-05-27 22:56:10 +02:00
Steve Amor 9fb35cc073 Refactors watch faces. Replace lv_tick_get() with xTaskGetTickCount() 2025-05-23 17:46:14 +01:00
Darius Arnold 248a6aea87
Fix nRF SDK download links (#2270)
nRF has moved the download to a new location- Update the build script to fetch from the same URL the download website currently provides.
2025-05-21 21:17:42 +01:00
Jean-François Milants 0880b08546 Fix conflict in NotificationManager 2025-05-21 20:53:47 +02:00
Julian Vos e27e51d36a fix: fix short ref not getting set in github builds 2025-05-21 20:18:23 +02:00
Matt Zrinsky fb70b538e4 Fix about screen vertical centering.
There was a newline at the end of the last line on the third screen,
which was causing the label to not be centered vertically.
Removing it fixes the centering.
2025-05-19 21:49:46 +02:00
Titus bb8923b56e
Center text on timer button (#2229) 2025-05-16 23:01:19 +02:00
Max Friedrich 4e1ee90286
Remove the vibration while charging (#1768)
* DisplayApp.cpp: Remove the vibration from OnChargingEvent

This fixes a bug where the vibration interupts the physical connection with the charger and therefore triggers a new charging event and vibration, ending in a cycle of vibrations while charging.

* remove OnChargingEvent message from DisplayApp

---------

Co-authored-by: minacode <minamoto9@web.de>
2025-05-16 21:29:06 +02:00
Titus 85be83beab
Add background color to progress bar when flashing firmware (#2237) 2025-05-15 20:46:25 +01:00
Max Friedrich 5b20e8e2ba
Simple calculator (#1483)
Co-authored-by: minacode <minamoto9@web.de>
Co-authored-by: Finlay Davidson <finlay.davidson@coderclass.nl>
Co-authored-by: SuIông N. <Boteium@users.noreply.github.com>
Co-authored-by: mark9064 <30447455+mark9064@users.noreply.github.com>
2025-05-12 23:32:03 +01:00
Max Jakobitsch 6a6981c912 Revert "docs: add git config items to docs"
This reverts commit 4066cc03004c89a7ac4d5a7a327729184ecac7b6.
2025-03-21 17:36:47 +01:00
Max Jakobitsch 483435ae3d docs: add git config items to docs 2025-03-21 17:36:47 +01:00
Max-Julian Jakobitsch 3659e9a4ba chore: remove defunct .gitconfig
not actually used by git, see https://git-scm.com/docs/git-config and https://stackoverflow.com/q/18329621
2025-03-21 17:36:47 +01:00
Lukas H. 445aa1da83 Update Apps.md 2025-03-21 17:33:50 +01:00
Lionel Elie Mamane e06dd405bc make cmake command coherent with previous documentation 2025-03-21 17:32:07 +01:00
lmamane c0638c6007
work around g++ version 12, 13 and 14 spurious warning (#2158)
which is an error since we compile with -Werror

Co-authored-by: Lionel Elie Mamane <lionel@mamane.lu>
2025-03-14 13:10:41 +01:00
Titus 30e56834d5
BatteryInfo App Redesign using arc (#2236)
Redesign BatteryInfo App using an Arch widget instead of a bar.

The colors I used are as follows:
- Discharging (default): green
- Charging: lime
- Battery full: blue
- Battery low (<10%): red
2025-03-14 13:07:46 +01:00
Steveis 728da0f4a0
Fix Infineat crash when charging with AOD (#2256)
Optimise the battery animation to not use 100% CPU (which causes DisplayApp to spin forever with AOD)
(DisplayApp also needs to be fixed in the future so it cannot spin infinitely)
2025-02-26 23:16:20 +00:00
Max Jakobitsch 993118a3bc
Fix EOL related build issues on Windows (#2077)
Prevent line endings from getting normalised to CRLF

Co-authored-by: NeroBurner <pyro4hell@gmail.com>
2025-02-11 23:43:10 +00:00
Titus d371ebc9e2
Improve no notification text (#2238)
Change the "No notification" text to "No notifications"
2025-02-10 19:28:20 +00:00
Felipe Martínez 7b39d81c8c
Add "Run on InfiniEmu" link to pull requests (#2150) 2025-01-28 19:57:07 +00:00
Jozef Mlich de87a0dd3d Fixes #2219: use artifact v4 instead of v3 2025-01-23 19:23:36 +00:00
ljahn dbe8820834
Alarm: Simplify alarm alerting screen (#2211)
Simplify alarm alerting screen and fix bug with
alerting on time value change

SetAlerting creates an lv_task to automatically call StopAlerting after one minute. This task will call an invalid function reference and lead to a crash under the following condition:

All exit paths but the time value change (so not considering this fix) call StopAlerting themselves, which also terminates the lv_task.
However, the value change callback only calls DisableAlarm, because its normal use case is for setting up an alarm, where you have to re-confirm enabling the alarm after every change you make.
DisableAlarm still sets isAlerting in the alarmController to false, probably because someone thought a currently alerting but also disabled alarm makes no sense, this was introduced in a0cd439.
That causes the destructor of Alarm to think there is nothing to do regarding the alerting when the alarm screen is dismissed.
Therefore it does not call StopAlerting and the lv_task is left with an invalid function pointer, because Alarm does not exist anymore once the lv_task finally goes to call the callback function
2025-01-21 09:10:41 +01:00
Jozef Mlich 3e23ee7c78 Show alarm controller state in status icon 2025-01-05 14:37:35 +01:00
Victor Kareh d69cfcfb13 weather: Fix inverted imperial forecast temperatures
When converting to imperial units, the min and max temperatures were
incorrectly inverted, causing confusion in the display.

Fixes https://github.com/InfiniTimeOrg/InfiniTime/issues/2183
2024-12-10 00:02:17 +01:00
Felipe Martínez b8c51abe69
Use all free RAM for FreeRTOS heap
* Use all free RAM for FreeRTOS heap
* Wrap newlib malloc and related functions
* Implement calloc
2024-12-09 00:10:09 +00:00
Jean-François Milants 2105a7b63d Set version to 1.15.0 2024-12-03 20:11:08 +01:00
Lionel Elie Mamane 79ee886904 spelling 2024-11-25 08:55:37 +01:00
Lionel Elie Mamane b1d70ae2ed remove unused include 2024-11-25 08:55:37 +01:00
xz-dev a77a3dcb8b cmake: fix python path with use multi-version python 2024-11-17 23:36:22 +00:00
mark9064 8aefa3b9a6 Ignore old GoToRunning messages 2024-11-17 15:35:15 +01:00
febrezo 6c7eb6630e Fix git hash calculation with Docker build
Fix error when compiling the Pinetime using the Docker image.
If done with Docker, the container does not trust the /sources
folder, leading to a blank response of the command that grabs
the git commit `git rev-parse --short HEAD`.

```
fatal: detected dubious ownership in repository at '/sources'
To add an exception for this directory, call:

        git config --global --add safe.directory /sources
PROJECT_GIT_COMMIT_HASH_SUCCESS? 128

BUILD CONFIGURATION
-------------------
    * Mode : Release
    * Version : 1.3.0
    * Toolchain : /opt/gcc-arm-none-eabi-10.3-2021.10
    * GitRef(S) :
    * NRF52 SDK : /opt/nRF5_SDK_15.3.0_59ac345
    * Target device : PINETIME
    * Build DFU (using adafruit-nrfutil) : Enabled
    * Build resources : Enabled
```

If the `git config --global --add safe.directory /sources` is
added to the Dockerfile, the problem is solved and the hash is
added correctly.
2024-11-17 15:22:22 +01:00
Dom Rodriguez 4dd0d60eeb ci: Normalise slash-containing github.head_ref values
This fixes CI on #2121.

Signed-off-by: Dom Rodriguez <shymega@shymega.org.uk>
2024-11-17 15:13:38 +01:00
Jozef Mlich a2ced5659d Update Amazfish details in README.md
The openrepos contain obsolete version (description on the openrepos page says that). New SailfishOS version is in chum repo. Additionally, there is Ubuntu Touch and Flatpak version.
2024-11-17 00:10:20 +00:00
Lionel Elie Mamane 5ea9c5537e Weather: fix min and max temperature being switched 2024-11-16 23:53:33 +00:00
FintasticMan f7c87a700d weather: Switch to std::optional for Forecast days
Also only iterate over the number of days actually in use, rather than
MaxNbForecastDays.
2024-11-04 21:22:38 +01:00
FintasticMan e247bd7019 Switch to simpler temperature interface 2024-11-04 21:22:38 +01:00
FintasticMan 29ad09f4ef weather: Refactor temperature type for type safety
There is now a Temperature struct in the weather service, which holds
the internal representation. There is also a temperature struct in the
Applications namespace, which holds the temperature in either Celsius or
Fahrenheit.
2024-11-04 21:22:38 +01:00
Samuel Dorsaz afeded0126 Update Date&Time settings label for "Date & Time" 2024-11-04 21:20:44 +01:00
mark9064 57b6db8b2a Remove OnTouchEvent 2024-10-27 19:51:01 +01:00
mark9064 0076962588 Unify touch panel handling 2024-10-27 19:51:01 +01:00
mark9064 e6ee548536 Process touch events only when awake 2024-10-27 19:51:01 +01:00
mark9064 1808634f0e Clear ongoing taps when going to sleep 2024-10-27 19:51:01 +01:00
Lionel Elie Mamane cfaad261dc SimpleWeatherService #include <array>
not <vector> as that is what is actually used.
Fixes build failure
InfiniTime/src/components/ble/SimpleWeatherService.h:86:18: error: field ‘location’ has incomplete type ‘Pinetime::Controllers::SimpleWeatherService::Location’ {aka ‘std::array<char, 33>’
2024-10-27 18:27:03 +01:00
FintasticMan f1651c8000 datetime: Set the default year to the year during compile 2024-10-27 17:01:07 +01:00
mark9064 8a2ee437f5 Restrict hardware reactivation when not sleeping 2024-10-27 16:56:47 +01:00
mark9064 06b721a71f Improve sleep time calculation docs 2024-10-27 16:56:47 +01:00
mark9064 771008495e Replace rounded div macro 2024-10-27 16:56:47 +01:00
mark9064 f032847ae1 Refactor into defined states 2024-10-27 16:56:47 +01:00
mark9064 97ba39988b 8hz idle 2024-10-27 16:56:47 +01:00
liamcharger 879bdccd92
README: Header redesign (#2032)
* Reformat README.md

* Update README.md

* Add files via upload

* Add files via upload

* Add new README.md header image

* Remove unnecessary <br> tag

* Scale watchface logo down; add downloads badge

* Remove unnecessary <br> tag

* Add <br> tag to maintain spacing consistency

* Remove incorrect link

* Add watchface logo with dark logo

* Replace logo image with dark version

* Add files via upload

* Add header image

* Fix row spacing in header image

* Remove unnecessary <br> tag

* Add corner radius to header image

* Add files via upload

* Update image background

* Add reference to InfiniTimeExplorer

* Remove unnecessary image

* Remove unnecessary image

* Remove unnecessary images

* Rename watchface_collage_no_shadow.png to watchface_collage.png

* Update header image URL

* Remove unnecessary <br> tag

* SPI transaction hooks

* Remove task to notify

* Refactor display WriteToRam

* Use functional abstraction for hooks

* Refactor lambdas

* Avoid storing lambda

* Rename to pre-transaction hook

* Use FreeRTOS delay instead of spinning the CPU

* Apply display driver datasheet delays

* Move includes back

* Include task header (Fixes sim)

* Make chime vibrate twice

* Remove commit from main

* README: improve wording

* Add bootloader to DeviceInformationService.cpp

* Add bootloader to DeviceInformationService.h

* Revert "Add bootloader to DeviceInformationService.h"

This reverts commit f3f0fd568d96dea1ebd30529b4b8b6d5f4d5444b.

* Revert "Add bootloader to DeviceInformationService.cpp"

This reverts commit 35570edafab1b061442ae89b01a2ced8b9a812bd.

* Delete doc/logo/watchface_collage.png

* Add files via upload

* Delete doc/logo/watchface_collage.png

* Add files via upload

* Replace collage with correct image

---------

Co-authored-by: mark9064 <30447455+mark9064@users.noreply.github.com>
2024-10-23 19:00:48 +02: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
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
Finlay Davidson a7746d3a31 notification: Initialise message
Prevents reading uninitialised memory if notification gets cut off due
to being more than 100 chars. The last character is assumed to be \0, but
it is actually uninitialised.
2023-03-16 22:18:04 +01:00
118 changed files with 2139 additions and 574 deletions

8
.gitattributes vendored
View file

@ -1,8 +1,9 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Prevent build errors on non lf systems (like Windows), we need files with lf as newlines.
* text=auto eol=lf
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
# to lf line endings on checkout.
*.c text
*.cpp text
*.h text
@ -13,3 +14,6 @@
*.bin binary
*.jpg binary
*.jpeg binary
# Declare (Unix-style) files that will always have LF line endings on checkout.
*.sh text eol=lf

View file

@ -1,5 +0,0 @@
[core]
whitespace = blank-at-eol,blank-at-eof,space-before-tab
autocrlf = input
[apply]
whitespace = fix

View file

@ -29,7 +29,7 @@ jobs:
run: tests/test-format.sh
- name: Upload patches
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: failure()
with:
name: Patches

View file

@ -22,6 +22,8 @@ jobs:
text_size: ${{ steps.output-sizes.outputs.text_size }}
data_size: ${{ steps.output-sizes.outputs.data_size }}
bss_size: ${{ steps.output-sizes.outputs.bss_size }}
firmware_artifact: ${{ steps.upload-firmware.outputs.artifact-id }}
resources_artifact: ${{ steps.upload-resources.outputs.artifact-id }}
env:
# InfiniTime sources are downloaded to the current directory.
# Override SOURCES_DIR in build.sh
@ -37,34 +39,44 @@ jobs:
apt-get -y install --no-install-recommends python3-pil
- name: Build
shell: bash
run: /opt/build.sh all
run: |
git config --global --add safe.directory /__w/InfiniTime/InfiniTime
/opt/build.sh all
- name: Output build size
id: output-sizes
shell: bash
run: |
. /opt/build.sh
.github/workflows/getSize.sh "$BUILD_DIR"/src/pinetime-app-*.out >> $GITHUB_OUTPUT
# Unzip the package because Upload Artifact will zip up the files
- name: Unzip DFU package
run: unzip ./build/output/pinetime-mcuboot-app-dfu-*.zip -d ./build/output/pinetime-mcuboot-app-dfu
- name: Set ref_name, but replace slashes with dashes.
shell: bash
env:
ref_name: ${{ github.head_ref || github.ref_name }}
run: echo "REF_NAME=${ref_name//\//-}" >> $GITHUB_ENV
- name: Upload DFU artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: InfiniTime DFU ${{ github.head_ref }}
name: InfiniTime DFU ${{ env.REF_NAME }}
path: ./build/output/pinetime-mcuboot-app-dfu/*
- name: Upload MCUBoot image artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: InfiniTime MCUBoot image ${{ github.head_ref }}
name: InfiniTime MCUBoot image ${{ env.REF_NAME }}
path: ./build/output/pinetime-mcuboot-app-image-*.bin
- name: Upload standalone ELF artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
id: upload-firmware
with:
name: InfiniTime image ${{ github.head_ref }}
name: InfiniTime image ${{ env.REF_NAME }}
path: ./build/output/src/pinetime-app-*.out
- name: Upload resources artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
id: upload-resources
with:
name: InfiniTime resources ${{ github.head_ref }}
name: InfiniTime resources ${{ env.REF_NAME }}
path: ./build/output/infinitime-resources-*.zip
build-simulator:
@ -103,9 +115,9 @@ jobs:
cmake --build build_lv_sim
- name: Upload simulator executable
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: infinisim-${{ github.head_ref }}
name: infinisim-${{ env.REF_NAME }}
path: build_lv_sim/infinisim
get-base-ref-size:
@ -150,6 +162,7 @@ jobs:
- name: Output build size
id: output-sizes
shell: bash
run: |
. /opt/build.sh
.github/workflows/getSize.sh "$BUILD_DIR"/src/pinetime-app-*.out >> $GITHUB_OUTPUT
@ -200,10 +213,12 @@ jobs:
| text | ${{ needs.build-firmware.outputs.text_size }}B | ${{ steps.output-sizes-diff.outputs.text_diff }}B |
| data | ${{ needs.build-firmware.outputs.data_size }}B | ${{ steps.output-sizes-diff.outputs.data_diff }}B |
| bss | ${{ needs.build-firmware.outputs.bss_size }}B | ${{ steps.output-sizes-diff.outputs.bss_diff }}B |
[Run in InfiniEmu](https://infiniemu.pipe01.net/?firmware=artifact://${{ github.repository }}/${{ needs.build-firmware.outputs.firmware_artifact }}&resources=artifact://${{ github.repository }}/${{ needs.build-firmware.outputs.resources_artifact }})
EOF
- name: Upload comment
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: comment
path: comment

5
.gitignore vendored
View file

@ -50,3 +50,8 @@ src/arm-none-eabi
# clangd
.cache/
# npm files
node_modules
package.json
package-lock.json

3
.gitmodules vendored
View file

@ -4,9 +4,6 @@
[submodule "src/libs/littlefs"]
path = src/libs/littlefs
url = https://github.com/littlefs-project/littlefs.git
[submodule "src/libs/QCBOR"]
path = src/libs/QCBOR
url = https://github.com/laurencelundblade/QCBOR.git
[submodule "src/libs/arduinoFFT"]
path = src/libs/arduinoFFT
url = https://github.com/kosme/arduinoFFT.git

View file

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

View file

@ -1,8 +1,24 @@
# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)
<div align="center">
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
![Header Image](doc/logo/watchface_collage.png)
Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.
<br>
[![GitHub tag](https://img.shields.io/github/tag/InfiniTimeOrg/InfiniTime?include_prereleases=&sort=semver&color=blue)](https://github.com/InfiniTimeOrg/InfiniTime/releases)
[![GitHub License](https://img.shields.io/github/license/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniLink/blob/main/LICENSE)
[![Issues - InfiniTime](https://img.shields.io/github/issues/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/issues)
[![Pull Requests - InfiniTime](https://img.shields.io/github/issues-pr/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/pulls)
[![Downloads - InfiniTime](https://img.shields.io/github/downloads/InfiniTimeOrg/InfiniTime/total)](https://github.com/InfiniTimeOrg/InfiniTime)
[![Stars - InfiniTime](https://img.shields.io/github/stars/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/stargazers)
[![Forks - InfiniTime](https://img.shields.io/github/forks/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/network/members)
# InfiniTime
*Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.*
<br>
</div>
## New to InfiniTime?
@ -17,13 +33,18 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic
### Companion apps
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
- [Amazfish](https://github.com/piggz/harbour-amazfish/) ([SailfishOS](https://sailfishos-chum.github.io/apps/harbour-amazfish/), [Ubuntu Touch](https://open-store.io/app/uk.co.piggz.amazfish), [Flatpak](https://flathub.org/apps/uk.co.piggz.amazfish))
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS)
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
- [InfiniTimeExplorer](https://infinitimeexplorer.netlify.app) (Web)
***Note**: We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
<br>
> *InfiniTimeExplorer is only compatible with web browsers that support Web BLE. Current fully supported browsers include Chrome and Microsoft Edge.*
>
> *We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
## Development

View file

@ -5,7 +5,7 @@
To build this project, you'll need:
- A cross-compiler : [ARM-GCC (10.3-2021.10)](https://developer.arm.com/downloads/-/gnu-rm)
- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip)
- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip)
- The Python 3 modules `cbor`, `intelhex`, `click` and `cryptography` modules for the `mcuboot` tool (see [requirements.txt](../tools/mcuboot/requirements.txt))
- To keep the system clean, you can install python modules into a python virtual environment (`venv`)
```sh
@ -66,7 +66,7 @@ DFU files are the files you'll need to install your build of InfiniTime using OT
#### CMake command
```
cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=...
cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -S ..
```
### Build the project

View file

@ -124,7 +124,7 @@ MyApp.h:
```cpp
#pragma once
#include "displayapp/Apps.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

View file

@ -33,7 +33,7 @@ RUN apt-get update -qq \
libpixman-1-dev \
libcairo2-dev \
libpango-1.0-0 \
ibpango1.0-dev \
libpango1.0-dev \
libpangocairo-1.0-0 \
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
@ -65,5 +65,8 @@ RUN bash -c "source /opt/build.sh; GetMcuBoot;"
# Add the infinitime user for connecting devcontainer
RUN adduser infinitime
# Configure Git to accept the /sources directory as safe
RUN git config --global --add safe.directory /sources
ENV SOURCES_DIR /sources
CMD ["/opt/build.sh"]

View file

@ -18,6 +18,10 @@ export npm_config_cache="${NPM_DIR}"
export BUILD_TYPE=${BUILD_TYPE:=Release}
export GCC_ARM_VER=${GCC_ARM_VER:="10.3-2021.10"}
export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"}
# convert to lower case and remove _ and . character
# the download URL uses the SLUG, but the extracted folder is named like the original value
NRF_SDK_VER_SLUG=${NRF_SDK_VER,,}
export NRF_SDK_VER_SLUG=${NRF_SDK_VER_SLUG//[_.]/}
MACHINE="$(uname -m)"
[ "$MACHINE" = "arm64" ] && MACHINE="aarch64"
@ -47,17 +51,29 @@ main() {
GetGcc() {
wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/$GCC_ARM_VER/$GCC_ARM_PATH-$MACHINE-linux.tar.bz2 -O - | tar -xj -C $TOOLS_DIR/
if [ ! -d "$TOOLS_DIR/$GCC_ARM_PATH" ]; then
echo "missing GCC path: $TOOLS_DIR/$GCC_ARM_PATH"
return 1
fi
}
GetMcuBoot() {
git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot"
pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt"
if [ ! -d "$TOOLS_DIR/mcuboot" ]; then
echo "missing mcuboot path: $TOOLS_DIR/mcuboot"
return 1
fi
}
GetNrfSdk() {
wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER
wget -q "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/$NRF_SDK_VER_SLUG.zip" -O /tmp/$NRF_SDK_VER
unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/"
rm /tmp/$NRF_SDK_VER
if [ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]; then
echo "missing NRF_SDK path: $TOOLS_DIR/$NRF_SDK_VER"
return 1
fi
}
CmakeGenerate() {

View file

@ -380,6 +380,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/Metronome.cpp
displayapp/screens/Motion.cpp
displayapp/screens/Weather.cpp
displayapp/screens/Calculator.cpp
displayapp/screens/FirmwareValidation.cpp
displayapp/screens/ApplicationList.cpp
displayapp/screens/Notifications.cpp
@ -418,6 +419,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/settings/SettingChimes.cpp
displayapp/screens/settings/SettingShakeThreshold.cpp
displayapp/screens/settings/SettingBluetooth.cpp
displayapp/screens/settings/SettingOTA.cpp
## Watch faces
displayapp/screens/WatchFaceAnalog.cpp
@ -426,6 +428,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/WatchFaceTerminal.cpp
displayapp/screens/WatchFacePineTimeStyle.cpp
displayapp/screens/WatchFaceCasioStyleG7710.cpp
displayapp/screens/WatchFacePrideFlag.cpp
##
@ -478,6 +481,7 @@ list(APPEND SOURCE_FILES
systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp
heartratetask/HeartRateTask.cpp
@ -542,6 +546,7 @@ list(APPEND RECOVERY_SOURCE_FILES
systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp
components/rle/RleDecoder.cpp
components/heartrate/HeartRateController.cpp
@ -660,6 +665,7 @@ set(INCLUDE_FILES
displayapp/InfiniTimeTheme.h
systemtask/SystemTask.h
systemtask/SystemMonitor.h
systemtask/WakeLock.h
displayapp/screens/Symbols.h
drivers/TwiMaster.h
heartratetask/HeartRateTask.h
@ -785,6 +791,10 @@ add_definitions(-DFREERTOS)
add_definitions(-D__STACK_SIZE=1024)
add_definitions(-D__HEAP_SIZE=0)
add_definitions(-DMYNEWT_VAL_BLE_LL_RFMGMT_ENABLE_TIME=1500)
add_definitions(-DLFS_CONFIG=libs/lfs_config.h)
# _sbrk is purposefully not implemented so that builds fail when it is used
add_link_options(-Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=calloc -Wl,-wrap=realloc -Wl,-wrap=_malloc_r -Wl,-wrap=_sbrk)
# Note: Only use this for debugging
# Derive the low frequency clock from the main clock (SYNT)

View file

@ -60,15 +60,6 @@ task.h is included from an application file. */
/* Assumes 8bit bytes! */
#define heapBITS_PER_BYTE ( ( size_t ) 8 )
/* Allocate the memory for the heap. */
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
/* The application writer has already defined the array used for the RTOS
heap - probably so it can be placed in a special segment or address. */
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
/* Define the linked list structure. This is used to link free blocks in order
of their memory address. */
typedef struct A_BLOCK_LINK
@ -113,6 +104,8 @@ application. When the bit is free the block is still part of the free heap
space. */
static size_t xBlockAllocatedBit = 0;
static size_t xHeapSize = 0;
/*-----------------------------------------------------------*/
void *pvPortMalloc( size_t xWantedSize )
@ -332,27 +325,38 @@ size_t xPortGetMinimumEverFreeHeapSize( void )
}
/*-----------------------------------------------------------*/
size_t xPortGetHeapSize( void )
{
return xHeapSize;
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks( void )
{
/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/
extern uint8_t *__HeapLimit; // Defined by nrf_common.ld
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
size_t xTotalHeapSize = ( size_t ) &__StackLimit - ( size_t ) &__HeapLimit;
uint8_t *pucHeap = ( uint8_t * ) &__HeapLimit;
xHeapSize = xTotalHeapSize;
/* Ensure the heap starts on a correctly aligned boundary. */
uxAddress = ( size_t ) ucHeap;
uxAddress = ( size_t ) pucHeap;
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
{
uxAddress += ( portBYTE_ALIGNMENT - 1 );
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
xTotalHeapSize -= uxAddress - ( size_t ) pucHeap;
}
pucAlignedHeap = ( uint8_t * ) uxAddress;

View file

@ -180,6 +180,7 @@ __STATIC_INLINE uint32_t ulPortRaiseBASEPRI( void )
/*-----------------------------------------------------------*/
size_t xPortGetHeapSize(void);
#ifdef __cplusplus
}

View file

@ -62,7 +62,6 @@
#define configTICK_RATE_HZ 1024
#define configMAX_PRIORITIES (3)
#define configMINIMAL_STACK_SIZE (120)
#define configTOTAL_HEAP_SIZE (1024 * 40)
#define configMAX_TASK_NAME_LEN (4)
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
@ -74,7 +73,7 @@
#define configUSE_QUEUE_SETS 0
#define configUSE_TIME_SLICING 0
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 1
#define configENABLE_BACKWARD_COMPATIBILITY 0
#define configUSE_TASK_NOTIFICATIONS 0
/* Hook function related definitions. */

View file

@ -19,11 +19,13 @@
#include "systemtask/SystemTask.h"
#include "task.h"
#include <chrono>
#include <libraries/log/nrf_log.h>
using namespace Pinetime::Controllers;
using namespace std::chrono_literals;
AlarmController::AlarmController(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} {
AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs)
: dateTimeController {dateTimeController}, fs {fs} {
}
namespace {
@ -36,11 +38,28 @@ namespace {
void AlarmController::Init(System::SystemTask* systemTask) {
this->systemTask = systemTask;
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm);
LoadSettingsFromFile();
if (alarm.isEnabled) {
NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling");
ScheduleAlarm();
}
}
void AlarmController::SaveAlarm() {
// verify if it is necessary to save
if (alarmChanged) {
SaveSettingsToFile();
}
alarmChanged = false;
}
void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
hours = alarmHr;
minutes = alarmMin;
if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
return;
}
alarm.hours = alarmHr;
alarm.minutes = alarmMin;
alarmChanged = true;
}
void AlarmController::ScheduleAlarm() {
@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() {
tm* tmAlarmTime = std::localtime(&ttAlarmTime);
// If the time being set has already passed today,the alarm should be set for tomorrow
if (hours < dateTimeController.Hours() || (hours == dateTimeController.Hours() && minutes <= dateTimeController.Minutes())) {
if (alarm.hours < dateTimeController.Hours() ||
(alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) {
tmAlarmTime->tm_mday += 1;
// tm_wday doesn't update automatically
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
}
tmAlarmTime->tm_hour = hours;
tmAlarmTime->tm_min = minutes;
tmAlarmTime->tm_hour = alarm.hours;
tmAlarmTime->tm_min = alarm.minutes;
tmAlarmTime->tm_sec = 0;
// if alarm is in weekday-only mode, make sure it shifts to the next weekday
if (recurrence == RecurType::Weekdays) {
if (alarm.recurrence == RecurType::Weekdays) {
if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
tmAlarmTime->tm_mday += 1;
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() {
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
xTimerStart(alarmTimer, 0);
state = AlarmState::Set;
if (!alarm.isEnabled) {
alarm.isEnabled = true;
alarmChanged = true;
}
}
uint32_t AlarmController::SecondsToAlarm() const {
@ -88,20 +111,71 @@ uint32_t AlarmController::SecondsToAlarm() const {
void AlarmController::DisableAlarm() {
xTimerStop(alarmTimer, 0);
state = AlarmState::Not_Set;
if (alarm.isEnabled) {
alarm.isEnabled = false;
alarmChanged = true;
}
}
void AlarmController::SetOffAlarmNow() {
state = AlarmState::Alerting;
isAlerting = true;
systemTask->PushMessage(System::Messages::SetOffAlarm);
}
void AlarmController::StopAlerting() {
// Alarm state is off unless this is a recurring alarm
if (recurrence == RecurType::None) {
state = AlarmState::Not_Set;
isAlerting = false;
// Disable alarm unless it is recurring
if (alarm.recurrence == RecurType::None) {
alarm.isEnabled = false;
alarmChanged = true;
} else {
// set next instance
ScheduleAlarm();
}
}
void AlarmController::SetRecurrence(RecurType recurrence) {
if (alarm.recurrence != recurrence) {
alarm.recurrence = recurrence;
alarmChanged = true;
}
}
void AlarmController::LoadSettingsFromFile() {
lfs_file_t alarmFile;
AlarmSettings alarmBuffer;
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) {
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file");
return;
}
fs.FileRead(&alarmFile, reinterpret_cast<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 {
class AlarmController {
public:
AlarmController(Controllers::DateTime& dateTimeController);
AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);
void Init(System::SystemTask* systemTask);
void SaveAlarm();
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
void ScheduleAlarm();
void DisableAlarm();
void SetOffAlarmNow();
uint32_t SecondsToAlarm() const;
void StopAlerting();
enum class AlarmState { Not_Set, Set, Alerting };
enum class RecurType { None, Daily, Weekdays };
uint8_t Hours() const {
return hours;
return alarm.hours;
}
uint8_t Minutes() const {
return minutes;
return alarm.minutes;
}
AlarmState State() const {
return state;
bool IsAlerting() const {
return isAlerting;
}
bool IsEnabled() const {
return alarm.isEnabled;
}
RecurType Recurrence() const {
return recurrence;
return alarm.recurrence;
}
void SetRecurrence(RecurType recurType) {
recurrence = recurType;
}
void SetRecurrence(RecurType recurrence);
private:
Controllers::DateTime& dateTimeController;
System::SystemTask* systemTask = nullptr;
TimerHandle_t alarmTimer;
// Versions 255 is reserved for now, so the version field can be made
// bigger, should it ever be needed.
static constexpr uint8_t alarmFormatVersion = 1;
struct AlarmSettings {
uint8_t version = alarmFormatVersion;
uint8_t hours = 7;
uint8_t minutes = 0;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
AlarmState state = AlarmState::Not_Set;
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

@ -1,6 +1,8 @@
#include "components/ble/DfuService.h"
#include <cstring>
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/settings/Settings.h"
#include "drivers/SpiNorFlash.h"
#include "systemtask/SystemTask.h"
#include <nrf_log.h>
@ -78,6 +80,18 @@ void DfuService::Init() {
}
int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
#ifndef PINETIME_IS_RECOVERY
if (systemTask.GetSettings().GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::Disabled) {
Pinetime::Controllers::NotificationManager::Notification notif;
memcpy(notif.message.data(), denyAlert, denyAlertLength);
notif.size = denyAlertLength;
notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
systemTask.GetNotificationManager().Push(std::move(notif));
systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification);
return BLE_ATT_ERR_INSUFFICIENT_AUTHOR;
}
#endif
if (bleController.IsFirmwareUpdating()) {
xTimerStart(timeoutTimer, 0);
}
@ -124,9 +138,11 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) {
bootloaderSize,
applicationSize);
// wait until SystemTask has finished waking up all devices
while (systemTask.IsSleeping()) {
vTaskDelay(50); // 50ms
// Wait until SystemTask has disabled sleeping
// This isn't quite correct, as we don't actually know
// if BleFirmwareUpdateStarted has been received yet
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
}
dfuImage.Erase();

View file

@ -20,6 +20,8 @@ namespace Pinetime {
namespace Controllers {
class Ble;
class Settings;
class NotificationManager;
class DfuService {
public:
@ -87,6 +89,9 @@ namespace Pinetime {
DfuImage dfuImage;
NotificationManager notificationManager;
static constexpr const char denyAlert[] = "InfiniTime\0Firmware update attempted, but disabled in settings.";
static constexpr const uint8_t denyAlertLength = sizeof(denyAlert); // for this to work denyAlert MUST be array
static constexpr uint16_t dfuServiceId {0x1530};
static constexpr uint16_t packetCharacteristicId {0x1532};
static constexpr uint16_t controlPointCharacteristicId {0x1531};

View file

@ -1,6 +1,8 @@
#include <nrf_log.h>
#include "FSService.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/settings/Settings.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers;
@ -49,6 +51,18 @@ void FSService::Init() {
}
int FSService::OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
#ifndef PINETIME_IS_RECOVERY
if (systemTask.GetSettings().GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::Disabled) {
Pinetime::Controllers::NotificationManager::Notification notif;
memcpy(notif.message.data(), denyAlert, denyAlertLength);
notif.size = denyAlertLength;
notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
systemTask.GetNotificationManager().Push(std::move(notif));
systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification);
return BLE_ATT_ERR_INSUFFICIENT_AUTHOR;
}
#endif
if (attributeHandle == versionCharacteristicHandle) {
NRF_LOG_INFO("FS_S : handle = %d", versionCharacteristicHandle);
int res = os_mbuf_append(context->om, &fsVersion, sizeof(fsVersion));

View file

@ -14,6 +14,8 @@ namespace Pinetime {
namespace Controllers {
class Ble;
class Settings;
class NotificationManager;
class FSService {
public:
@ -26,6 +28,10 @@ namespace Pinetime {
private:
Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::FS& fs;
static constexpr const char denyAlert[] = "InfiniTime\0File access attempted, but disabled in settings.";
static constexpr const uint8_t denyAlertLength = sizeof(denyAlert); // for this to work denyAlert MUST be array
static constexpr uint16_t FSServiceId {0xFEBB};
static constexpr uint16_t fsVersionId {0x0100};
static constexpr uint16_t fsTransferId {0x0200};

View file

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

View file

@ -25,6 +25,7 @@
#include <host/ble_uuid.h>
#undef max
#undef min
#include <FreeRTOS.h>
namespace Pinetime {
namespace Controllers {
@ -70,9 +71,9 @@ namespace Pinetime {
uint16_t eventHandle {};
std::string artistName {"Waiting for"};
std::string albumName {};
std::string trackName {"track information.."};
std::string trackName;
std::string albumName;
std::string artistName {"Not Playing"};
bool playing {false};

View file

@ -454,9 +454,15 @@ void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
/* Wakeup Spi and SpiNorFlash before accessing the file system
* This should be fixed in the FS driver
*/
systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
vTaskDelay(10);
// This isn't quite correct
// SystemTask could receive EnableSleeping right after passing this check
// We need some guarantee that the SystemTask has processed the above message
// before we can continue
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
}
lfs_file_t file_p;

View file

@ -28,7 +28,7 @@ namespace Pinetime {
using Id = uint8_t;
using Idx = uint8_t;
std::array<char, MessageSize + 1> message;
std::array<char, MessageSize + 1> message{};
uint8_t size;
Categories category = Categories::Unknown;
Id id = 0;

View file

@ -42,9 +42,9 @@ namespace {
std::memcpy(cityName.data(), &dataBuffer[16], 32);
cityName[32] = '\0';
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
ToInt16(&dataBuffer[10]),
ToInt16(&dataBuffer[12]),
ToInt16(&dataBuffer[14]),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])),
SimpleWeatherService::Icons {dataBuffer[16 + 32]},
std::move(cityName));
}
@ -52,12 +52,12 @@ namespace {
SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
auto timestamp = static_cast<uint64_t>(ToUInt64(&dataBuffer[2]));
std::array<SimpleWeatherService::Forecast::Day, SimpleWeatherService::MaxNbForecastDays> days;
std::array<std::optional<SimpleWeatherService::Forecast::Day>, SimpleWeatherService::MaxNbForecastDays> days;
const uint8_t nbDaysInBuffer = dataBuffer[10];
const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer);
for (int i = 0; i < nbDays; i++) {
days[i] = SimpleWeatherService::Forecast::Day {ToInt16(&dataBuffer[11 + (i * 5)]),
ToInt16(&dataBuffer[13 + (i * 5)]),
days[i] = SimpleWeatherService::Forecast::Day {SimpleWeatherService::Temperature(ToInt16(&dataBuffer[11 + (i * 5)])),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[13 + (i * 5)])),
SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}};
}
return SimpleWeatherService::Forecast {timestamp, nbDays, days};
@ -98,9 +98,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
currentWeather = CreateCurrentWeather(dataBuffer);
NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s",
currentWeather->timestamp,
currentWeather->temperature,
currentWeather->minTemperature,
currentWeather->maxTemperature,
currentWeather->temperature.PreciseCelsius(),
currentWeather->minTemperature.PreciseCelsius(),
currentWeather->maxTemperature.PreciseCelsius(),
currentWeather->iconId,
currentWeather->location.data());
}
@ -112,9 +112,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
for (int i = 0; i < 5; i++) {
NRF_LOG_INFO("\t[%d] Min: %d - Max : %d - Icon : %d",
i,
forecast->days[i].minTemperature,
forecast->days[i].maxTemperature,
forecast->days[i].iconId);
forecast->days[i]->minTemperature.PreciseCelsius(),
forecast->days[i]->maxTemperature.PreciseCelsius(),
forecast->days[i]->iconId);
}
}
break;

View file

@ -19,7 +19,7 @@
#include <cstdint>
#include <string>
#include <vector>
#include <array>
#include <memory>
#define min // workaround: nimble's min/max macros conflict with libstdc++
@ -61,13 +61,42 @@ namespace Pinetime {
Unknown = 255
};
class Temperature {
public:
explicit Temperature(int16_t raw) : raw {raw} {
}
[[nodiscard]] int16_t PreciseCelsius() const {
return raw;
}
[[nodiscard]] int16_t PreciseFahrenheit() const {
return raw * 9 / 5 + 3200;
}
[[nodiscard]] int16_t Celsius() const {
return (PreciseCelsius() + 50) / 100;
}
[[nodiscard]] int16_t Fahrenheit() const {
return (PreciseFahrenheit() + 50) / 100;
}
bool operator==(const Temperature& other) const {
return raw == other.raw;
}
private:
int16_t raw;
};
using Location = std::array<char, 33>; // 32 char + \0 (end of string)
struct CurrentWeather {
CurrentWeather(uint64_t timestamp,
int16_t temperature,
int16_t minTemperature,
int16_t maxTemperature,
Temperature temperature,
Temperature minTemperature,
Temperature maxTemperature,
Icons iconId,
Location&& location)
: timestamp {timestamp},
@ -79,9 +108,9 @@ namespace Pinetime {
}
uint64_t timestamp;
int16_t temperature;
int16_t minTemperature;
int16_t maxTemperature;
Temperature temperature;
Temperature minTemperature;
Temperature maxTemperature;
Icons iconId;
Location location;
@ -93,14 +122,14 @@ namespace Pinetime {
uint8_t nbDays;
struct Day {
int16_t minTemperature;
int16_t maxTemperature;
Temperature minTemperature;
Temperature maxTemperature;
Icons iconId;
bool operator==(const Day& other) const;
};
std::array<Day, MaxNbForecastDays> days;
std::array<std::optional<Day>, MaxNbForecastDays> days;
bool operator==(const Forecast& other) const;
};
@ -108,10 +137,6 @@ namespace Pinetime {
std::optional<CurrentWeather> Current() const;
std::optional<Forecast> GetForecast() const;
static int16_t CelsiusToFahrenheit(int16_t celsius) {
return celsius * 9 / 5 + 3200;
}
private:
// 00050000-78fc-48fe-8e23-433b3a1942d0
static constexpr ble_uuid128_t BaseUuid() {

View file

@ -7,16 +7,31 @@
using namespace Pinetime::Controllers;
namespace {
char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
char const* DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
char const* MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
char const* MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
constexpr const char* const DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
constexpr const char* const DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
constexpr const char* const DaysString[] = {"--", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"};
constexpr const char* const DaysStringLow[] = {"--", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
constexpr const char* const MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
constexpr const char* const MonthsStringLow[] =
{"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
constexpr int compileTimeAtoi(const char* str) {
int result = 0;
while (*str >= '0' && *str <= '9') {
result = result * 10 + *str - '0';
str++;
}
return result;
}
}
DateTime::DateTime(Controllers::Settings& settingsController) : settingsController {settingsController} {
mutex = xSemaphoreCreateMutex();
ASSERT(mutex != nullptr);
xSemaphoreGive(mutex);
// __DATE__ is a string of the format "MMM DD YYYY", so an offset of 7 gives the start of the year
SetTime(compileTimeAtoi(&__DATE__[7]), 1, 1, 0, 0, 0);
}
void DateTime::SetCurrentTime(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> t) {
@ -46,7 +61,9 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour,
UpdateTime(previousSystickCounter, true);
xSemaphoreGive(mutex);
if (systemTask != nullptr) {
systemTask->PushMessage(System::Messages::OnNewTime);
}
}
void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
@ -129,6 +146,10 @@ const char* DateTime::DayOfWeekShortToString() const {
return DaysStringShort[static_cast<uint8_t>(DayOfWeek())];
}
const char* DateTime::DayOfWeekToString() const {
return DaysString[static_cast<uint8_t>(DayOfWeek())];
}
const char* DateTime::MonthShortToStringLow(Months month) {
return MonthsStringLow[static_cast<uint8_t>(month)];
}
@ -137,6 +158,10 @@ const char* DateTime::DayOfWeekShortToStringLow(Days day) {
return DaysStringShortLow[static_cast<uint8_t>(day)];
}
const char* DateTime::DayOfWeekToStringLow(Days day) {
return DaysStringLow[static_cast<uint8_t>(day)];
}
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
this->systemTask = systemTask;
}

View file

@ -41,7 +41,7 @@ namespace Pinetime {
*
* used to update difference between utc and local time (see UtcOffset())
*
* parameters are in quarters of an our. Following the BLE CTS specification,
* parameters are in quarters of an hour. Following the BLE CTS specification,
* timezone is expected to be constant over DST which will be reported in
* dst field.
*/
@ -121,8 +121,10 @@ namespace Pinetime {
const char* MonthShortToString() const;
const char* DayOfWeekShortToString() const;
const char* DayOfWeekToString() const;
static const char* MonthShortToStringLow(Months month);
static const char* DayOfWeekShortToStringLow(Days day);
static const char* DayOfWeekToStringLow(Days day);
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime();

View file

@ -142,7 +142,7 @@ Ppg::Ppg() {
spectrum.fill(0.0f);
}
int8_t Ppg::Preprocess(uint32_t hrs, uint32_t als) {
int8_t Ppg::Preprocess(uint16_t hrs, uint16_t als) {
if (dataIndex < dataLength) {
dataHRS[dataIndex++] = hrs;
}

View file

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

View file

@ -4,6 +4,7 @@
#include "components/brightness/BrightnessController.h"
#include "components/fs/FS.h"
#include "displayapp/apps/Apps.h"
#include <nrf_log.h>
namespace Pinetime {
namespace Controllers {
@ -36,6 +37,8 @@ namespace Pinetime {
};
enum class PTSGaugeStyle : uint8_t { Full, Half, Numeric };
enum class PTSWeather : uint8_t { On, Off };
enum class PrideFlag : uint8_t { Gay, Trans, Bi, Lesbian };
enum class DfuAndFsMode : uint8_t { Disabled, Enabled, EnabledTillReboot };
struct PineTimeStyle {
Colors ColorTime = Colors::Teal;
@ -154,6 +157,16 @@ namespace Pinetime {
return settings.PTS.weatherEnable;
};
void SetPrideFlag(PrideFlag prideFlag) {
if (prideFlag != settings.prideFlag)
settingsChanged = true;
settings.prideFlag = prideFlag;
};
PrideFlag GetPrideFlag() const {
return settings.prideFlag;
};
void SetAppMenu(uint8_t menu) {
appMenu = menu;
};
@ -298,10 +311,33 @@ namespace Pinetime {
return bleRadioEnabled;
};
void SetDfuAndFsMode(DfuAndFsMode mode) {
if (mode == GetDfuAndFsMode()) {
return;
}
if (mode == DfuAndFsMode::Enabled || GetDfuAndFsMode() == DfuAndFsMode::Enabled) {
settingsChanged = true;
}
settings.dfuAndFsEnabledOnBoot = (mode == DfuAndFsMode::Enabled);
dfuAndFsEnabledTillReboot = (mode == DfuAndFsMode::EnabledTillReboot);
};
DfuAndFsMode GetDfuAndFsMode() {
if (dfuAndFsEnabledTillReboot) {
if (settings.dfuAndFsEnabledOnBoot) { // ensure both variables are in consistent state
settingsChanged = true;
settings.dfuAndFsEnabledOnBoot = false;
NRF_LOG_ERROR("Settings: DfuAndFsMode data corrupted");
}
return DfuAndFsMode::EnabledTillReboot;
}
return (settings.dfuAndFsEnabledOnBoot ? DfuAndFsMode::Enabled : DfuAndFsMode::Disabled);
};
private:
Pinetime::Controllers::FS& fs;
static constexpr uint32_t settingsVersion = 0x0008;
static constexpr uint32_t settingsVersion = 0x0009;
struct SettingsData {
uint32_t version = settingsVersion;
@ -319,12 +355,16 @@ namespace Pinetime {
PineTimeStyle PTS;
PrideFlag prideFlag = PrideFlag::Gay;
WatchFaceInfineat watchFaceInfineat;
std::bitset<5> wakeUpMode {0};
uint16_t shakeWakeThreshold = 150;
Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
bool dfuAndFsEnabledOnBoot = false;
};
SettingsData settings;
@ -337,6 +377,7 @@ namespace Pinetime {
* to off (false) on every boot because we always want ble to be enabled on startup
*/
bool bleRadioEnabled = true;
bool dfuAndFsEnabledTillReboot = false;
void LoadSettingsFromFile();
void SaveSettingsToFile();

View file

@ -30,6 +30,7 @@
#include "displayapp/screens/Weather.h"
#include "displayapp/screens/PassKey.h"
#include "displayapp/screens/Error.h"
#include "displayapp/screens/Calculator.h"
#include "drivers/Cst816s.h"
#include "drivers/St7789.h"
@ -49,10 +50,13 @@
#include "displayapp/screens/settings/SettingChimes.h"
#include "displayapp/screens/settings/SettingShakeThreshold.h"
#include "displayapp/screens/settings/SettingBluetooth.h"
#include "displayapp/screens/settings/SettingOTA.h"
#include "libs/lv_conf.h"
#include "UserApps.h"
#include <algorithm>
using namespace Pinetime::Applications;
using namespace Pinetime::Applications::Display;
@ -126,15 +130,6 @@ void DisplayApp::Start(System::BootErrors error) {
bootError = error;
lvgl.Init();
motorController.Init();
if (error == System::BootErrors::TouchController) {
LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
} else {
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
}
if (pdPASS != xTaskCreate(DisplayApp::Process, "displayapp", 800, this, 0, &taskHandle)) {
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
}
@ -143,26 +138,42 @@ void DisplayApp::Start(System::BootErrors error) {
void DisplayApp::Process(void* instance) {
auto* app = static_cast<DisplayApp*>(instance);
NRF_LOG_INFO("displayapp task started!");
app->InitHw();
app->Init();
if (app->bootError == System::BootErrors::TouchController) {
app->LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
} else {
app->LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
}
while (true) {
app->Refresh();
}
}
void DisplayApp::InitHw() {
void DisplayApp::Init() {
lcd.Init();
motorController.Init();
brightnessController.Init();
ApplyBrightness();
lcd.Init();
lvgl.Init();
}
TickType_t DisplayApp::CalculateSleepTime() {
// Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame
// Next frame time is frame count * refresh period (ms) * tick rate
auto RoundedDiv = [](uint32_t a, uint32_t b) {
return ((a + (b / 2)) / b);
};
// RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours (=overflow frame count / always on refresh period)
constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
// Divide both the numerator and denominator by 8 to increase the number of ticks (frames) before the overflow tick is reached
TickType_t elapsedTarget = ROUNDED_DIV((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8);
// ROUNDED_DIV overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours
constexpr TickType_t overflowTick = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
// Divide both the numerator and denominator by 8 (=GCD(1000,1024))
// to increase the number of ticks (frames) before the overflow tick is reached
TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8);
// Assumptions
@ -170,17 +181,17 @@ TickType_t DisplayApp::CalculateSleepTime() {
// Needed for division trick above
static_assert(configTICK_RATE_HZ % 8 == 0);
// Local tick count must always wraparound before the system tick count does
// As a static assert we can use 64 bit ints and therefore dodge overflows
// Frame count must always wraparound more often than the system tick count does
// Always on overflow time (ms) < system tick overflow time (ms)
static_assert((uint64_t) overflowTick * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
// Using 64bit ints here to avoid overflow
static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
if (alwaysOnTickCount == overflowTick) {
alwaysOnTickCount = 0;
if (alwaysOnFrameCount == overflowFrameCount) {
alwaysOnFrameCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
}
if (elapsedTarget > ticksElapsed) {
return elapsedTarget - ticksElapsed;
if (targetRenderTick > ticksElapsed) {
return targetRenderTick - ticksElapsed;
} else {
return 0;
}
@ -220,7 +231,9 @@ void DisplayApp::Refresh() {
TickType_t queueTimeout;
switch (state) {
case States::Idle:
if (settingsController.GetAlwaysOnDisplay()) {
queueTimeout = portMAX_DELAY;
break;
case States::AOD:
if (!currentScreen->IsRunning()) {
LoadPreviousScreen();
}
@ -235,13 +248,10 @@ void DisplayApp::Refresh() {
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;
alwaysOnFrameCount += 1;
queueTimeout = CalculateSleepTime();
}
};
}
} else {
queueTimeout = portMAX_DELAY;
}
break;
case States::Running:
@ -255,9 +265,20 @@ void DisplayApp::Refresh() {
isDimmed = true;
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
}
if (IsPastSleepTime()) {
systemTask->PushMessage(System::Messages::GoToSleep);
state = States::Idle;
if (IsPastSleepTime() && uxQueueMessagesWaiting(msgQueue) == 0) {
PushMessageToSystemTask(System::Messages::GoToSleep);
// Can't set state to Idle here, something may send
// DisableSleeping before this GoToSleep arrives
// Instead we check we have no messages queued before sending GoToSleep
// This works as the SystemTask is higher priority than DisplayApp
// As soon as we send GoToSleep, SystemTask pre-empts DisplayApp
// Whenever DisplayApp is running again, it is guaranteed that
// SystemTask has handled the message
// If it responded, we will have a GoToSleep waiting in the queue
// By checking that there are no messages in the queue, we avoid
// resending GoToSleep when we already have a response
// SystemTask is resilient to duplicate messages, this is an
// optimisation to reduce pressure on the message queues
}
} else if (isDimmed) {
isDimmed = false;
@ -273,12 +294,22 @@ void DisplayApp::Refresh() {
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
switch (msg) {
case Messages::GoToSleep:
case Messages::GoToAOD:
// Checking if SystemTask is sleeping is purely an optimisation.
// If it's no longer sleeping since it sent GoToSleep, it has
// cancelled the sleep and transitioned directly from
// GoingToSleep->Running, so we are about to receive GoToRunning
// and can ignore this message. If it wasn't ignored, DisplayApp
// would go to sleep and then immediately re-wake
if (state != States::Running || !systemTask->IsSleeping()) {
break;
}
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
brightnessController.Lower();
vTaskDelay(100);
}
// Turn brightness down (or set to AlwaysOn mode)
if (settingsController.GetAlwaysOnDisplay()) {
if (msg == Messages::GoToAOD) {
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
} else {
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
@ -291,23 +322,34 @@ void DisplayApp::Refresh() {
while (!lv_task_handler()) {
};
}
// Turn LCD display off (or set to low power for AlwaysOn mode)
if (settingsController.GetAlwaysOnDisplay()) {
// Clear any ongoing touch pressed events
// Without this LVGL gets stuck in the pressed state and will keep refreshing the
// display activity timer causing the screen to never sleep after timeout
lvgl.ClearTouchState();
if (msg == Messages::GoToAOD) {
lcd.LowPowerOn();
// Record idle entry time
alwaysOnTickCount = 0;
alwaysOnFrameCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD);
state = States::AOD;
} else {
lcd.Sleep();
}
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle;
}
break;
case Messages::NotifyDeviceActivity:
lv_disp_trig_activity(nullptr);
break;
case Messages::GoToRunning:
if (settingsController.GetAlwaysOnDisplay()) {
// If SystemTask is sleeping, the GoToRunning message is old
// and must be ignored. Otherwise DisplayApp will use SPI
// that is powered down and cause bad behaviour
if (state == States::Running || systemTask->IsSleeping()) {
break;
}
if (state == States::AOD) {
lcd.LowPowerOff();
} else {
lcd.Wakeup();
@ -436,13 +478,10 @@ void DisplayApp::Refresh() {
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
motorController.RunForDuration(35);
break;
case Messages::OnChargingEvent:
motorController.RunForDuration(15);
break;
}
}
if (touchHandler.IsTouching()) {
if (state == States::Running && touchHandler.IsTouching()) {
currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY());
}
@ -479,26 +518,25 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
switch (app) {
case Apps::Launcher: {
std::array<Screens::Tile::Applications, UserAppTypes::Count> apps;
int i = 0;
for (const auto& userApp : userApps) {
apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
}
std::ranges::transform(userApps, apps.begin(), [this](const auto& userApp) {
return Screens::Tile::Applications {userApp.icon, userApp.app, userApp.isAvailable(controllers.filesystem)};
});
currentScreen = std::make_unique<Screens::ApplicationList>(this,
settingsController,
batteryController,
bleController,
alarmController,
dateTimeController,
filesystem,
std::move(apps));
} break;
case Apps::Clock: {
const auto* watchFace =
std::find_if(userWatchFaces.begin(), userWatchFaces.end(), [this](const WatchFaceDescription& watchfaceDescription) {
const auto* watchFace = std::ranges::find_if(userWatchFaces, [this](const WatchFaceDescription& watchfaceDescription) {
return watchfaceDescription.watchFace == settingsController.GetWatchFace();
});
if (watchFace != userWatchFaces.end())
if (watchFace != userWatchFaces.end()) {
currentScreen.reset(watchFace->create(controllers));
else {
} else {
currentScreen.reset(userWatchFaces[0].create(controllers));
}
settingsController.SetAppMenu(0);
@ -541,18 +579,19 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
brightnessController,
motorController,
settingsController,
bleController);
bleController,
alarmController);
break;
case Apps::Settings:
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
break;
case Apps::SettingWatchFace: {
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
int i = 0;
for (const auto& userWatchFace : userWatchFaces) {
items[i++] =
Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)};
}
std::ranges::transform(userWatchFaces, items.begin(), [this](const WatchFaceDescription& userWatchFace) {
return Screens::SettingWatchFace::Item {userWatchFace.name,
userWatchFace.watchFace,
userWatchFace.isAvailable(controllers.filesystem)};
});
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
} break;
case Apps::SettingTimeFormat:
@ -565,7 +604,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
break;
case Apps::SettingDisplay:
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController);
currentScreen = std::make_unique<Screens::SettingDisplay>(settingsController);
break;
case Apps::SettingSteps:
currentScreen = std::make_unique<Screens::SettingSteps>(settingsController);
@ -582,6 +621,9 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
case Apps::SettingBluetooth:
currentScreen = std::make_unique<Screens::SettingBluetooth>(this, settingsController);
break;
case Apps::SettingOTA:
currentScreen = std::make_unique<Screens::SettingOTA>(this, settingsController);
break;
case Apps::BatteryInfo:
currentScreen = std::make_unique<Screens::BatteryInfo>(batteryController);
break;
@ -600,7 +642,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
break;
default: {
const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) {
const auto* d = std::ranges::find_if(userApps, [app](const AppDescription& appDescription) {
return appDescription.app == app;
});
if (d != userApps.end()) {

View file

@ -49,7 +49,7 @@ namespace Pinetime {
namespace Applications {
class DisplayApp {
public:
enum class States { Idle, Running };
enum class States { Idle, Running, AOD };
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
DisplayApp(Drivers::St7789& lcd,
@ -121,7 +121,7 @@ namespace Pinetime {
TouchEvents GetGesture();
static void Process(void* instance);
void InitHw();
void Init();
void Refresh();
void LoadNewScreen(Apps app, DisplayApp::FullRefreshDirections direction);
void LoadScreen(Apps app, DisplayApp::FullRefreshDirections direction);
@ -139,7 +139,7 @@ namespace Pinetime {
bool isDimmed = false;
TickType_t CalculateSleepTime();
TickType_t alwaysOnTickCount;
TickType_t alwaysOnFrameCount;
TickType_t alwaysOnStartTime;
// If this is to be changed, make sure the actual always on refresh rate is changed
// by configuring the LCD refresh timings

View file

@ -248,6 +248,8 @@ void LittleVgl::SetNewTouchPoint(int16_t x, int16_t y, bool contact) {
}
}
// Cancel an ongoing tap
// Signifies that LVGL should not handle the current tap
void LittleVgl::CancelTap() {
if (tapped) {
isCancelled = true;
@ -255,6 +257,13 @@ void LittleVgl::CancelTap() {
}
}
// Clear the current tapped state
// Signifies that touch input processing is suspended
void LittleVgl::ClearTouchState() {
touchPoint = {-1, -1};
tapped = false;
}
bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) {
ptr->point.x = touchPoint.x;
ptr->point.y = touchPoint.y;

View file

@ -26,6 +26,7 @@ namespace Pinetime {
void SetFullRefresh(FullRefreshDirections direction);
void SetNewTouchPoint(int16_t x, int16_t y, bool contact);
void CancelTap();
void ClearTouchState();
bool GetFullRefresh() {
bool returnValue = fullRefresh;

View file

@ -6,6 +6,7 @@ namespace Pinetime {
namespace Display {
enum class Messages : uint8_t {
GoToSleep,
GoToAOD,
GoToRunning,
UpdateBleConnection,
TouchEvent,
@ -23,7 +24,6 @@ namespace Pinetime {
AlarmTriggered,
Chime,
BleRadioEnableToggle,
OnChargingEvent,
};
}
}

View file

@ -14,6 +14,7 @@
#include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFacePineTimeStyle.h"
#include "displayapp/screens/WatchFaceTerminal.h"
#include "displayapp/screens/WatchFacePrideFlag.h"
namespace Pinetime {
namespace Applications {
@ -25,6 +26,7 @@ namespace Pinetime {
Apps app;
const char* icon;
Screens::Screen* (*create)(AppControllers& controllers);
bool (*isAvailable)(Controllers::FS& fileSystem);
};
struct WatchFaceDescription {
@ -36,7 +38,7 @@ namespace Pinetime {
template <Apps t>
consteval AppDescription CreateAppDescription() {
return {AppTraits<t>::app, AppTraits<t>::icon, &AppTraits<t>::Create};
return {AppTraits<t>::app, AppTraits<t>::icon, &AppTraits<t>::Create, &AppTraits<t>::IsAvailable};
}
template <WatchFace t>

View file

@ -26,6 +26,7 @@ namespace Pinetime {
StopWatch,
Metronome,
Motion,
Calculator,
Steps,
Dice,
Weather,
@ -42,6 +43,7 @@ namespace Pinetime {
SettingChimes,
SettingShakeThreshold,
SettingBluetooth,
SettingOTA,
Error
};
@ -52,6 +54,7 @@ namespace Pinetime {
Terminal,
Infineat,
CasioStyleG7710,
PrideFlag,
};
template <Apps>

View file

@ -13,6 +13,7 @@ else ()
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calculator")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")
#set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion")
set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
@ -27,6 +28,7 @@ else()
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PrideFlag")
set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
endif()

View file

@ -11,6 +11,7 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/jetbrains_mono_bold_20.c_M.patch
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
# FindPython3 module introduces with CMake 3.12
# https://cmake.org/cmake/help/latest/module/FindPython3.html
set(Python3_FIND_STRATEGY LOCATION) # https://discourse.cmake.org/t/find-package-python3-is-not-finding-the-correct-python/10563
find_package(Python3 REQUIRED)
else()
set(Python3_EXECUTABLE "python")

View file

@ -7,7 +7,7 @@
},
{
"file": "FontAwesome5-Solid+Brands+Regular.woff",
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf1ec, 0xf55a, 0xf3ed"
}
],
"bpp": 1,

View file

@ -48,7 +48,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
Controllers::Settings::ClockType clockType,
System::SystemTask& systemTask,
Controllers::MotorController& motorController)
: alarmController {alarmController}, systemTask {systemTask}, motorController {motorController} {
: alarmController {alarmController}, wakeLock(systemTask), motorController {motorController} {
hourCounter.Create();
lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
@ -77,7 +77,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
btnStop = lv_btn_create(lv_scr_act(), nullptr);
btnStop->user_data = this;
lv_obj_set_event_cb(btnStop, btnEventHandler);
lv_obj_set_size(btnStop, 115, 50);
lv_obj_set_size(btnStop, 240, 70);
lv_obj_align(btnStop, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
lv_obj_set_style_local_bg_color(btnStop, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
txtStop = lv_label_create(btnStop, nullptr);
@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
UpdateAlarmTime();
if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) {
if (alarmController.IsAlerting()) {
SetAlerting();
} else {
SetSwitchState(LV_ANIM_OFF);
@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
}
Alarm::~Alarm() {
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
if (alarmController.IsAlerting()) {
StopAlerting();
}
lv_obj_clean(lv_scr_act());
alarmController.SaveAlarm();
}
void Alarm::DisableAlarm() {
if (alarmController.State() == AlarmController::AlarmState::Set) {
if (alarmController.IsEnabled()) {
alarmController.DisableAlarm();
lv_switch_off(enableSwitch, LV_ANIM_ON);
}
@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() {
HideInfo();
return true;
}
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
if (alarmController.IsAlerting()) {
StopAlerting();
return true;
}
@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() {
bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
// Don't allow closing the screen by swiping while the alarm is alerting
return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown;
return alarmController.IsAlerting() && event == TouchEvents::SwipeDown;
}
void Alarm::OnValueChanged() {
@ -202,10 +203,14 @@ void Alarm::UpdateAlarmTime() {
void Alarm::SetAlerting() {
lv_obj_set_hidden(enableSwitch, true);
lv_obj_set_hidden(btnRecur, true);
lv_obj_set_hidden(btnInfo, true);
hourCounter.HideControls();
minuteCounter.HideControls();
lv_obj_set_hidden(btnStop, false);
taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this);
motorController.StartRinging();
systemTask.PushMessage(System::Messages::DisableSleeping);
wakeLock.Lock();
}
void Alarm::StopAlerting() {
@ -216,21 +221,20 @@ void Alarm::StopAlerting() {
lv_task_del(taskStopAlarm);
taskStopAlarm = nullptr;
}
systemTask.PushMessage(System::Messages::EnableSleeping);
lv_obj_set_hidden(enableSwitch, false);
wakeLock.Release();
lv_obj_set_hidden(btnStop, true);
hourCounter.ShowControls();
minuteCounter.ShowControls();
lv_obj_set_hidden(btnInfo, false);
lv_obj_set_hidden(btnRecur, false);
lv_obj_set_hidden(enableSwitch, false);
}
void Alarm::SetSwitchState(lv_anim_enable_t anim) {
switch (alarmController.State()) {
case AlarmController::AlarmState::Set:
if (alarmController.IsEnabled()) {
lv_switch_on(enableSwitch, anim);
break;
case AlarmController::AlarmState::Not_Set:
} else {
lv_switch_off(enableSwitch, anim);
break;
default:
break;
}
}
@ -247,7 +251,7 @@ void Alarm::ShowInfo() {
txtMessage = lv_label_create(btnMessage, nullptr);
lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY);
if (alarmController.State() == AlarmController::AlarmState::Set) {
if (alarmController.IsEnabled()) {
auto timeToAlarm = alarmController.SecondsToAlarm();
auto daysToAlarm = timeToAlarm / 86400;

View file

@ -22,6 +22,7 @@
#include "displayapp/screens/Screen.h"
#include "displayapp/widgets/Counter.h"
#include "displayapp/Controllers.h"
#include "systemtask/WakeLock.h"
#include "Symbols.h"
namespace Pinetime {
@ -43,7 +44,7 @@ namespace Pinetime {
private:
Controllers::AlarmController& alarmController;
System::SystemTask& systemTask;
System::WakeLock wakeLock;
Controllers::MotorController& motorController;
lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch;
@ -77,6 +78,10 @@ namespace Pinetime {
*controllers.systemTask,
controllers.motorController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -21,6 +21,7 @@ ApplicationList::ApplicationList(DisplayApp* app,
Pinetime::Controllers::Settings& settingsController,
const Pinetime::Controllers::Battery& batteryController,
const Pinetime::Controllers::Ble& bleController,
const Pinetime::Controllers::AlarmController& alarmController,
Controllers::DateTime& dateTimeController,
Pinetime::Controllers::FS& filesystem,
std::array<Tile::Applications, UserAppTypes::Count>&& apps)
@ -28,6 +29,7 @@ ApplicationList::ApplicationList(DisplayApp* app,
settingsController {settingsController},
batteryController {batteryController},
bleController {bleController},
alarmController {alarmController},
dateTimeController {dateTimeController},
filesystem {filesystem},
apps {std::move(apps)},
@ -59,6 +61,7 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) co
settingsController,
batteryController,
bleController,
alarmController,
dateTimeController,
pageApps);
}

View file

@ -18,6 +18,7 @@ namespace Pinetime {
Pinetime::Controllers::Settings& settingsController,
const Pinetime::Controllers::Battery& batteryController,
const Pinetime::Controllers::Ble& bleController,
const Pinetime::Controllers::AlarmController& alarmController,
Controllers::DateTime& dateTimeController,
Pinetime::Controllers::FS& filesystem,
std::array<Tile::Applications, UserAppTypes::Count>&& apps);
@ -32,6 +33,7 @@ namespace Pinetime {
Controllers::Settings& settingsController;
const Pinetime::Controllers::Battery& batteryController;
const Pinetime::Controllers::Ble& bleController;
const Pinetime::Controllers::AlarmController& alarmController;
Controllers::DateTime& dateTimeController;
Pinetime::Controllers::FS& filesystem;
std::array<Tile::Applications, UserAppTypes::Count> apps;

View file

@ -10,33 +10,35 @@ BatteryInfo::BatteryInfo(const Pinetime::Controllers::Battery& batteryController
batteryPercent = batteryController.PercentRemaining();
batteryVoltage = batteryController.Voltage();
charging_bar = lv_bar_create(lv_scr_act(), nullptr);
lv_obj_set_size(charging_bar, 200, 15);
lv_bar_set_range(charging_bar, 0, 100);
lv_obj_align(charging_bar, nullptr, LV_ALIGN_CENTER, 0, 10);
lv_bar_set_anim_time(charging_bar, 1000);
lv_obj_set_style_local_radius(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt);
lv_obj_set_style_local_bg_opa(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100);
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED);
lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON);
chargingArc = lv_arc_create(lv_scr_act(), nullptr);
lv_arc_set_rotation(chargingArc, 270);
lv_arc_set_bg_angles(chargingArc, 0, 360);
lv_arc_set_adjustable(chargingArc, false);
lv_obj_set_size(chargingArc, 180, 180);
lv_obj_align(chargingArc, nullptr, LV_ALIGN_CENTER, 0, -30);
lv_arc_set_value(chargingArc, batteryPercent);
lv_obj_set_style_local_bg_opa(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, LV_OPA_0);
lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt);
lv_obj_set_style_local_border_width(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 2);
lv_obj_set_style_local_radius(chargingArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 0);
lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_LIME);
status = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(status, "Reading Battery status");
lv_label_set_align(status, LV_LABEL_ALIGN_CENTER);
lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20);
lv_obj_align(status, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -17);
percent = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76);
lv_label_set_text_fmt(percent, "%02i%%", batteryPercent);
lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
lv_label_set_text_fmt(percent, "%i%%", batteryPercent);
lv_label_set_align(percent, LV_LABEL_ALIGN_LEFT);
lv_obj_align(percent, nullptr, LV_ALIGN_CENTER, 0, -60);
lv_obj_align(percent, chargingArc, LV_ALIGN_CENTER, 0, 0);
voltage = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(voltage, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange);
lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10);
lv_label_set_align(voltage, LV_LABEL_ALIGN_CENTER);
lv_obj_align(voltage, nullptr, LV_ALIGN_CENTER, 0, 95);
lv_obj_align(voltage, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -7);
taskRefresh = lv_task_create(RefreshTaskCallback, 5000, LV_TASK_PRIO_MID, this);
Refresh();
@ -53,22 +55,26 @@ void BatteryInfo::Refresh() {
batteryVoltage = batteryController.Voltage();
if (batteryController.IsCharging()) {
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED);
lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_LIME);
lv_label_set_text_static(status, "Charging");
} else if (batteryPercent == 100) {
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE);
lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE);
lv_label_set_text_static(status, "Fully charged");
} else if (batteryPercent < 10) {
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_YELLOW);
} else if (batteryPercent > 15) {
lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_GREEN);
lv_label_set_text_static(status, "Discharging");
} else if (batteryPercent > 5) {
lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_label_set_text_static(status, "Battery low");
} else {
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, Colors::highlight);
lv_label_set_text_static(status, "Discharging");
lv_obj_set_style_local_line_color(chargingArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, Colors::deepOrange);
lv_label_set_text_static(status, "Battery critical");
}
lv_label_set_text_fmt(percent, "%02i%%", batteryPercent);
lv_label_set_text_fmt(percent, "%i%%", batteryPercent);
lv_obj_align(percent, chargingArc, LV_ALIGN_CENTER, 0, 0);
lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20);
lv_obj_align(status, voltage, LV_ALIGN_IN_BOTTOM_MID, 0, -27);
lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10);
lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON);
lv_arc_set_value(chargingArc, batteryPercent);
}

View file

@ -24,7 +24,7 @@ namespace Pinetime {
lv_obj_t* voltage;
lv_obj_t* percent;
lv_obj_t* charging_bar;
lv_obj_t* chargingArc;
lv_obj_t* status;
lv_task_t* taskRefresh;

View file

@ -0,0 +1,375 @@
#include <cmath>
#include <cinttypes>
#include "Calculator.h"
#include "displayapp/InfiniTimeTheme.h"
#include "Symbols.h"
using namespace Pinetime::Applications::Screens;
static void eventHandler(lv_obj_t* obj, lv_event_t event) {
auto app = static_cast<Calculator*>(obj->user_data);
app->OnButtonEvent(obj, event);
}
Calculator::~Calculator() {
lv_obj_clean(lv_scr_act());
}
constexpr const char* const buttonMap[] = {
"7", "8", "9", Symbols::backspace, "\n", "4", "5", "6", "+ -", "\n", "1", "2", "3", "* /", "\n", "0", ".", "(-)", "=", ""};
Calculator::Calculator() {
resultLabel = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(resultLabel, LV_LABEL_LONG_CROP);
lv_label_set_align(resultLabel, LV_LABEL_ALIGN_RIGHT);
lv_label_set_text_fmt(resultLabel, "%" PRId64, result);
lv_obj_set_size(resultLabel, 200, 20);
lv_obj_set_pos(resultLabel, 10, 5);
valueLabel = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(valueLabel, LV_LABEL_LONG_CROP);
lv_label_set_align(valueLabel, LV_LABEL_ALIGN_RIGHT);
lv_label_set_text_fmt(valueLabel, "%" PRId64, value);
lv_obj_set_size(valueLabel, 200, 20);
lv_obj_set_pos(valueLabel, 10, 35);
buttonMatrix = lv_btnmatrix_create(lv_scr_act(), nullptr);
buttonMatrix->user_data = this;
lv_obj_set_event_cb(buttonMatrix, eventHandler);
lv_btnmatrix_set_map(buttonMatrix, const_cast<const char**>(buttonMap));
lv_btnmatrix_set_one_check(buttonMatrix, true);
lv_obj_set_size(buttonMatrix, 238, 180);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt);
lv_obj_set_style_local_pad_inner(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_top(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_bottom(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_left(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_pad_right(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1);
lv_obj_align(buttonMatrix, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
lv_obj_set_style_local_bg_opa(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_OPA_COVER);
lv_obj_set_style_local_bg_grad_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128);
lv_obj_set_style_local_bg_main_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128);
}
void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) {
if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) {
HandleInput();
}
}
void Calculator::HandleInput() {
const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix);
if (buttonText == nullptr) {
return;
}
if ((equalSignPressedBefore && (*buttonText != '=')) || (error != Error::None)) {
ResetInput();
UpdateOperation();
}
// we only compare the first char because it is enough
switch (*buttonText) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
// *buttonText is the first char in buttonText
// "- '0'" results in the int value of the char
uint8_t digit = (*buttonText) - '0';
int8_t sign = (value < 0) ? -1 : 1;
// if this is true, we already pressed the . button
if (offset < FIXED_POINT_OFFSET) {
value += sign * offset * digit;
offset /= 10;
} else if (value <= MAX_VALUE / 10) {
value *= 10;
value += sign * offset * digit;
}
} break;
// unary minus
case '(':
value = -value;
break;
case '.':
if (offset == FIXED_POINT_OFFSET) {
offset /= 10;
}
break;
// for every operator we:
// - eval the current operator if value > FIXED_POINT_OFFSET
// - then set the new operator
// - + and - as well as * and / cycle on the same button
case '+':
if (value != 0) {
Eval();
ResetInput();
}
switch (operation) {
case '+':
operation = '-';
break;
case '-':
operation = ' ';
break;
default:
operation = '+';
break;
}
UpdateOperation();
break;
case '*':
if (value != 0) {
Eval();
ResetInput();
}
switch (operation) {
case '*':
operation = '/';
break;
case '/':
operation = ' ';
break;
default:
operation = '*';
break;
}
UpdateOperation();
break;
// this is a little hacky because it matches only the first char
case Symbols::backspace[0]:
if (value != 0) {
// delete one value digit
if (offset < FIXED_POINT_OFFSET) {
if (offset == 0) {
offset = 1;
} else {
offset *= 10;
}
} else {
value /= 10;
}
if (offset < FIXED_POINT_OFFSET) {
value -= value % (10 * offset);
} else {
value -= value % offset;
}
} else if (offset < FIXED_POINT_OFFSET) {
if (offset == 0) {
offset = 1;
} else {
offset *= 10;
}
} else {
// reset the result
result = 0;
}
if (value == 0) {
operation = ' ';
UpdateOperation();
}
break;
case '=':
equalSignPressedBefore = true;
Eval();
// If the operation is ' ' then we move the value to the result.
// We reset the input after this.
// This seems more convenient.
if (operation == ' ') {
ResetInput();
}
break;
}
UpdateValueLabel();
UpdateResultLabel();
}
void Calculator::UpdateOperation() const {
switch (operation) {
case '+':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
case '-':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
case '*':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
case '/':
lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR);
lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt);
lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange);
lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
default:
lv_btnmatrix_clear_btn_ctrl_all(buttonMatrix, LV_BTNMATRIX_CTRL_CHECK_STATE);
break;
}
}
void Calculator::ResetInput() {
value = 0;
offset = FIXED_POINT_OFFSET;
operation = ' ';
equalSignPressedBefore = false;
error = Error::None;
}
void Calculator::UpdateResultLabel() const {
int64_t integer = result / FIXED_POINT_OFFSET;
int64_t remainder = result % FIXED_POINT_OFFSET;
bool negative = (remainder < 0);
if (remainder == 0) {
lv_label_set_text_fmt(resultLabel, "%" PRId64, integer);
return;
}
if (remainder < 0) {
remainder = -remainder;
}
uint8_t minWidth = N_DECIMALS;
// cut "0"-digits on the right
while ((remainder > 0) && (remainder % 10 == 0)) {
remainder /= 10;
minWidth--;
}
if ((integer == 0) && negative) {
lv_label_set_text_fmt(resultLabel, "-0.%0*" PRId64, minWidth, remainder);
} else {
lv_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, remainder);
}
}
void Calculator::UpdateValueLabel() {
switch (error) {
case Error::TooLarge:
lv_label_set_text_static(valueLabel, "too large");
break;
case Error::ZeroDivision:
lv_label_set_text_static(valueLabel, "zero division");
break;
case Error::None:
default: {
int64_t integer = value / FIXED_POINT_OFFSET;
int64_t remainder = value % FIXED_POINT_OFFSET;
bool negative = (remainder < 0);
int64_t printRemainder = remainder < 0 ? -remainder : remainder;
uint8_t minWidth = 0;
int64_t tmpOffset = offset;
if (tmpOffset == 0) {
tmpOffset = 1;
minWidth = 1;
}
while (tmpOffset < FIXED_POINT_OFFSET) {
tmpOffset *= 10;
minWidth++;
}
minWidth--;
for (uint8_t i = minWidth; i < N_DECIMALS; i++) {
printRemainder /= 10;
}
if ((integer == 0) && negative) {
lv_label_set_text_fmt(valueLabel, "-0.%0*" PRId64, minWidth, printRemainder);
} else if (offset == FIXED_POINT_OFFSET) {
lv_label_set_text_fmt(valueLabel, "%" PRId64, integer);
} else if ((offset == (FIXED_POINT_OFFSET / 10)) && (remainder == 0)) {
lv_label_set_text_fmt(valueLabel, "%" PRId64 ".", integer);
} else {
lv_label_set_text_fmt(valueLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, printRemainder);
}
} break;
}
}
// update the result based on value and operation
void Calculator::Eval() {
switch (operation) {
case ' ':
result = value;
break;
case '+':
// check for overflow
if (((result > 0) && (value > (MAX_VALUE - result))) || ((result < 0) && (value < (MIN_VALUE - result)))) {
error = Error::TooLarge;
break;
}
result += value;
break;
case '-':
// check for overflow
if (((result < 0) && (value > (MAX_VALUE + result))) || ((result > 0) && (value < (MIN_VALUE + result)))) {
error = Error::TooLarge;
break;
}
result -= value;
break;
case '*':
// check for overflow
// while dividing we eliminate the fixed point offset
// therefore we have to multiply it again for the comparison with value
// we also assume here that MAX_VALUE == -MIN_VALUE
if ((result != 0) && (std::abs(value) > (FIXED_POINT_OFFSET * (MAX_VALUE / std::abs(result))))) {
error = Error::TooLarge;
break;
}
result *= value;
// fixed point offset was multiplied too
result /= FIXED_POINT_OFFSET;
break;
case '/':
// check for zero division
if (value == 0) {
error = Error::ZeroDivision;
break;
}
// fixed point offset will be divided too
result *= FIXED_POINT_OFFSET;
result /= value;
break;
default:
break;
}
}

View file

@ -0,0 +1,87 @@
#pragma once
#include "displayapp/screens/Screen.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace {
constexpr int64_t powi(int64_t base, uint8_t exponent) {
int64_t value = 1;
while (exponent) {
value *= base;
exponent--;
}
return value;
}
}
namespace Pinetime {
namespace Applications {
namespace Screens {
class Calculator : public Screen {
public:
~Calculator() override;
Calculator();
void OnButtonEvent(lv_obj_t* obj, lv_event_t event);
private:
lv_obj_t* buttonMatrix {};
lv_obj_t* valueLabel {};
lv_obj_t* resultLabel {};
void Eval();
void ResetInput();
void HandleInput();
void UpdateValueLabel();
void UpdateResultLabel() const;
void UpdateOperation() const;
// change this if you want to change the number of decimals
// keep in mind, that we only have 12 digits in total (see MAX_DIGITS)
// due to the fixed point implementation
static constexpr uint8_t N_DECIMALS = 3;
// this is the constant default offset
static constexpr int64_t FIXED_POINT_OFFSET = powi(10, N_DECIMALS);
// this is the current offset, may vary after pressing '.'
int64_t offset = FIXED_POINT_OFFSET;
// the screen can show 12 chars
// but two are needed for '.' and '-'
static constexpr uint8_t MAX_DIGITS = 12;
static constexpr int64_t MAX_VALUE = powi(10, MAX_DIGITS) - 1;
// this is assumed in the multiplication overflow!
static constexpr int64_t MIN_VALUE = -MAX_VALUE;
int64_t value = 0;
int64_t result = 0;
char operation = ' ';
bool equalSignPressedBefore = false;
enum Error {
TooLarge,
ZeroDivision,
None,
};
Error error = Error::None;
};
}
template <>
struct AppTraits<Apps::Calculator> {
static constexpr Apps app = Apps::Calculator;
static constexpr const char* icon = Screens::Symbols::calculator;
static Screens::Screen* Create(AppControllers& /* controllers */) {
return new Screens::Calculator();
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -56,6 +56,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -2,6 +2,7 @@
#include <lvgl/lvgl.h>
#include "components/ble/BleController.h"
#include "displayapp/DisplayApp.h"
#include "displayapp/InfiniTimeTheme.h"
using namespace Pinetime::Applications::Screens;
@ -12,6 +13,9 @@ FirmwareUpdate::FirmwareUpdate(const Pinetime::Controllers::Ble& bleController)
lv_obj_align(titleLabel, nullptr, LV_ALIGN_IN_TOP_MID, 0, 50);
bar1 = lv_bar_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_color(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt);
lv_obj_set_style_local_bg_opa(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100);
lv_obj_set_style_local_radius(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
lv_obj_set_size(bar1, 200, 30);
lv_obj_align(bar1, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_bar_set_range(bar1, 0, 1000);
@ -75,7 +79,7 @@ void FirmwareUpdate::DisplayProgression() const {
const uint32_t total = bleController.FirmwareUpdateTotalBytes();
const int16_t permille = current / (total / 1000);
lv_label_set_text_fmt(percentLabel, "%d %%", permille / 10);
lv_label_set_text_fmt(percentLabel, "%d%%", permille / 10);
lv_bar_set_value(bar1, permille, LV_ANIM_OFF);
}

View file

@ -15,7 +15,7 @@ namespace {
}
FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController)
: systemTask {systemTask}, brightnessController {brightnessController} {
: wakeLock(systemTask), brightnessController {brightnessController} {
previousBrightnessLevel = brightnessController.Level();
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
@ -47,14 +47,13 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo
backgroundAction->user_data = this;
lv_obj_set_event_cb(backgroundAction, EventHandler);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
wakeLock.Lock();
}
FlashLight::~FlashLight() {
lv_obj_clean(lv_scr_act());
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
brightnessController.Set(previousBrightnessLevel);
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
}
void FlashLight::SetColors() {

View file

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

View file

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

View file

@ -4,6 +4,7 @@
#include <chrono>
#include "displayapp/screens/Screen.h"
#include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "Symbols.h"
#include <lvgl/src/lv_core/lv_style.h>
#include <lvgl/src/lv_core/lv_obj.h>
@ -27,7 +28,7 @@ namespace Pinetime {
private:
Controllers::HeartRateController& heartRateController;
Pinetime::System::SystemTask& systemTask;
Pinetime::System::WakeLock wakeLock;
void UpdateStartStopButton(bool isRunning);
lv_obj_t* label_hr;
lv_obj_t* label_bpm;
@ -47,6 +48,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::HeartRate(controllers.heartRateController, *controllers.systemTask);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -47,6 +47,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::InfiniPaint(controllers.lvgl, controllers.motorController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

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

View file

@ -1,6 +1,7 @@
#pragma once
#include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "components/motor/MotorController.h"
#include "displayapp/screens/Screen.h"
#include "Symbols.h"
@ -21,7 +22,7 @@ namespace Pinetime {
TickType_t startTime = 0;
TickType_t tappedTime = 0;
Controllers::MotorController& motorController;
System::SystemTask& systemTask;
System::WakeLock wakeLock;
int16_t bpm = 120;
uint8_t bpb = 4;
uint8_t counter = 1;
@ -46,6 +47,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Metronome(controllers.motorController, *controllers.systemTask);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -41,6 +41,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Motion(controllers.motionController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -23,6 +23,7 @@
#include "displayapp/icons/music/disc.c"
#include "displayapp/icons/music/disc_f_1.c"
#include "displayapp/icons/music/disc_f_2.c"
#include "displayapp/InfiniTimeTheme.h"
using namespace Pinetime::Applications::Screens;
@ -52,8 +53,7 @@ Music::Music(Pinetime::Controllers::MusicService& music) : musicService(music) {
lv_style_init(&btn_style);
lv_style_set_radius(&btn_style, LV_STATE_DEFAULT, 20);
lv_style_set_bg_color(&btn_style, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_style_set_bg_opa(&btn_style, LV_STATE_DEFAULT, LV_OPA_50);
lv_style_set_bg_color(&btn_style, LV_STATE_DEFAULT, Colors::bgAlt);
btnVolDown = lv_btn_create(lv_scr_act(), nullptr);
btnVolDown->user_data = this;
@ -114,18 +114,18 @@ Music::Music(Pinetime::Controllers::MusicService& music) : musicService(music) {
constexpr int8_t MIDDLE_OFFSET = -25;
txtArtist = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(txtArtist, LV_LABEL_LONG_SROLL_CIRC);
lv_obj_align(txtArtist, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 1 * FONT_HEIGHT);
lv_obj_align(txtArtist, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 2 * FONT_HEIGHT + LINE_PAD);
lv_label_set_align(txtArtist, LV_ALIGN_IN_LEFT_MID);
lv_obj_set_width(txtArtist, LV_HOR_RES - 12);
lv_label_set_text_static(txtArtist, "Artist Name");
lv_label_set_text_static(txtArtist, "");
lv_obj_set_style_local_text_color(txtArtist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
txtTrack = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(txtTrack, LV_LABEL_LONG_SROLL_CIRC);
lv_obj_align(txtTrack, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 2 * FONT_HEIGHT + LINE_PAD);
lv_obj_align(txtTrack, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 1 * FONT_HEIGHT);
lv_label_set_align(txtTrack, LV_ALIGN_IN_LEFT_MID);
lv_obj_set_width(txtTrack, LV_HOR_RES - 12);
lv_label_set_text_static(txtTrack, "This is a very long getTrack name");
lv_label_set_text_static(txtTrack, "");
/** Init animation */
imgDisc = lv_img_create(lv_scr_act(), nullptr);

View file

@ -94,6 +94,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Music(*controllers.musicService);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -67,6 +67,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Navigation(*controllers.navigationService);
};
static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
return Screens::Navigation::IsAvailable(filesystem);
};
};
}
}

View file

@ -20,7 +20,7 @@ Notifications::Notifications(DisplayApp* app,
notificationManager {notificationManager},
alertNotificationService {alertNotificationService},
motorController {motorController},
systemTask {systemTask},
wakeLock(systemTask),
mode {mode} {
notificationManager.ClearNewNotificationFlag();
@ -40,7 +40,7 @@ Notifications::Notifications(DisplayApp* app,
validDisplay = false;
}
if (mode == Modes::Preview) {
systemTask.PushMessage(System::Messages::DisableSleeping);
wakeLock.Lock();
if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) {
motorController.StartRinging();
} else {
@ -65,7 +65,6 @@ Notifications::~Notifications() {
lv_task_del(taskRefresh);
// make sure we stop any vibrations before exiting
motorController.StopRinging();
systemTask.PushMessage(System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act());
}
@ -82,7 +81,6 @@ void Notifications::Refresh() {
} else if (mode == Modes::Preview && dismissingNotification) {
running = false;
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController);
} else if (dismissingNotification) {
dismissingNotification = false;
@ -113,15 +111,15 @@ void Notifications::Refresh() {
alertNotificationService,
motorController);
} else {
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController);
running = false;
}
}
running = currentItem->IsRunning() && running;
running = running && currentItem->IsRunning();
}
void Notifications::OnPreviewInteraction() {
systemTask.PushMessage(System::Messages::EnableSleeping);
wakeLock.Release();
motorController.StopRinging();
if (timeoutLine != nullptr) {
lv_obj_del(timeoutLine);
@ -173,7 +171,9 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
} else if (nextMessage.valid) {
currentId = nextMessage.id;
} else {
// don't update id, won't be found be refresh and try to load latest message or no message box
// don't update id, notification manager will try to fetch
// but not find it. Refresh will try to load latest message
// or dismiss to watchface
}
DismissToBlack();
return true;
@ -246,8 +246,8 @@ namespace {
Notifications::NotificationItem::NotificationItem(Pinetime::Controllers::AlertNotificationService& alertNotificationService,
Pinetime::Controllers::MotorController& motorController)
: NotificationItem("Notification",
"No notification to display",
: NotificationItem("Notifications",
"No notifications to display",
0,
Controllers::NotificationManager::Categories::Unknown,
0,

View file

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

View file

@ -57,6 +57,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Paddle(controllers.lvgl);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -51,6 +51,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Steps(controllers.motionController, controllers.settingsController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

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

View file

@ -7,6 +7,7 @@
#include "portmacro_cmsis.h"
#include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
@ -43,7 +44,7 @@ namespace Pinetime {
void Start();
void Pause();
Pinetime::System::SystemTask& systemTask;
Pinetime::System::WakeLock wakeLock;
States currentState = States::Init;
TickType_t startTime;
TickType_t oldTimeElapsed = 0;
@ -68,6 +69,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::StopWatch(*controllers.systemTask);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -8,6 +8,7 @@ namespace Pinetime {
static constexpr const char* batteryHalf = "\xEF\x89\x82";
static constexpr const char* heartBeat = "\xEF\x88\x9E";
static constexpr const char* bluetooth = "\xEF\x8A\x94";
static constexpr const char* shieldAlt = "\xEF\x8F\xAD";
static constexpr const char* plug = "\xEF\x87\xA6";
static constexpr const char* shoe = "\xEF\x95\x8B";
static constexpr const char* clock = "\xEF\x80\x97";
@ -39,6 +40,8 @@ namespace Pinetime {
static constexpr const char* eye = "\xEF\x81\xAE";
static constexpr const char* home = "\xEF\x80\x95";
static constexpr const char* sleep = "\xEE\xBD\x84";
static constexpr const char* calculator = "\xEF\x87\xAC";
static constexpr const char* backspace = "\xEF\x95\x9A";
// fontawesome_weathericons.c
// static constexpr const char* sun = "\xEF\x86\x85";

View file

@ -40,8 +40,7 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel,
const Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: app {app},
dateTimeController {dateTimeController},
: dateTimeController {dateTimeController},
batteryController {batteryController},
brightnessController {brightnessController},
bleController {bleController},
@ -196,10 +195,10 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
"#808080 SPI Flash# %02x-%02x-%02x\n"
"\n"
"#808080 Memory heap#\n"
" #808080 Free# %d\n"
" #808080 Free# %d/%d\n"
" #808080 Min free# %d\n"
" #808080 Alloc err# %d\n"
" #808080 Ovrfl err# %d\n",
" #808080 Ovrfl err# %d",
bleAddr[5],
bleAddr[4],
bleAddr[3],
@ -210,6 +209,7 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
spiFlashId.type,
spiFlashId.density,
xPortGetFreeHeapSize(),
xPortGetHeapSize(),
xPortGetMinimumEverFreeHeapSize(),
mallocFailedCount,
stackOverflowCount);
@ -241,7 +241,12 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
lv_table_set_col_width(infoTask, 3, 90);
auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr);
// g++ emits a spurious warning (and thus error because we compile with -Werror)
// due to the way std::sort is implemented
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
std::sort(tasksStatus, tasksStatus + nb, sortById);
#pragma GCC diagnostic pop
for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) {
char buffer[11] = {0};

View file

@ -35,7 +35,6 @@ namespace Pinetime {
bool OnTouchEvent(TouchEvents event) override;
private:
DisplayApp* app;
Pinetime::Controllers::DateTime& dateTimeController;
const Pinetime::Controllers::Battery& batteryController;
Pinetime::Controllers::BrightnessController& brightnessController;

View file

@ -29,9 +29,13 @@ Tile::Tile(uint8_t screenID,
Controllers::Settings& settingsController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
const Controllers::AlarmController& alarmController,
Controllers::DateTime& dateTimeController,
std::array<Applications, 6>& applications)
: app {app}, dateTimeController {dateTimeController}, pageIndicator(screenID, numScreens), statusIcons(batteryController, bleController) {
: app {app},
dateTimeController {dateTimeController},
pageIndicator(screenID, numScreens),
statusIcons(batteryController, bleController, alarmController) {
settingsController.SetAppMenu(screenID);
@ -66,8 +70,7 @@ Tile::Tile(uint8_t screenID,
lv_obj_align(btnm1, nullptr, LV_ALIGN_CENTER, 0, 10);
lv_obj_set_style_local_radius(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, 20);
lv_obj_set_style_local_bg_opa(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, LV_OPA_50);
lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt);
lv_obj_set_style_local_bg_opa(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, LV_OPA_50);
lv_obj_set_style_local_bg_color(btnm1, LV_BTNMATRIX_PART_BTN, LV_STATE_DISABLED, Colors::bgDark);
lv_obj_set_style_local_pad_all(btnm1, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 0);

View file

@ -28,6 +28,7 @@ namespace Pinetime {
Controllers::Settings& settingsController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
const Controllers::AlarmController& alarmController,
Controllers::DateTime& dateTimeController,
std::array<Applications, 6>& applications);

View file

@ -59,8 +59,8 @@ Timer::Timer(Controllers::Timer& timerController) : timer {timerController} {
lv_obj_set_event_cb(btnPlayPause, btnEventHandler);
lv_obj_set_size(btnPlayPause, LV_HOR_RES, 50);
txtPlayPause = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0);
// Create the label as a child of the button so it stays centered by default
txtPlayPause = lv_label_create(btnPlayPause, nullptr);
if (timer.IsRunning()) {
SetTimerRunning();

View file

@ -1,7 +1,6 @@
#pragma once
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "systemtask/SystemTask.h"
#include "displayapp/LittleVgl.h"
#include "displayapp/widgets/Counter.h"
@ -57,5 +56,9 @@ namespace Pinetime::Applications {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Timer(controllers.timer);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}

View file

@ -45,6 +45,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& /*controllers*/) {
return new Screens::Twos();
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -1,5 +1,6 @@
#include "displayapp/screens/WatchFaceAnalog.h"
#include <cmath>
#include <limits>
#include <lvgl/lvgl.h>
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/BleIcon.h"
@ -15,8 +16,10 @@ namespace {
constexpr int16_t MinuteLength = 90;
constexpr int16_t SecondLength = 110;
// LVGL sin isn't constexpr (though it could be if it were C++) so fix size here
// All the types are hardcoded anyway and would need changing if the size changed
// sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
constexpr int16_t LV_TRIG_SCALE = std::numeric_limits<int16_t>::max(); // = _lv_trigo_sin(90)
int16_t Cosine(int16_t angle) {
return _lv_trigo_sin(angle + 90);

View file

@ -2,6 +2,7 @@
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/WeatherSymbols.h"
@ -18,6 +19,7 @@ using namespace Pinetime::Applications::Screens;
WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
const Controllers::AlarmController& alarmController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
@ -30,7 +32,7 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
heartRateController {heartRateController},
motionController {motionController},
weatherService {weatherService},
statusIcons(batteryController, bleController) {
statusIcons(batteryController, bleController, alarmController) {
statusIcons.Create();
@ -174,13 +176,12 @@ void WatchFaceDigital::Refresh() {
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
int16_t temp = optCurrentWeather->temperature.Celsius();
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
temp = optCurrentWeather->temperature.Fahrenheit();
tempUnit = 'F';
}
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else {

View file

@ -17,6 +17,7 @@ namespace Pinetime {
class Settings;
class Battery;
class Ble;
class AlarmController;
class NotificationManager;
class HeartRateController;
class MotionController;
@ -30,6 +31,7 @@ namespace Pinetime {
WatchFaceDigital(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
const Controllers::AlarmController& alarmController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
@ -84,6 +86,7 @@ namespace Pinetime {
return new Screens::WatchFaceDigital(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.alarmController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,

View file

@ -316,7 +316,7 @@ WatchFaceInfineat::~WatchFaceInfineat() {
bool WatchFaceInfineat::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) {
lv_obj_set_hidden(btnSettings, false);
savedTick = lv_tick_get();
savedTick = xTaskGetTickCount();
return true;
}
// Prevent screen from sleeping when double tapping with settings on
@ -434,12 +434,15 @@ void WatchFaceInfineat::Refresh() {
batteryPercentRemaining = batteryController.PercentRemaining();
isCharging = batteryController.IsCharging();
if (batteryController.IsCharging()) { // Charging battery animation
chargingBatteryPercent += 1;
// Charging battery animation
if (batteryController.IsCharging() && (xTaskGetTickCount() - chargingAnimationTick > pdMS_TO_TICKS(150))) {
// Dividing 100 by the height gives the battery percentage required to shift the animation by 1 pixel
chargingBatteryPercent += 100 / lv_obj_get_height(logoPine);
if (chargingBatteryPercent > 100) {
chargingBatteryPercent = batteryPercentRemaining.Get();
}
SetBatteryLevel(chargingBatteryPercent);
chargingAnimationTick = xTaskGetTickCount();
} else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) {
chargingBatteryPercent = batteryPercentRemaining.Get();
SetBatteryLevel(chargingBatteryPercent);
@ -460,7 +463,7 @@ void WatchFaceInfineat::Refresh() {
}
if (!lv_obj_get_hidden(btnSettings)) {
if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) {
if ((savedTick > 0) && (xTaskGetTickCount() - savedTick > pdMS_TO_TICKS(3000))) {
lv_obj_set_hidden(btnSettings, true);
savedTick = 0;
}

View file

@ -45,8 +45,9 @@ namespace Pinetime {
static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
private:
uint32_t savedTick = 0;
uint8_t chargingBatteryPercent = 101; // not a mistake ;)
TickType_t savedTick = 0;
TickType_t chargingAnimationTick = 0;
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
Utility::DirtyValue<bool> isCharging {};

View file

@ -22,7 +22,7 @@
#include "displayapp/screens/WatchFacePineTimeStyle.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include <displayapp/Colors.h>
#include "displayapp/Colors.h"
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/BleIcon.h"
#include "displayapp/screens/NotificationIcon.h"
@ -407,7 +407,7 @@ bool WatchFacePineTimeStyle::OnTouchEvent(Pinetime::Applications::TouchEvents ev
if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnClose)) {
lv_obj_set_hidden(btnSetColor, false);
lv_obj_set_hidden(btnSetOpts, false);
savedTick = lv_tick_get();
savedTick = xTaskGetTickCount();
return true;
}
if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && (lv_obj_get_hidden(btnClose) == false)) {
@ -543,11 +543,10 @@ void WatchFacePineTimeStyle::Refresh() {
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
int16_t temp = optCurrentWeather->temperature.Celsius();
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
temp = optCurrentWeather->temperature.Fahrenheit();
}
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°", temp);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else {
@ -559,7 +558,7 @@ void WatchFacePineTimeStyle::Refresh() {
}
if (!lv_obj_get_hidden(btnSetColor)) {
if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) {
if ((savedTick > 0) && (xTaskGetTickCount() - savedTick > pdMS_TO_TICKS(3000))) {
lv_obj_set_hidden(btnSetColor, true);
lv_obj_set_hidden(btnSetOpts, true);
savedTick = 0;

View file

@ -52,7 +52,7 @@ namespace Pinetime {
Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
uint8_t currentDay = 0;
uint32_t savedTick = 0;
TickType_t savedTick = 0;
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
Utility::DirtyValue<bool> isCharging {};

View file

@ -0,0 +1,337 @@
#include "displayapp/screens/WatchFacePrideFlag.h"
#include <lvgl/lvgl.h>
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "displayapp/screens/Symbols.h"
using namespace Pinetime::Applications::Screens;
namespace {
void EventHandler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<WatchFacePrideFlag*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
Pinetime::Controllers::Settings::PrideFlag GetNext(Pinetime::Controllers::Settings::PrideFlag prideFlag) {
const auto prideFlagAsInt = static_cast<uint8_t>(prideFlag);
Pinetime::Controllers::Settings::PrideFlag nextFlag;
if (prideFlagAsInt < 3) {
nextFlag = static_cast<Pinetime::Controllers::Settings::PrideFlag>(prideFlagAsInt + 1);
} else {
nextFlag = static_cast<Pinetime::Controllers::Settings::PrideFlag>(0);
}
return nextFlag;
}
Pinetime::Controllers::Settings::PrideFlag GetPrevious(Pinetime::Controllers::Settings::PrideFlag prideFlag) {
const auto prideFlagAsInt = static_cast<uint8_t>(prideFlag);
Pinetime::Controllers::Settings::PrideFlag prevFlag;
if (prideFlagAsInt > 0) {
prevFlag = static_cast<Pinetime::Controllers::Settings::PrideFlag>(prideFlagAsInt - 1);
} else {
prevFlag = static_cast<Pinetime::Controllers::Settings::PrideFlag>(3);
}
return prevFlag;
}
template <std::size_t N>
class PrideFlagData {
public:
constexpr PrideFlagData(const std::array<lv_color_t, N>& sectionColours,
lv_color_t defaultTopLabelColour,
lv_color_t labelTimeColour,
lv_color_t defaultBottomLabelColour)
: sectionColours {sectionColours},
defaultTopLabelColour {defaultTopLabelColour},
labelTimeColour {labelTimeColour},
defaultBottomLabelColour {defaultBottomLabelColour} {
// Space between adjacent text values calculated according to the following equation
spacing = static_cast<uint8_t>(1.5f * static_cast<float>(N) + 40.5f);
}
std::array<lv_color_t, N> sectionColours;
lv_color_t defaultTopLabelColour;
lv_color_t labelTimeColour;
lv_color_t defaultBottomLabelColour;
uint8_t spacing;
};
constexpr lv_color_t lightBlue = LV_COLOR_MAKE(0x00, 0xbf, 0xf3);
constexpr lv_color_t lightPink = LV_COLOR_MAKE(0xf4, 0x9a, 0xc1);
constexpr lv_color_t hotPink = LV_COLOR_MAKE(0xd6, 0x02, 0x70);
constexpr lv_color_t grayPurple = LV_COLOR_MAKE(0x9b, 0x4f, 0x96);
constexpr lv_color_t darkBlue = LV_COLOR_MAKE(0x00, 0x38, 0xa8);
constexpr lv_color_t orange = LV_COLOR_MAKE(0xef, 0x76, 0x27);
constexpr lv_color_t lightOrange = LV_COLOR_MAKE(0xff, 0x9b, 0x55);
constexpr lv_color_t lightPurple = LV_COLOR_MAKE(0xd4, 0x61, 0xa6);
constexpr lv_color_t darkPurple = LV_COLOR_MAKE(0xb5, 0x56, 0x90);
constexpr lv_color_t magenta = LV_COLOR_MAKE(0xa5, 0x00, 0x62);
constexpr lv_color_t darkGreen = LV_COLOR_MAKE(0x07, 0x8d, 0x70);
constexpr lv_color_t cyan = LV_COLOR_MAKE(0x26, 0xce, 0xaa);
constexpr lv_color_t lightGreen = LV_COLOR_MAKE(0x98, 0xe8, 0xc1);
constexpr lv_color_t indigo = LV_COLOR_MAKE(0x50, 0x49, 0xcc);
constexpr lv_color_t steelBlue = LV_COLOR_MAKE(0x3d, 0x1a, 0x78);
constexpr std::array<lv_color_t, 7> gayColours {darkGreen, cyan, lightGreen, LV_COLOR_WHITE, lightBlue, indigo, steelBlue};
constexpr std::array<lv_color_t, 5> transColours {lightBlue, lightPink, LV_COLOR_WHITE, lightPink, lightBlue};
constexpr std::array<lv_color_t, 5> biColours {hotPink, hotPink, grayPurple, darkBlue, darkBlue};
constexpr std::array<lv_color_t, 7> lesbianColours {LV_COLOR_RED, orange, lightOrange, LV_COLOR_WHITE, lightPurple, darkPurple, magenta};
constexpr PrideFlagData gayFlagData(gayColours, LV_COLOR_BLACK, LV_COLOR_BLACK, LV_COLOR_WHITE);
constexpr PrideFlagData transFlagData(transColours, LV_COLOR_WHITE, LV_COLOR_BLACK, LV_COLOR_WHITE);
constexpr PrideFlagData biFlagData(biColours, LV_COLOR_BLACK, LV_COLOR_WHITE, LV_COLOR_BLACK);
constexpr PrideFlagData lesbianFlagData(lesbianColours, LV_COLOR_WHITE, LV_COLOR_BLACK, LV_COLOR_WHITE);
}
WatchFacePrideFlag::WatchFacePrideFlag(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController)
: currentDateTime {{}},
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificationManager {notificationManager},
settingsController {settingsController},
motionController {motionController} {
bluetoothStatus = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(bluetoothStatus, "");
lv_obj_align(bluetoothStatus, nullptr, LV_ALIGN_IN_TOP_RIGHT, -16, 0);
batteryValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(batteryValue, true);
lv_label_set_align(batteryValue, LV_LABEL_ALIGN_CENTER);
lv_obj_set_auto_realign(batteryValue, true);
notificationText = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(notificationText, nullptr, LV_ALIGN_IN_LEFT_MID, 0, -110);
lv_obj_set_style_local_text_color(notificationText, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
btnClose = lv_btn_create(lv_scr_act(), nullptr);
btnClose->user_data = this;
lv_obj_set_size(btnClose, 60, 60);
lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80);
lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50);
lv_obj_t* lblClose = lv_label_create(btnClose, nullptr);
lv_label_set_text_static(lblClose, "X");
lv_obj_set_event_cb(btnClose, EventHandler);
lv_obj_set_hidden(btnClose, true);
btnNextFlag = lv_btn_create(lv_scr_act(), nullptr);
btnNextFlag->user_data = this;
lv_obj_set_size(btnNextFlag, 60, 60);
lv_obj_align(btnNextFlag, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 80);
lv_obj_set_style_local_bg_opa(btnNextFlag, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50);
lv_obj_t* lblNextBG = lv_label_create(btnNextFlag, nullptr);
lv_label_set_text_static(lblNextBG, ">");
lv_obj_set_event_cb(btnNextFlag, EventHandler);
lv_obj_set_hidden(btnNextFlag, true);
btnPrevFlag = lv_btn_create(lv_scr_act(), nullptr);
btnPrevFlag->user_data = this;
lv_obj_set_size(btnPrevFlag, 60, 60);
lv_obj_align(btnPrevFlag, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 80);
lv_obj_set_style_local_bg_opa(btnPrevFlag, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_50);
lv_obj_t* lblPrevFlag = lv_label_create(btnPrevFlag, nullptr);
lv_label_set_text_static(lblPrevFlag, "<");
lv_obj_set_event_cb(btnPrevFlag, EventHandler);
lv_obj_set_hidden(btnPrevFlag, true);
labelDate = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(labelDate, true);
lv_label_set_align(labelDate, LV_LABEL_ALIGN_CENTER);
lv_obj_set_auto_realign(labelDate, true);
labelTime = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(labelTime, true);
lv_obj_align(labelTime, lv_scr_act(), LV_ALIGN_CENTER, 0, -1);
lv_label_set_align(labelTime, LV_LABEL_ALIGN_CENTER);
lv_obj_set_style_local_text_font(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
lv_obj_set_auto_realign(labelTime, true);
labelDay = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(labelDay, true);
lv_label_set_align(labelDay, LV_LABEL_ALIGN_CENTER);
lv_obj_set_auto_realign(labelDay, true);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(stepValue, true);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, 96);
lv_label_set_align(stepValue, LV_LABEL_ALIGN_CENTER);
lv_obj_set_auto_realign(stepValue, true);
UpdateScreen(settingsController.GetPrideFlag());
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFacePrideFlag::~WatchFacePrideFlag() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
bool WatchFacePrideFlag::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnClose)) {
lv_obj_set_hidden(btnPrevFlag, false);
lv_obj_set_hidden(btnNextFlag, false);
lv_obj_set_hidden(btnClose, false);
savedTick = xTaskGetTickCount();
return true;
}
if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) {
return true;
}
return false;
}
void WatchFacePrideFlag::CloseMenu() {
settingsController.SaveSettings();
lv_obj_set_hidden(btnClose, true);
lv_obj_set_hidden(btnNextFlag, true);
lv_obj_set_hidden(btnPrevFlag, true);
}
void WatchFacePrideFlag::Refresh() {
powerPresent = batteryController.IsPowerPresent();
bleState = bleController.IsConnected();
batteryPercentRemaining = batteryController.PercentRemaining();
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated() || themeChanged) {
lv_label_set_text_fmt(batteryValue, "%d%%", batteryPercentRemaining.Get());
if (batteryController.IsPowerPresent()) {
lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging");
}
}
if (bleState.IsUpdated()) {
if (bleState.Get()) {
lv_label_set_text_static(bluetoothStatus, Symbols::bluetooth);
} else {
lv_label_set_text_static(bluetoothStatus, "");
}
}
notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
if (notificationState.Get()) {
lv_label_set_text_static(notificationText, "You have\nmail!");
} else {
lv_label_set_text_static(notificationText, "");
}
}
currentDateTime = std::chrono::time_point_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated() || themeChanged) {
uint8_t hour = dateTimeController.Hours();
const uint8_t minute = dateTimeController.Minutes();
const uint8_t second = dateTimeController.Seconds();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
if (hour == 0) {
ampmChar = "AM";
hour = 12;
} else if (hour == 12) {
ampmChar = "PM";
} else if (hour > 12) {
hour = hour - 12;
ampmChar = "PM";
}
}
lv_label_set_text_fmt(labelTime, "%02d:%02d:%02d", hour, minute, second);
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated() || ampmChar.IsUpdated() || themeChanged) {
const uint16_t year = dateTimeController.Year();
const Controllers::DateTime::Months month = dateTimeController.Month();
const uint8_t day = dateTimeController.Day();
const Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek();
lv_label_set_text_fmt(labelDate, "%02d-%02d-%04d", day, static_cast<uint8_t>(month), year);
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_label_set_text_fmt(labelDay, "%s %s", dateTimeController.DayOfWeekToStringLow(dayOfWeek), ampmChar);
} else {
lv_label_set_text_fmt(labelDay, "%s", dateTimeController.DayOfWeekToStringLow(dayOfWeek));
}
}
}
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated() || themeChanged) {
lv_label_set_text_fmt(stepValue, "%lu steps", stepCount.Get());
}
if (themeChanged) {
themeChanged = false;
}
}
void WatchFacePrideFlag::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) {
auto valueFlag = settingsController.GetPrideFlag();
bool flagChanged = false;
if (object == btnClose) {
CloseMenu();
}
if (object == btnNextFlag) {
valueFlag = GetNext(valueFlag);
flagChanged = true;
}
if (object == btnPrevFlag) {
valueFlag = GetPrevious(valueFlag);
flagChanged = true;
}
settingsController.SetPrideFlag(valueFlag);
if (flagChanged) {
UpdateScreen(valueFlag);
}
}
}
bool WatchFacePrideFlag::OnButtonPushed() {
if (!lv_obj_get_hidden(btnClose)) {
CloseMenu();
return true;
}
return false;
}
void WatchFacePrideFlag::UpdateScreen(const Pinetime::Controllers::Settings::PrideFlag prideFlag) {
auto UseFlagData = [this]<size_t N>(PrideFlagData<N> flagData) {
backgroundSections.reserve(N);
for (size_t i = 0; i < N; i++) {
backgroundSections.push_back(lv_obj_create(lv_scr_act(), nullptr));
lv_obj_set_style_local_bg_color(backgroundSections[i], LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, flagData.sectionColours[i]);
lv_obj_set_size(backgroundSections[i], LV_HOR_RES, (LV_VER_RES / N) + 1);
lv_obj_set_pos(backgroundSections[i], 0, i * LV_VER_RES / N);
lv_obj_set_style_local_radius(backgroundSections[i], LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 0);
lv_obj_move_background(backgroundSections[i]);
}
lv_obj_set_style_local_text_color(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.labelTimeColour);
lv_obj_set_style_local_text_color(batteryValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultTopLabelColour);
lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultTopLabelColour);
lv_obj_set_style_local_text_color(labelDay, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultBottomLabelColour);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, flagData.defaultBottomLabelColour);
lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_CENTER, 0, -2 * flagData.spacing);
lv_obj_align(labelDate, lv_scr_act(), LV_ALIGN_CENTER, 0, -1 * flagData.spacing);
lv_obj_align(labelDay, lv_scr_act(), LV_ALIGN_CENTER, 0, flagData.spacing);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, 2 * flagData.spacing);
};
themeChanged = true;
for (lv_obj_t* backgroundSection : backgroundSections) {
lv_obj_del(backgroundSection);
}
backgroundSections.clear();
switch (prideFlag) {
case Pinetime::Controllers::Settings::PrideFlag::Gay:
UseFlagData(gayFlagData);
break;
case Pinetime::Controllers::Settings::PrideFlag::Trans:
UseFlagData(transFlagData);
break;
case Pinetime::Controllers::Settings::PrideFlag::Bi:
UseFlagData(biFlagData);
break;
case Pinetime::Controllers::Settings::PrideFlag::Lesbian:
UseFlagData(lesbianFlagData);
break;
}
}

View file

@ -0,0 +1,102 @@
#pragma once
#include <lvgl/src/lv_core/lv_obj.h>
#include <chrono>
#include <vector>
#include <cstdint>
#include <array>
#include <FreeRTOS.h>
#include "displayapp/screens/Screen.h"
#include "utility/DirtyValue.h"
#include "components/settings/Settings.h"
#include "components/battery/BatteryController.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFacePrideFlag : public Screen {
public:
WatchFacePrideFlag(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController);
~WatchFacePrideFlag() override;
bool OnTouchEvent(TouchEvents event) override;
bool OnButtonPushed() override;
void Refresh() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
private:
void UpdateScreen(Pinetime::Controllers::Settings::PrideFlag);
Utility::DirtyValue<uint8_t> batteryPercentRemaining;
Utility::DirtyValue<bool> powerPresent;
Utility::DirtyValue<bool> bleState;
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>> currentDateTime;
Utility::DirtyValue<uint32_t> stepCount;
Utility::DirtyValue<bool> notificationState;
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
// Must be wrapped in a dirty value, since it is displayed in the day but is updated twice a day
Utility::DirtyValue<const char*> ampmChar {"AM"};
TickType_t savedTick = 0;
std::vector<lv_obj_t*> backgroundSections;
bool themeChanged = false;
lv_obj_t* bluetoothStatus;
lv_obj_t* labelTime;
lv_obj_t* labelDate;
lv_obj_t* labelDay;
lv_obj_t* batteryValue;
lv_obj_t* stepValue;
lv_obj_t* notificationText;
lv_obj_t* btnClose;
lv_obj_t* btnNextFlag;
lv_obj_t* btnPrevFlag;
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::MotionController& motionController;
lv_task_t* taskRefresh;
void CloseMenu();
};
}
template <>
struct WatchFaceTraits<WatchFace::PrideFlag> {
static constexpr WatchFace watchFace = WatchFace::PrideFlag;
static constexpr const char* name = "Pride Flag";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFacePrideFlag(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.motionController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}

View file

@ -1,5 +1,7 @@
#include "displayapp/screens/Weather.h"
#include <lvgl/lvgl.h>
#include "components/ble/SimpleWeatherService.h"
#include "components/datetime/DateTimeController.h"
#include "components/settings/Settings.h"
@ -10,31 +12,27 @@
using namespace Pinetime::Applications::Screens;
namespace {
lv_color_t TemperatureColor(int16_t temperature) {
if (temperature <= 0) { // freezing
lv_color_t TemperatureColor(Pinetime::Controllers::SimpleWeatherService::Temperature temp) {
if (temp.Celsius() <= 0) { // freezing
return Colors::blue;
} else if (temperature <= 400) { // ice
} else if (temp.Celsius() <= 4) { // ice
return LV_COLOR_CYAN;
} else if (temperature >= 2700) { // hot
} else if (temp.Celsius() >= 27) { // hot
return Colors::deepOrange;
}
return Colors::orange; // normal
}
uint8_t TemperatureStyle(int16_t temperature) {
if (temperature <= 0) { // freezing
uint8_t TemperatureStyle(Pinetime::Controllers::SimpleWeatherService::Temperature temp) {
if (temp.Celsius() <= 0) { // freezing
return LV_TABLE_PART_CELL3;
} else if (temperature <= 400) { // ice
} else if (temp.Celsius() <= 4) { // ice
return LV_TABLE_PART_CELL4;
} else if (temperature >= 2700) { // hot
} else if (temp.Celsius() >= 27) { // hot
return LV_TABLE_PART_CELL6;
}
return LV_TABLE_PART_CELL5; // normal
}
int16_t RoundTemperature(int16_t temp) {
return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
}
}
Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
@ -120,22 +118,25 @@ void Weather::Refresh() {
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
int16_t minTemp = optCurrentWeather->minTemperature;
int16_t maxTemp = optCurrentWeather->maxTemperature;
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, TemperatureColor(temp));
int16_t temp = optCurrentWeather->temperature.Celsius();
int16_t minTemp = optCurrentWeather->minTemperature.Celsius();
int16_t maxTemp = optCurrentWeather->maxTemperature.Celsius();
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
temp = optCurrentWeather->temperature.Fahrenheit();
minTemp = optCurrentWeather->minTemperature.Fahrenheit();
maxTemp = optCurrentWeather->maxTemperature.Fahrenheit();
tempUnit = 'F';
}
lv_obj_set_style_local_text_color(temperature,
LV_LABEL_PART_MAIN,
LV_STATE_DEFAULT,
TemperatureColor(optCurrentWeather->temperature));
lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId));
lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp));
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
lv_label_set_text_fmt(minTemperature, "%d°", minTemp);
lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp);
} else {
lv_label_set_text(icon, "");
lv_label_set_text(condition, "");
@ -152,24 +153,22 @@ void Weather::Refresh() {
if (optCurrentForecast) {
std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp));
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
int16_t maxTemp = optCurrentForecast->days[i].maxTemperature;
int16_t minTemp = optCurrentForecast->days[i].minTemperature;
lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp));
lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp));
for (int i = 0; i < optCurrentForecast->nbDays; i++) {
int16_t maxTemp = optCurrentForecast->days[i]->maxTemperature.Celsius();
int16_t minTemp = optCurrentForecast->days[i]->minTemperature.Celsius();
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
maxTemp = optCurrentForecast->days[i]->maxTemperature.Fahrenheit();
minTemp = optCurrentForecast->days[i]->minTemperature.Fahrenheit();
}
lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(optCurrentForecast->days[i]->maxTemperature));
lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(optCurrentForecast->days[i]->minTemperature));
uint8_t wday = localTime.tm_wday + i + 1;
if (wday > 7) {
wday -= 7;
}
maxTemp = RoundTemperature(maxTemp);
minTemp = RoundTemperature(minTemp);
const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday));
lv_table_set_cell_value(forecast, 0, i, dayOfWeek);
lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId));
lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId));
// Pad cells based on the largest number of digits on each column
char maxPadding[3] = " ";
char minPadding[3] = " ";

View file

@ -51,6 +51,10 @@ namespace Pinetime {
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Weather(controllers.settingsController, *controllers.weatherController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
};
};
}
}

View file

@ -33,13 +33,14 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
Controllers::BrightnessController& brightness,
Controllers::MotorController& motorController,
Pinetime::Controllers::Settings& settingsController,
const Controllers::Ble& bleController)
const Controllers::Ble& bleController,
const Controllers::AlarmController& alarmController)
: app {app},
dateTimeController {dateTimeController},
brightness {brightness},
motorController {motorController},
settingsController {settingsController},
statusIcons(batteryController, bleController) {
statusIcons(batteryController, bleController, alarmController) {
statusIcons.Create();

View file

@ -23,7 +23,8 @@ namespace Pinetime {
Controllers::BrightnessController& brightness,
Controllers::MotorController& motorController,
Pinetime::Controllers::Settings& settingsController,
const Controllers::Ble& bleController);
const Controllers::Ble& bleController,
const Controllers::AlarmController& alarmController);
~QuickSettings() override;

View file

@ -24,8 +24,7 @@ namespace {
constexpr std::array<uint16_t, 6> SettingDisplay::options;
SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: app {app}, settingsController {settingsController} {
SettingDisplay::SettingDisplay(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} {
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr);

View file

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

View file

@ -0,0 +1,58 @@
#include "displayapp/screens/settings/SettingOTA.h"
#include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h"
#include "displayapp/Messages.h"
#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
using namespace Pinetime::Applications::Screens;
namespace {
struct Option {
const char* name;
Pinetime::Controllers::Settings::DfuAndFsMode mode;
};
constexpr std::array<Option, 3> options = {{
{"Enabled", Pinetime::Controllers::Settings::DfuAndFsMode::Enabled},
{"Disabled", Pinetime::Controllers::Settings::DfuAndFsMode::Disabled},
{"Till reboot", Pinetime::Controllers::Settings::DfuAndFsMode::EnabledTillReboot},
}};
std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
std::array<Pinetime::Applications::Screens::CheckboxList::Item, CheckboxList::MaxItems> optionArray;
for (size_t i = 0; i < CheckboxList::MaxItems; i++) {
if (i >= options.size()) {
optionArray[i].name = "";
optionArray[i].enabled = false;
} else {
optionArray[i].name = options[i].name;
optionArray[i].enabled = true;
}
}
return optionArray;
};
}
SettingOTA::SettingOTA(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: app {app},
settingsController {settingsController},
checkboxList(
0,
1,
"Firmware & files",
Symbols::shieldAlt,
settingsController.GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::Enabled ? 0
: settingsController.GetDfuAndFsMode() == Pinetime::Controllers::Settings::DfuAndFsMode::EnabledTillReboot ? 2
: 1,
[&settings = settingsController](uint32_t index) {
settings.SetDfuAndFsMode(options[index].mode);
},
CreateOptionArray()) {
}
SettingOTA::~SettingOTA() {
lv_obj_clean(lv_scr_act());
settingsController.SaveSettings();
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <array>
#include <cstdint>
#include <lvgl/lvgl.h>
#include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/CheckboxList.h"
namespace Pinetime {
namespace Applications {
namespace Screens {
class SettingOTA : public Screen {
public:
SettingOTA(DisplayApp* app, Pinetime::Controllers::Settings& settingsController);
~SettingOTA() override;
private:
DisplayApp* app;
Pinetime::Controllers::Settings& settingsController;
CheckboxList checkboxList;
};
}
}
}

View file

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

View file

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

View file

@ -8,8 +8,6 @@
using namespace Pinetime::Applications::Screens;
constexpr std::array<SettingWakeUp::Option, 5> SettingWakeUp::options;
namespace {
void event_handler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<SettingWakeUp*>(obj->user_data);
@ -45,7 +43,7 @@ SettingWakeUp::SettingWakeUp(Pinetime::Controllers::Settings& settingsController
for (unsigned int i = 0; i < options.size(); i++) {
cbOption[i] = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text(cbOption[i], options[i].name);
lv_checkbox_set_text_static(cbOption[i], options[i].name);
if (settingsController.isWakeUpModeOn(static_cast<Controllers::Settings::WakeUpMode>(i))) {
lv_checkbox_set_checked(cbOption[i], true);
}

View file

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

Some files were not shown because too many files have changed in this diff Show more