183 Commits

Author SHA1 Message Date
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
mark9064
c8236afbef Restrict debugging monitor to debug builds 2024-09-14 12:10:55 +02:00
mark9064
5040733a97 Clean unused DisplayApp messages 2024-09-14 12:10:14 +02:00
mark9064
fd019c7aad Use DirtyValue for timer 2024-09-14 11:20:47 +02:00
mark9064
975bfc5420 Size optimise NRF SDK build 2024-09-14 11:19:34 +02:00
Victor Kareh
2625ed39e5 DisplayApp: Go to clock on sleep if no app loaded
When turning off the screen, if there is no actual app loaded (i.e. we
are still in the Launcher, Notifications, QuickSettings, or Settings
screens) we should just reload the Clock app directly.
2024-08-22 17:34:25 +02:00
Derry Tutt
a3dbcd62f6 Documentation improvements (#2091)
Add documentation about watch faces and applications.
Update getting started documentation.

Co-authored-by: tituscmd <154823939+tituscmd@users.noreply.github.com>
2024-08-18 16:18:15 +02:00
mark9064
4fddf93114 Advertise HR service 2024-08-18 12:00:13 +02:00
Derry Tutt
83922fb3de Remove space before colon 2024-08-18 11:58:56 +02:00
Felipe Martínez
95917c65a5 Update main.yml 2024-08-18 11:55:03 +02:00
Jean-François Milants
3a0d673df4 Display the SPI flash JEDEC IDs in SystemInformation.
This is needed since a new memory chip will be used in future batches of PineTimes.
2024-08-05 20:34:41 +02:00
mark9064
53dc9dafe7 aod: simplify AOD disablement based on notification status 2024-08-05 20:32:43 +02:00
mark9064
a407902b06 aod: avoid spinning DisplayApp under high LVGL load 2024-08-05 20:32:43 +02:00
mark9064
3e8accde69 aod: run LVGL task handler until all work finished 2024-08-05 20:32:43 +02:00
mark9064
2bb611db8e aod: constant frequency idle frames 2024-08-05 20:32:43 +02:00
mark9064
ef88e8165c aod: porch control: 2Hz idle + 75Hz on 2024-08-05 20:32:43 +02:00
mark9064
da9ab4a7b4 aod: lower lcd voltage 2024-08-05 20:32:43 +02:00
John Crawford
0bcd7e0009 aod: lower voltage going to the display 2024-08-05 20:32:43 +02:00
mark9064
bf69e0dcc5 aod: fix flashlight brightness restore 2024-08-05 20:32:43 +02:00
mark9064
947c4f5067 aod: fix brightness getting stuck high 2024-08-05 20:32:43 +02:00
John Crawford
0960d67001 aod: lower refresh rate when always on 2024-08-05 20:32:43 +02:00
John Crawford
5385f7e275 aod: switch to 8 colors when always on 2024-08-05 20:32:43 +02:00
John Crawford
e884b053d3 aod: disable while in notification sleep 2024-08-05 20:32:43 +02:00
John Crawford
85a2181b64 aod: integrate with display timeout 2024-08-05 20:32:43 +02:00
mark9064
3dca742b65 aod: PPI/RTC-based backlight brightness 2024-08-05 20:32:43 +02:00
KaffeinatedKat
20ac7e8df3 feat: always on display 2024-08-05 20:32:43 +02:00
mark9064
f8f8993fac Batch display command arguments 2024-06-19 17:25:15 +02:00
Max Buchholz
fdc3b8badb README: Change links from Pine wiki to new documentation
As the Wiki was replaced by a new documentation website and is read-only, we should update the links, to point to the most up-to-date documentation.
2024-06-15 12:00:37 +02:00
Jean-François Milants
f9a16feeaf Continuous time updates
Add TODO.md in src/components/datetime. This file give detailed information about a refactoring of the DateTimeController that would be nice to do in the future.
2024-06-09 18:34:07 +02:00
mark9064
4930c0cab7 Include assert (fix sim) 2024-06-09 18:34:07 +02:00
mark9064
a449b272f7 Continuous time updates 2024-06-09 18:34:07 +02:00
Felipe Martínez
9e406c70f9 Remove unnecessary BMA421 reads 2024-06-02 15:48:26 +02:00
Felipe Martinez
0c87bc27b2 Move motorController.Init call to DisplayApp::Start 2024-06-02 15:14:02 +02:00
mark9064
0dcfb2edb7 Fix erratum 58 workaround 2024-05-12 13:57:12 +02:00
mark9064
06c6935315 Include task header (Fixes sim) 2024-05-01 16:17:59 +02:00
mark9064
47c104643d Move includes back 2024-05-01 16:17:59 +02:00
mark9064
7b1110187e Apply display driver datasheet delays 2024-05-01 16:17:59 +02:00
mark9064
7e460d3c80 Use FreeRTOS delay instead of spinning the CPU 2024-05-01 16:17:59 +02:00
mark9064
7a9211587a Rename to pre-transaction hook 2024-05-01 16:13:47 +02:00
mark9064
24e6a2f8ab Avoid storing lambda 2024-05-01 16:13:47 +02:00
mark9064
9a7ba405e1 Refactor lambdas 2024-05-01 16:13:47 +02:00
mark9064
940cd3459f Use functional abstraction for hooks 2024-05-01 16:13:47 +02:00
mark9064
869bec8f88 Refactor display WriteToRam 2024-05-01 16:13:47 +02:00
mark9064
ee925200c3 Remove task to notify 2024-05-01 16:13:47 +02:00
mark9064
079e676baf SPI transaction hooks 2024-05-01 16:13:47 +02:00
John Crawford
6b5235c301 fix: heartrate app displays --- instead of 000 (#1887) 2024-04-12 14:50:33 +02:00
BloodStainedCrow
57e625d4dc Unify docker devcontainer with dockerfile used for CI (#1587)
* Only use one Dockerfile and build.sh script for both docker and devcontainer
* Remove all now unneccessary tasks and scripts
* Update to clang-format-14
* Move devcontainer.json into root folder
* Fix conditional statements in Dockerfile
* Move .devcontainer/README into doc/usingDevcontainers
* Remove obsolete VSCode Task
* Change standard compiler path to the correct compiler
* Set GDB Path for debugging
* Hide broken buttons from CMake Extension
* Refactor .devcontainer
* Remove unneccessary postBuildCommand
* Add devcontainer dependencies to all docker images
* Add Devcontainer Debug launch config
* Add an additional c_cpp_properties config as a fallback for devcontainer
* Remove obsolete Docker Argument
* Fix wrong C/Cpp versions
* Fix silent fail of gdb, add libncurses5
2024-03-23 10:45:45 +01:00
Victor Kareh
9a5f516c42 WatchFaceDigital: Remove unused variables 2024-03-20 20:31:35 +01:00
JF
4ca2112890 Emit the message BleRadioEnableToggle to DisplayApp only if the enable state of the radio has actually changed. (#2037)
This fixes an issue where the BLE connected logo would disappear when opening and closing the BLE setting (without changing it) while InfiniTime was already connected to a companion app.

Co-authored-by: JustScott <development@justscott.me>
2024-03-15 09:20:19 +01:00
FintasticMan
7dbb8f54c6 chrono: Resolve TODOs related to C++20 chrono feats 2024-03-14 21:09:03 +01:00
adumelie
14c6270903 Typo in example code 2024-03-14 20:27:19 +01:00
adumelie
ef38472278 More typos in code example 2024-03-14 20:27:19 +01:00
adumelie
4c9d5332c7 Typo in code example 2024-03-14 20:27:19 +01:00
adumelie
9aa0bc8ecf Fix and update documentation
Fixed missing colon typo in template for minimal app example
Updated reference to Apps enum after file move
2024-03-14 20:27:19 +01:00
Ritvik
4d86a3c6c1 Fix conflicts of min and max from stl_algo.h (included from atomic) 2024-03-13 21:58:13 +01:00
Victor Kareh
3b4b5a5163 ApplicationList: Reset app menu screen when loading watch face
This prevents the application list from loading in the last used screen
and instead goes back to the first screen whenever the watch face is
loaded.

Fixes #2006
2024-03-12 21:01:42 +01:00
FintasticMan
f2df0c45ef lowersleep: Improve algorithm by checking wrist angle
Inspired by https://github.com/InfiniTimeOrg/InfiniTime/pull/827#issuecomment-1881580414.
2024-03-12 19:14:58 +01:00
mark9064
636af4d33d Simplify ISR task wake checks
The macro checks the variable, so we don't need to check it ourselves
2024-03-12 19:09:01 +01:00
mark9064
70f6604878 Fix chimes 2024-03-12 19:08:17 +01:00
Victor Kareh
004b2bf3a0 README: Fix broken URLs 2024-02-25 13:17:19 +01:00
Victor Kareh
6ab512a6b6 weather: Define function to round and render temperature 2024-02-18 12:57:48 +01:00
Victor Kareh
68ae335a97 weather: Pad forecast temperatures
This ensures temperatures are correctly aligned with one another
2024-02-18 12:57:48 +01:00
Victor Kareh
1857b02efa weather: Colorize forecast temperatures 2024-02-18 12:57:48 +01:00
Victor Kareh
f422929d8c weather: Add new app with forecast 2024-02-18 12:57:48 +01:00
Victor Kareh
5d971690cb DateTimeController: Make DayOfWeekShortToStringLow static
This allows it to be used outside of the current datetime context and
makes it consistent with the MonthShortToStringLow function.
2024-02-18 12:57:48 +01:00
Tim Teichmann
b0a0afdd4b README: Make hex and UTF-8 code consistent for the chosen example 2024-02-15 21:20:33 +01:00
mark9064
93d7da1691 Fix OOB flash write when DFU interrupted
If a DFU is restarted, the write indices aren't reset causing the image to be written out of bounds. The CRC check prevents the faulty image from booting but LittleFS still gets nuked.
2024-02-14 19:12:08 +01:00
Victor Kareh
fa1af55a45 Alarm: Replace clock icon with bell 2024-02-12 16:08:40 +01:00
Graham Jones
c2c53bc6ab bma421: Change acceleration values to 'binary milli-G' units
Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
2024-02-11 20:46:26 +01:00
Victor Kareh
a49dc15a6e WatchFaceDigital: Move weather widget away from status icons
The weather widget is too high and could overlap the status icons.
Moving it to match the rest of the face avoids this issue and makes it
align with the rest of the theme.
2024-02-11 14:13:17 +01:00
mark9064
4c274421d9 Remove GFX (and unused GFX dependencies) 2024-02-11 13:28:01 +01:00
Victor Kareh
2db920599e SimpleWeatherService: Add forecast operator overrides (#2011)
Any screen that relies on DirtyValue to display up-to-date forecast data
would require the struct to provide an operator override for comparison.
2024-02-10 19:24:46 +01:00
kieranc
44be356dc2 navigation: Missing character for font 2024-02-07 08:46:09 +01:00
Victor Kareh
2135e12b33 WatchFaceDigital: Add weather display
If weather is available, display the cloud icon and temperature.
2024-01-23 23:39:28 +01:00
apilat
a6cd3679eb Only inhibit sleep if motion notifications are enabled, not just Bluetooth 2024-01-23 14:58:21 +01:00
apilat
074df0526f Keep updating motion during sleep when Bluetooth is on 2024-01-23 14:58:21 +01:00
Yusuf Ebrahim
a40168a9d7 New dice-rolling app: InfiniDice! (#1326)
Add new App `Dice.h` to randomly roll the dice(s).
The number of dice can range from 1-9 (default 1), and the sides can
range from d2-d99 (default d2).

To have a haptic feedback we make Dice vibrate on roll.

Regarding the use of C++ `<random>` library:
There are known problems with `rand()` and `srand()` (see https://en.cppreference.com/w/cpp/numeric/random/rand)
and the `<random>` library is preferred for this reason. The function used from
`<random>` also avoids a very rare bias that would occur using `rand()` and modulo,
when `RAND_MAX` is not a multiple of `d` and the initially generated number falls in
the last "short" segment. This commit also updates the seed to derive entropy
(via `seed_seq`) from a mix of the system tick count and the x,y,z components of the
PineTime motion controller -- taking inspiration from and with credit to @w4tsn
(https://github.com/InfiniTimeOrg/InfiniTime/pull/1199)

Thanks for suggestions:
* in Dice, when rolling 1d2, also show "HEADS" or "TAILS" -- suggestion by @medeyko
* ui adjustments and result realignment -- suggestion by @Boteium

---------

Co-authored-by: NeroBurner <pyro4hell@gmail.com>
Co-authored-by: Riku Isokoski <riksu9000@gmail.com>
Co-authored-by: Paul Weiß <45500341+Poohl@users.noreply.github.com>
Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
2024-01-23 09:45:52 +01:00
Reinhold Gschweicher
a481af06cf lv_img_conv: support other modes like 'P'
Support other image modes like `P`, which uses 8 bits per pixel and a
color palette to save space.

Luckily the Pillow module can do the mode conversion for us.

Fixes: https://github.com/InfiniTimeOrg/InfiniTime/issues/1985
2024-01-23 08:51:14 +01:00
Lennart Jahn
034d83fe6b Split declaration and implementation of GetSymbol 2024-01-16 22:34:51 +01:00
Victor Kareh
264b5bed43 WatchFacePineTimeStyle: Fix conditional in weather display (#1965)
Since returning a valid weather is always considered an updated value,
if the current weather is empty, the face will attempt to display the
temperature and icon as empty values, rather than clearing the labels.
2024-01-14 22:37:26 +01:00
FintasticMan
0503248a25 hook: Find correct clang-format version better 2024-01-12 14:42:53 +01:00
FintasticMan
c634a4e3b4 hook: Use clang-format with highest version 2024-01-12 14:42:53 +01:00
FintasticMan
ab8e267e28 hook: Update pre-commit git hook 2024-01-12 14:42:53 +01:00
Jean-François Milants
ecf2f564f7 Set version to 1.14.0 2024-01-06 15:21:29 +01:00
Jean-François Milants
6505336d60 CMake watch faces selection
Documentation : watch faces are not system apps anymore.

Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
2024-01-06 14:44:10 +01:00
Jean-François Milants
25b3e2461d CMake watch faces selection
Improve wording and replace "watchface" by "watch face" in Apps.md.
Improve CMake readability regarding watch face selection

Co-authored-by: Reinhold Gschweicher <pyro4hell@gmail.com>
2024-01-06 14:44:10 +01:00
Jean-François Milants
72c992c84e Watch face selection using CMake
Update Apps.md to mention the selection of watchfaces using Cmake.
2024-01-06 14:44:10 +01:00
Jean-François Milants
22f6d4a40b Watch face selection using CMake
The list of watch face to build into the firmware is now set by CMake (-DENABLE_WATCHFACES).

Fix SettingWatchFace : convert to index to/from WatchFace when needed.
2024-01-06 14:44:10 +01:00
Reinhold Gschweicher
12acef6a71 apps: restore default apps ordering in CMake
Restore the default list of apps to compile. The ordering was changed in
the changeset to make the app-list configurable through a CMake-variable
in https://github.com/InfiniTimeOrg/InfiniTime/pull/1928

In the process have one app per line to create the default app list in
CMake. This makes git diffs easer and more readable.
2024-01-06 12:38:36 +01:00
Jean-François Milants
f34aede897 Simple Weather Service : Fix timestamp
Wording improvements in doc.

Co-authored-by: Reinhold Gschweicher <pyro4hell@gmail.com>
2024-01-04 13:00:01 +01:00
Jean-François Milants
95e2f9e701 Simple Weather Service : Fix timestamp
Mention that the timestamp should be expressed in localtime time.
2024-01-04 13:00:01 +01:00
Jean-François Milants
b4ff1f9ca2 Simple Weather Service : Fix timestamp
In the documentation, specify that the timestamp is expressed in seconds from epoch (instead of nanoseconds).
SimpleWeatherService now uses "localtime" (GetCurrentDateTime()) instead of UTC time.
2024-01-04 13:00:01 +01:00
FintasticMan
9b36afc787 pinetimestyle: Round the displayed temperature
Instead of truncating. This fixes the displayed temperature being 1
degree lower than expected when using GadgetBridge.
2024-01-03 21:01:53 +01:00
FintasticMan
3f51923c3b docs: Fix simple weather forecast message type 2024-01-03 20:58:58 +01:00
liamcharger
82db3dcd18 Update incorrect icon in weather settings 2023-12-30 20:11:13 +01:00
liamcharger
ab8515f766 PineTimeStyle: fix brief display of weather dummy data 2023-12-30 20:09:10 +01:00
liamcharger
f17bbc38da Remove 'Looking for Maintainer' 2023-12-27 20:59:57 +01:00
Derry Tutt
02a7a3a45f Update README.md 2023-12-23 21:45:57 +01:00
Derry Tutt
b0f1a49775 Typo fixed 2023-12-23 21:45:57 +01:00
FintasticMan
e88d0afabf pinetimestyle: Display temp in Fahrenheit with setting 2023-12-23 21:42:16 +01:00
FintasticMan
d889f3e444 settings: Add settings item for weather format 2023-12-23 21:42:16 +01:00
FintasticMan
c04813b6d3 weather: Add function for temperature in Fahrenheit 2023-12-23 21:42:16 +01:00
Jean-François Milants
fc5424cb72 User applications selection using CMake
Fix include path since last rebase.
2023-12-23 21:29:13 +01:00
Jean-François Milants
2ee2b8523a User applications selection using CMake
Fix typos in Apps.md and add new line in src/displayapp/apps/CMakeLists.txt
2023-12-23 21:29:13 +01:00
JF
06171dad38 Fix trailing space in doc/code/Apps.md
Co-authored-by: NeroBurner <pyro4hell@gmail.com>
2023-12-23 21:29:13 +01:00
Jean-François Milants
7794378e96 CMake user application selection
Move ENABLE_USERAPPS and USERAPP_TYPES from the root CMake file to src/displayapp/apps/CMakeLists.txt so we do not need to repeat it in InfiniSim
2023-12-23 21:29:13 +01:00
Jean-François Milants
f0e8bb26e9 Watch face selection with CMake
Move displayapp/Apps.h into a header only library (to make the integration easier in InfiniSim.
2023-12-23 21:29:13 +01:00
Jean-François Milants
1ad7840072 Watch face selection at build time
Replace the options that allowed to select the user apps independently by a single string variable that contains the ordered list of apps to build.
2023-12-23 21:29:13 +01:00
Jean-François Milants
f2996f54a8 CMake User Applications selection
Update documentation about building a new application and add instructions to add the app in CMake files.
2023-12-23 21:29:13 +01:00
Jean-François Milants
e00b98b82c CMake User Applications selection
Revert changes that need "return PROPAGATE" since this is not available in our Docker build (it needs CMake 3.25 and we have 3.22).
2023-12-23 21:29:13 +01:00
Jean-François Milants
2bc9318451 CMake User Applications selection
Use CMake's configure_file() functionality to generate the list of User Applications.

All the apps included in current versions of InfiniTime are enabled by default, but this can now be overridden by setting variables ENABLE_APP_XXX to True or False.

CMake CMP0140 is set to NEW to enable the return PROPAGATE functionality.
2023-12-23 21:29:13 +01:00
Jean-François Milants
ca7d8a668d Simple Weather Service
Fix ToUInt64() in SimpleWeatherService.cpp.
Fix typo in SimpleWeatherService.md.
2023-12-23 21:12:25 +01:00
Jean-François Milants
d56df38aeb Simple Weather Service
Fix typo in doc/ble.md.
2023-12-23 21:12:25 +01:00
Jean-François Milants
6f83a3bade Simple Weather Service
Fix code formatting.
2023-12-23 21:12:25 +01:00
Jean-François Milants
e5b73212f6 Simple Weather Service
Store temperatures as int16_t (instead of uint8_t previously). The temperature is expressed in °C * 100.
2023-12-23 21:12:25 +01:00
Jean-François Milants
ad090ab188 Simple Weather Service
Move the function GetIcon that converts SimpleWeatherService::Icons to char (symbol) into a new header file so that it can be used by other apps and companion apps.
2023-12-23 21:12:25 +01:00
Jean-François Milants
ef2c431569 Simple Weather Service
Code improvements : icon fields are now typed as Icons, move the location string when creating a new instance of CurrentWeather, fix SimpleWeatherService::CurrentWeather::operator== (location was missing from the comparison).
2023-12-23 21:12:25 +01:00
Jean-François Milants
199aefc617 Simple Weather Service - code cleaning and improvements
Rename Symbols::cloud_meatball to Symbols::cloudMeatball.
2023-12-23 21:12:25 +01:00
Jean-François Milants
d29eb1ea99 Simple Weather Service - code cleaning and improvements
Remove unused Weather debug app.
Fix formatting in SimpleWeatherService.cpp.
2023-12-23 21:12:25 +01:00
Jean-François Milants
3a8c7dc038 Simple Weather Service - code cleaning and improvements
Add missing icons (heavy clouds, thunderstorm, snow).
Remove unneeded comparison operator (!=), improve conversion of Timestamp and MessageType, order includes.
Fix typo in documentation.
Remove not related change in StopWatch.
2023-12-23 21:12:25 +01:00
Jean-François Milants
fe4b07c610 Simple Weather Service : fix out of bounds access while creating Forecast instance. 2023-12-23 21:12:25 +01:00
Jean-François Milants
50c679023f SimpleWeather service : new weather implementation
Fix recovery firmware and code formatting.
2023-12-23 21:12:25 +01:00
Jean-François Milants
c94a59e7d3 SimpleWeather service : new weather implementation
This new implementation of the weather feature provides a new BLE API and a new weather service.
The API uses a single characteristic that allows companion apps to write the weather conditions (current and forecast for the next 5 days).
The SimpleWeather service exposes those data as std::optional fields.

This new implementation replaces the previous WeahterService.

The API is documented in docs/SimpleWeatherService.md.
2023-12-23 21:12:25 +01:00
Jean-François Milants
088082d32d Watch face selection at build time
Do not assign specific values to the fields of the enum WatchFace.
2023-12-21 20:49:22 +01:00
Jean-François Milants
39bc166e54 Watch face selection at build time
Watch faces can now be selected at buid time. It's implemented in a similar way than the selection of user apps, using a list of watch face description that is generated at build time (consteval, constexpr)
2023-12-21 20:49:22 +01:00
Reinhold Gschweicher
a544da9ed1 cmake: update the TARGET_DEVICE selection choices as well
The properties are used for CMake-GUI configuration applications to
provide a set of values by dropdown.
2023-12-16 11:43:31 +01:00
FintasticMan
69b9d30a30 p8: Fix build when building for P8 variants
Building with a TARGET_DEVICE set to any of the P8 variants' names
caused the build to fail, because they contained hyphens.
The build defines a macro `TARGET_DEVICE_$VARIANT`, which fails if
`$VARIANT` contains a hyphen.
2023-12-16 11:43:31 +01:00
Jean-François Milants
d79766bccd Allow zero app to be selected at build time
Fix 'nScreens' calculation in ApplicationList so that we can build the project with zero user app selected.
2023-12-10 18:16:41 +01:00
mark9064
80607282dd Update VS Code config to C++20 2023-12-10 12:02:42 +01:00
mark9064
54b4750c6f Apply suggestion 2023-12-09 21:20:21 +01:00
mark9064
41a4813c8b Fix calculation of number of app screens 2023-12-09 21:20:21 +01:00
Mingjie Shen
42fcb99b38 StopWatch: Add an extra space to the string without the hour
so that it's just as long as with the hour.
2023-12-01 08:52:42 +01:00
Mingjie Shen
cd8216d1c9 SettingDisplay: Reduce buffer size
Given that 2^16 / 1000 is 65, we can make the buffer only 3 chars.
2023-12-01 08:52:42 +01:00
Mingjie Shen
c9fbcd8818 Fix potential buffer overflows when calling sprintf
1. Replace sprintf with snprintf, which is safer
2. An unsigned int or unsigned long int requires 11 bytes to print
   (including the null terminator)
3. Use PRIu16 macro to print uint16_t
4. Format string "#%2d %2d:%02d:%02d.%02d\n" in
   StopWatch::stopLapBtnEventHandler() requires at least 17 bytes.
   The 16-byte buffer would clearly be overrun if sprintf were used.
2023-12-01 08:52:42 +01:00
FintasticMan
e89e5e4d66 apps: Restore app list order
The build-time selection PR updated the order, this restores what it was
before.
2023-11-29 17:17:16 +01:00
Jean-François Milants
f3d4f04827 Application selection at build time : update documentation
Update documentation about the apps (Apps.md) : fix obsolete information, add doc about user/system apps and update the part about the implementation of a new app.
2023-11-19 21:13:55 +01:00
Jean-François Milants
473d9c4fa4 Fix code formatting 2023-11-19 21:13:55 +01:00
Jean-François Milants
97ba1a456d Add missing new line at end of file. 2023-11-19 21:13:55 +01:00
Jean-François Milants
02af09d943 Application selection at build time
Fix DisplayAppRecovery so it builds with -std=c++20.
2023-11-19 21:13:55 +01:00
Jean-François Milants
63e0c4f4ef Application selection at build time
A list of "user applications" is built at compile time. It contains all the info needed to create the application at runtime (ptr to a create() function) and to display the app in the application menu. All applications declare a TypeTrait with these information.
When a new app must be loaded, DisplayApp first check if this app is a System app (in which case it creates it like it did before). If it's not a System app, it looks for the app in the list of User applications and creates it if it found it.
Those changes allow to more easily add new app and to select which app must be built into the firmware.
Switch to C++20 (and fix a few issues in SpiMaster.cpp and Watchdog.cpp.
2023-11-19 21:13:55 +01:00
Jean-François Milants
f6d7f602f5 Fix deadlock when a lot of notifications are received in a very short time span.
When a notification is received, SystemTask sends messages to DisplayApp, which loads the Notifications apps that sends a few messages to SystemApp.
When notification are received too quickly, DisplayApp and SystemTask cannot process those messages fast enough (probably because of the time it takes to refresh the display) and the message queues fill up. When they are full, the current implementation just waits until there's room available to store the event. In this case, since both tasks exchange messages, they end up in a deadlock.

This fix consists in setting the timeout value to 0 (non-blocking mode) for the NewNotification messages on the DisplayApp side. This will prevent the SystemTask from being blocked (which would result in the watchdog reseting the watch). A more generic approach should be design in the future.
2023-11-19 20:58:29 +01:00
Alex E
930284adc2 docs: Update Apps.md to remove Screen param (#1752)
This is needed since #1629.

Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
2023-11-16 18:46:25 +01:00
Ben Merritt
9b8eb75f34 docker: Install Node.js in a non-deprecated way (#1849) 2023-11-12 14:09:41 +01:00
Kieran Cawthray
b191a30947 Tidy up 2023-11-11 18:07:07 +01:00
Kieran Cawthray
d930fd4fa2 Initial commit 2023-11-11 18:07:07 +01:00
Reinhold Gschweicher
e6b96c2863 CI: install build resource dependency python3-pil package
Used by script `lv_img_conv.py`, should be provided by docker image, but
until then explicitly install in workflow.
2023-10-26 22:45:01 +02:00
Reinhold Gschweicher
77546c9fe2 lv_img_conv_py: minimal python port of node module
Create a minimal python port of the node.js module `lv_img_conv`. Only
the currently in use color formats `CF_INDEXED_1_BIT` and
`CF_TRUE_COLOR_ALPHA` are implemented.

Output only as binary with format `ARGB8565_RBSWAP`.

This is enough to create the `resources-1.13.0.zip`.

Python3 implements "propper" "banker's rounding" by rounding to the nearest
even number. Javascript rounds to the nearest integer.
To have the same output as the original JavaScript implementation add a custom
rounding function, which does "school" rounding (to the nearest integer)

Update CMake file in `resources` folder to call `lv_img_conf.py` instead of
node module.

For docker-files install `python3-pil` package for `lv_img_conv.py` script.
And remove the `lv_img_conv` node installation.

---

gen_img: special handling for python lv_img_conv script

Not needed on Linux systems, as the shebang of the python script is read
and used. But just to be sure use the python interpreter found by CMake.
Also helps if tried to run on Windows host.

---

doc: buildAndProgram: remove node script lv_img_conv mention

Remove node script `lv_img_conv` mention and replace it for
runtime-depency `python3-pil` of python script `lv_img_conv.py`.
2023-10-26 22:45:01 +02:00
FintasticMan
eac460f030 weather: Fix GetCurrent* functions returning future events (#1879) 2023-10-06 19:54:20 +02:00
Steve Amor
46b664b528 Corrects typo for make option for recovery-loader 2023-10-04 20:24:37 +02:00
201 changed files with 4324 additions and 3554 deletions

32
.devcontainer.json Normal file
View File

@@ -0,0 +1,32 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp
{
"build": {
"dockerfile": "docker/Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
// Set *default* container specific settings.json values on container create.
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash"
}
},
"terminal.integrated.defaultProfile.linux": "bash",
"editor.formatOnSave": true,
// FIXME: This and the Dockerfile might get out of sync
"clang-format.executable": "clang-format-14"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-vscode.cpptools",
"ms-vscode.cmake-tools",
"marus25.cortex-debug",
"notskm.clang-tidy",
"mjohns.clang-format"
]
}
},
"remoteUser": "infinitime"
}

View File

@@ -1,65 +0,0 @@
FROM ubuntu:latest
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq \
&& apt-get install -y \
# x86_64 / generic packages
bash \
build-essential \
cmake \
git \
make \
python3 \
python3-pip \
tar \
unzip \
wget \
curl \
dos2unix \
clang-format-12 \
clang-tidy \
locales \
libncurses5 \
# aarch64 packages
libffi-dev \
libssl-dev \
python3-dev \
rustc \
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
#SET LOCALE
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN pip3 install adafruit-nrfutil
# required for McuBoot
RUN pip3 install setuptools_rust
WORKDIR /opt/
# build.sh knows how to compile but it problimatic on Win10
COPY build.sh .
RUN chmod +x build.sh
# create_build_openocd.sh uses cmake to crate to build directory
COPY create_build_openocd.sh .
RUN chmod +x create_build_openocd.sh
# Lets get each in a separate docker layer for better downloads
# GCC
# RUN bash -c "source /opt/build.sh; GetGcc;"
RUN wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -O - | tar -xj -C /opt
# NrfSdk
# RUN bash -c "source /opt/build.sh; GetNrfSdk;"
RUN wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip" -O /tmp/nRF5_SDK_15.3.0_59ac345
RUN unzip -q /tmp/nRF5_SDK_15.3.0_59ac345 -d /opt
RUN rm /tmp/nRF5_SDK_15.3.0_59ac345
# McuBoot
# RUN bash -c "source /opt/build.sh; GetMcuBoot;"
RUN git clone https://github.com/mcu-tools/mcuboot.git
RUN pip3 install -r ./mcuboot/scripts/requirements.txt
RUN adduser infinitime
ENV NRF5_SDK_PATH /opt/nRF5_SDK_15.3.0_59ac345
ENV ARM_NONE_EABI_TOOLCHAIN_PATH /opt/gcc-arm-none-eabi-9-2020-q2-update
ENV SOURCES_DIR /workspaces/InfiniTime

View File

@@ -1,88 +0,0 @@
#!/bin/bash
(return 0 2>/dev/null) && SOURCED="true" || SOURCED="false"
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
set -x
set -e
# Default locations if the var isn't already set
export TOOLS_DIR="${TOOLS_DIR:=/opt}"
export SOURCES_DIR="${SOURCES_DIR:=/sources}"
export BUILD_DIR="${BUILD_DIR:=$SOURCES_DIR/build}"
export OUTPUT_DIR="${OUTPUT_DIR:=$BUILD_DIR/output}"
export BUILD_TYPE=${BUILD_TYPE:=Release}
export GCC_ARM_VER=${GCC_ARM_VER:="gcc-arm-none-eabi-9-2020-q2-update"}
export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"}
MACHINE="$(uname -m)"
[[ "$MACHINE" == "arm64" ]] && MACHINE="aarch64"
main() {
local target="$1"
mkdir -p "$TOOLS_DIR"
[[ ! -d "$TOOLS_DIR/$GCC_ARM_VER" ]] && GetGcc
[[ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]] && GetNrfSdk
[[ ! -d "$TOOLS_DIR/mcuboot" ]] && GetMcuBoot
mkdir -p "$BUILD_DIR"
CmakeGenerate
CmakeBuild $target
BUILD_RESULT=$?
if [ "$DISABLE_POSTBUILD" != "true" -a "$BUILD_RESULT" == 0 ]; then
source "$BUILD_DIR/post_build.sh"
fi
# assuming post_build.sh will never fail on a successful build
return $BUILD_RESULT
}
GetGcc() {
GCC_SRC="$GCC_ARM_VER-$MACHINE-linux.tar.bz"
wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/$GCC_SRC -O - | tar -xj -C $TOOLS_DIR/
}
GetMcuBoot() {
git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot"
pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt"
}
GetNrfSdk() {
wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER
unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/"
rm /tmp/$NRF_SDK_VER
}
CmakeGenerate() {
# We can swap the CD and trailing SOURCES_DIR for -B and -S respectively
# once we go to newer CMake (Ubuntu 18.10 gives us CMake 3.10)
cd "$BUILD_DIR"
cmake -G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \
-DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
-DBUILD_RESOURCES=0
"$SOURCES_DIR"
cmake -L -N .
}
CmakeBuild() {
local target="$1"
[[ -n "$target" ]] && target="--target $target"
if cmake --build "$BUILD_DIR" --config $BUILD_TYPE $target -- -j$(nproc)
then return 0; else return 1;
fi
}
if [[ $SOURCED == "false" ]]; then
# It is important to return exit code of main
# To be future-proof, this is handled explicitely
main "$@"
BUILD_RESULT=$?
exit $BUILD_RESULT
else
echo "Sourced!"
fi

View File

@@ -1,2 +0,0 @@
#!/bin/bash
cmake --build /workspaces/Pinetime/build --config Release -- -j6 pinetime-app

View File

@@ -1,3 +0,0 @@
#!/bin/bash
rm -rf build/
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 -S . -Bbuild

View File

@@ -1,38 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp
{
// "name": "Pinetime",
// "image": "feabhas/pinetime-dev"
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick an Debian / Ubuntu OS version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04
// "args": { "VARIANT": "ubuntu-20.04" }
},
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"editor.formatOnSave": true,
"clang-format.executable": "clang-format-12"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-vscode.cpptools",
"ms-vscode.cmake-tools",
"marus25.cortex-debug",
"notskm.clang-tidy",
"mjohns.clang-format"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "bash /opt/create_build_openocd.sh",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
// "remoteUser": "vscode"
"remoteUser": "infinitime"
}

View File

@@ -1,2 +0,0 @@
#!/bin/bash
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 ${SOURCES_DIR}

View File

@@ -3,7 +3,7 @@ name: CI
# Run this workflow whenever the build may be affected # Run this workflow whenever the build may be affected
on: on:
push: push:
branches: [ main, wb/fuzzy-clock ] branches: [ main ]
paths-ignore: paths-ignore:
- 'doc/**' - 'doc/**'
- '**.md' - '**.md'
@@ -31,6 +31,10 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Install resource build dependencies
run: |
apt-get update
apt-get -y install --no-install-recommends python3-pil
- name: Build - name: Build
shell: bash shell: bash
run: /opt/build.sh all run: /opt/build.sh all
@@ -42,20 +46,30 @@ jobs:
# Unzip the package because Upload Artifact will zip up the files # Unzip the package because Upload Artifact will zip up the files
- name: Unzip DFU package - name: Unzip DFU package
run: unzip ./build/output/pinetime-mcuboot-app-dfu-*.zip -d ./build/output/pinetime-mcuboot-app-dfu 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 - name: Upload DFU artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: InfiniTime DFU ${{ github.head_ref }} name: InfiniTime DFU ${{ env.REF_NAME }}
path: ./build/output/pinetime-mcuboot-app-dfu/* path: ./build/output/pinetime-mcuboot-app-dfu/*
- name: Upload MCUBoot image artifacts - name: Upload MCUBoot image artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: InfiniTime MCUBoot image ${{ github.head_ref }} name: InfiniTime MCUBoot image ${{ env.REF_NAME }}
path: ./build/output/pinetime-mcuboot-app-image-*.bin path: ./build/output/pinetime-mcuboot-app-image-*.bin
- name: Upload standalone ELF artifacts
uses: actions/upload-artifact@v3
with:
name: InfiniTime image ${{ env.REF_NAME }}
path: ./build/output/src/pinetime-app-*.out
- name: Upload resources artifacts - name: Upload resources artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: InfiniTime resources ${{ github.head_ref }} name: InfiniTime resources ${{ env.REF_NAME }}
path: ./build/output/infinitime-resources-*.zip path: ./build/output/infinitime-resources-*.zip
build-simulator: build-simulator:
@@ -96,7 +110,7 @@ jobs:
- name: Upload simulator executable - name: Upload simulator executable
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: infinisim-${{ github.head_ref }} name: infinisim-${{ env.REF_NAME }}
path: build_lv_sim/infinisim path: build_lv_sim/infinisim
get-base-ref-size: get-base-ref-size:

3
.gitmodules vendored
View File

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

View File

@@ -1,4 +1,9 @@
{ {
"env": {
// TODO: This is a duplication of the configuration set in /docker/build.sh!
"TOOLS_DIR": "/opt",
"GCC_ARM_PATH": "gcc-arm-none-eabi-10.3-2021.10"
},
"configurations": [ "configurations": [
{ {
"name": "nrfCC", "name": "nrfCC",
@@ -10,11 +15,26 @@
"defines": [], "defines": [],
"compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc", "compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc",
"cStandard": "c11", "cStandard": "c11",
"cppStandard": "c++14", "cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm",
"configurationProvider": "ms-vscode.cpp-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
},
{
"name": "nrfCC Devcontainer",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/src/**",
"${workspaceFolder}/src"
],
"defines": [],
"compilerPath": "${TOOLS_DIR}/${GCC_ARM_PATH}/bin/arm-none-eabi-gcc",
"cStandard": "c99",
"cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm", "intelliSenseMode": "linux-gcc-arm",
"configurationProvider": "ms-vscode.cpp-tools", "configurationProvider": "ms-vscode.cpp-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json" "compileCommands": "${workspaceFolder}/build/compile_commands.json"
} }
], ],
"version": 4 "version": 4
} }

6
.vscode/cmake-kits.json vendored Normal file
View File

@@ -0,0 +1,6 @@
[
{
"name": "InfiniTime Compiler",
"environmentSetupScript": "${workspaceFolder}/docker/build.sh"
}
]

45
.vscode/launch.json vendored
View File

@@ -1,20 +1,18 @@
{ {
"version": "0.1.0", "version": "0.1.0",
"configurations": [ "configurations": [
{ {
"name": "Debug - Openocd docker Remote", "name": "Debug - Openocd docker Remote",
"type":"cortex-debug", "type": "cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}", "executable": "${command:cmake.launchTargetPath}",
"request": "launch", "request": "launch",
"servertype": "external", "servertype": "external",
// This may need to be arm-none-eabi-gdb depending on your system "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance // Connect to an already running OpenOCD instance
"gdbTarget": "host.docker.internal:3333", "gdbTarget": "host.docker.internal:3333",
"svdFile": "${workspaceRoot}/nrf52.svd", "svdFile": "${workspaceRoot}/nrf52.svd",
"runToMain": true, "runToEntryPoint": "main",
// Work around for stopping at main on restart // Work around for stopping at main on restart
"postRestartCommands": [ "postRestartCommands": [
"break main", "break main",
@@ -23,18 +21,16 @@
}, },
{ {
"name": "Debug - Openocd Local", "name": "Debug - Openocd Local",
"type":"cortex-debug", "type": "cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}", "executable": "${command:cmake.launchTargetPath}",
"request": "launch", "request": "launch",
"servertype": "openocd", "servertype": "openocd",
// This may need to be arm-none-eabi-gdb depending on your system "gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance // Connect to an already running OpenOCD instance
"gdbTarget": "localhost:3333", "gdbTarget": "localhost:3333",
"svdFile": "${workspaceRoot}/nrf52.svd", "svdFile": "${workspaceRoot}/nrf52.svd",
"runToMain": true, "runToEntryPoint": "main",
// Work around for stopping at main on restart // Work around for stopping at main on restart
"postRestartCommands": [ "postRestartCommands": [
"break main", "break main",
@@ -51,6 +47,11 @@
"showDevDebugOutput": false, "showDevDebugOutput": false,
"servertype": "openocd", "servertype": "openocd",
"runToMain": true, "runToMain": true,
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
"continue"
],
// Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb) // Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb)
"armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin", "armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin",
"svdFile": "${workspaceRoot}/nrf52.svd", "svdFile": "${workspaceRoot}/nrf52.svd",
@@ -58,7 +59,25 @@
"interface/stlink.cfg", "interface/stlink.cfg",
"target/nrf52.cfg" "target/nrf52.cfg"
], ],
} },
{
"name": "Debug - Openocd Devcontainer",
"type": "cortex-debug",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "external",
// FIXME: This is hardcoded. I have no idea how to use the values set in build.sh here
"gdbPath": "/opt/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance
"gdbTarget": "host.docker.internal:3333",
"svdFile": "${workspaceRoot}/nrf52.svd",
"runToEntryPoint": "main",
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
"continue"
]
},
] ]
} }

15
.vscode/settings.json vendored
View File

@@ -1,9 +1,20 @@
{ {
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"cmake.configureArgs": [ "cmake.configureArgs": [
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}", "-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}",
"-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}", "-DNRF5_SDK_PATH=${env:TOOLS_DIR}/${env:NRF_SDK_VER}",
], ],
"cmake.statusbar.advanced": {
"launch": {
"visibility": "hidden"
},
"launchTarget": {
"visibility": "hidden"
},
"debug": {
"visibility": "hidden"
}
},
"cmake.generator": "Unix Makefiles", "cmake.generator": "Unix Makefiles",
"clang-tidy.buildPath": "build/compile_commands.json", "clang-tidy.buildPath": "build/compile_commands.json",
"files.associations": { "files.associations": {

22
.vscode/tasks.json vendored
View File

@@ -1,20 +1,6 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{
"label": "create openocd build",
"type": "shell",
"command": "/opt/create_build_openocd.sh",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
},
{ {
"label": "update submodules", "label": "update submodules",
"type": "shell", "type": "shell",
@@ -31,14 +17,6 @@
"panel": "shared" "panel": "shared"
}, },
"problemMatcher": [] "problemMatcher": []
},
{
"label": "BuildInit",
"dependsOn": [
"update submodules",
"create openocd build"
],
"problemMatcher": []
} }
] ]
} }

View File

@@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 3.10)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release") set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM) project(pinetime VERSION 1.15.0 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 20)
# set(CMAKE_GENERATOR "Unix Makefiles") # set(CMAKE_GENERATOR "Unix Makefiles")
set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_C_EXTENSIONS OFF)
@@ -13,7 +13,6 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(NRF_TARGET "nrf52") set(NRF_TARGET "nrf52")
if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH) if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
@@ -33,7 +32,7 @@ if(BUILD_RESOURCES)
endif() endif()
set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device") set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device")
set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY-TFK5 MOY-TIN5 MOY-TON5 MOY-UNK) set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY_TFK5 MOY_TIN5 MOY_TON5 MOY_UNK)
set(PROJECT_GIT_COMMIT_HASH "") set(PROJECT_GIT_COMMIT_HASH "")
@@ -70,5 +69,4 @@ set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generate
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/Version.h) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/Version.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docker/post_build.sh.in ${CMAKE_CURRENT_BINARY_DIR}/post_build.sh) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docker/post_build.sh.in ${CMAKE_CURRENT_BINARY_DIR}/post_build.sh)
add_subdirectory(src) add_subdirectory(src)

View File

@@ -1,27 +1,50 @@
# [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://www.pine64.org/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? ## New to InfiniTime?
- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
- [Updating the software](doc/gettingStarted/updating-software.md) - [Updating the software](doc/gettingStarted/updating-software.md)
- [About the firmware and bootloader](doc/gettingStarted/about-software.md) - [About the firmware and bootloader](doc/gettingStarted/about-software.md)
- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle) - [Available apps](doc/gettingStarted/Applications.md)
- [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather) - [Available watch faces](/doc/gettingStarted/Watchfaces.md)
- [PineTimeStyle Watch face](https://pine64.org/documentation/PineTime/Watchfaces/PineTimeStyle)
- [Weather integration](https://pine64.org/documentation/PineTime/Software/InfiniTime_weather/)
### Companion apps ### Companion apps
- [Gadgetbridge](https://gadgetbridge.org/) (Android) - [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) - [Siglo](https://github.com/alexr4535/siglo) (Linux)
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) **[Looking for a new maintainer]** - [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS)
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux) - [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
- [WatchMate](https://github.com/azymohliad/watchmate) (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 ## Development
@@ -35,7 +58,7 @@ Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/p
### Contributing ### Contributing
- [How to contribute?](CONTRIBUTING.md) - [How to contribute](CONTRIBUTING.md)
- [Coding conventions](doc/coding-convention.md) - [Coding conventions](doc/coding-convention.md)
### Build, flash and debug ### Build, flash and debug
@@ -72,17 +95,17 @@ This project is released under the GNU General Public License version 3 or, at y
It integrates the following projects: It integrates the following projects:
- RTOS : **[FreeRTOS](https://freertos.org)** under the MIT license - RTOS: **[FreeRTOS](https://freertos.org)** under the MIT license
- UI : **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license - UI: **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
- BLE stack : **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license - BLE stack: **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
- Font : **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license - Font: **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
## Credits ## Credits
Im not working alone on this project. First, many people create PR for this project. Then, there is the whole #pinetime community : a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums. Im not working alone on this project. First, many people create pull requests for this project. Then, there is the whole #pinetime community: a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums.
Here are some people I would like to highlight: Here are some people I would like to highlight:
- [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver. - [Atc1441](https://github.com/atc1441/): He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
- [Koen](https://github.com/bosmoment) : Hes working on a firmware based on RiotOS. He integrated similar libs as me : NimBLE, LittleVGL,… His help was invaluable too! - [Koen](https://github.com/bosmoment): Hes working on a firmware based on RiotOS. He integrated similar libs as me: NimBLE, LittleVGL,… His help was invaluable too!
- [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime! - [Lup Yuen Lee](https://github.com/lupyuen): He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!

View File

@@ -7,9 +7,9 @@ My own contribution is little more than a brute force conversion to
python3. It is sparsely tested so there are likely to be a few python3. It is sparsely tested so there are likely to be a few
remaining bytes versus string bugs remaining in the places I didn't test remaining bytes versus string bugs remaining in the places I didn't test
. I used it primarily as part of . I used it primarily as part of
[wasp-os](https://github.com/daniel-thompson/wasp-os) as a way to [wasp-os](https://github.com/wasp-os/wasp-os) as a way to
deliver OTA updates to nRF52-based smart watches, especially the deliver OTA updates to nRF52-based smart watches, especially the
[Pine64 PineTime](https://www.pine64.org/pinetime/). [Pine64 PineTime](https://pine64.org/devices/pinetime/).
## What does it do? ## What does it do?

View File

@@ -21,3 +21,5 @@ The current raw motion values. This is a 3 `int16_t` array:
- [0] : X - [0] : X
- [1] : Y - [1] : Y
- [2] : Z - [2] : Z
The three motion values are in units of "binary milli-g", where 1g is represented by a value of 1024.

View File

@@ -0,0 +1,69 @@
# Simple Weather Service
## Introduction
The Simple Weather Service provides a simple and straightforward API to specify the current weather and the forecast for the next 5 days.
It effectively replaces the original Weather Service (from InfiniTime 1.8) since InfiniTime 1.14.
## Service
The service UUID is `00050000-78fc-48fe-8e23-433b3a1942d0`.
## Characteristics
## Weather data (UUID 00050001-78fc-48fe-8e23-433b3a1942d0)
The host uses this characteristic to update the current weather information and the forecast for the next 5 days.
This characteristics accepts a byte array with the following 2-Bytes header:
- [0] Message Type :
- `0` : Current weather
- `1` : Forecast
- [1] Message Version : Version `0` is currently supported. Other versions might be added in future releases
### Current Weather
The byte array must contain the following data:
- [0] : Message type = `0`
- [1] : Message version = `0`
- [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970) in local time (the same timezone as the one used to set the time)
- [10, 11] : Current temperature (°C * 100)
- [12, 13] : Minimum temperature (°C * 100)
- [14, 15] : Maximum temperature (°C * 100)
- [16]..[47] : location (string, unused characters should be set to `0`)
- [48] : icon ID
- 0 = Sun, clear sky
- 1 = Few clouds
- 2 = Clouds
- 3 = Heavy clouds
- 4 = Clouds & rain
- 5 = Rain
- 6 = Thunderstorm
- 7 = Snow
- 8 = Mist, smog
### Forecast
The byte array must contain the following data:
- [0] : Message type = `1`
- [1] : Message version = `0`
- [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970) in local time (the same timezone as the one used to set the time)
- [10] Number of days (Max 5, fields for unused days should be set to `0`)
- [11,12] Day 0 Minimum temperature (°C * 100)
- [13,14] Day 0 Maximum temperature (°C * 100)
- [15] Day 0 Icon ID
- [16,17] Day 1 Minimum temperature (°C * 100)
- [18,19] Day 1 Maximum temperature (°C * 100)
- [20] Day 1 Icon ID
- [21,22] Day 2 Minimum temperature (°C * 100)
- [23,24] Day 2 Maximum temperature (°C * 100)
- [25] Day 2 Icon ID
- [26,27] Day 3 Minimum temperature (°C * 100)
- [28,29] Day 3 Maximum temperature (°C * 100)
- [30] Day 3 Icon ID
- [31,32] Day 4 Minimum temperature (°C * 100)
- [33,34] Day 4 Maximum temperature (°C * 100)
- [35] Day 4 Icon ID

View File

@@ -92,7 +92,10 @@ The following custom services are implemented in InfiniTime:
- Since InfiniTime 1.8: - Since InfiniTime 1.8:
- [Weather Service](/src/components/ble/weather/WeatherService.h): `00040000-78fc-48fe-8e23-433b3a1942d0` - ~~Weather Service: `00040000-78fc-48fe-8e23-433b3a1942d0`~~ (replaced by Simple Weather Service in InfiniTime 1.14)
- Since InfiniTime 1.14
- [Simple Weather Service](SimpleWeatherService.md) : `00050000-78fc-48fe-8e23-433b3a1942d0`
--- ---

View File

@@ -42,8 +42,8 @@ CMake configures the project according to variables you specify the command line
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`| **NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
**CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug` **CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
**BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1` **BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1`
**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [lv_img_conv](https://github.com/lvgl/lv_img_conv). |`-DBUILD_RESOURCES=1` **BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [python3-pil/pillow](https://pillow.readthedocs.io) module). |`-DBUILD_RESOURCES=1`
**TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY-TFK5, MOY-TIN5, MOY-TON5, MOY-UNK`|`-DTARGET_DEVICE=PINETIME` (Default) **TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY_TFK5, MOY_TIN5, MOY_TON5, MOY_UNK`|`-DTARGET_DEVICE=PINETIME` (Default)
#### (\*) Note about **CMAKE_BUILD_TYPE** #### (\*) Note about **CMAKE_BUILD_TYPE**
By default, this variable is set to *Release*. It compiles the code with size and speed optimizations. We use this value for all the binaries we publish when we [release](https://github.com/InfiniTimeOrg/InfiniTime/releases) new versions of InfiniTime. By default, this variable is set to *Release*. It compiles the code with size and speed optimizations. We use this value for all the binaries we publish when we [release](https://github.com/InfiniTimeOrg/InfiniTime/releases) new versions of InfiniTime.
@@ -98,4 +98,4 @@ Binary files are generated into the folder `src`:
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware - **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
- **pinetime-mcuboot-app-dfu** : DFU file of the firmware - **pinetime-mcuboot-app-dfu** : DFU file of the firmware
The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader** The same files are generated for **pinetime-recovery** and **pinetime-recovery-loader**

View File

@@ -32,7 +32,7 @@ The .devcontainer folder contains the configuration and scripts for using a Dock
Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up. Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up.
More documentation is available in the [readme in .devcontainer](../.devcontainer/README.md) More documentation is available in the [readme in .devcontainer](usingDevcontainers.md)
### DevContainer on Ubuntu ### DevContainer on Ubuntu

View File

@@ -9,59 +9,144 @@ This page will teach you:
The user interface of InfiniTime is made up of **screens**. The user interface of InfiniTime is made up of **screens**.
Screens that are opened from the app launcher are considered **apps**. Screens that are opened from the app launcher are considered **apps**.
Every app in InfiniTime is it's own class. Every app in InfiniTime is its own class.
An instance of the class is created when the app is launched, and destroyed when the user exits the app. An instance of the class is created when the app is launched, and destroyed when the user exits the app.
Apps run inside the "displayapp" task (briefly discussed [here](./Intro.md)). Apps run inside the `DisplayApp` task (briefly discussed [here](./Intro.md)).
Apps are responsible for everything drawn on the screen when they are running. Apps are responsible for everything drawn on the screen when they are running.
By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected. Apps can be refreshed periodically and reacts to external events (touch or button).
## Interface ## Interface
Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`. Every app class is declared inside the namespace `Pinetime::Applications::Screens`
The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen. and inherits
Other parameters should be references to controllers that the app needs. from [`Pinetime::Applications::Screens::Screen`](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Screen.h).
A destructor is needed to clean up LVGL and restore any changes (for example re-enable sleeping).
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
If an app only needs to display some text and do something upon a touch screen button press,
it does not need to override any of these functions, as LVGL can also handle touch events for you.
If you have any doubts, you can always look at how the other apps function for reference.
### Continuous updating Each app defines its own constructor.
The constructors mostly take references to InfiniTime `Controllers` (ex: Alarm, DateTime, BLE services, Settings,...)
the app needs for its operations. The constructor is responsible for initializing the UI of the app.
If your app needs to be updated continuously, you can do so by overriding the `Refresh()` function in your class The **destructor** cleans up LVGL and restores any changes (for example re-enable sleeping).
and calling `lv_task_create` inside the constructor.
An example call could look like this: App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)`
and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
```cpp Apps that need to be refreshed periodically create an `lv_task` (using `lv_task_create()`)
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); that will call the method `Refresh()` periodically.
## App types
There are basically 3 types of applications : **system** apps and **user** apps and **watch faces**.
**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
Settings, notifications and the application launcher are examples of such system applications.
**User** applications are optionally built into the firmware. They extend the functionalities of the system.
**Watch faces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware.
The distinction between **system** apps, **user** apps and watch faces allows for more flexibility and customization.
This allows to easily select which user applications and watch faces must be built into the firmware
without overflowing the system memory.
## Apps and watch faces initialization
Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
This method simply call the creates an instance of the class that corresponds to the app specified in parameters.
The constructor of **system** apps is called directly. If the application is a **user** app,
the corresponding `AppDescription` is first retrieved from `userApps`
and then the function `create` is called to create an instance of the app.
Watch faces are handled in a very similar way as the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`.
## User application selection at build time
The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
in `UserApps.h`. This method takes the list of applications that must be built into the firmware image.
This list of applications is defined as a list `Apps` enum values named `UserAppTypes` in `Apps.h`.
For each application listed in `UserAppTypes`, an entry of type `AppDescription` is added to the array `userApps`.
This entry is created by using the information provided by a template `AppTraits`
that is customized for every user application.
Here is an example of an AppTraits customized for the Alarm application.
It defines the type of application, its icon and a function that returns an instance of the application.
```c++
template <>
struct AppTraits<Apps::Alarm> {
static constexpr Apps app = Apps::Alarm;
static constexpr const char* icon = Screens::Symbols::clock;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Alarm(controllers.alarmController,
controllers.settingsController.GetClockType(),
*controllers.systemTask,
controllers.motorController);
};
};
``` ```
With `taskRefresh` being a member variable of your class and of type `lv_task_t*`. This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
Remember to delete the task again using `lv_task_del`. to list all available applications.
The function `RefreshTaskCallback` is inherited from `Screen` and just calls your `Refresh` function.
## Watch face selection at build time
The list of available watch faces is also generated at build time by the `consteval`
function `CreateWatchFaceDescriptions()` in `UserApps.h` in the same way as the **user** apps.
Watch faces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated.
Here is an example of `WatchFaceTraits`:
```c++
template <>
struct WatchFaceTraits<WatchFace::Analog> {
static constexpr WatchFace watchFace = WatchFace::Analog;
static constexpr const char* name = "Analog face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceAnalog(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
```
## Creating your own app ## Creating your own app
A minimal app could look like this: A minimal user app could look like this:
MyApp.h: MyApp.h:
```cpp ```cpp
#pragma once #pragma once
#include "displayapp/Apps.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include <lvgl/lvgl.h> #include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
namespace Screens { namespace Screens {
class MyApp : public Screen { class MyApp : public Screen {
public: public:
MyApp(DisplayApp* app); MyApp();
~MyApp() override; ~MyApp() override;
}; };
} }
template <>
struct AppTraits<Apps::MyApp> {
static constexpr Apps app = Apps::MyApp;
static constexpr const char* icon = Screens::Symbols::myApp;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::MyApp();
}
};
} }
} }
``` ```
@@ -70,11 +155,10 @@ MyApp.cpp:
```cpp ```cpp
#include "displayapp/screens/MyApp.h" #include "displayapp/screens/MyApp.h"
#include "displayapp/DisplayApp.h"
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
MyApp::MyApp(DisplayApp* app) : Screen(app) { MyApp::MyApp() {
lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(title, "My test application"); lv_label_set_text_static(title, "My test application");
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
@@ -86,20 +170,42 @@ MyApp::~MyApp() {
} }
``` ```
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/) Both of these files should be in [displayapp/screens/](/src/displayapp/screens/).
or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a setting app.
Now we have our very own app, but InfiniTime does not know about it yet. Now we have our very own app, but InfiniTime does not know about it yet.
The first step is to include your MyApp.cpp (or any new cpp files for that matter) The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt). in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
The next step to making it launchable is to give your app an id. The next step to making it launch-able is to give your app an id.
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)). To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/apps/Apps.h](/src/displayapp/apps/Apps.h.in)).
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp). Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
Now, go to the function `DisplayApp::LoadScreen` and add another case to the switch statement. to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
If your application is a **system** application, go to the function `DisplayApp::LoadScreen`
and add another case to the switch statement.
The case will be the id you gave your app earlier. The case will be the id you gave your app earlier.
If your app needs any additional arguments, this is the place to pass them. If your app needs any additional arguments, this is the place to pass them.
If you want to add your app in the app launcher, add your app in [displayapp/screens/ApplicationList.h](/src/displayapp/screens/ApplicationList.h) to the array containing the applications and their corresponding symbol. If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.h](/src/displayapp/screens/settings/Settings.h). If your application is a **user** application, you don't need to add anything in DisplayApp,
everything will be automatically generated for you.
The user application will also be automatically be added to the app launcher menu.
Since the list of **user** application is generated by CMake, you need to add the variable `ENABLE_USERAPPS` to the command line of CMake.
This variable must be set with a string composed of an ordered list of the **user** applications that must be built into the firmware.
The items of the list are fields from the enumeration `Apps`.
Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the application will be listed in this specific order in the application menu).
```cmake
$ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ...
```
Similarly, the list of watch faces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake.
It must be set with the comma separated list of watch faces that will be built into the firmware.
Ex: build the firmware with 3 watch faces : Analog, PineTimeStyle and Infineat:
```cmake
$ cmake ... -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::PineTimeStyle,WatchFace::Infineat" ...
```
You should now be able to [build](../buildAndProgram.md) the firmware You should now be able to [build](../buildAndProgram.md) the firmware
and flash it to your PineTime. Yay! and flash it to your PineTime. Yay!

View File

@@ -0,0 +1,99 @@
# Applications
InfiniTime has 13 apps on the `main` branch at the time of writing.
## List of apps
- Stopwatch
- Alarm
- Timer
- Steps
- Heartrate
- Music
- InfiniPaint
- Paddle
- 2
- InfiniDice
- Metronome
- Maps
- Weather
### Stopwatch
![Stopwatch UI](/doc/gettingStarted/AppsScreenshots/stopwatch.png)
- Press the Start button (bottom right) to start or stop the timer.
- You can also press the side button while the timer is running to pause the timer.
- Press the Flag button (bottom left) to add a lap.
- The stopwatch will not yet continue counting time while the app is closed.
### Alarm
![Alarm UI](/doc/gettingStarted/AppsScreenshots/alarm.png)
- Ajust the time with the time picker.
- Press the Info button in the top middle to see time remaning.
- Use the toggle in the bottom left to turn the alarm on/off.
- Use the button in the bottom right to change the alarm frequency.
- You can choose between once, daily, or Monday - Friday.
### Timer
![Timer UI](/doc/gettingStarted/AppsScreenshots/timer.png)
- Ajust how long the timer should go for with the time picker.
- Press the Start button at the bottom to start/stop the timer.
### Steps
![Steps UI](/doc/gettingStarted/AppsScreenshots/steps.png)
- The total count of steps for the current display will show in the middle of the screen.
- The Reset button in the bottom middle resets the Trip counter. (Total of all steps taken.)
- The progress circle shows the percentage of your daily goal completed.
### Heartrate
![Heartrate UI](/doc/gettingStarted/AppsScreenshots/Heartrate.png)
- Press Start to start measuring your heartrate.
- It may take a bit to get the first measurement.
### Music
![Music UI](/doc/gettingStarted/AppsScreenshots/Music.png)
- This app shows currently playing music.
- Please note that this app is not very useful without a device connected.
- Press the button in the center to play/pause, and the buttons on the left and right to go to the previous and next tracks, respectively.
- Swipe up to get to volume controls.
### InfiniPaint
![InfiniPaint UI](/doc/gettingStarted/AppsScreenshots/Paint.png)
- This app does not allow you to swipe from the top to exit, use the side button instead.
- Draw on the screen to add lines.
- Hold down in one spot to change paint colors.
### Paddle
![Paddle UI](/doc/gettingStarted/AppsScreenshots/Pong.png)
- This app does not allow you to swipe from the top to exit, use the side button instead.
- Drag your finger to move the paddle.
- Goal: Don't let the ball go off the left side of the screen.
### 2
![2 UI](/doc/gettingStarted/AppsScreenshots/2048.png)
- This app does not allow you to swipe from the top to exit, use the side button instead.
- Play a game of 2048.
- Swipe up, down, left, or right tomove the tiles.
- When two tiles with the same number run into each other, they will add together.
- Goal: Don't let the screen fill up with tiles, and get to the 2048 tile to win.
### InfiniDice
![InfiniDice UI](/doc/gettingStarted/AppsScreenshots/Dice.png)
- Ajust the count to change the number of dice.
- Ajust the sides to change the number of sides.
- Press the button at the bottom to roll.
- The result will be on the right side of the screen.
### Metronome
![Metronome UI](/doc/gettingStarted/AppsScreenshots/Metronome.png)
- Ajust the BPM with the circular slider.
- A bug currently makes it always snap to 98 BPM.
- Use the button in the bottom left to start the metronome.
### Maps
![Maps UI](/doc/gettingStarted/AppsScreenshots/Maps.png)
- This app shows info from a navigation app.
- Please note that this app is not very useful without a device connected.
### Weather
![Weather UI](/doc/gettingStarted/AppsScreenshots/Weather.png)
- This app shows weather info.
- Please note that this app is not very useful without a device connected.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,31 @@
# Watchfaces
InfiniTime has 6 apps on the `main` branch at the time of writing.
## List of apps
- Digital
- Analog
- PineTimeStyle
- Terminal
- Infinineat
- Casio G7710
### Digital
![Digital face](/doc/gettingStarted/Watchfaces/Digital.png)
### Analog
![Analog face](/doc/gettingStarted/Watchfaces/Analog.png)
### PineTimeStyle
![PineTimeStyle face](/doc/gettingStarted/Watchfaces/PineTimeStyle.png)
- You can long-press on the display to change colors, step style, and weather.
### Terminal
![Terminal face](/doc/gettingStarted/Watchfaces/Terminal.png)
### Infinineat
![Infinineat face](/doc/gettingStarted/Watchfaces/Infinineat.png)
- You can long-press on the display to change colors.
### Casio G7710
![Casio G7710 face](/doc/gettingStarted/Watchfaces/CasioG7710.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 256 KiB

View File

@@ -14,7 +14,7 @@ You can sync the time using companion apps.
- Gadgetbridge automatically synchronizes the time when you connect it to your watch. More information on Gadgetbridge [here](/doc/gettingStarted/ota-gadgetbridge.md) - Gadgetbridge automatically synchronizes the time when you connect it to your watch. More information on Gadgetbridge [here](/doc/gettingStarted/ota-gadgetbridge.md)
- [Sync the time with NRFConnect](/doc/gettingStarted/time-nrfconnect.md) - [Sync the time with NRFConnect](/doc/gettingStarted/time-nrfconnect.md)
- Sync the time with your browser https://hubmartin.github.io/WebBLEWatch/ - [Sync the time with your browser](https://hubmartin.github.io/WebBLEWatch/)
You can also set the time in the settings without a companion app. (version >1.7.0) You can also set the time in the settings without a companion app. (version >1.7.0)
@@ -46,7 +46,7 @@ On the bottom right, you can see how many steps you have taken today.
![Settings](ui/settings.jpg) ![Settings](ui/settings.jpg)
- Swipe **up** to display the application menus. Apps (stopwatch, music, step, games,...) can be started from this menu. - Swipe **up** to display the application menus. Apps (stopwatch, music, step, games,...) can be started from this menu.
- Swipe **down** to display the notification panel. Notification sent by your companion app will be displayed here. - Swipe **down** to display the notification panel. Notifications sent by your companion app will be displayed here.
- Swipe **right** to display the Quick Actions menu. This menu allows you to - Swipe **right** to display the Quick Actions menu. This menu allows you to
- Set the brightness of the display - Set the brightness of the display
- Start the **flashlight** app - Start the **flashlight** app

View File

@@ -1,29 +1,35 @@
# Connecting to Gadgetbridge # Connecting to Gadgetbridge
Launch Gadgetbridge and tap on the **"+"** button on the bottom right to add a new device: Launch Gadgetbridge and tap on the menu button in the top left:
![Gadgetbridge 0](gadgetbridge0.jpg) ![Gadgetbridge 0](gadgetbridge0.jpg)
Wait for the scan to complete, your PineTime should be detected: Press the "Connect new device" button:
![Gadgetbridge 1](gadgetbridge1.jpg) ![Gadgetbridge 1](gadgetbridge1.jpg)
Your PineTime should appear on the list. Tap on it.
Tap on it. Gadgdetbridge will pair and connect to your device: Tap on it. Gadgdetbridge will pair and connect to your device:
![Gadgetbridge 2](gadgetbridge2.jpg) ![Gadgetbridge 2](gadgetbridge2.jpg)
# Updating with Gadgetbridge # Updating with Gadgetbridge
Now that Gadgetbridge is connected to your PineTime, use a file browser application and find the DFU file (`pinetime-mcuboot-app-dfu-x.x.x.zip`) you downloaded previously. Tap on it and open it using the Gadgetbridge application/firmware installer: Now that Gadgetbridge is connected to your PineTime, press the three dots on the device card:
![Gadgetbridge 3](gadgetbridge3.jpg) ![Gadgetbridge 3](gadgetbridge3.jpg)
Read the warning carefully and tap **Install**: Now press the "File Installer" button:
![Gadgetbridge 4](gadgetbridge4.jpg) ![Gadgetbridge 4](gadgetbridge4.jpg)
Wait for the transfer to finish. Your PineTime should reset and reboot with the new version of InfiniTime! Select the firmware you downloaded (`pinetime-mcuboot-app-dfu-x.x.x.zip`) from the [Releases tab](https://github.com/InfiniTimeOrg/InfiniTime/releases/latest):
Don't forget to **validate** your firmware. In the InfiniTime go to the settings (swipe right, select gear icon) and Firmware option and click **validate**. Otherwise after reboot the previous firmware will be used.
![Gadgetbridge 5](gadgetbridge5.jpg) ![Gadgetbridge 5](gadgetbridge5.jpg)
Wait for the transfer to finish. There will be a progress bar on both the watch and the phone. Your PineTime should reboot with the new version of InfiniTime!
Don't forget to **validate** your firmware. In the InfiniTime go to the settings (swipe right, select gear icon) and scroll to the Firmware option and click **validate**. Otherwise, after reboot the previous firmware will be used.
![Validate](validate.png)

View File

@@ -6,7 +6,7 @@ If you just want to flash or upgrade InfiniTime on your PineTime, this page is f
You can check the InfiniTime version by first swiping right on the watch face to open quick settings, tapping the cogwheel to open settings, swipe up until you find an entry named "About" and tap on it. You can check the InfiniTime version by first swiping right on the watch face to open quick settings, tapping the cogwheel to open settings, swipe up until you find an entry named "About" and tap on it.
![InfiniTime 1.0 version](version-1.0.jpg) ![InfiniTime 1.14 version](version.png)
PineTimes shipped after June 2021 will ship with the latest version of [the bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader/releases/tag/1.0.0) and [recovery firmware](https://github.com/InfiniTimeOrg/InfiniTime/releases/tag/0.14.1) PineTimes shipped after June 2021 will ship with the latest version of [the bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader/releases/tag/1.0.0) and [recovery firmware](https://github.com/InfiniTimeOrg/InfiniTime/releases/tag/0.14.1)
@@ -49,7 +49,7 @@ Since those resources are not part of the firmware, they need to be flashed and
Resources are packaged into a single .zip file named `infinitime-resources-x.y.z.zip` (where `x`, `y` and `z` are the version numbers of InfiniTime). Resources are packaged into a single .zip file named `infinitime-resources-x.y.z.zip` (where `x`, `y` and `z` are the version numbers of InfiniTime).
You can use the companion app of your choice to flash the resources. You can use the companion app of your choice to flash the resources.
**Note : at the time of writing this page, [Amazfish](https://github.com/piggz/harbour-amazfish) and [ITD](https://gitea.arsenm.dev/Arsen6331/itd) have already integrated this functionality. Other companion apps will hopefully implement it soon!* **Note: at the time of writing this page, [Amazfish](https://github.com/piggz/harbour-amazfish) and [ITD](https://gitea.arsenm.dev/Arsen6331/itd) have already integrated this functionality. Other companion apps will hopefully implement it soon!*
## Amazfish ## Amazfish
Use the `Download file` functionality of Amazfish. Use the `Download file` functionality of Amazfish.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

View File

@@ -1,7 +1,13 @@
FROM ubuntu:22.04 FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG NODE_MAJOR=20
RUN apt-get update -qq \ RUN apt-get update -qq \
&& apt-get install -y ca-certificates curl gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
&& apt-get update -qq \
&& apt-get install -y \ && apt-get install -y \
# x86_64 / generic packages # x86_64 / generic packages
bash \ bash \
@@ -9,13 +15,14 @@ RUN apt-get update -qq \
cmake \ cmake \
git \ git \
make \ make \
nodejs \
python3 \ python3 \
python3-pip \ python3-pip \
python3-pil \
python-is-python3 \ python-is-python3 \
tar \ tar \
unzip \ unzip \
wget \ wget \
curl \
# aarch64 packages # aarch64 packages
libffi-dev \ libffi-dev \
libssl-dev \ libssl-dev \
@@ -28,10 +35,15 @@ RUN apt-get update -qq \
libpango-1.0-0 \ libpango-1.0-0 \
ibpango1.0-dev \ ibpango1.0-dev \
libpangocairo-1.0-0 \ libpangocairo-1.0-0 \
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*; && rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
# Add the necessary apt-gets for the devcontainer
RUN apt-get update -qq \
&& apt-get install -y \
clang-format-14 \
clang-tidy \
libncurses5
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting # Git needed for PROJECT_GIT_COMMIT_HASH variable setting
RUN pip3 install adafruit-nrfutil RUN pip3 install adafruit-nrfutil
@@ -39,10 +51,6 @@ RUN pip3 install -Iv cryptography==3.3
RUN pip3 install cbor RUN pip3 install cbor
RUN npm i lv_font_conv@1.5.2 -g RUN npm i lv_font_conv@1.5.2 -g
RUN npm i ts-node@10.9.1 -g
RUN npm i @swc/core -g
RUN npm i lv_img_conv@0.3.0 -g
# build.sh knows how to compile # build.sh knows how to compile
COPY build.sh /opt/ COPY build.sh /opt/
@@ -54,5 +62,11 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;"
# McuBoot # McuBoot
RUN bash -c "source /opt/build.sh; GetMcuBoot;" 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 ENV SOURCES_DIR /sources
CMD ["/opt/build.sh"] CMD ["/opt/build.sh"]

View File

@@ -1,25 +1,33 @@
#!/bin/bash #!/bin/sh
if clang-format --version | grep -q 'version 11\.'; then
CLANG_FORMAT_EXECUTABLE="clang-format" name="clang-format"
else
CLANG_FORMAT_EXECUTABLE="clang-format-11" if [ -z "$(command -v "git-$name")" ]; then
name="$(basename -a $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'git-clang-format*') | sort | tail -n 1 | sed 's/^git-//')"
fi fi
if ! command -v $CLANG_FORMAT_EXECUTABLE &> /dev/null minVersion="14.0.0"
then
echo $CLANG_FORMAT_EXECUTABLE does not exist, make sure to install it
exit 1
fi
for FILE in $(git diff --cached --name-only) for file in $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'clang-format*'); do
do curBin="$file"
if [[ "$FILE" =~ src/[A-Za-z0-9\ \-]+*\.(c|h|cpp|cc)$ ]]; then curVersion="$("$curBin" --version | cut -d ' ' -f 3)"
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE if [ "$(printf '%s\n' "$curVersion" "$version" "$minVersion" | sort -V | tail -n 1)" = "$curVersion" ]; then
git add -- $FILE bin="$curBin"
elif [[ "$FILE" =~ src/(components|displayapp|drivers|heartratetask|logging|systemtask)/.*\.(c|h|cpp|cc)$ ]]; then version="$curVersion"
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE fi
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE done
git add -- $FILE
fi if [ -z "$name" ] || [ -z "$bin" ]; then
echo "Could not find a suitable clang-format installation. Install clang-format that includes the git-clang-format script, with at least version $minVersion"
exit 1
fi
args="--binary $bin -q --extensions cpp,h --style file --staged -- :!src/FreeRTOS :!src/libs"
changedFiles="$(git "$name" --diffstat $args)"
git "$name" $args
echo "$changedFiles" | head -n -1 | cut -d ' ' -f 2 | while read -r file; do
git add -- "$file"
done done

View File

@@ -1,31 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs; let
py4McuBoot = python3.withPackages (ps: with ps; [
cbor
intelhex
click
cryptography
]);
in mkShell {
packages = [
gcc-arm-embedded-10
nrf5-sdk
cmake
nodePackages.lv_font_conv
lv_img_conv
py4McuBoot
clang-tools
SDL2
libpng
python3Packages.adafruit-nrfutil
];
ARM_NONE_EABI_TOOLCHAIN_PATH="${gcc-arm-embedded-10}";
NRF5_SDK_PATH="${nrf5-sdk}/share/nRF5_SDK";
CMAKE_BUILD_TYPE="Release";
BUILD_DFU=1;
BUILD_RESOURCES=1;
TARGET_DEVICE="PINETIME";
}

View File

@@ -355,14 +355,6 @@ set(LVGL_SRC
libs/lvgl/src/lv_widgets/lv_win.c libs/lvgl/src/lv_widgets/lv_win.c
) )
set(QCBOR_SRC
libs/QCBOR/src/ieee754.c
libs/QCBOR/src/qcbor_decode.c
libs/QCBOR/src/qcbor_encode.c
libs/QCBOR/src/qcbor_err_to_str.c
libs/QCBOR/src/UsefulBuf.c
)
list(APPEND IMAGE_FILES list(APPEND IMAGE_FILES
displayapp/icons/battery/batteryicon.c displayapp/icons/battery/batteryicon.c
) )
@@ -373,8 +365,9 @@ list(APPEND SOURCE_FILES
logging/NrfLogger.cpp logging/NrfLogger.cpp
displayapp/DisplayApp.cpp displayapp/DisplayApp.cpp
displayapp/screens/Screen.cpp displayapp/screens/Screen.cpp
displayapp/screens/Clock.cpp
displayapp/screens/Tile.cpp displayapp/screens/Tile.cpp
displayapp/screens/InfiniPaint.cpp
displayapp/screens/Paddle.cpp
displayapp/screens/StopWatch.cpp displayapp/screens/StopWatch.cpp
displayapp/screens/BatteryIcon.cpp displayapp/screens/BatteryIcon.cpp
displayapp/screens/BleIcon.cpp displayapp/screens/BleIcon.cpp
@@ -383,26 +376,27 @@ list(APPEND SOURCE_FILES
displayapp/screens/Label.cpp displayapp/screens/Label.cpp
displayapp/screens/FirmwareUpdate.cpp displayapp/screens/FirmwareUpdate.cpp
displayapp/screens/Music.cpp displayapp/screens/Music.cpp
displayapp/screens/Weather.cpp
displayapp/screens/Navigation.cpp displayapp/screens/Navigation.cpp
displayapp/screens/Metronome.cpp displayapp/screens/Metronome.cpp
displayapp/screens/Motion.cpp displayapp/screens/Motion.cpp
displayapp/screens/Weather.cpp
displayapp/screens/FirmwareValidation.cpp displayapp/screens/FirmwareValidation.cpp
displayapp/screens/ApplicationList.cpp displayapp/screens/ApplicationList.cpp
displayapp/screens/Notifications.cpp displayapp/screens/Notifications.cpp
displayapp/screens/Twos.cpp displayapp/screens/Twos.cpp
displayapp/screens/HeartRate.cpp displayapp/screens/HeartRate.cpp
displayapp/screens/Motion.cpp
displayapp/screens/FlashLight.cpp displayapp/screens/FlashLight.cpp
displayapp/screens/List.cpp displayapp/screens/List.cpp
displayapp/screens/CheckboxList.cpp displayapp/screens/CheckboxList.cpp
displayapp/screens/BatteryInfo.cpp displayapp/screens/BatteryInfo.cpp
displayapp/screens/Steps.cpp displayapp/screens/Steps.cpp
displayapp/screens/Timer.cpp displayapp/screens/Timer.cpp
displayapp/screens/Dice.cpp
displayapp/screens/PassKey.cpp displayapp/screens/PassKey.cpp
displayapp/screens/Error.cpp displayapp/screens/Error.cpp
displayapp/screens/Alarm.cpp displayapp/screens/Alarm.cpp
displayapp/screens/Styles.cpp displayapp/screens/Styles.cpp
displayapp/screens/WeatherSymbols.cpp
displayapp/Colors.cpp displayapp/Colors.cpp
displayapp/widgets/Counter.cpp displayapp/widgets/Counter.cpp
displayapp/widgets/PageIndicator.cpp displayapp/widgets/PageIndicator.cpp
@@ -414,6 +408,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/settings/Settings.cpp displayapp/screens/settings/Settings.cpp
displayapp/screens/settings/SettingWatchFace.cpp displayapp/screens/settings/SettingWatchFace.cpp
displayapp/screens/settings/SettingTimeFormat.cpp displayapp/screens/settings/SettingTimeFormat.cpp
displayapp/screens/settings/SettingWeatherFormat.cpp
displayapp/screens/settings/SettingWakeUp.cpp displayapp/screens/settings/SettingWakeUp.cpp
displayapp/screens/settings/SettingDisplay.cpp displayapp/screens/settings/SettingDisplay.cpp
displayapp/screens/settings/SettingSteps.cpp displayapp/screens/settings/SettingSteps.cpp
@@ -428,9 +423,9 @@ list(APPEND SOURCE_FILES
displayapp/screens/WatchFaceAnalog.cpp displayapp/screens/WatchFaceAnalog.cpp
displayapp/screens/WatchFaceDigital.cpp displayapp/screens/WatchFaceDigital.cpp
displayapp/screens/WatchFaceInfineat.cpp displayapp/screens/WatchFaceInfineat.cpp
displayapp/screens/WatchFaceTerminal.cpp
displayapp/screens/WatchFacePineTimeStyle.cpp displayapp/screens/WatchFacePineTimeStyle.cpp
displayapp/screens/WatchFaceCasioStyleG7710.cpp displayapp/screens/WatchFaceCasioStyleG7710.cpp
displayapp/screens/WatchFaceFuzzy.cpp
## ##
@@ -459,7 +454,7 @@ list(APPEND SOURCE_FILES
components/ble/CurrentTimeService.cpp components/ble/CurrentTimeService.cpp
components/ble/AlertNotificationService.cpp components/ble/AlertNotificationService.cpp
components/ble/MusicService.cpp components/ble/MusicService.cpp
components/ble/weather/WeatherService.cpp components/ble/SimpleWeatherService.cpp
components/ble/NavigationService.cpp components/ble/NavigationService.cpp
components/ble/BatteryInformationService.cpp components/ble/BatteryInformationService.cpp
components/ble/FSService.cpp components/ble/FSService.cpp
@@ -483,6 +478,7 @@ list(APPEND SOURCE_FILES
systemtask/SystemTask.cpp systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp drivers/TwiMaster.cpp
heartratetask/HeartRateTask.cpp heartratetask/HeartRateTask.cpp
@@ -528,7 +524,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/ble/CurrentTimeService.cpp components/ble/CurrentTimeService.cpp
components/ble/AlertNotificationService.cpp components/ble/AlertNotificationService.cpp
components/ble/MusicService.cpp components/ble/MusicService.cpp
components/ble/weather/WeatherService.cpp components/ble/SimpleWeatherService.cpp
components/ble/BatteryInformationService.cpp components/ble/BatteryInformationService.cpp
components/ble/FSService.cpp components/ble/FSService.cpp
components/ble/ImmediateAlertService.cpp components/ble/ImmediateAlertService.cpp
@@ -547,8 +543,8 @@ list(APPEND RECOVERY_SOURCE_FILES
systemtask/SystemTask.cpp systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp drivers/TwiMaster.cpp
components/gfx/Gfx.cpp
components/rle/RleDecoder.cpp components/rle/RleDecoder.cpp
components/heartrate/HeartRateController.cpp components/heartrate/HeartRateController.cpp
heartratetask/HeartRateTask.cpp heartratetask/HeartRateTask.cpp
@@ -578,7 +574,6 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
components/rle/RleDecoder.cpp components/rle/RleDecoder.cpp
components/gfx/Gfx.cpp
drivers/St7789.cpp drivers/St7789.cpp
components/brightness/BrightnessController.cpp components/brightness/BrightnessController.cpp
@@ -596,9 +591,10 @@ set(INCLUDE_FILES
displayapp/Messages.h displayapp/Messages.h
displayapp/TouchEvents.h displayapp/TouchEvents.h
displayapp/screens/Screen.h displayapp/screens/Screen.h
displayapp/screens/Clock.h
displayapp/screens/Tile.h displayapp/screens/Tile.h
displayapp/screens/InfiniPaint.h
displayapp/screens/StopWatch.h displayapp/screens/StopWatch.h
displayapp/screens/Paddle.h
displayapp/screens/BatteryIcon.h displayapp/screens/BatteryIcon.h
displayapp/screens/BleIcon.h displayapp/screens/BleIcon.h
displayapp/screens/NotificationIcon.h displayapp/screens/NotificationIcon.h
@@ -610,12 +606,12 @@ set(INCLUDE_FILES
displayapp/screens/ApplicationList.h displayapp/screens/ApplicationList.h
displayapp/screens/CheckboxList.h displayapp/screens/CheckboxList.h
displayapp/Apps.h displayapp/Apps.h
displayapp/WatchFaces.h
displayapp/screens/Notifications.h displayapp/screens/Notifications.h
displayapp/screens/HeartRate.h displayapp/screens/HeartRate.h
displayapp/screens/Metronome.h displayapp/screens/Metronome.h
displayapp/screens/Motion.h displayapp/screens/Motion.h
displayapp/screens/Timer.h displayapp/screens/Timer.h
displayapp/screens/Dice.h
displayapp/screens/Alarm.h displayapp/screens/Alarm.h
displayapp/Colors.h displayapp/Colors.h
displayapp/widgets/Counter.h displayapp/widgets/Counter.h
@@ -655,7 +651,7 @@ set(INCLUDE_FILES
components/ble/BleClient.h components/ble/BleClient.h
components/ble/HeartRateService.h components/ble/HeartRateService.h
components/ble/MotionService.h components/ble/MotionService.h
components/ble/weather/WeatherService.h components/ble/SimpleWeatherService.h
components/settings/Settings.h components/settings/Settings.h
components/timer/Timer.h components/timer/Timer.h
components/alarm/AlarmController.h components/alarm/AlarmController.h
@@ -666,6 +662,7 @@ set(INCLUDE_FILES
displayapp/InfiniTimeTheme.h displayapp/InfiniTimeTheme.h
systemtask/SystemTask.h systemtask/SystemTask.h
systemtask/SystemMonitor.h systemtask/SystemMonitor.h
systemtask/WakeLock.h
displayapp/screens/Symbols.h displayapp/screens/Symbols.h
drivers/TwiMaster.h drivers/TwiMaster.h
heartratetask/HeartRateTask.h heartratetask/HeartRateTask.h
@@ -802,18 +799,18 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
if(TARGET_DEVICE STREQUAL "PINETIME") if(TARGET_DEVICE STREQUAL "PINETIME")
add_definitions(-DDRIVER_PINMAP_PINETIME) add_definitions(-DDRIVER_PINMAP_PINETIME)
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
elseif(TARGET_DEVICE STREQUAL "MOY-TFK5") # P8a elseif(TARGET_DEVICE STREQUAL "MOY_TFK5") # P8a
add_definitions(-DDRIVER_PINMAP_P8) add_definitions(-DDRIVER_PINMAP_P8)
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
elseif(TARGET_DEVICE STREQUAL "MOY-TIN5") # P8a variant 2 elseif(TARGET_DEVICE STREQUAL "MOY_TIN5") # P8a variant 2
add_definitions(-DDRIVER_PINMAP_P8) add_definitions(-DDRIVER_PINMAP_P8)
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
elseif(TARGET_DEVICE STREQUAL "MOY-TON5") # P8b elseif(TARGET_DEVICE STREQUAL "MOY_TON5") # P8b
add_definitions(-DDRIVER_PINMAP_P8) add_definitions(-DDRIVER_PINMAP_P8)
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500) add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1) add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
elseif(TARGET_DEVICE STREQUAL "MOY-UNK") # P8b mirrored elseif(TARGET_DEVICE STREQUAL "MOY_UNK") # P8b mirrored
add_definitions(-DDRIVER_PINMAP_P8) add_definitions(-DDRIVER_PINMAP_P8)
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500) add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
@@ -854,6 +851,8 @@ target_compile_options(infinitime_fonts PUBLIC
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}> $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
) )
add_subdirectory(displayapp/apps)
# NRF SDK # NRF SDK
add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES}) add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES})
target_include_directories(nrf-sdk SYSTEM PUBLIC . ../) target_include_directories(nrf-sdk SYSTEM PUBLIC . ../)
@@ -864,7 +863,6 @@ target_compile_options(nrf-sdk PRIVATE
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}> $<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}> $<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}> $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
-O3
) )
# NimBLE # NimBLE
@@ -891,27 +889,6 @@ target_compile_options(lvgl PRIVATE
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}> $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
) )
# QCBOR
add_library(QCBOR STATIC ${QCBOR_SRC})
target_include_directories(QCBOR SYSTEM PUBLIC libs/QCBOR/inc)
# This is required with the current configuration
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_FLOAT_HW_USE)
# These are for space-saving
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_PREFERRED_FLOAT)
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_EXP_AND_MANTISSA)
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_STRINGS)
#target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_ARRAYS)
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_UNCOMMON_TAGS)
target_compile_definitions(QCBOR PUBLIC USEFULBUF_CONFIG_LITTLE_ENDIAN)
set_target_properties(QCBOR PROPERTIES LINKER_LANGUAGE C)
target_compile_options(QCBOR PRIVATE
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
-O3
)
# LITTLEFS_SRC # LITTLEFS_SRC
add_library(littlefs STATIC ${LITTLEFS_SRC}) add_library(littlefs STATIC ${LITTLEFS_SRC})
target_include_directories(littlefs SYSTEM PUBLIC . ../) target_include_directories(littlefs SYSTEM PUBLIC . ../)
@@ -930,7 +907,7 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld") set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES}) add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME}) set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts) target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
target_compile_options(${EXECUTABLE_NAME} PUBLIC target_compile_options(${EXECUTABLE_NAME} PUBLIC
${COMMON_FLAGS} ${COMMON_FLAGS}
${WARNING_FLAGS} ${WARNING_FLAGS}
@@ -964,7 +941,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERS
set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld") set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES}) add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts) target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME}) set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
${COMMON_FLAGS} ${COMMON_FLAGS}
@@ -1006,7 +983,7 @@ endif()
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery") set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES}) add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts) target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME}) set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY") target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
@@ -1038,7 +1015,7 @@ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-$
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex) set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES}) add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts) target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}) set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY") target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
@@ -1078,7 +1055,7 @@ endif()
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader") set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES}) add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts) target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME}) set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
${COMMON_FLAGS} ${COMMON_FLAGS}
@@ -1113,7 +1090,7 @@ set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_N
set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME_HEX ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.hex) set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME_HEX ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.hex)
set(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) set(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES}) add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts) target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}) set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
${COMMON_FLAGS} ${COMMON_FLAGS}

View File

@@ -75,6 +75,7 @@
#define configUSE_TIME_SLICING 0 #define configUSE_TIME_SLICING 0
#define configUSE_NEWLIB_REENTRANT 0 #define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 1 #define configENABLE_BACKWARD_COMPATIBILITY 1
#define configUSE_TASK_NOTIFICATIONS 0
/* Hook function related definitions. */ /* Hook function related definitions. */
#define configUSE_IDLE_HOOK 0 #define configUSE_IDLE_HOOK 0

View File

@@ -19,11 +19,13 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "task.h" #include "task.h"
#include <chrono> #include <chrono>
#include <libraries/log/nrf_log.h>
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
using namespace std::chrono_literals; using namespace std::chrono_literals;
AlarmController::AlarmController(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} { AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs)
: dateTimeController {dateTimeController}, fs {fs} {
} }
namespace { namespace {
@@ -36,11 +38,28 @@ namespace {
void AlarmController::Init(System::SystemTask* systemTask) { void AlarmController::Init(System::SystemTask* systemTask) {
this->systemTask = systemTask; this->systemTask = systemTask;
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm); alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm);
LoadSettingsFromFile();
if (alarm.isEnabled) {
NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling");
ScheduleAlarm();
}
}
void AlarmController::SaveAlarm() {
// verify if it is necessary to save
if (alarmChanged) {
SaveSettingsToFile();
}
alarmChanged = false;
} }
void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) { void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
hours = alarmHr; if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
minutes = alarmMin; return;
}
alarm.hours = alarmHr;
alarm.minutes = alarmMin;
alarmChanged = true;
} }
void AlarmController::ScheduleAlarm() { void AlarmController::ScheduleAlarm() {
@@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() {
tm* tmAlarmTime = std::localtime(&ttAlarmTime); tm* tmAlarmTime = std::localtime(&ttAlarmTime);
// If the time being set has already passed today,the alarm should be set for tomorrow // If the time being set has already passed today,the alarm should be set for tomorrow
if (hours < dateTimeController.Hours() || (hours == dateTimeController.Hours() && minutes <= dateTimeController.Minutes())) { if (alarm.hours < dateTimeController.Hours() ||
(alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) {
tmAlarmTime->tm_mday += 1; tmAlarmTime->tm_mday += 1;
// tm_wday doesn't update automatically // tm_wday doesn't update automatically
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7; tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
} }
tmAlarmTime->tm_hour = hours; tmAlarmTime->tm_hour = alarm.hours;
tmAlarmTime->tm_min = minutes; tmAlarmTime->tm_min = alarm.minutes;
tmAlarmTime->tm_sec = 0; tmAlarmTime->tm_sec = 0;
// if alarm is in weekday-only mode, make sure it shifts to the next weekday // if alarm is in weekday-only mode, make sure it shifts to the next weekday
if (recurrence == RecurType::Weekdays) { if (alarm.recurrence == RecurType::Weekdays) {
if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
tmAlarmTime->tm_mday += 1; tmAlarmTime->tm_mday += 1;
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days } else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
@@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() {
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0); xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
xTimerStart(alarmTimer, 0); xTimerStart(alarmTimer, 0);
state = AlarmState::Set; if (!alarm.isEnabled) {
alarm.isEnabled = true;
alarmChanged = true;
}
} }
uint32_t AlarmController::SecondsToAlarm() const { uint32_t AlarmController::SecondsToAlarm() const {
@@ -88,20 +111,72 @@ uint32_t AlarmController::SecondsToAlarm() const {
void AlarmController::DisableAlarm() { void AlarmController::DisableAlarm() {
xTimerStop(alarmTimer, 0); xTimerStop(alarmTimer, 0);
state = AlarmState::Not_Set; isAlerting = false;
if (alarm.isEnabled) {
alarm.isEnabled = false;
alarmChanged = true;
}
} }
void AlarmController::SetOffAlarmNow() { void AlarmController::SetOffAlarmNow() {
state = AlarmState::Alerting; isAlerting = true;
systemTask->PushMessage(System::Messages::SetOffAlarm); systemTask->PushMessage(System::Messages::SetOffAlarm);
} }
void AlarmController::StopAlerting() { void AlarmController::StopAlerting() {
// Alarm state is off unless this is a recurring alarm isAlerting = false;
if (recurrence == RecurType::None) { // Disable alarm unless it is recurring
state = AlarmState::Not_Set; if (alarm.recurrence == RecurType::None) {
alarm.isEnabled = false;
alarmChanged = true;
} else { } else {
// set next instance // set next instance
ScheduleAlarm(); ScheduleAlarm();
} }
} }
void AlarmController::SetRecurrence(RecurType recurrence) {
if (alarm.recurrence != recurrence) {
alarm.recurrence = recurrence;
alarmChanged = true;
}
}
void AlarmController::LoadSettingsFromFile() {
lfs_file_t alarmFile;
AlarmSettings alarmBuffer;
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) {
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file");
return;
}
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(&alarmBuffer), sizeof(alarmBuffer));
fs.FileClose(&alarmFile);
if (alarmBuffer.version != alarmFormatVersion) {
NRF_LOG_WARNING("[AlarmController] Loaded alarm settings has version %u instead of %u, discarding",
alarmBuffer.version,
alarmFormatVersion);
return;
}
alarm = alarmBuffer;
NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file");
}
void AlarmController::SaveSettingsToFile() const {
lfs_dir systemDir;
if (fs.DirOpen("/.system", &systemDir) != LFS_ERR_OK) {
fs.DirCreate("/.system");
}
fs.DirClose(&systemDir);
lfs_file_t alarmFile;
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) {
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file for saving");
return;
}
fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(&alarm), sizeof(alarm));
fs.FileClose(&alarmFile);
NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version);
}

View File

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

View File

@@ -124,9 +124,11 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) {
bootloaderSize, bootloaderSize,
applicationSize); applicationSize);
// wait until SystemTask has finished waking up all devices // Wait until SystemTask has disabled sleeping
while (systemTask.IsSleeping()) { // This isn't quite correct, as we don't actually know
vTaskDelay(50); // 50ms // if BleFirmwareUpdateStarted has been received yet
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
} }
dfuImage.Erase(); dfuImage.Erase();
@@ -357,6 +359,8 @@ void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t exp
this->totalSize = totalSize; this->totalSize = totalSize;
this->expectedCrc = expectedCrc; this->expectedCrc = expectedCrc;
this->ready = true; this->ready = true;
totalWriteIndex = 0;
bufferWriteIndex = 0;
} }
void DfuService::DfuImage::Append(uint8_t* data, size_t size) { void DfuService::DfuImage::Append(uint8_t* data, size_t size) {

View File

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

View File

@@ -2,9 +2,9 @@
#define min // workaround: nimble's min/max macros conflict with libstdc++ #define min // workaround: nimble's min/max macros conflict with libstdc++
#define max #define max
#include <host/ble_gap.h> #include <host/ble_gap.h>
#include <atomic>
#undef max #undef max
#undef min #undef min
#include <atomic>
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@@ -21,14 +21,14 @@ namespace Pinetime {
void SubscribeNotification(uint16_t attributeHandle); void SubscribeNotification(uint16_t attributeHandle);
void UnsubscribeNotification(uint16_t attributeHandle); void UnsubscribeNotification(uint16_t attributeHandle);
static constexpr uint16_t heartRateServiceId {0x180D};
static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId};
private: private:
NimbleController& nimble; NimbleController& nimble;
Controllers::HeartRateController& heartRateController; Controllers::HeartRateController& heartRateController;
static constexpr uint16_t heartRateServiceId {0x180D};
static constexpr uint16_t heartRateMeasurementId {0x2A37}; static constexpr uint16_t heartRateMeasurementId {0x2A37};
static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId};
static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId}; static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId};
struct ble_gatt_chr_def characteristicDefinition[2]; struct ble_gatt_chr_def characteristicDefinition[2];

View File

@@ -120,3 +120,7 @@ void MotionService::UnsubscribeNotification(uint16_t attributeHandle) {
else if (attributeHandle == motionValuesHandle) else if (attributeHandle == motionValuesHandle)
motionValuesNoficationEnabled = false; motionValuesNoficationEnabled = false;
} }
bool MotionService::IsMotionNotificationSubscribed() const {
return motionValuesNoficationEnabled;
}

View File

@@ -21,6 +21,7 @@ namespace Pinetime {
void SubscribeNotification(uint16_t attributeHandle); void SubscribeNotification(uint16_t attributeHandle);
void UnsubscribeNotification(uint16_t attributeHandle); void UnsubscribeNotification(uint16_t attributeHandle);
bool IsMotionNotificationSubscribed() const;
private: private:
NimbleController& nimble; NimbleController& nimble;

View File

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

View File

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

View File

@@ -158,7 +158,10 @@ void NimbleController::StartAdvertising() {
} }
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
fields.uuids128 = &dfuServiceUuid; fields.uuids16 = &HeartRateService::heartRateServiceUuid;
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;
fields.uuids128 = &DfuService::serviceUuid;
fields.num_uuids128 = 1; fields.num_uuids128 = 1;
fields.uuids128_is_complete = 1; fields.uuids128_is_complete = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
@@ -451,9 +454,15 @@ void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
/* Wakeup Spi and SpiNorFlash before accessing the file system /* Wakeup Spi and SpiNorFlash before accessing the file system
* This should be fixed in the FS driver * This should be fixed in the FS driver
*/ */
systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
vTaskDelay(10);
// This isn't quite correct
// SystemTask could receive EnableSleeping right after passing this check
// We need some guarantee that the SystemTask has processed the above message
// before we can continue
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
}
lfs_file_t file_p; lfs_file_t file_p;

View File

@@ -21,7 +21,7 @@
#include "components/ble/NavigationService.h" #include "components/ble/NavigationService.h"
#include "components/ble/ServiceDiscovery.h" #include "components/ble/ServiceDiscovery.h"
#include "components/ble/MotionService.h" #include "components/ble/MotionService.h"
#include "components/ble/weather/WeatherService.h" #include "components/ble/SimpleWeatherService.h"
#include "components/fs/FS.h" #include "components/fs/FS.h"
namespace Pinetime { namespace Pinetime {
@@ -67,7 +67,7 @@ namespace Pinetime {
return anService; return anService;
}; };
Pinetime::Controllers::WeatherService& weather() { Pinetime::Controllers::SimpleWeatherService& weather() {
return weatherService; return weatherService;
}; };
@@ -99,7 +99,7 @@ namespace Pinetime {
AlertNotificationClient alertNotificationClient; AlertNotificationClient alertNotificationClient;
CurrentTimeService currentTimeService; CurrentTimeService currentTimeService;
MusicService musicService; MusicService musicService;
WeatherService weatherService; SimpleWeatherService weatherService;
NavigationService navService; NavigationService navService;
BatteryInformationService batteryInformationService; BatteryInformationService batteryInformationService;
ImmediateAlertService immediateAlertService; ImmediateAlertService immediateAlertService;
@@ -112,10 +112,6 @@ namespace Pinetime {
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE; uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
uint8_t fastAdvCount = 0; uint8_t fastAdvCount = 0;
uint8_t bondId[16] = {0}; uint8_t bondId[16] = {0};
ble_uuid128_t dfuServiceUuid {
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
}; };
static NimbleController* nptr; static NimbleController* nptr;

View File

@@ -0,0 +1,173 @@
/* Copyright (C) 2023 Jean-François Milants
This file is part of InfiniTime.
InfiniTime is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
InfiniTime is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "components/ble/SimpleWeatherService.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <nrf_log.h>
using namespace Pinetime::Controllers;
namespace {
enum class MessageType : uint8_t { CurrentWeather, Forecast, Unknown };
uint64_t ToUInt64(const uint8_t* data) {
return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24) + (static_cast<uint64_t>(data[4]) << 32) +
(static_cast<uint64_t>(data[5]) << 40) + (static_cast<uint64_t>(data[6]) << 48) + (static_cast<uint64_t>(data[7]) << 56);
}
int16_t ToInt16(const uint8_t* data) {
return data[0] + (data[1] << 8);
}
SimpleWeatherService::CurrentWeather CreateCurrentWeather(const uint8_t* dataBuffer) {
SimpleWeatherService::Location cityName;
std::memcpy(cityName.data(), &dataBuffer[16], 32);
cityName[32] = '\0';
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])),
SimpleWeatherService::Icons {dataBuffer[16 + 32]},
std::move(cityName));
}
SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
auto timestamp = static_cast<uint64_t>(ToUInt64(&dataBuffer[2]));
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 {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};
}
MessageType GetMessageType(const uint8_t* data) {
auto messageType = static_cast<MessageType>(*data);
if (messageType > MessageType::Unknown) {
return MessageType::Unknown;
}
return messageType;
}
uint8_t GetVersion(const uint8_t* dataBuffer) {
return dataBuffer[1];
}
}
int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) {
return static_cast<Pinetime::Controllers::SimpleWeatherService*>(arg)->OnCommand(ctxt);
}
SimpleWeatherService::SimpleWeatherService(DateTime& dateTimeController) : dateTimeController(dateTimeController) {
}
void SimpleWeatherService::Init() {
ble_gatts_count_cfg(serviceDefinition);
ble_gatts_add_svcs(serviceDefinition);
}
int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
const auto* buffer = ctxt->om;
const auto* dataBuffer = buffer->om_data;
switch (GetMessageType(dataBuffer)) {
case MessageType::CurrentWeather:
if (GetVersion(dataBuffer) == 0) {
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.PreciseCelsius(),
currentWeather->minTemperature.PreciseCelsius(),
currentWeather->maxTemperature.PreciseCelsius(),
currentWeather->iconId,
currentWeather->location.data());
}
break;
case MessageType::Forecast:
if (GetVersion(dataBuffer) == 0) {
forecast = CreateForecast(dataBuffer);
NRF_LOG_INFO("Forecast : Timestamp : %d", forecast->timestamp);
for (int i = 0; i < 5; i++) {
NRF_LOG_INFO("\t[%d] Min: %d - Max : %d - Icon : %d",
i,
forecast->days[i]->minTemperature.PreciseCelsius(),
forecast->days[i]->maxTemperature.PreciseCelsius(),
forecast->days[i]->iconId);
}
}
break;
default:
break;
}
return 0;
}
std::optional<SimpleWeatherService::CurrentWeather> SimpleWeatherService::Current() const {
if (currentWeather) {
auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch();
auto weatherTpSecond = std::chrono::seconds {currentWeather->timestamp};
auto weatherTp = std::chrono::duration_cast<std::chrono::seconds>(weatherTpSecond);
auto delta = currentTime - weatherTp;
if (delta < std::chrono::hours {24}) {
return currentWeather;
}
}
return {};
}
std::optional<SimpleWeatherService::Forecast> SimpleWeatherService::GetForecast() const {
if (forecast) {
auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch();
auto weatherTpSecond = std::chrono::seconds {forecast->timestamp};
auto weatherTp = std::chrono::duration_cast<std::chrono::seconds>(weatherTpSecond);
auto delta = currentTime - weatherTp;
if (delta < std::chrono::hours {24}) {
return this->forecast;
}
}
return {};
}
bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const {
return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp &&
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
std::strcmp(this->location.data(), other.location.data()) == 0;
}
bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature;
}
bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const {
for (int i = 0; i < this->nbDays; i++) {
if (this->days[i] != other.days[i]) {
return false;
}
}
return this->timestamp == other.timestamp && this->nbDays == other.nbDays;
}

View File

@@ -0,0 +1,174 @@
/* Copyright (C) 2023 Jean-François Milants
This file is part of InfiniTime.
InfiniTime is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
InfiniTime is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include <string>
#include <array>
#include <memory>
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
#include <host/ble_gap.h>
#include <host/ble_uuid.h>
#include <optional>
#include <cstring>
#undef max
#undef min
#include "components/datetime/DateTimeController.h"
int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
namespace Pinetime {
namespace Controllers {
class SimpleWeatherService {
public:
explicit SimpleWeatherService(DateTime& dateTimeController);
void Init();
int OnCommand(struct ble_gatt_access_ctxt* ctxt);
static constexpr uint8_t MaxNbForecastDays = 5;
enum class Icons : uint8_t {
Sun = 0, // ClearSky
CloudsSun = 1, // FewClouds
Clouds = 2, // Scattered clouds
BrokenClouds = 3,
CloudShowerHeavy = 4, // shower rain
CloudSunRain = 5, // rain
Thunderstorm = 6,
Snow = 7,
Smog = 8, // Mist
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,
Temperature temperature,
Temperature minTemperature,
Temperature maxTemperature,
Icons iconId,
Location&& location)
: timestamp {timestamp},
temperature {temperature},
minTemperature {minTemperature},
maxTemperature {maxTemperature},
iconId {iconId},
location {std::move(location)} {
}
uint64_t timestamp;
Temperature temperature;
Temperature minTemperature;
Temperature maxTemperature;
Icons iconId;
Location location;
bool operator==(const CurrentWeather& other) const;
};
struct Forecast {
uint64_t timestamp;
uint8_t nbDays;
struct Day {
Temperature minTemperature;
Temperature maxTemperature;
Icons iconId;
bool operator==(const Day& other) const;
};
std::array<std::optional<Day>, MaxNbForecastDays> days;
bool operator==(const Forecast& other) const;
};
std::optional<CurrentWeather> Current() const;
std::optional<Forecast> GetForecast() const;
private:
// 00050000-78fc-48fe-8e23-433b3a1942d0
static constexpr ble_uuid128_t BaseUuid() {
return CharUuid(0x00, 0x00);
}
// 0005yyxx-78fc-48fe-8e23-433b3a1942d0
static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
.value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x05, 0x00}};
}
ble_uuid128_t weatherUuid {BaseUuid()};
ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = &weatherDataCharUuid.u,
.access_cb = WeatherCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_WRITE,
.val_handle = &eventHandle},
{0}};
const struct ble_gatt_svc_def serviceDefinition[2] = {
{.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition},
{0}};
uint16_t eventHandle {};
Pinetime::Controllers::DateTime& dateTimeController;
std::optional<CurrentWeather> currentWeather;
std::optional<Forecast> forecast;
};
}
}

View File

@@ -1,385 +0,0 @@
/* Copyright (C) 2021 Avamander
This file is part of InfiniTime.
InfiniTime is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
InfiniTime is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
/**
* Different weather events, weather data structures used by {@link WeatherService.h}
*
* How to upload events to the timeline?
*
* All timeline write payloads are simply CBOR-encoded payloads of the structs described below.
*
* All payloads have a mandatory header part and the dynamic part that
* depends on the event type specified in the header. If you don't,
* you'll get an error returned. Data is relatively well-validated,
* so keep in the bounds of the data types given.
*
* Write all struct members (CamelCase keys) into a single finite-sized map, and write it to the characteristic.
* Mind the MTU.
*
* How to debug?
*
* There's a Screen that you can compile into your firmware that shows currently valid events.
* You can adapt that to display something else. That part right now is very much work in progress
* because the exact requirements are not yet known.
*
*
* Implemented based on and other material:
* https://en.wikipedia.org/wiki/METAR
* https://www.weather.gov/jetstream/obscurationtypes
* http://www.faraim.org/aim/aim-4-03-14-493.html
*/
namespace Pinetime {
namespace Controllers {
class WeatherData {
public:
/**
* Visibility obscuration types
*/
enum class obscurationtype {
/** No obscuration */
None = 0,
/** Water particles suspended in the air; low visibility; does not fall */
Fog = 1,
/** Tiny, dry particles in the air; invisible to the eye; opalescent */
Haze = 2,
/** Small fire-created particles suspended in the air */
Smoke = 3,
/** Fine rock powder, from for example volcanoes */
Ash = 4,
/** Fine particles of earth suspended in the air by the wind */
Dust = 5,
/** Fine particles of sand suspended in the air by the wind */
Sand = 6,
/** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */
Mist = 7,
/** This is SPECIAL in the sense that the thing raining down is doing the obscuration */
Precipitation = 8,
Length
};
/**
* Types of precipitation
*/
enum class precipitationtype {
/**
* No precipitation
*
* Theoretically we could just _not_ send the event, but then
* how do we differentiate between no precipitation and
* no information about precipitation
*/
None = 0,
/** Drops larger than a drizzle; also widely separated drizzle */
Rain = 1,
/** Fairly uniform rain consisting of fine drops */
Drizzle = 2,
/** Rain that freezes upon contact with objects and ground */
FreezingRain = 3,
/** Rain + hail; ice pellets; small translucent frozen raindrops */
Sleet = 4,
/** Larger ice pellets; falling separately or in irregular clumps */
Hail = 5,
/** Hail with smaller grains of ice; mini-snowballs */
SmallHail = 6,
/** Snow... */
Snow = 7,
/** Frozen drizzle; very small snow crystals */
SnowGrains = 8,
/** Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */
IceCrystals = 9,
/** It's raining down ash, e.g. from a volcano */
Ash = 10,
Length
};
/**
* These are special events that can "enhance" the "experience" of existing weather events
*/
enum class specialtype {
/** Strong wind with a sudden onset that lasts at least a minute */
Squall = 0,
/** Series of waves in a water body caused by the displacement of a large volume of water */
Tsunami = 1,
/** Violent; rotating column of air */
Tornado = 2,
/** Unplanned; unwanted; uncontrolled fire in an area */
Fire = 3,
/** Thunder and/or lightning */
Thunder = 4,
Length
};
/**
* These are used for weather timeline manipulation
* that isn't just adding to the stack of weather events
*/
enum class controlcodes {
/** How much is stored already */
GetLength = 0,
/** This wipes the entire timeline */
DelTimeline = 1,
/** There's a currently valid timeline event with the given type */
HasValidEvent = 3,
Length
};
/**
* Events have types
* then they're easier to parse after sending them over the air
*/
enum class eventtype : uint8_t {
/** @see obscuration */
Obscuration = 0,
/** @see precipitation */
Precipitation = 1,
/** @see wind */
Wind = 2,
/** @see temperature */
Temperature = 3,
/** @see airquality */
AirQuality = 4,
/** @see special */
Special = 5,
/** @see pressure */
Pressure = 6,
/** @see location */
Location = 7,
/** @see cloud */
Clouds = 8,
/** @see humidity */
Humidity = 9,
Length
};
/**
* Valid event query
*
* NOTE: Not currently available, until needs are better known
*/
class ValidEventQuery {
public:
static constexpr controlcodes code = controlcodes::HasValidEvent;
eventtype eventType;
};
/** The header used for further parsing */
class TimelineHeader {
public:
/**
* UNIX timestamp
* TODO: This is currently WITH A TIMEZONE OFFSET!
* Please send events with the timestamp offset by the timezone.
**/
uint64_t timestamp;
/**
* Time in seconds until the event expires
*
* 32 bits ought to be enough for everyone
*
* If there's a newer event of the same type then it overrides this one, even if it hasn't expired
*/
uint32_t expires;
/**
* What type of weather-related event
*/
eventtype eventType;
};
/** Specifies how cloudiness is stored */
class Clouds : public TimelineHeader {
public:
/** Cloud coverage in percentage, 0-100% */
uint8_t amount;
};
/** Specifies how obscuration is stored */
class Obscuration : public TimelineHeader {
public:
/** Type of precipitation */
obscurationtype type;
/**
* Visibility distance in meters
* 65535 is reserved for unspecified
*/
uint16_t amount;
};
/** Specifies how precipitation is stored */
class Precipitation : public TimelineHeader {
public:
/** Type of precipitation */
precipitationtype type;
/**
* How much is it going to rain? In millimeters
* 255 is reserved for unspecified
**/
uint8_t amount;
};
/**
* How wind speed is stored
*
* In order to represent bursts of wind instead of constant wind,
* you have minimum and maximum speeds.
*
* As direction can fluctuate wildly and some watch faces might wish to display it nicely,
* we're following the aerospace industry weather report option of specifying a range.
*/
class Wind : public TimelineHeader {
public:
/** Meters per second */
uint8_t speedMin;
/** Meters per second */
uint8_t speedMax;
/** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
uint8_t directionMin;
/** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
uint8_t directionMax;
};
/**
* How temperature is stored
*
* As it's annoying to figure out the dewpoint on the watch,
* please send it from the companion
*
* We don't do floats, picodegrees are not useful. Make sure to multiply.
*/
class Temperature : public TimelineHeader {
public:
/**
* Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
* -32768 is reserved for "no data"
*/
int16_t temperature;
/**
* Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
* -32768 is reserved for "no data"
*/
int16_t dewPoint;
};
/**
* How location info is stored
*
* This can be mostly static with long expiration,
* as it usually is, but it could change during a trip for ex.
* so we allow changing it dynamically.
*
* Location info can be for some kind of map watch face
* or daylight calculations, should those be required.
*
*/
class Location : public TimelineHeader {
public:
/** Location name */
std::string location;
/** Altitude relative to sea level in meters */
int16_t altitude;
/** Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
int32_t latitude;
/** Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
int32_t longitude;
};
/**
* How humidity is stored
*/
class Humidity : public TimelineHeader {
public:
/** Relative humidity, 0-100% */
uint8_t humidity;
};
/**
* How air pressure is stored
*/
class Pressure : public TimelineHeader {
public:
/** Air pressure in hectopascals (hPa) */
int16_t pressure;
};
/**
* How special events are stored
*/
class Special : public TimelineHeader {
public:
/** Special event's type */
specialtype type;
};
/**
* How air quality is stored
*
* These events are a bit more complex because the topic is not simple,
* the intention is to heavy-lift the annoying preprocessing from the watch
* this allows watch face or watchapp makers to generate accurate alerts and graphics
*
* If this needs further enforced standardization, pull requests are welcome
*/
class AirQuality : public TimelineHeader {
public:
/**
* The name of the pollution
*
* for the sake of better compatibility with watchapps
* that might want to use this data for say visuals
* don't localize the name.
*
* Ideally watchapp itself localizes the name, if it's at all needed.
*
* E.g.
* For generic ones use "PM0.1", "PM5", "PM10"
* For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3"
* For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores
*/
std::string polluter;
/**
* Amount of the pollution in SI units,
* otherwise it's going to be difficult to create UI, alerts
* and so on and for.
*
* See more:
* https://ec.europa.eu/environment/air/quality/standards.htm
* http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
*
* Example units:
* count/m³ for pollen
* µgC/m³ for micrograms of organic carbon
* µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust
* mg/m³ CO2, CO
* ng/m³ for heavy metals
*
* List is not comprehensive, should be improved.
* The current ones are what watchapps assume!
*
* Note: ppb and ppm to concentration should be calculated on the companion, using
* the correct formula (taking into account temperature and air pressure)
*
* Note2: The amount is off by times 100, for two decimal places of precision.
* E.g. 54.32µg/m³ is 5432
*
*/
uint32_t amount;
};
};
}
}

View File

@@ -1,605 +0,0 @@
/* Copyright (C) 2021 Avamander
This file is part of InfiniTime.
InfiniTime is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
InfiniTime is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <qcbor/qcbor_spiffy_decode.h>
#include "WeatherService.h"
#include "libs/QCBOR/inc/qcbor/qcbor.h"
int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) {
return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(ctxt);
}
namespace Pinetime {
namespace Controllers {
WeatherService::WeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) {
nullHeader = &nullTimelineheader;
nullTimelineheader->timestamp = 0;
}
void WeatherService::Init() {
uint8_t res = 0;
res = ble_gatts_count_cfg(serviceDefinition);
ASSERT(res == 0);
res = ble_gatts_add_svcs(serviceDefinition);
ASSERT(res == 0);
}
int WeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
const uint8_t packetLen = OS_MBUF_PKTLEN(ctxt->om); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
if (packetLen <= 0) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
// Decode
QCBORDecodeContext decodeContext;
UsefulBufC encodedCbor = {ctxt->om->om_data, OS_MBUF_PKTLEN(ctxt->om)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL);
// KINDLY provide us a fixed-length map
QCBORDecode_EnterMap(&decodeContext, nullptr);
// Always encodes to the smallest number of bytes based on the value
int64_t tmpTimestamp = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp);
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
int64_t tmpExpires = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires);
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpExpires < 0 || tmpExpires > 4294967295) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
int64_t tmpEventType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType);
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpEventType < 0 ||
tmpEventType >= static_cast<int64_t>(WeatherData::eventtype::Length)) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
switch (static_cast<WeatherData::eventtype>(tmpEventType)) {
case WeatherData::eventtype::AirQuality: {
std::unique_ptr<WeatherData::AirQuality> airquality = std::make_unique<WeatherData::AirQuality>();
airquality->timestamp = tmpTimestamp;
airquality->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
airquality->expires = tmpExpires;
UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Polluter", &stringBuf);
if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
airquality->polluter = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
int64_t tmpAmount = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
if (tmpAmount < 0 || tmpAmount > 4294967295) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
if (!AddEventToTimeline(std::move(airquality))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Obscuration: {
std::unique_ptr<WeatherData::Obscuration> obscuration = std::make_unique<WeatherData::Obscuration>();
obscuration->timestamp = tmpTimestamp;
obscuration->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
obscuration->expires = tmpExpires;
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::obscurationtype::Length)) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
obscuration->type = static_cast<WeatherData::obscurationtype>(tmpType);
int64_t tmpAmount = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
if (tmpAmount < 0 || tmpAmount > 65535) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
if (!AddEventToTimeline(std::move(obscuration))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Precipitation: {
std::unique_ptr<WeatherData::Precipitation> precipitation = std::make_unique<WeatherData::Precipitation>();
precipitation->timestamp = tmpTimestamp;
precipitation->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
precipitation->expires = tmpExpires;
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::precipitationtype::Length)) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
precipitation->type = static_cast<WeatherData::precipitationtype>(tmpType);
int64_t tmpAmount = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
if (tmpAmount < 0 || tmpAmount > 255) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
if (!AddEventToTimeline(std::move(precipitation))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Wind: {
std::unique_ptr<WeatherData::Wind> wind = std::make_unique<WeatherData::Wind>();
wind->timestamp = tmpTimestamp;
wind->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
wind->expires = tmpExpires;
int64_t tmpMin = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMin);
if (tmpMin < 0 || tmpMin > 255) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpMax = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax);
if (tmpMax < 0 || tmpMax > 255) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpDMin = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin);
if (tmpDMin < 0 || tmpDMin > 255) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpDMax = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax);
if (tmpDMax < 0 || tmpDMax > 255) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
if (!AddEventToTimeline(std::move(wind))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Temperature: {
std::unique_ptr<WeatherData::Temperature> temperature = std::make_unique<WeatherData::Temperature>();
temperature->timestamp = tmpTimestamp;
temperature->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
temperature->expires = tmpExpires;
int64_t tmpTemperature = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Temperature", &tmpTemperature);
if (tmpTemperature < -32768 || tmpTemperature > 32767) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
temperature->temperature =
static_cast<int16_t>(tmpTemperature); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpDewPoint = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint);
if (tmpDewPoint < -32768 || tmpDewPoint > 32767) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
temperature->dewPoint =
static_cast<int16_t>(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
if (!AddEventToTimeline(std::move(temperature))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Special: {
std::unique_ptr<WeatherData::Special> special = std::make_unique<WeatherData::Special>();
special->timestamp = tmpTimestamp;
special->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
special->expires = tmpExpires;
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::specialtype::Length)) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
special->type = static_cast<WeatherData::specialtype>(tmpType);
if (!AddEventToTimeline(std::move(special))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Pressure: {
std::unique_ptr<WeatherData::Pressure> pressure = std::make_unique<WeatherData::Pressure>();
pressure->timestamp = tmpTimestamp;
pressure->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
pressure->expires = tmpExpires;
int64_t tmpPressure = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure);
if (tmpPressure < 0 || tmpPressure >= 65535) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
if (!AddEventToTimeline(std::move(pressure))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Location: {
std::unique_ptr<WeatherData::Location> location = std::make_unique<WeatherData::Location>();
location->timestamp = tmpTimestamp;
location->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
location->expires = tmpExpires;
UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Location", &stringBuf);
if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
location->location = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
int64_t tmpAltitude = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude);
if (tmpAltitude < -32768 || tmpAltitude >= 32767) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
location->altitude = static_cast<int16_t>(tmpAltitude);
int64_t tmpLatitude = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude);
if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
location->latitude = static_cast<int32_t>(tmpLatitude);
int64_t tmpLongitude = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude);
if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
location->latitude = static_cast<int32_t>(tmpLongitude);
if (!AddEventToTimeline(std::move(location))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Clouds: {
std::unique_ptr<WeatherData::Clouds> clouds = std::make_unique<WeatherData::Clouds>();
clouds->timestamp = tmpTimestamp;
clouds->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
clouds->expires = tmpExpires;
int64_t tmpAmount = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
if (tmpAmount < 0 || tmpAmount > 255) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
clouds->amount = static_cast<uint8_t>(tmpAmount);
if (!AddEventToTimeline(std::move(clouds))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
case WeatherData::eventtype::Humidity: {
std::unique_ptr<WeatherData::Humidity> humidity = std::make_unique<WeatherData::Humidity>();
humidity->timestamp = tmpTimestamp;
humidity->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
humidity->expires = tmpExpires;
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType);
if (tmpType < 0 || tmpType >= 255) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
humidity->humidity = static_cast<uint8_t>(tmpType);
if (!AddEventToTimeline(std::move(humidity))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
break;
}
default: {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
}
QCBORDecode_ExitMap(&decodeContext);
GetTimelineLength();
TidyTimeline();
if (QCBORDecode_Finish(&decodeContext) != QCBOR_SUCCESS) {
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
} else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
// Encode
uint8_t buffer[64];
QCBOREncodeContext encodeContext;
/* TODO: This is very much still a test endpoint
* it needs a characteristic UUID check
* and actual implementations that show
* what actually has to be read.
* WARN: Consider commands not part of the API for now!
*/
QCBOREncode_Init(&encodeContext, UsefulBuf_FROM_BYTE_ARRAY(buffer));
QCBOREncode_OpenMap(&encodeContext);
QCBOREncode_AddTextToMap(&encodeContext, "test", UsefulBuf_FROM_SZ_LITERAL("test"));
QCBOREncode_AddInt64ToMap(&encodeContext, "test", 1ul);
QCBOREncode_CloseMap(&encodeContext);
UsefulBufC encodedEvent;
auto uErr = QCBOREncode_Finish(&encodeContext, &encodedEvent);
if (uErr != 0) {
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
auto res = os_mbuf_append(ctxt->om, &buffer, sizeof(buffer));
if (res == 0) {
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
return 0;
}
return 0;
}
std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(*this->nullHeader);
}
std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
}
}
return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(*this->nullHeader);
}
size_t WeatherService::GetTimelineLength() const {
return timeline.size();
}
bool WeatherService::AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event) {
if (timeline.size() == timeline.max_size()) {
return false;
}
timeline.push_back(std::move(event));
return true;
}
bool WeatherService::HasTimelineEventOfType(const WeatherData::eventtype type) const {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : timeline) {
if (header->eventType == type && IsEventStillValid(header, currentTimestamp)) {
return true;
}
}
return false;
}
void WeatherService::TidyTimeline() {
uint64_t timeCurrent = GetCurrentUnixTimestamp();
timeline.erase(std::remove_if(std::begin(timeline),
std::end(timeline),
[&](std::unique_ptr<WeatherData::TimelineHeader> const& header) {
return !IsEventStillValid(header, timeCurrent);
}),
std::end(timeline));
std::sort(std::begin(timeline), std::end(timeline), CompareTimelineEvents);
}
bool WeatherService::CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
const std::unique_ptr<WeatherData::TimelineHeader>& second) {
return first->timestamp > second->timestamp;
}
bool WeatherService::IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp) {
// Not getting timestamp in isEventStillValid for more speed
return uniquePtr->timestamp + uniquePtr->expires >= timestamp;
}
uint64_t WeatherService::GetCurrentUnixTimestamp() const {
return std::chrono::duration_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime().time_since_epoch()).count();
}
int16_t WeatherService::GetTodayMinTemp() const {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) +
((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds());
uint64_t currentDayStart = currentDayEnd - 86400;
int16_t result = -32768;
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart &&
header->timestamp < currentDayEnd &&
reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
if (result == -32768) {
result = temperature;
} else if (result > temperature) {
result = temperature;
} else {
// The temperature in this item is higher than the lowest we've found
}
}
}
return result;
}
int16_t WeatherService::GetTodayMaxTemp() const {
uint64_t currentTimestamp = GetCurrentUnixTimestamp();
uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) +
((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds());
uint64_t currentDayStart = currentDayEnd - 86400;
int16_t result = -32768;
for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart &&
header->timestamp < currentDayEnd &&
reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
if (result == -32768) {
result = temperature;
} else if (result < temperature) {
result = temperature;
} else {
// The temperature in this item is lower than the highest we've found
}
}
}
return result;
}
void WeatherService::CleanUpQcbor(QCBORDecodeContext* decodeContext) {
QCBORDecode_ExitMap(decodeContext);
QCBORDecode_Finish(decodeContext);
}
}
}

View File

@@ -1,169 +0,0 @@
/* Copyright (C) 2021 Avamander
This file is part of InfiniTime.
InfiniTime is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
InfiniTime is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <memory>
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
#include <host/ble_gap.h>
#include <host/ble_uuid.h>
#undef max
#undef min
#include "WeatherData.h"
#include "libs/QCBOR/inc/qcbor/qcbor.h"
#include "components/datetime/DateTimeController.h"
int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
namespace Pinetime {
namespace Controllers {
class WeatherService {
public:
explicit WeatherService(const DateTime& dateTimeController);
void Init();
int OnCommand(struct ble_gatt_access_ctxt* ctxt);
/*
* Helper functions for quick access to currently valid data
*/
std::unique_ptr<WeatherData::Location>& GetCurrentLocation();
std::unique_ptr<WeatherData::Clouds>& GetCurrentClouds();
std::unique_ptr<WeatherData::Obscuration>& GetCurrentObscuration();
std::unique_ptr<WeatherData::Precipitation>& GetCurrentPrecipitation();
std::unique_ptr<WeatherData::Wind>& GetCurrentWind();
std::unique_ptr<WeatherData::Temperature>& GetCurrentTemperature();
std::unique_ptr<WeatherData::Humidity>& GetCurrentHumidity();
std::unique_ptr<WeatherData::Pressure>& GetCurrentPressure();
std::unique_ptr<WeatherData::AirQuality>& GetCurrentQuality();
/**
* Searches for the current day's maximum temperature
* @return -32768 if there's no data, degrees Celsius times 100 otherwise
*/
int16_t GetTodayMaxTemp() const;
/**
* Searches for the current day's minimum temperature
* @return -32768 if there's no data, degrees Celsius times 100 otherwise
*/
int16_t GetTodayMinTemp() const;
/*
* Management functions
*/
/**
* Adds an event to the timeline
* @return
*/
bool AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event);
/**
* Gets the current timeline length
*/
size_t GetTimelineLength() const;
/**
* Checks if an event of a certain type exists in the timeline
*/
bool HasTimelineEventOfType(WeatherData::eventtype type) const;
private:
// 00040000-78fc-48fe-8e23-433b3a1942d0
static constexpr ble_uuid128_t BaseUuid() {
return CharUuid(0x00, 0x00);
}
// 0004yyxx-78fc-48fe-8e23-433b3a1942d0
static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
.value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}};
}
ble_uuid128_t weatherUuid {BaseUuid()};
/**
* Just write timeline data here.
*
* See {@link WeatherData.h} for more information.
*/
ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
/**
* This doesn't take timeline data, provides some control over it.
*
* NOTE: Currently not supported. Companion app implementer feedback required.
* There's very little point in solidifying an API before we know the needs.
*/
ble_uuid128_t weatherControlCharUuid {CharUuid(0x00, 0x02)};
const struct ble_gatt_chr_def characteristicDefinition[3] = {
{.uuid = &weatherDataCharUuid.u,
.access_cb = WeatherCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_WRITE,
.val_handle = &eventHandle},
{.uuid = &weatherControlCharUuid.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ},
{nullptr}};
const struct ble_gatt_svc_def serviceDefinition[2] = {
{.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition},
{0}};
uint16_t eventHandle {};
const Pinetime::Controllers::DateTime& dateTimeController;
std::vector<std::unique_ptr<WeatherData::TimelineHeader>> timeline;
std::unique_ptr<WeatherData::TimelineHeader> nullTimelineheader = std::make_unique<WeatherData::TimelineHeader>();
std::unique_ptr<WeatherData::TimelineHeader>* nullHeader;
/**
* Cleans up the timeline of expired events
*/
void TidyTimeline();
/**
* Compares two timeline events
*/
static bool CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
const std::unique_ptr<WeatherData::TimelineHeader>& second);
/**
* Returns current UNIX timestamp
*/
uint64_t GetCurrentUnixTimestamp() const;
/**
* Checks if the event hasn't gone past and expired
*
* @param header timeline event to check
* @param currentTimestamp what's the time right now
* @return if the event is valid
*/
static bool IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp);
/**
* This is a helper function that closes a QCBOR map and decoding context cleanly
*/
void CleanUpQcbor(QCBORDecodeContext* decodeContext);
};
}
}

View File

@@ -2,38 +2,138 @@
#include <hal/nrf_gpio.h> #include <hal/nrf_gpio.h>
#include "displayapp/screens/Symbols.h" #include "displayapp/screens/Symbols.h"
#include "drivers/PinMap.h" #include "drivers/PinMap.h"
#include <libraries/delay/nrf_delay.h>
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
namespace {
// reinterpret_cast is not constexpr so this is the best we can do
static NRF_RTC_Type* const RTC = reinterpret_cast<NRF_RTC_Type*>(NRF_RTC2_BASE);
}
void BrightnessController::Init() { void BrightnessController::Init() {
nrf_gpio_cfg_output(PinMap::LcdBacklightLow); nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
nrf_gpio_cfg_output(PinMap::LcdBacklightMedium); nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
nrf_gpio_cfg_output(PinMap::LcdBacklightHigh); nrf_gpio_cfg_output(PinMap::LcdBacklightHigh);
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
static_assert(timerFrequency == 32768, "Change the prescaler below");
RTC->PRESCALER = 0;
// CC1 switches the backlight on (pin transitions from high to low) and resets the counter to 0
RTC->CC[1] = timerPeriod;
// Enable compare events for CC0,CC1
RTC->EVTEN = 0b0000'0000'0000'0011'0000'0000'0000'0000;
// Disable all interrupts
RTC->INTENCLR = 0b0000'0000'0000'1111'0000'0000'0000'0011;
Set(level); Set(level);
} }
void BrightnessController::ApplyBrightness(uint16_t rawBrightness) {
// The classic off, low, medium, high brightnesses are at {0, timerPeriod, timerPeriod*2, timerPeriod*3}
// These brightness levels do not use PWM: they only set/clear the corresponding pins
// Any brightness level between the above levels is achieved with efficient RTC based PWM on the next pin up
// E.g 2.5*timerPeriod corresponds to medium brightness with 50% PWM on the high pin
// Note: Raw brightness does not necessarily correspond to a linear perceived brightness
uint8_t pin;
if (rawBrightness > 2 * timerPeriod) {
rawBrightness -= 2 * timerPeriod;
pin = PinMap::LcdBacklightHigh;
} else if (rawBrightness > timerPeriod) {
rawBrightness -= timerPeriod;
pin = PinMap::LcdBacklightMedium;
} else {
pin = PinMap::LcdBacklightLow;
}
if (rawBrightness == timerPeriod || rawBrightness == 0) {
if (lastPin != UNSET) {
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
nrf_ppi_channel_disable(ppiBacklightOff);
nrf_ppi_channel_disable(ppiBacklightOn);
nrfx_gpiote_out_uninit(lastPin);
nrf_gpio_cfg_output(lastPin);
}
lastPin = UNSET;
if (rawBrightness == 0) {
nrf_gpio_pin_set(pin);
} else {
nrf_gpio_pin_clear(pin);
}
} else {
// If the pin on which we are doing PWM is changing
// Disable old PWM channel (if exists) and set up new one
if (lastPin != pin) {
if (lastPin != UNSET) {
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
nrf_ppi_channel_disable(ppiBacklightOff);
nrf_ppi_channel_disable(ppiBacklightOn);
nrfx_gpiote_out_uninit(lastPin);
nrf_gpio_cfg_output(lastPin);
}
nrfx_gpiote_out_config_t gpioteCfg = {.action = NRF_GPIOTE_POLARITY_TOGGLE,
.init_state = NRF_GPIOTE_INITIAL_VALUE_LOW,
.task_pin = true};
APP_ERROR_CHECK(nrfx_gpiote_out_init(pin, &gpioteCfg));
nrfx_gpiote_out_task_enable(pin);
nrf_ppi_channel_endpoint_setup(ppiBacklightOff,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[0]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_channel_endpoint_setup(ppiBacklightOn,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[1]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast<uint32_t>(&RTC->TASKS_CLEAR));
nrf_ppi_channel_enable(ppiBacklightOff);
nrf_ppi_channel_enable(ppiBacklightOn);
} else {
// If the pin used for PWM isn't changing, we only need to set the pin state to the initial value (low)
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
// Due to errata 20,179 and the intricacies of RTC timing, keep it simple: override the pin state
nrfx_gpiote_out_task_force(pin, false);
}
// CC0 switches the backlight off (pin transitions from low to high)
RTC->CC[0] = rawBrightness;
RTC->TASKS_CLEAR = 1;
RTC->TASKS_START = 1;
lastPin = pin;
}
switch (pin) {
case PinMap::LcdBacklightHigh:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
break;
case PinMap::LcdBacklightMedium:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
break;
case PinMap::LcdBacklightLow:
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
}
}
void BrightnessController::Set(BrightnessController::Levels level) { void BrightnessController::Set(BrightnessController::Levels level) {
this->level = level; this->level = level;
switch (level) { switch (level) {
default: default:
case Levels::High: case Levels::High:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow); ApplyBrightness(3 * timerPeriod);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
break; break;
case Levels::Medium: case Levels::Medium:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow); ApplyBrightness(2 * timerPeriod);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
break; break;
case Levels::Low: case Levels::Low:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow); ApplyBrightness(timerPeriod);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium); break;
nrf_gpio_pin_set(PinMap::LcdBacklightHigh); case Levels::AlwaysOn:
ApplyBrightness(timerPeriod / 10);
break; break;
case Levels::Off: case Levels::Off:
nrf_gpio_pin_set(PinMap::LcdBacklightLow); ApplyBrightness(0);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
break; break;
} }
} }

View File

@@ -2,11 +2,14 @@
#include <cstdint> #include <cstdint>
#include "nrf_ppi.h"
#include "nrfx_gpiote.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
class BrightnessController { class BrightnessController {
public: public:
enum class Levels { Off, Low, Medium, High }; enum class Levels { Off, AlwaysOn, Low, Medium, High };
void Init(); void Init();
void Set(Levels level); void Set(Levels level);
@@ -20,6 +23,25 @@ namespace Pinetime {
private: private:
Levels level = Levels::High; Levels level = Levels::High;
static constexpr uint8_t UNSET = UINT8_MAX;
uint8_t lastPin = UNSET;
// Maximum time (μs) it takes for the RTC to fully stop
static constexpr uint8_t rtcStopTime = 46;
// Frequency of timer used for PWM (Hz)
static constexpr uint16_t timerFrequency = 32768;
// Backlight PWM frequency (Hz)
static constexpr uint16_t pwmFreq = 1000;
// Wraparound point in timer ticks
// Defines the number of brightness levels between each pin
static constexpr uint16_t timerPeriod = timerFrequency / pwmFreq;
// Warning: nimble reserves some PPIs
// https://github.com/InfiniTimeOrg/InfiniTime/blob/034d83fe6baf1ab3875a34f8cee387e24410a824/src/libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c#L53
// SpiMaster uses PPI 0 for an erratum workaround
// Channel 1, 2 should be free to use
static constexpr nrf_ppi_channel_t ppiBacklightOn = NRF_PPI_CHANNEL1;
static constexpr nrf_ppi_channel_t ppiBacklightOff = NRF_PPI_CHANNEL2;
void ApplyBrightness(uint16_t val);
}; };
} }
} }

View File

@@ -1,22 +1,42 @@
#include "components/datetime/DateTimeController.h" #include "components/datetime/DateTimeController.h"
#include <libraries/log/nrf_log.h> #include <libraries/log/nrf_log.h>
#include <systemtask/SystemTask.h> #include <systemtask/SystemTask.h>
#include <hal/nrf_rtc.h>
#include "nrf_assert.h"
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
namespace { namespace {
char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"}; constexpr const char* const DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
char const* DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; constexpr const 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"}; constexpr const 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 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} { 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) { void DateTime::SetCurrentTime(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> t) {
xSemaphoreTake(mutex, portMAX_DELAY);
this->currentDateTime = t; this->currentDateTime = t;
UpdateTime(previousSystickCounter); // Update internal state without updating the time UpdateTime(previousSystickCounter, true); // Update internal state without updating the time
xSemaphoreGive(mutex);
} }
void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
@@ -29,15 +49,19 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour,
/* .tm_year = */ year - 1900, /* .tm_year = */ year - 1900,
}; };
tm.tm_isdst = -1; // Use DST value from local time zone
currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm));
NRF_LOG_INFO("%d %d %d ", day, month, year); NRF_LOG_INFO("%d %d %d ", day, month, year);
NRF_LOG_INFO("%d %d %d ", hour, minute, second); NRF_LOG_INFO("%d %d %d ", hour, minute, second);
UpdateTime(previousSystickCounter); tm.tm_isdst = -1; // Use DST value from local time zone
systemTask->PushMessage(System::Messages::OnNewTime); xSemaphoreTake(mutex, portMAX_DELAY);
currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm));
UpdateTime(previousSystickCounter, true);
xSemaphoreGive(mutex);
if (systemTask != nullptr) {
systemTask->PushMessage(System::Messages::OnNewTime);
}
} }
void DateTime::SetTimeZone(int8_t timezone, int8_t dst) { void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
@@ -45,25 +69,34 @@ void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
dstOffset = dst; dstOffset = dst;
} }
void DateTime::UpdateTime(uint32_t systickCounter) { std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> DateTime::CurrentDateTime() {
xSemaphoreTake(mutex, portMAX_DELAY);
UpdateTime(nrf_rtc_counter_get(portNRF_RTC_REG), false);
xSemaphoreGive(mutex);
return currentDateTime;
}
void DateTime::UpdateTime(uint32_t systickCounter, bool forceUpdate) {
// Handle systick counter overflow // Handle systick counter overflow
uint32_t systickDelta = 0; uint32_t systickDelta = 0;
if (systickCounter < previousSystickCounter) { if (systickCounter < previousSystickCounter) {
systickDelta = 0xffffff - previousSystickCounter; systickDelta = static_cast<uint32_t>(portNRF_RTC_MAXTICKS) - previousSystickCounter;
systickDelta += systickCounter + 1; systickDelta += systickCounter + 1;
} else { } else {
systickDelta = systickCounter - previousSystickCounter; systickDelta = systickCounter - previousSystickCounter;
} }
/* auto correctedDelta = systickDelta / configTICK_RATE_HZ;
* 1000 ms = 1024 ticks // If a second hasn't passed, there is nothing to do
*/ // If the time has been changed, set forceUpdate to trigger internal state updates
auto correctedDelta = systickDelta / 1024; if (correctedDelta == 0 && !forceUpdate) {
auto rest = systickDelta % 1024; return;
}
auto rest = systickDelta % configTICK_RATE_HZ;
if (systickCounter >= rest) { if (systickCounter >= rest) {
previousSystickCounter = systickCounter - rest; previousSystickCounter = systickCounter - rest;
} else { } else {
previousSystickCounter = 0xffffff - (rest - systickCounter); previousSystickCounter = static_cast<uint32_t>(portNRF_RTC_MAXTICKS) - (rest - systickCounter - 1);
} }
currentDateTime += std::chrono::seconds(correctedDelta); currentDateTime += std::chrono::seconds(correctedDelta);
@@ -115,8 +148,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
return MonthsStringLow[static_cast<uint8_t>(month)]; return MonthsStringLow[static_cast<uint8_t>(month)];
} }
const char* DateTime::DayOfWeekShortToStringLow() const { const char* DateTime::DayOfWeekShortToStringLow(Days day) {
return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())]; return DaysStringShortLow[static_cast<uint8_t>(day)];
} }
void DateTime::Register(Pinetime::System::SystemTask* systemTask) { void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
@@ -140,9 +173,9 @@ std::string DateTime::FormattedTime() {
hour12 = (hour == 12) ? 12 : hour - 12; hour12 = (hour == 12) ? 12 : hour - 12;
amPmStr = "PM"; amPmStr = "PM";
} }
sprintf(buff, "%i:%02i %s", hour12, minute, amPmStr); snprintf(buff, sizeof(buff), "%i:%02i %s", hour12, minute, amPmStr);
} else { } else {
sprintf(buff, "%02i:%02i", hour, minute); snprintf(buff, sizeof(buff), "%02i:%02i", hour, minute);
} }
return std::string(buff); return std::string(buff);
} }

View File

@@ -5,6 +5,8 @@
#include <ctime> #include <ctime>
#include <string> #include <string>
#include "components/settings/Settings.h" #include "components/settings/Settings.h"
#include <FreeRTOS.h>
#include <semphr.h>
namespace Pinetime { namespace Pinetime {
namespace System { namespace System {
@@ -39,14 +41,12 @@ namespace Pinetime {
* *
* used to update difference between utc and local time (see UtcOffset()) * 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 * timezone is expected to be constant over DST which will be reported in
* dst field. * dst field.
*/ */
void SetTimeZone(int8_t timezone, int8_t dst); void SetTimeZone(int8_t timezone, int8_t dst);
void UpdateTime(uint32_t systickCounter);
uint16_t Year() const { uint16_t Year() const {
return 1900 + localTime.tm_year; return 1900 + localTime.tm_year;
} }
@@ -122,14 +122,12 @@ namespace Pinetime {
const char* MonthShortToString() const; const char* MonthShortToString() const;
const char* DayOfWeekShortToString() const; const char* DayOfWeekShortToString() const;
static const char* MonthShortToStringLow(Months month); static const char* MonthShortToStringLow(Months month);
const char* DayOfWeekShortToStringLow() const; static const char* DayOfWeekShortToStringLow(Days day);
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const { std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime();
return currentDateTime;
}
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> UTCDateTime() const { std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> UTCDateTime() {
return currentDateTime - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60); return CurrentDateTime() - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60);
} }
std::chrono::seconds Uptime() const { std::chrono::seconds Uptime() const {
@@ -141,10 +139,14 @@ namespace Pinetime {
std::string FormattedTime(); std::string FormattedTime();
private: private:
void UpdateTime(uint32_t systickCounter, bool forceUpdate);
std::tm localTime; std::tm localTime;
int8_t tzOffset = 0; int8_t tzOffset = 0;
int8_t dstOffset = 0; int8_t dstOffset = 0;
SemaphoreHandle_t mutex = nullptr;
uint32_t previousSystickCounter = 0; uint32_t previousSystickCounter = 0;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> currentDateTime; std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> currentDateTime;
std::chrono::seconds uptime {0}; std::chrono::seconds uptime {0};

View File

@@ -0,0 +1,41 @@
# Refactoring needed
## Context
The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) highlighted some
limitations in the design of DateTimeController: the granularity of the time returned by `DateTime::CurrentDateTime()`
is limited by the frequency at which SystemTask calls `DateTime::UpdateTime()`, which is currently set to 100ms.
@mark9064 provided more details
in [this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041#issuecomment-2048528967).
The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) provided some changes
to `DateTime` controller that improves the granularity of the time returned by `DateTime::CurrentDateTime()`.
However, the review showed that `DateTime` cannot be `const` anymore, even when it's only used to get the current time,
since `DateTime::CurrentDateTime()` changes the internal state of the instance.
We tried to identify alternative implementation that would have maintained the "const correctness" but we eventually
figured that this would lead to a re-design of `DateTime` which was out of scope of the initial PR (Continuous time
updates and always on display).
So we decided to merge this PR #2041 and agree to fix/improve `DateTime` later on.
## What needs to be done?
Improve/redesign `DateTime` so that it
* provides a very granular (ideally down to the millisecond) date and time via `CurrentDateTime()`.
* can be declared/passed as `const` when it's only used to **get** the time.
* limits the use of mutex as much as possible (an ideal implementation would not use any mutex, but this might not be
possible).
* improves the design of `DateTime::Seconds()`, `DateTime::Minutes()`, `DateTime::Hours()`, etc as
explained [in this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054#pullrequestreview-2037033105).
Once this redesign is implemented, all instances/references to `DateTime` should be reviewed and updated to use `const`
where appropriate.
Please check the following PR to get more context about this redesign:
* [#2041 - Continuous time updates by @mark9064](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041)
* [#2054 - Continuous time update - Alternative implementation to #2041 by @JF002](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054)

View File

@@ -1,196 +0,0 @@
#include "components/gfx/Gfx.h"
#include "drivers/St7789.h"
using namespace Pinetime::Components;
Gfx::Gfx(Pinetime::Drivers::St7789& lcd) : lcd {lcd} {
}
void Gfx::Init() {
}
void Gfx::ClearScreen() {
SetBackgroundColor(0x0000);
state.remainingIterations = 240 + 1;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(0, 0, width, height, reinterpret_cast<const uint8_t*>(buffer), width * 2);
WaitTransferFinished();
}
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
SetBackgroundColor(color);
state.remainingIterations = h;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(buffer), width * 2);
WaitTransferFinished();
}
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
state.remainingIterations = h;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.color = 0x00;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(b), width * 2);
WaitTransferFinished();
}
void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap) {
if (y > (height - p_font->height)) {
// Not enough space to write even single char.
return;
}
uint8_t current_x = x;
uint8_t current_y = y;
for (size_t i = 0; text[i] != '\0'; i++) {
if (text[i] == '\n') {
current_x = x;
current_y += p_font->height + p_font->height / 10;
} else {
DrawChar(p_font, (uint8_t) text[i], &current_x, current_y, color);
}
uint8_t char_idx = text[i] - p_font->startChar;
uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits;
if (current_x > (width - char_width)) {
if (wrap) {
current_x = x;
current_y += p_font->height + p_font->height / 10;
} else {
break;
}
if (y > (height - p_font->height)) {
break;
}
}
}
}
void Gfx::DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color) {
uint8_t char_idx = c - font->startChar;
uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
uint16_t bg = 0x0000;
if (c == ' ') {
*x += font->height / 2;
return;
}
// Build first line
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
buffer[(j * 8) + k] = color;
} else {
buffer[(j * 8) + k] = bg;
}
}
}
state.remainingIterations = font->height + 0;
state.currentIteration = 0;
state.busy = true;
state.action = Action::DrawChar;
state.font = const_cast<FONT_INFO*>(font);
state.character = c;
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(*x, y, bytes_in_line * 8, font->height, reinterpret_cast<const uint8_t*>(&buffer), bytes_in_line * 8 * 2);
WaitTransferFinished();
*x += font->charInfo[char_idx].widthBits + font->spacePixels;
}
void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
lcd.DrawPixel(x, y, color);
}
void Gfx::Sleep() {
lcd.Sleep();
}
void Gfx::Wakeup() {
lcd.Wakeup();
}
void Gfx::SetBackgroundColor(uint16_t color) {
for (int i = 0; i < width; i++) {
buffer[i] = color;
}
}
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
if (!state.busy)
return false;
state.remainingIterations--;
if (state.remainingIterations == 0) {
state.busy = false;
NotifyEndOfTransfer(state.taskToNotify);
return false;
}
if (state.action == Action::FillRectangle) {
*data = reinterpret_cast<uint8_t*>(buffer);
size = width * 2;
} else if (state.action == Action::DrawChar) {
uint16_t bg = 0x0000;
uint8_t char_idx = state.character - state.font->startChar;
uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration + 1) * bytes_in_line) + j]) {
buffer[(j * 8) + k] = state.color;
} else {
buffer[(j * 8) + k] = bg;
}
}
}
*data = reinterpret_cast<uint8_t*>(buffer);
size = bytes_in_line * 8 * 2;
}
state.currentIteration++;
return true;
}
void Gfx::NotifyEndOfTransfer(TaskHandle_t task) {
if (task != nullptr) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
void Gfx::WaitTransferFinished() const {
ulTaskNotifyTake(pdTRUE, 500);
}
void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines);
}
void Gfx::SetScrollStartLine(uint16_t line) {
lcd.VerticalScrollStartAddress(line);
}

View File

@@ -1,62 +0,0 @@
#pragma once
#include <FreeRTOS.h>
#include <nrf_font.h>
#include <task.h>
#include <cstddef>
#include <cstdint>
#include "drivers/BufferProvider.h"
namespace Pinetime {
namespace Drivers {
class St7789;
}
namespace Components {
class Gfx : public Pinetime::Drivers::BufferProvider {
public:
explicit Gfx(Drivers::St7789& lcd);
void Init();
void ClearScreen();
void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap);
void DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void SetScrollStartLine(uint16_t line);
void Sleep();
void Wakeup();
bool GetNextBuffer(uint8_t** buffer, size_t& size) override;
void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
private:
static constexpr uint8_t width = 240;
static constexpr uint8_t height = 240;
enum class Action { None, FillRectangle, DrawChar };
struct State {
State() : busy {false}, action {Action::None}, remainingIterations {0}, currentIteration {0} {
}
volatile bool busy;
volatile Action action;
volatile uint16_t remainingIterations;
volatile uint16_t currentIteration;
volatile FONT_INFO* font;
volatile uint16_t color;
volatile uint8_t character;
volatile TaskHandle_t taskToNotify = nullptr;
};
volatile State state;
uint16_t buffer[width]; // 1 line buffer
Drivers::St7789& lcd;
void SetBackgroundColor(uint16_t color);
void WaitTransferFinished() const;
void NotifyEndOfTransfer(TaskHandle_t task);
};
}
}

View File

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

View File

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

View File

@@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
service->OnNewStepCountValue(nbSteps); service->OnNewStepCountValue(nbSteps);
} }
if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) { if (service != nullptr && (xHistory[0] != x || yHistory[0] != y || zHistory[0] != z)) {
service->OnNewMotionValues(x, y, z); service->OnNewMotionValues(x, y, z);
} }
lastTime = time; lastTime = time;
time = xTaskGetTickCount(); time = xTaskGetTickCount();
lastX = this->x; xHistory++;
this->x = x; xHistory[0] = x;
yHistory++; yHistory++;
yHistory[0] = y; yHistory[0] = y;
zHistory++; zHistory++;
@@ -67,20 +67,26 @@ MotionController::AccelStats MotionController::GetAccelStats() const {
AccelStats stats; AccelStats stats;
for (uint8_t i = 0; i < AccelStats::numHistory; i++) { for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
stats.xMean += xHistory[histSize - i];
stats.yMean += yHistory[histSize - i]; stats.yMean += yHistory[histSize - i];
stats.zMean += zHistory[histSize - i]; stats.zMean += zHistory[histSize - i];
stats.prevXMean += xHistory[1 + i];
stats.prevYMean += yHistory[1 + i]; stats.prevYMean += yHistory[1 + i];
stats.prevZMean += zHistory[1 + i]; stats.prevZMean += zHistory[1 + i];
} }
stats.xMean /= AccelStats::numHistory;
stats.yMean /= AccelStats::numHistory; stats.yMean /= AccelStats::numHistory;
stats.zMean /= AccelStats::numHistory; stats.zMean /= AccelStats::numHistory;
stats.prevXMean /= AccelStats::numHistory;
stats.prevYMean /= AccelStats::numHistory; stats.prevYMean /= AccelStats::numHistory;
stats.prevZMean /= AccelStats::numHistory; stats.prevZMean /= AccelStats::numHistory;
for (uint8_t i = 0; i < AccelStats::numHistory; i++) { for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
stats.xVariance += (xHistory[histSize - i] - stats.xMean) * (xHistory[histSize - i] - stats.xMean);
stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean); stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean); stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
} }
stats.xVariance /= AccelStats::numHistory;
stats.yVariance /= AccelStats::numHistory; stats.yVariance /= AccelStats::numHistory;
stats.zVariance /= AccelStats::numHistory; stats.zVariance /= AccelStats::numHistory;
@@ -93,7 +99,7 @@ bool MotionController::ShouldRaiseWake() const {
constexpr int16_t yThresh = -64; constexpr int16_t yThresh = -64;
constexpr int16_t rollDegreesThresh = -45; constexpr int16_t rollDegreesThresh = -45;
if (x < -xThresh || x > xThresh) { if (std::abs(stats.xMean) > xThresh) {
return false; return false;
} }
@@ -107,8 +113,9 @@ bool MotionController::ShouldRaiseWake() const {
bool MotionController::ShouldShakeWake(uint16_t thresh) { bool MotionController::ShouldShakeWake(uint16_t thresh) {
/* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */ /* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
int32_t speed = int32_t speed = std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 +
std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime); (xHistory[0] - xHistory[histSize - 1]) / 4) *
100 / (time - lastTime);
// (.2 * speed) + ((1 - .2) * accumulatedSpeed); // (.2 * speed) + ((1 - .2) * accumulatedSpeed);
accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5; accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
@@ -116,6 +123,11 @@ bool MotionController::ShouldShakeWake(uint16_t thresh) {
} }
bool MotionController::ShouldLowerSleep() const { bool MotionController::ShouldLowerSleep() const {
if ((stats.xMean > 887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) > 30) ||
(stats.xMean < -887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) < -30)) {
return true;
}
if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) { if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
return false; return false;
} }

View File

@@ -21,7 +21,7 @@ namespace Pinetime {
void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps); void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
int16_t X() const { int16_t X() const {
return x; return xHistory[0];
} }
int16_t Y() const { int16_t Y() const {
@@ -62,6 +62,10 @@ namespace Pinetime {
this->service = service; this->service = service;
} }
Pinetime::Controllers::MotionService* GetService() const {
return service;
}
private: private:
uint32_t nbSteps = 0; uint32_t nbSteps = 0;
uint32_t currentTripSteps = 0; uint32_t currentTripSteps = 0;
@@ -72,11 +76,14 @@ namespace Pinetime {
struct AccelStats { struct AccelStats {
static constexpr uint8_t numHistory = 2; static constexpr uint8_t numHistory = 2;
int16_t xMean = 0;
int16_t yMean = 0; int16_t yMean = 0;
int16_t zMean = 0; int16_t zMean = 0;
int16_t prevXMean = 0;
int16_t prevYMean = 0; int16_t prevYMean = 0;
int16_t prevZMean = 0; int16_t prevZMean = 0;
uint32_t xVariance = 0;
uint32_t yVariance = 0; uint32_t yVariance = 0;
uint32_t zVariance = 0; uint32_t zVariance = 0;
}; };
@@ -85,9 +92,8 @@ namespace Pinetime {
AccelStats stats = {}; AccelStats stats = {};
int16_t lastX = 0;
int16_t x = 0;
static constexpr uint8_t histSize = 8; static constexpr uint8_t histSize = 8;
Utility::CircularBuffer<int16_t, histSize> xHistory = {};
Utility::CircularBuffer<int16_t, histSize> yHistory = {}; Utility::CircularBuffer<int16_t, histSize> yHistory = {};
Utility::CircularBuffer<int16_t, histSize> zHistory = {}; Utility::CircularBuffer<int16_t, histSize> zHistory = {};
int32_t accumulatedSpeed = 0; int32_t accumulatedSpeed = 0;

View File

@@ -3,13 +3,14 @@
#include <bitset> #include <bitset>
#include "components/brightness/BrightnessController.h" #include "components/brightness/BrightnessController.h"
#include "components/fs/FS.h" #include "components/fs/FS.h"
#include "displayapp/WatchFaces.h" #include "displayapp/apps/Apps.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
class Settings { class Settings {
public: public:
enum class ClockType : uint8_t { H24, H12 }; enum class ClockType : uint8_t { H24, H12 };
enum class WeatherFormat : uint8_t { Metric, Imperial };
enum class Notification : uint8_t { On, Off, Sleep }; enum class Notification : uint8_t { On, Off, Sleep };
enum class ChimesOption : uint8_t { None, Hours, HalfHours }; enum class ChimesOption : uint8_t { None, Hours, HalfHours };
enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 }; enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 };
@@ -180,6 +181,17 @@ namespace Pinetime {
return settings.clockType; return settings.clockType;
}; };
void SetWeatherFormat(WeatherFormat weatherFormat) {
if (weatherFormat != settings.weatherFormat) {
settingsChanged = true;
}
settings.weatherFormat = weatherFormat;
};
WeatherFormat GetWeatherFormat() const {
return settings.weatherFormat;
};
void SetNotificationStatus(Notification status) { void SetNotificationStatus(Notification status) {
if (status != settings.notificationStatus) { if (status != settings.notificationStatus) {
settingsChanged = true; settingsChanged = true;
@@ -202,6 +214,21 @@ namespace Pinetime {
return settings.screenTimeOut; return settings.screenTimeOut;
}; };
bool GetAlwaysOnDisplay() const {
return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep;
};
void SetAlwaysOnDisplaySetting(bool state) {
if (state != settings.alwaysOnDisplay) {
settingsChanged = true;
}
settings.alwaysOnDisplay = state;
}
bool GetAlwaysOnDisplaySetting() const {
return settings.alwaysOnDisplay;
}
void SetShakeThreshold(uint16_t thresh) { void SetShakeThreshold(uint16_t thresh) {
if (settings.shakeWakeThreshold != thresh) { if (settings.shakeWakeThreshold != thresh) {
settings.shakeWakeThreshold = thresh; settings.shakeWakeThreshold = thresh;
@@ -274,14 +301,17 @@ namespace Pinetime {
private: private:
Pinetime::Controllers::FS& fs; Pinetime::Controllers::FS& fs;
static constexpr uint32_t settingsVersion = 0x0006; static constexpr uint32_t settingsVersion = 0x0008;
struct SettingsData { struct SettingsData {
uint32_t version = settingsVersion; uint32_t version = settingsVersion;
uint32_t stepsGoal = 10000; uint32_t stepsGoal = 10000;
uint32_t screenTimeOut = 15000; uint32_t screenTimeOut = 15000;
bool alwaysOnDisplay = false;
ClockType clockType = ClockType::H24; ClockType clockType = ClockType::H24;
WeatherFormat weatherFormat = WeatherFormat::Metric;
Notification notificationStatus = Notification::On; Notification notificationStatus = Notification::On;
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital; Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;

View File

@@ -1,41 +0,0 @@
#pragma once
namespace Pinetime {
namespace Applications {
enum class Apps {
None,
Launcher,
Clock,
SysInfo,
FirmwareUpdate,
FirmwareValidation,
NotificationsPreview,
Notifications,
Timer,
Alarm,
FlashLight,
BatteryInfo,
Music,
Twos,
HeartRate,
Navigation,
StopWatch,
Metronome,
Motion,
Steps,
PassKey,
QuickSettings,
Settings,
SettingWatchFace,
SettingTimeFormat,
SettingDisplay,
SettingWakeUp,
SettingSteps,
SettingSetDateTime,
SettingChimes,
SettingShakeThreshold,
SettingBluetooth,
Error
};
}
}

View File

@@ -0,0 +1,56 @@
#pragma once
namespace Pinetime {
namespace Applications {
class DisplayApp;
}
namespace Components {
class LittleVgl;
}
namespace Controllers {
class Battery;
class Ble;
class DateTime;
class NotificationManager;
class HeartRateController;
class Settings;
class MotorController;
class MotionController;
class AlarmController;
class BrightnessController;
class SimpleWeatherService;
class FS;
class Timer;
class MusicService;
class NavigationService;
}
namespace System {
class SystemTask;
}
namespace Applications {
struct AppControllers {
const Pinetime::Controllers::Battery& batteryController;
const Pinetime::Controllers::Ble& bleController;
Pinetime::Controllers::DateTime& dateTimeController;
Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Controllers::HeartRateController& heartRateController;
Pinetime::Controllers::Settings& settingsController;
Pinetime::Controllers::MotorController& motorController;
Pinetime::Controllers::MotionController& motionController;
Pinetime::Controllers::AlarmController& alarmController;
Pinetime::Controllers::BrightnessController& brightnessController;
Pinetime::Controllers::SimpleWeatherService* weatherController;
Pinetime::Controllers::FS& filesystem;
Pinetime::Controllers::Timer& timer;
Pinetime::System::SystemTask* systemTask;
Pinetime::Applications::DisplayApp* displayApp;
Pinetime::Components::LittleVgl& lvgl;
Pinetime::Controllers::MusicService* musicService;
Pinetime::Controllers::NavigationService* navigationService;
};
}
}

View File

@@ -11,9 +11,10 @@
#include "components/motion/MotionController.h" #include "components/motion/MotionController.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "displayapp/screens/ApplicationList.h" #include "displayapp/screens/ApplicationList.h"
#include "displayapp/screens/Clock.h"
#include "displayapp/screens/FirmwareUpdate.h" #include "displayapp/screens/FirmwareUpdate.h"
#include "displayapp/screens/FirmwareValidation.h" #include "displayapp/screens/FirmwareValidation.h"
#include "displayapp/screens/InfiniPaint.h"
#include "displayapp/screens/Paddle.h"
#include "displayapp/screens/StopWatch.h" #include "displayapp/screens/StopWatch.h"
#include "displayapp/screens/Metronome.h" #include "displayapp/screens/Metronome.h"
#include "displayapp/screens/Music.h" #include "displayapp/screens/Music.h"
@@ -25,9 +26,10 @@
#include "displayapp/screens/FlashLight.h" #include "displayapp/screens/FlashLight.h"
#include "displayapp/screens/BatteryInfo.h" #include "displayapp/screens/BatteryInfo.h"
#include "displayapp/screens/Steps.h" #include "displayapp/screens/Steps.h"
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Weather.h"
#include "displayapp/screens/PassKey.h" #include "displayapp/screens/PassKey.h"
#include "displayapp/screens/Error.h" #include "displayapp/screens/Error.h"
#include "displayapp/screens/Weather.h"
#include "drivers/Cst816s.h" #include "drivers/Cst816s.h"
#include "drivers/St7789.h" #include "drivers/St7789.h"
@@ -39,6 +41,7 @@
#include "displayapp/screens/settings/Settings.h" #include "displayapp/screens/settings/Settings.h"
#include "displayapp/screens/settings/SettingWatchFace.h" #include "displayapp/screens/settings/SettingWatchFace.h"
#include "displayapp/screens/settings/SettingTimeFormat.h" #include "displayapp/screens/settings/SettingTimeFormat.h"
#include "displayapp/screens/settings/SettingWeatherFormat.h"
#include "displayapp/screens/settings/SettingWakeUp.h" #include "displayapp/screens/settings/SettingWakeUp.h"
#include "displayapp/screens/settings/SettingDisplay.h" #include "displayapp/screens/settings/SettingDisplay.h"
#include "displayapp/screens/settings/SettingSteps.h" #include "displayapp/screens/settings/SettingSteps.h"
@@ -48,6 +51,7 @@
#include "displayapp/screens/settings/SettingBluetooth.h" #include "displayapp/screens/settings/SettingBluetooth.h"
#include "libs/lv_conf.h" #include "libs/lv_conf.h"
#include "UserApps.h"
using namespace Pinetime::Applications; using namespace Pinetime::Applications;
using namespace Pinetime::Applications::Display; using namespace Pinetime::Applications::Display;
@@ -77,7 +81,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem) Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: lcd {lcd}, : lcd {lcd},
touchPanel {touchPanel}, touchPanel {touchPanel},
batteryController {batteryController}, batteryController {batteryController},
@@ -93,8 +98,27 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
brightnessController {brightnessController}, brightnessController {brightnessController},
touchHandler {touchHandler}, touchHandler {touchHandler},
filesystem {filesystem}, filesystem {filesystem},
spiNorFlash {spiNorFlash},
lvgl {lcd, filesystem}, lvgl {lcd, filesystem},
timer(this, TimerCallback) { timer(this, TimerCallback),
controllers {batteryController,
bleController,
dateTimeController,
notificationManager,
heartRateController,
settingsController,
motorController,
motionController,
alarmController,
brightnessController,
nullptr,
filesystem,
timer,
nullptr,
this,
lvgl,
nullptr,
nullptr} {
} }
void DisplayApp::Start(System::BootErrors error) { void DisplayApp::Start(System::BootErrors error) {
@@ -103,6 +127,7 @@ void DisplayApp::Start(System::BootErrors error) {
bootError = error; bootError = error;
lvgl.Init(); lvgl.Init();
motorController.Init();
if (error == System::BootErrors::TouchController) { if (error == System::BootErrors::TouchController) {
LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None); LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
@@ -120,9 +145,6 @@ void DisplayApp::Process(void* instance) {
NRF_LOG_INFO("displayapp task started!"); NRF_LOG_INFO("displayapp task started!");
app->InitHw(); app->InitHw();
// Send a dummy notification to unlock the lvgl display driver for the first iteration
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
while (true) { while (true) {
app->Refresh(); app->Refresh();
} }
@@ -131,10 +153,47 @@ void DisplayApp::Process(void* instance) {
void DisplayApp::InitHw() { void DisplayApp::InitHw() {
brightnessController.Init(); brightnessController.Init();
ApplyBrightness(); ApplyBrightness();
motorController.Init();
lcd.Init(); lcd.Init();
} }
TickType_t DisplayApp::CalculateSleepTime() {
// Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame
// Next frame time is frame count * refresh period (ms) * tick rate
auto RoundedDiv = [](uint32_t a, uint32_t b) {
return ((a + (b / 2)) / b);
};
// RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours (=overflow frame count / always on refresh period)
constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
// Divide both the numerator and denominator by 8 (=GCD(1000,1024))
// to increase the number of ticks (frames) before the overflow tick is reached
TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8);
// Assumptions
// Tick rate is multiple of 8
// Needed for division trick above
static_assert(configTICK_RATE_HZ % 8 == 0);
// Frame count must always wraparound more often than the system tick count does
// Always on overflow time (ms) < system tick overflow time (ms)
// Using 64bit ints here to avoid overflow
static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
if (alwaysOnFrameCount == overflowFrameCount) {
alwaysOnFrameCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
}
if (targetRenderTick > ticksElapsed) {
return targetRenderTick - ticksElapsed;
} else {
return 0;
}
}
void DisplayApp::Refresh() { void DisplayApp::Refresh() {
auto LoadPreviousScreen = [this]() { auto LoadPreviousScreen = [this]() {
FullRefreshDirections returnDirection; FullRefreshDirections returnDirection;
@@ -158,21 +217,6 @@ void DisplayApp::Refresh() {
LoadScreen(returnAppStack.Pop(), returnDirection); LoadScreen(returnAppStack.Pop(), returnDirection);
}; };
auto DimScreen = [this]() {
if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
isDimmed = true;
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
}
};
auto RestoreBrightness = [this]() {
if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
isDimmed = false;
lv_disp_trig_activity(nullptr);
ApplyBrightness();
}
};
auto IsPastDimTime = [this]() -> bool { auto IsPastDimTime = [this]() -> bool {
return lv_disp_get_inactive_time(nullptr) >= pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000); return lv_disp_get_inactive_time(nullptr) >= pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000);
}; };
@@ -186,6 +230,27 @@ void DisplayApp::Refresh() {
case States::Idle: case States::Idle:
queueTimeout = portMAX_DELAY; queueTimeout = portMAX_DELAY;
break; break;
case States::AOD:
if (!currentScreen->IsRunning()) {
LoadPreviousScreen();
}
// Check we've slept long enough
// Might not be true if the loop received an event
// If not true, then wait that amount of time
queueTimeout = CalculateSleepTime();
if (queueTimeout == 0) {
// Only advance the tick count when LVGL is done
// Otherwise keep running the task handler while it still has things to draw
// Note: under high graphics load, LVGL will always have more work to do
if (lv_task_handler() > 0) {
// Drop frames that we've missed if drawing/event handling took way longer than expected
while (queueTimeout == 0) {
alwaysOnFrameCount += 1;
queueTimeout = CalculateSleepTime();
}
}
}
break;
case States::Running: case States::Running:
if (!currentScreen->IsRunning()) { if (!currentScreen->IsRunning()) {
LoadPreviousScreen(); LoadPreviousScreen();
@@ -194,14 +259,27 @@ void DisplayApp::Refresh() {
if (!systemTask->IsSleepDisabled() && IsPastDimTime()) { if (!systemTask->IsSleepDisabled() && IsPastDimTime()) {
if (!isDimmed) { if (!isDimmed) {
DimScreen(); isDimmed = true;
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
} }
if (IsPastSleepTime()) { if (IsPastSleepTime() && uxQueueMessagesWaiting(msgQueue) == 0) {
systemTask->PushMessage(System::Messages::GoToSleep); PushMessageToSystemTask(System::Messages::GoToSleep);
state = States::Idle; // Can't set state to Idle here, something may send
// DisableSleeping before this GoToSleep arrives
// Instead we check we have no messages queued before sending GoToSleep
// This works as the SystemTask is higher priority than DisplayApp
// As soon as we send GoToSleep, SystemTask pre-empts DisplayApp
// Whenever DisplayApp is running again, it is guaranteed that
// SystemTask has handled the message
// If it responded, we will have a GoToSleep waiting in the queue
// By checking that there are no messages in the queue, we avoid
// resending GoToSleep when we already have a response
// SystemTask is resilient to duplicate messages, this is an
// optimisation to reduce pressure on the message queues
} }
} else if (isDimmed) { } else if (isDimmed) {
RestoreBrightness(); isDimmed = false;
ApplyBrightness();
} }
break; break;
default: default:
@@ -212,30 +290,73 @@ void DisplayApp::Refresh() {
Messages msg; Messages msg;
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) { if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
switch (msg) { switch (msg) {
case Messages::DimScreen:
DimScreen();
break;
case Messages::RestoreBrightness:
RestoreBrightness();
break;
case Messages::GoToSleep: case Messages::GoToSleep:
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { 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(); brightnessController.Lower();
vTaskDelay(100); vTaskDelay(100);
} }
lcd.Sleep(); // Turn brightness down (or set to AlwaysOn mode)
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); if (msg == Messages::GoToAOD) {
state = States::Idle; brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
} else {
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
}
// Since the active screen is not really an app, go back to Clock.
if (currentApp == Apps::Launcher || currentApp == Apps::Notifications || currentApp == Apps::QuickSettings ||
currentApp == Apps::Settings) {
LoadScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
// Wait for the clock app to load before moving on.
while (!lv_task_handler()) {
};
}
// Clear any ongoing touch pressed events
// Without this LVGL gets stuck in the pressed state and will keep refreshing the
// display activity timer causing the screen to never sleep after timeout
lvgl.ClearTouchState();
if (msg == Messages::GoToAOD) {
lcd.LowPowerOn();
// Record idle entry time
alwaysOnFrameCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD);
state = States::AOD;
} else {
lcd.Sleep();
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle;
}
break;
case Messages::NotifyDeviceActivity:
lv_disp_trig_activity(nullptr);
break; break;
case Messages::GoToRunning: case Messages::GoToRunning:
lcd.Wakeup(); // If SystemTask is sleeping, the GoToRunning message is old
// and must be ignored. Otherwise DisplayApp will use SPI
// that is powered down and cause bad behaviour
if (state == States::Running || systemTask->IsSleeping()) {
break;
}
if (state == States::AOD) {
lcd.LowPowerOff();
} else {
lcd.Wakeup();
}
lv_disp_trig_activity(nullptr); lv_disp_trig_activity(nullptr);
ApplyBrightness(); ApplyBrightness();
state = States::Running; state = States::Running;
break; break;
case Messages::UpdateBleConnection: case Messages::UpdateBleConnection:
// clockScreen.SetBleConnectionState(bleController.IsConnected() ? Screens::Clock::BleConnectionStates::Connected : // Only used for recovery firmware
// Screens::Clock::BleConnectionStates::NotConnected);
break; break;
case Messages::NewNotification: case Messages::NewNotification:
LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down); LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down);
@@ -350,22 +471,17 @@ void DisplayApp::Refresh() {
case Messages::BleRadioEnableToggle: case Messages::BleRadioEnableToggle:
PushMessageToSystemTask(System::Messages::BleRadioEnableToggle); PushMessageToSystemTask(System::Messages::BleRadioEnableToggle);
break; break;
case Messages::UpdateDateTime:
// Added to remove warning
// What should happen here?
break;
case Messages::Chime: case Messages::Chime:
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
motorController.RunForDuration(35); motorController.RunForDuration(35);
break; break;
case Messages::OnChargingEvent: case Messages::OnChargingEvent:
RestoreBrightness();
motorController.RunForDuration(15); motorController.RunForDuration(15);
break; break;
} }
} }
if (touchHandler.IsTouching()) { if (state == States::Running && touchHandler.IsTouching()) {
currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY()); currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY());
} }
@@ -400,26 +516,32 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
SetFullRefresh(direction); SetFullRefresh(direction);
switch (app) { switch (app) {
case Apps::Launcher: case Apps::Launcher: {
currentScreen = std::array<Screens::Tile::Applications, UserAppTypes::Count> apps;
std::make_unique<Screens::ApplicationList>(this, settingsController, batteryController, bleController, dateTimeController, filesystem); int i = 0;
break; for (const auto& userApp : userApps) {
case Apps::Motion: apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
// currentScreen = std::make_unique<Screens::Motion>(motionController); }
// break; currentScreen = std::make_unique<Screens::ApplicationList>(this,
case Apps::None: settingsController,
case Apps::Clock: batteryController,
currentScreen = std::make_unique<Screens::Clock>(dateTimeController, bleController,
batteryController, dateTimeController,
bleController, filesystem,
notificationManager, std::move(apps));
settingsController, } break;
heartRateController, case Apps::Clock: {
motionController, const auto* watchFace =
systemTask->nimble().weather(), std::find_if(userWatchFaces.begin(), userWatchFaces.end(), [this](const WatchFaceDescription& watchfaceDescription) {
filesystem); return watchfaceDescription.watchFace == settingsController.GetWatchFace();
break; });
if (watchFace != userWatchFaces.end())
currentScreen.reset(watchFace->create(controllers));
else {
currentScreen.reset(userWatchFaces[0].create(controllers));
}
settingsController.SetAppMenu(0);
} break;
case Apps::Error: case Apps::Error:
currentScreen = std::make_unique<Screens::Error>(bootError); currentScreen = std::make_unique<Screens::Error>(bootError);
break; break;
@@ -451,14 +573,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
*systemTask, *systemTask,
Screens::Notifications::Modes::Preview); Screens::Notifications::Modes::Preview);
break; break;
case Apps::Timer:
currentScreen = std::make_unique<Screens::Timer>(timer);
break;
case Apps::Alarm:
currentScreen = std::make_unique<Screens::Alarm>(alarmController, settingsController.GetClockType(), *systemTask, motorController);
break;
// Settings
case Apps::QuickSettings: case Apps::QuickSettings:
currentScreen = std::make_unique<Screens::QuickSettings>(this, currentScreen = std::make_unique<Screens::QuickSettings>(this,
batteryController, batteryController,
@@ -471,17 +585,26 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
case Apps::Settings: case Apps::Settings:
currentScreen = std::make_unique<Screens::Settings>(this, settingsController); currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
break; break;
case Apps::SettingWatchFace: case Apps::SettingWatchFace: {
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, settingsController, filesystem); std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
break; int i = 0;
for (const auto& userWatchFace : userWatchFaces) {
items[i++] =
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: case Apps::SettingTimeFormat:
currentScreen = std::make_unique<Screens::SettingTimeFormat>(settingsController); currentScreen = std::make_unique<Screens::SettingTimeFormat>(settingsController);
break; break;
case Apps::SettingWeatherFormat:
currentScreen = std::make_unique<Screens::SettingWeatherFormat>(settingsController);
break;
case Apps::SettingWakeUp: case Apps::SettingWakeUp:
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController); currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
break; break;
case Apps::SettingDisplay: case Apps::SettingDisplay:
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController); currentScreen = std::make_unique<Screens::SettingDisplay>(settingsController);
break; break;
case Apps::SettingSteps: case Apps::SettingSteps:
currentScreen = std::make_unique<Screens::SettingSteps>(settingsController); currentScreen = std::make_unique<Screens::SettingSteps>(settingsController);
@@ -509,37 +632,23 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
bleController, bleController,
watchdog, watchdog,
motionController, motionController,
touchPanel); touchPanel,
spiNorFlash);
break; break;
case Apps::FlashLight: case Apps::FlashLight:
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController); currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
break; break;
case Apps::StopWatch: default: {
currentScreen = std::make_unique<Screens::StopWatch>(*systemTask); const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) {
break; return appDescription.app == app;
case Apps::Twos: });
currentScreen = std::make_unique<Screens::Twos>(); if (d != userApps.end()) {
break; currentScreen.reset(d->create(controllers));
case Apps::Music: } else {
currentScreen = std::make_unique<Screens::Music>(systemTask->nimble().music()); currentScreen.reset(userWatchFaces[0].create(controllers));
break; }
case Apps::Navigation:
currentScreen = std::make_unique<Screens::Navigation>(systemTask->nimble().navigation());
break;
case Apps::HeartRate:
currentScreen = std::make_unique<Screens::HeartRate>(heartRateController, *systemTask);
break;
case Apps::Metronome:
currentScreen = std::make_unique<Screens::Metronome>(motorController, *systemTask);
break;
/* Weather debug app
case Apps::Weather:
currentScreen = std::make_unique<Screens::Weather>(this, systemTask->nimble().weather());
break;
*/
case Apps::Steps:
currentScreen = std::make_unique<Screens::Steps>(motionController, settingsController);
break; break;
}
} }
currentApp = app; currentApp = app;
} }
@@ -548,11 +657,17 @@ void DisplayApp::PushMessage(Messages msg) {
if (in_isr()) { if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken); xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) { portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
} else { } else {
xQueueSend(msgQueue, &msg, portMAX_DELAY); TickType_t timeout = portMAX_DELAY;
// Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
// deadlock between SystemTask and DisplayApp when their respective message queues are getting full
// when a lot of notifications are received on a very short time span.
if (msg == Messages::NewNotification) {
timeout = static_cast<TickType_t>(0);
}
xQueueSend(msgQueue, &msg, timeout);
} }
} }
@@ -589,6 +704,19 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
this->systemTask = systemTask; this->systemTask = systemTask;
this->controllers.systemTask = systemTask;
}
void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) {
this->controllers.weatherController = weatherService;
}
void DisplayApp::Register(Pinetime::Controllers::MusicService* musicService) {
this->controllers.musicService = musicService;
}
void DisplayApp::Register(Pinetime::Controllers::NavigationService* NavigationService) {
this->controllers.navigationService = NavigationService;
} }
void DisplayApp::ApplyBrightness() { void DisplayApp::ApplyBrightness() {

View File

@@ -4,7 +4,7 @@
#include <task.h> #include <task.h>
#include <memory> #include <memory>
#include <systemtask/Messages.h> #include <systemtask/Messages.h>
#include "displayapp/Apps.h" #include "displayapp/apps/Apps.h"
#include "displayapp/LittleVgl.h" #include "displayapp/LittleVgl.h"
#include "displayapp/TouchEvents.h" #include "displayapp/TouchEvents.h"
#include "components/brightness/BrightnessController.h" #include "components/brightness/BrightnessController.h"
@@ -20,6 +20,7 @@
#include "BootErrors.h" #include "BootErrors.h"
#include "utility/StaticStack.h" #include "utility/StaticStack.h"
#include "displayapp/Controllers.h"
namespace Pinetime { namespace Pinetime {
@@ -38,6 +39,7 @@ namespace Pinetime {
class HeartRateController; class HeartRateController;
class MotionController; class MotionController;
class TouchHandler; class TouchHandler;
class SimpleWeatherService;
} }
namespace System { namespace System {
@@ -47,7 +49,7 @@ namespace Pinetime {
namespace Applications { namespace Applications {
class DisplayApp { class DisplayApp {
public: public:
enum class States { Idle, Running }; enum class States { Idle, Running, AOD };
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim }; enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
DisplayApp(Drivers::St7789& lcd, DisplayApp(Drivers::St7789& lcd,
@@ -64,7 +66,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem); Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start(System::BootErrors error); void Start(System::BootErrors error);
void PushMessage(Display::Messages msg); void PushMessage(Display::Messages msg);
@@ -73,6 +76,9 @@ namespace Pinetime {
void SetFullRefresh(FullRefreshDirections direction); void SetFullRefresh(FullRefreshDirections direction);
void Register(Pinetime::System::SystemTask* systemTask); void Register(Pinetime::System::SystemTask* systemTask);
void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
void Register(Pinetime::Controllers::MusicService* musicService);
void Register(Pinetime::Controllers::NavigationService* NavigationService);
private: private:
Pinetime::Drivers::St7789& lcd; Pinetime::Drivers::St7789& lcd;
@@ -91,11 +97,13 @@ namespace Pinetime {
Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::BrightnessController& brightnessController;
Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::TouchHandler& touchHandler;
Pinetime::Controllers::FS& filesystem; Pinetime::Controllers::FS& filesystem;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
Pinetime::Controllers::FirmwareValidator validator; Pinetime::Controllers::FirmwareValidator validator;
Pinetime::Components::LittleVgl lvgl; Pinetime::Components::LittleVgl lvgl;
Pinetime::Controllers::Timer timer; Pinetime::Controllers::Timer timer;
AppControllers controllers;
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
States state = States::Running; States state = States::Running;
@@ -129,6 +137,13 @@ namespace Pinetime {
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections; Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections;
bool isDimmed = false; bool isDimmed = false;
TickType_t CalculateSleepTime();
TickType_t alwaysOnFrameCount;
TickType_t alwaysOnStartTime;
// If this is to be changed, make sure the actual always on refresh rate is changed
// by configuring the LCD refresh timings
static constexpr uint32_t alwaysOnRefreshPeriod = 500;
}; };
} }
} }

View File

@@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& /*alarmController*/, Pinetime::Controllers::AlarmController& /*alarmController*/,
Pinetime::Controllers::BrightnessController& /*brightnessController*/, Pinetime::Controllers::BrightnessController& /*brightnessController*/,
Pinetime::Controllers::TouchHandler& /*touchHandler*/, Pinetime::Controllers::TouchHandler& /*touchHandler*/,
Pinetime::Controllers::FS& /*filesystem*/) Pinetime::Controllers::FS& /*filesystem*/,
Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/)
: lcd {lcd}, bleController {bleController} { : lcd {lcd}, bleController {bleController} {
} }
@@ -38,9 +39,6 @@ void DisplayApp::Process(void* instance) {
auto* app = static_cast<DisplayApp*>(instance); auto* app = static_cast<DisplayApp*>(instance);
NRF_LOG_INFO("displayapp task started!"); NRF_LOG_INFO("displayapp task started!");
// Send a dummy notification to unlock the lvgl display driver for the first iteration
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
app->InitHw(); app->InitHw();
while (true) { while (true) {
app->Refresh(); app->Refresh();
@@ -94,7 +92,6 @@ void DisplayApp::DisplayLogo(uint16_t color) {
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack); Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
for (int i = 0; i < displayWidth; i++) { for (int i = 0; i < displayWidth; i++) {
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel); rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
ulTaskNotifyTake(pdTRUE, 500);
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel); lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
} }
} }
@@ -103,21 +100,25 @@ void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) {
const uint8_t barHeight = 20; const uint8_t barHeight = 20;
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color); std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
for (int i = 0; i < barHeight; i++) { for (int i = 0; i < barHeight; i++) {
ulTaskNotifyTake(pdTRUE, 500);
uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth)); uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel); lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
} }
} }
void DisplayApp::PushMessage(Display::Messages msg) { void DisplayApp::PushMessage(Display::Messages msg) {
BaseType_t xHigherPriorityTaskWoken; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken); xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) { portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
/* Actual macro used here is port specific. */
// TODO : should I do something here?
}
} }
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) { void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
} }
void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* /*weatherService*/) {
}
void DisplayApp::Register(Pinetime::Controllers::MusicService* /*musicService*/) {
}
void DisplayApp::Register(Pinetime::Controllers::NavigationService* /*NavigationService*/) {
}

View File

@@ -5,13 +5,12 @@
#include <drivers/SpiMaster.h> #include <drivers/SpiMaster.h>
#include <bits/unique_ptr.h> #include <bits/unique_ptr.h>
#include <queue.h> #include <queue.h>
#include "components/gfx/Gfx.h"
#include "drivers/Cst816s.h" #include "drivers/Cst816s.h"
#include <drivers/Watchdog.h> #include <drivers/Watchdog.h>
#include <components/motor/MotorController.h> #include <components/motor/MotorController.h>
#include "BootErrors.h" #include "BootErrors.h"
#include "displayapp/TouchEvents.h" #include "displayapp/TouchEvents.h"
#include "displayapp/Apps.h" #include "displayapp/apps/Apps.h"
#include "displayapp/Messages.h" #include "displayapp/Messages.h"
namespace Pinetime { namespace Pinetime {
@@ -19,6 +18,7 @@ namespace Pinetime {
class St7789; class St7789;
class Cst816S; class Cst816S;
class Watchdog; class Watchdog;
class SpiNorFlash;
} }
namespace Controllers { namespace Controllers {
@@ -34,6 +34,9 @@ namespace Pinetime {
class AlarmController; class AlarmController;
class BrightnessController; class BrightnessController;
class FS; class FS;
class SimpleWeatherService;
class MusicService;
class NavigationService;
} }
namespace System { namespace System {
@@ -57,7 +60,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem); Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start(); void Start();
void Start(Pinetime::System::BootErrors) { void Start(Pinetime::System::BootErrors) {
@@ -66,6 +70,9 @@ namespace Pinetime {
void PushMessage(Pinetime::Applications::Display::Messages msg); void PushMessage(Pinetime::Applications::Display::Messages msg);
void Register(Pinetime::System::SystemTask* systemTask); void Register(Pinetime::System::SystemTask* systemTask);
void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
void Register(Pinetime::Controllers::MusicService* musicService);
void Register(Pinetime::Controllers::NavigationService* NavigationService);
private: private:
TaskHandle_t taskHandle; TaskHandle_t taskHandle;

View File

@@ -152,10 +152,6 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) { void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
uint16_t y1, y2, width, height = 0; uint16_t y1, y2, width, height = 0;
ulTaskNotifyTake(pdTRUE, 200);
// Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
// which cannot be set/clear during a transfer.
if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) { if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines; writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
} else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) { } else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
@@ -219,7 +215,6 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
if (height > 0) { if (height > 0) {
lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2); lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
ulTaskNotifyTake(pdTRUE, 100);
} }
uint16_t pixOffset = width * height; uint16_t pixOffset = width * height;
@@ -253,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() { void LittleVgl::CancelTap() {
if (tapped) { if (tapped) {
isCancelled = true; isCancelled = true;
@@ -260,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) { bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) {
ptr->point.x = touchPoint.x; ptr->point.x = touchPoint.x;
ptr->point.y = touchPoint.y; ptr->point.y = touchPoint.y;

View File

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

View File

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

60
src/displayapp/UserApps.h Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include "displayapp/apps/Apps.h"
#include "Controllers.h"
#include "displayapp/screens/Alarm.h"
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Timer.h"
#include "displayapp/screens/Twos.h"
#include "displayapp/screens/Tile.h"
#include "displayapp/screens/ApplicationList.h"
#include "displayapp/screens/WatchFaceDigital.h"
#include "displayapp/screens/WatchFaceAnalog.h"
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFacePineTimeStyle.h"
#include "displayapp/screens/WatchFaceTerminal.h"
namespace Pinetime {
namespace Applications {
namespace Screens {
class Screen;
}
struct AppDescription {
Apps app;
const char* icon;
Screens::Screen* (*create)(AppControllers& controllers);
};
struct WatchFaceDescription {
WatchFace watchFace;
const char* name;
Screens::Screen* (*create)(AppControllers& controllers);
bool (*isAvailable)(Controllers::FS& fileSystem);
};
template <Apps t>
consteval AppDescription CreateAppDescription() {
return {AppTraits<t>::app, AppTraits<t>::icon, &AppTraits<t>::Create};
}
template <WatchFace t>
consteval WatchFaceDescription CreateWatchFaceDescription() {
return {WatchFaceTraits<t>::watchFace, WatchFaceTraits<t>::name, &WatchFaceTraits<t>::Create, &WatchFaceTraits<t>::IsAvailable};
}
template <template <Apps...> typename T, Apps... ts>
consteval std::array<AppDescription, sizeof...(ts)> CreateAppDescriptions(T<ts...>) {
return {CreateAppDescription<ts>()...};
}
template <template <WatchFace...> typename T, WatchFace... ts>
consteval std::array<WatchFaceDescription, sizeof...(ts)> CreateWatchFaceDescriptions(T<ts...>) {
return {CreateWatchFaceDescription<ts>()...};
}
constexpr auto userApps = CreateAppDescriptions(UserAppTypes {});
constexpr auto userWatchFaces = CreateWatchFaceDescriptions(UserWatchFaceTypes {});
}
}

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