Merge branch 'JF002:develop' into set-datetime-manually

This commit is contained in:
Tim 2021-08-08 14:37:48 +02:00 committed by GitHub
commit 3e9c30a18f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 972 additions and 579 deletions

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "src/libs/lvgl"] [submodule "src/libs/lvgl"]
path = src/libs/lvgl path = src/libs/lvgl
url = https://github.com/joaquimorg/lvgl.git url = https://github.com/joaquimorg/lvgl.git
[submodule "src/libs/littlefs"]
path = src/libs/littlefs
url = https://github.com/littlefs-project/littlefs.git

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(pinetime VERSION 1.2.0 LANGUAGES C CXX ASM) project(pinetime VERSION 1.3.0 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 14)
@ -21,10 +21,6 @@ if (NOT NRF5_SDK_PATH)
message(FATAL_ERROR "The path to the NRF52 SDK must be specified on the command line (add -DNRF5_SDK_PATH=<path>") message(FATAL_ERROR "The path to the NRF52 SDK must be specified on the command line (add -DNRF5_SDK_PATH=<path>")
endif () endif ()
if(NOT USE_JLINK AND NOT USE_GDB_CLIENT AND NOT USE_OPENOCD)
set(USE_JLINK true)
endif()
if(USE_JLINK) if(USE_JLINK)
if (NOT NRFJPROG) if (NOT NRFJPROG)
message(FATAL_ERROR "the path to the tool nrfjprog must be specified on the command line (add -DNRFJPROG=<path>") message(FATAL_ERROR "the path to the tool nrfjprog must be specified on the command line (add -DNRFJPROG=<path>")

View file

@ -1,4 +1,3 @@
# PineTime # PineTime
[![Build PineTime Firmware](https://github.com/JF002/InfiniTime/workflows/Build%20PineTime%20Firmware/badge.svg?branch=master)](https://github.com/JF002/InfiniTime/actions) [![Build PineTime Firmware](https://github.com/JF002/InfiniTime/workflows/Build%20PineTime%20Firmware/badge.svg?branch=master)](https://github.com/JF002/InfiniTime/actions)
@ -12,6 +11,7 @@
The **Pinetime** smartwatch is built around the NRF52832 MCU (512KB Flash, 64KB RAM), a 240*240 LCD display driven by the ST7789 controller, an accelerometer, a heart rate sensor, and a vibration motor. The **Pinetime** smartwatch is built around the NRF52832 MCU (512KB Flash, 64KB RAM), a 240*240 LCD display driven by the ST7789 controller, an accelerometer, a heart rate sensor, and a vibration motor.
# InfiniTime # InfiniTime
![InfiniTime logo](images/infinitime-logo.jpg "InfiniTime Logo") ![InfiniTime logo](images/infinitime-logo.jpg "InfiniTime Logo")
The goal of this project is to design an open-source firmware for the Pinetime smartwatch : The goal of this project is to design an open-source firmware for the Pinetime smartwatch :
@ -41,23 +41,27 @@ As of now, here is the list of achievements of this project:
* Brightness settings * Brightness settings
* Flashlight * Flashlight
* Settings * Settings
- 2 watch faces: - 3 watch faces:
* Digital * Digital
* Analog * Analog
* [PineTimeStyle](https://wiki.pine64.org/wiki/PineTimeStyle)
- Multiple 'apps' : - Multiple 'apps' :
* Music (control the playback of the music on your phone) * Music (control the playback of music on your phone)
* Heart rate (controls the heart rate sensor and display current heartbeat) * Heart rate (measure your heart rate)
* Navigation (displays navigation instructions coming from the companion app) * Navigation (displays navigation instructions coming from the companion app)
* Notification (displays the last notification received) * Notification (displays the last notification received)
* Paddle (single player pong-like game) * Paddle (single player pong-like game)
* Two (2048 clone game) * Twos (2048 clone game)
* Stopwatch (with all the necessary functions such as play, pause, lap, stop) * Stopwatch
* Motion sensor and step counter (displays the number of steps and the state of the motion sensor in real-time) * Steps (displays the number of steps taken)
* Timer (set a countdown timer that will notify you when it expires)
* Metronome (vibrates to a given bpm with a customizable beats per bar)
- User settings: - User settings:
* Display timeout * Display timeout
* Wake-up condition * Wake-up condition
* Time format (12/24h) * Time format (12/24h)
* Default watch face * Default watch face
* Daily step goal
* Battery status * Battery status
* Firmware validation * Firmware validation
* System information * System information
@ -72,14 +76,17 @@ As of now, here is the list of achievements of this project:
## Documentation ## Documentation
### Getting started ### Getting started
- [Getting started with InfiniTime 1.0 (quick user guide, update bootloader and InfiniTime,...)](doc/gettingStarted/gettingStarted-1.0.md) - [Getting started with InfiniTime 1.0 (quick user guide, update bootloader and InfiniTime,...)](doc/gettingStarted/gettingStarted-1.0.md)
- [Flash, upgrade (OTA), time synchronization,...](doc/gettingStarted/ota-gadgetbridge-nrfconnect.md) - [Flash, upgrade (OTA), time synchronization,...](doc/gettingStarted/ota-gadgetbridge-nrfconnect.md)
### Develop ### Develop
- [Generate the fonts and symbols](src/displayapp/fonts/README.md) - [Generate the fonts and symbols](src/displayapp/fonts/README.md)
- [Creating a stopwatch in Pinetime(article)](https://pankajraghav.com/2021/04/03/PINETIME-STOPCLOCK.html) - [Creating a stopwatch in Pinetime(article)](https://pankajraghav.com/2021/04/03/PINETIME-STOPCLOCK.html)
### Build, flash and debug ### Build, flash and debug
- [Project branches](doc/branches.md) - [Project branches](doc/branches.md)
- [Versioning](doc/versioning.md) - [Versioning](doc/versioning.md)
- [Files included in the release notes](doc/filesInReleaseNotes.md) - [Files included in the release notes](doc/filesInReleaseNotes.md)
@ -92,20 +99,23 @@ As of now, here is the list of achievements of this project:
- Using files from the releases - Using files from the releases
### Contribute ### Contribute
- [How to contribute ?](doc/contribute.md) - [How to contribute ?](doc/contribute.md)
### API ### API
- [BLE implementation and API](./doc/ble.md) - [BLE implementation and API](./doc/ble.md)
### Architecture and technical topics ### Architecture and technical topics
- [Memory analysis](./doc/MemoryAnalysis.md) - [Memory analysis](./doc/MemoryAnalysis.md)
### Using the firmware ### Using the firmware
- [Integration with Gadgetbridge](doc/companionapps/Gadgetbridge.md) - [Integration with Gadgetbridge](doc/companionapps/Gadgetbridge.md)
- [Integration with AmazFish](doc/companionapps/Amazfish.md) - [Integration with AmazFish](doc/companionapps/Amazfish.md)
- [Firmware update, OTA](doc/companionapps/NrfconnectOTA.md) - [Firmware update, OTA](doc/companionapps/NrfconnectOTA.md)
## TODO - contribute ## TODO - contribute
This project is far from being finished, and there are still a lot of things to do for this project to become a firmware usable by the general public. This project is far from being finished, and there are still a lot of things to do for this project to become a firmware usable by the general public.
@ -125,6 +135,7 @@ Here a quick list out of my head of things to do for this project:
Do not hesitate to clone/fork the code, hack it and create pull-requests. I'll do my best to review and merge them :) Do not hesitate to clone/fork the code, hack it and create pull-requests. I'll do my best to review and merge them :)
## Licenses ## Licenses
This project is released under the GNU General Public License version 3 or, at your option, any later version. This project is released under the GNU General Public License version 3 or, at your option, any later version.
It integrates the following projects: It integrates the following projects:
@ -134,6 +145,7 @@ It integrates the following projects:
- 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 projects. 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 PR for this projects. 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:

View file

@ -1,7 +1,7 @@
# Build a stub for PineTime using NRF52-DK # Build a stub for PineTime using NRF52-DK
[NRF52-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52-DK) is the official developpment kit for NRF52832 SoC from Nordic Semiconductor. [NRF52-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52-DK) is the official developpment kit for NRF52832 SoC from Nordic Semiconductor.
It can be very useful for PineTime developpment: It can be very useful for PineTime development:
* You can use it embedded JLink SWD programmer/debugger to program and debug you code on the PineTime * You can use it embedded JLink SWD programmer/debugger to program and debug you code on the PineTime
* As it's based on the same SoC than the PineTime, you can program it to actually run the same code than the PineTime. * As it's based on the same SoC than the PineTime, you can program it to actually run the same code than the PineTime.
@ -47,4 +47,4 @@ You also need to enable the I/O expander to disconnect pins from buttons and led
| --------- | --------- | | --------- | --------- |
| DETECT | GND | | DETECT | GND |
Now, you should be able to program the SoC on the NRF52-DK board, and use it as if it was running on the pintime. Now, you should be able to program the SoC on the NRF52-DK board, and use it as if it was running on the PineTime.

View file

@ -5,9 +5,9 @@ This page describes the BLE implementation and API built in this firmware.
**Note** : I'm a beginner in BLE related technologies and the information of this document reflect my current knowledge and understanding of the BLE stack. These informations might be erroneous or incomplete. Feel free to submit a PR if you think you can improve these. **Note** : I'm a beginner in BLE related technologies and the information of this document reflect my current knowledge and understanding of the BLE stack. These informations might be erroneous or incomplete. Feel free to submit a PR if you think you can improve these.
## BLE Connection ## BLE Connection
When starting the firmware start a BLE advertising : it send small messages that can be received by any *central* device in range. This allows the device to announce its presence to other devices. When starting the firmware start a BLE advertising : it sends small messages that can be received by any *central* device in range. This allows the device to announce its presence to other devices.
A companion application (running on a PC, RasberryPi, smartphone) which received this avertising packet can request a connection to the device. This connection procedure allows the 2 devices to negociate communication parameters, security keys,... A companion application (running on a PC, RaspberryPi, smartphone) which received this avertising packet can request a connection to the device. This connection procedure allows the 2 devices to negotiate communication parameters, security keys,...
When the connection is established, the pinetime will try to discover services running on the companion application. For now **CTS** (**C**urrent **T**ime **S**ervice) and **ANS** (**A**lert **N**otification **S**ervice) are supported. When the connection is established, the pinetime will try to discover services running on the companion application. For now **CTS** (**C**urrent **T**ime **S**ervice) and **ANS** (**A**lert **N**otification **S**ervice) are supported.

View file

@ -2,7 +2,7 @@
The branching model of this project is based on the workflow named [Git flow](https://nvie.com/posts/a-successful-git-branching-model/). The branching model of this project is based on the workflow named [Git flow](https://nvie.com/posts/a-successful-git-branching-model/).
It is based on 2 main branches: It is based on 2 main branches:
- **master** : this branch is always ready to be reployed. It means that at any time, we should be able to build the branch and release a new version of the application. - **master** : this branch is always ready to be deployed. It means that at any time, we should be able to build the branch and release a new version of the application.
- **develop** : this branch contains the latest development that will be integrated in the next release once it's considered as stable. - **develop** : this branch contains the latest development that will be integrated in the next release once it's considered as stable.
New features should be implemented in **feature branches** created from **develop**. When the feature is ready, a pull-request is created and it'll be merge into **develop** when it is succesfully reviewed and accepted. New features should be implemented in **feature branches** created from **develop**. When the feature is ready, a pull-request is created and it'll be merge into **develop** when it is succesfully reviewed and accepted.

View file

@ -27,7 +27,7 @@ CMake configures the project according to variables you specify the command line
**NRFJPROG**|Path to the NRFJProg executable. Used only if `USE_JLINK` is 1.|`-DNRFJPROG=/opt/nrfjprog/nrfjprog` **NRFJPROG**|Path to the NRFJProg executable. Used only if `USE_JLINK` is 1.|`-DNRFJPROG=/opt/nrfjprog/nrfjprog`
**GDB_CLIENT_BIN_PATH**|Path to arm-none-eabi-gdb executable. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_BIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb` **GDB_CLIENT_BIN_PATH**|Path to arm-none-eabi-gdb executable. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_BIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb`
**GDB_CLIENT_TARGET_REMOTE**|Target remote connection string. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_TARGET_REMOTE=/dev/ttyACM0` **GDB_CLIENT_TARGET_REMOTE**|Target remote connection string. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_TARGET_REMOTE=/dev/ttyACM0`
**BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-BUILD_DFU=1` **BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1`
####(**) 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/JF002/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/JF002/InfiniTime/releases) new versions of InfiniTime.
@ -163,7 +163,7 @@ J-Link>g
``` ```
#### JLink RTT #### JLink RTT
RTT is a feature from Segger's JLink devices that allows bidirectionnal communication between the debugger and the target. This feature can be used to get the logs from the embedded software on the development computer. RTT is a feature from Segger's JLink devices that allows bidirectional communication between the debugger and the target. This feature can be used to get the logs from the embedded software on the development computer.
- Program the MCU with the code (see above) - Program the MCU with the code (see above)
- Start JLinkExe - Start JLinkExe

View file

@ -13,7 +13,7 @@ Based on Ubuntu 18.04 with the following build dependencies:
The `infinitime-build` image contains all the dependencies you need. The default `CMD` will compile sources found in `/sources`, so you need only mount your code. The `infinitime-build` image contains all the dependencies you need. The default `CMD` will compile sources found in `/sources`, so you need only mount your code.
This example will build the firmware, generate the MCUBoot image and generate the DFU file. For cloning the repo, see [these instructions](../doc/buildAndProgram.md#clone-the-repo). Outputs will be written to **<project_root>/build/output**: This example will build the firmware, generate the MCUBoot image and generate the DFU file. For cloning the repo, see [these instructions](../doc/buildAndProgram.md#clone-the-repo). Outputs will be written to **<project_root>/build/output**:
```bash ```bash
cd <project_root> # e.g. cd ./work/Pinetime cd <project_root> # e.g. cd ./work/Pinetime

View file

@ -48,7 +48,7 @@ When reviewing PR, the author and contributors will first look at the **descript
Then, reviewing **a few files that were modified for a single purpose** is a lot more easier than to review 30 files modified for many reasons (bug fix, UI improvements, typos in doc,...), even if all these changes make sense. Also, it's possible that we agree on some modification but not on some other, and we won't be able to merge the PR because of the changes that are not accepted. Then, reviewing **a few files that were modified for a single purpose** is a lot more easier than to review 30 files modified for many reasons (bug fix, UI improvements, typos in doc,...), even if all these changes make sense. Also, it's possible that we agree on some modification but not on some other, and we won't be able to merge the PR because of the changes that are not accepted.
We do our best to keep the code as consistent as possible, and that mean we pay attention to the **formatting** of the code. If the code formatting is not consistent with our code base, we'll ask you to review it, which will take more time. We do our best to keep the code as consistent as possible, and that means we pay attention to the **formatting** of the code. If the code formatting is not consistent with our code base, we'll ask you to review it, which will take more time.
The last step of the review consists in **testing** the modification. If it doesn't work out of the box, we'll ask your to review your code and to ensure that it works as expected. The last step of the review consists in **testing** the modification. If it doesn't work out of the box, we'll ask your to review your code and to ensure that it works as expected.

View file

@ -18,7 +18,7 @@ This firmware is standalone, meaning that it does not need a bootloader to actua
**This firmware must be flashed at address 0x00 in the main flash memory** **This firmware must be flashed at address 0x00 in the main flash memory**
### Bootloader ### Bootloader
The bootloader is maintained by [lupyuen](https://github.com/lupyuen) and is a binary version of [this release](https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v5.0.4). The bootloader is maintained by [lupyuen](https://github.com/lupyuen) and is a binary version of [this release](https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v5.0.4).
- **bootloader.hex** : Firmware in Intel HEX file format. - **bootloader.hex** : Firmware in Intel HEX file format.

View file

@ -1,7 +1,9 @@
# Getting started with InfiniTime 1.0 # Getting started with InfiniTime 1.0
On April 22 2021, InfiniTime and Pine64 [announced the release of InfiniTime 1.0](https://www.pine64.org/2021/04/22/its-time-infinitime-1-0/) and the availability of PineTime smartwatches as *enthusiast grade end-user product*. This page aims to guide you with your first step with your new PineTime. On April 22 2021, InfiniTime and Pine64 [announced the release of InfiniTime 1.0](https://www.pine64.org/2021/04/22/its-time-infinitime-1-0/) and the availability of PineTime smartwatches as *enthusiast grade end-user product*. This page aims to guide you with your first step with your new PineTime.
## Firmware, InfiniTime, Bootloader, Recovery firmware, OTA, DFU... What is it? ## Firmware, InfiniTime, Bootloader, Recovery firmware, OTA, DFU... What is it?
You might have already seen these words by reading the announcement, release notes, or [the wiki guide](https://wiki.pine64.org/wiki/Upgrade_PineTime_to_InfiniTime_1.0.0) and, you may find them misleading if you're not familiar with the project. You might have already seen these words by reading the announcement, release notes, or [the wiki guide](https://wiki.pine64.org/wiki/Upgrade_PineTime_to_InfiniTime_1.0.0) and, you may find them misleading if you're not familiar with the project.
Basically, a **firmware** is just a software running on the embedded hardware of a device, the PineTime in this case. Basically, a **firmware** is just a software running on the embedded hardware of a device, the PineTime in this case.
@ -13,9 +15,10 @@ Basically, a **firmware** is just a software running on the embedded hardware of
**OTA** and **DFU** refer to the update of the firmware over BLE (**B**luetooth **L**ow **E**nergy). **OTA** means **O**ver **T**he **A**ir, this is a functionality that allows the user to update the firmware how their device using a wireless communication like BLE. When we talk about **DFU** (**D**igital **F**irmware **U**pdate), we refer to the file format and protocol used to send the update of the firmware to the watch over-the-air. InfiniTime implement the (legacy) DFU protocol from Nordic Semiconductor (NRF). **OTA** and **DFU** refer to the update of the firmware over BLE (**B**luetooth **L**ow **E**nergy). **OTA** means **O**ver **T**he **A**ir, this is a functionality that allows the user to update the firmware how their device using a wireless communication like BLE. When we talk about **DFU** (**D**igital **F**irmware **U**pdate), we refer to the file format and protocol used to send the update of the firmware to the watch over-the-air. InfiniTime implement the (legacy) DFU protocol from Nordic Semiconductor (NRF).
## How to check the version of InfiniTime and the bootloader? ## How to check the version of InfiniTime and the bootloader?
Since September 2020, all PineTimes (devkits or sealed) are flashed using the **[first iteration of the bootloader](https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v4.1.7)** and **[InfiniTime 0.7.1](https://github.com/JF002/InfiniTime/releases/tag/0.7.1)**. There was no recovery firmware at that time. Since September 2020, all PineTimes (devkits or sealed) are flashed using the **[first iteration of the bootloader](https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v4.1.7)** and **[InfiniTime 0.7.1](https://github.com/JF002/InfiniTime/releases/tag/0.7.1)**. There was no recovery firmware at that time.
The bootloader only runs when the watch starts (from an empty battery, for example) or after a reset (after a succesful OTA or a manual reset - long push on the button). The bootloader only runs when the watch starts (from an empty battery, for example) or after a reset (after a successful OTA or a manual reset - long push on the button).
You can recognize this first iteration of the bootloader with it greenish **PINETIME** logo. You can recognize this first iteration of the bootloader with it greenish **PINETIME** logo.
@ -30,14 +33,14 @@ And for version >= 1.0 :
![InfiniTime 1.0 version](version-1.0.jpg) ![InfiniTime 1.0 version](version-1.0.jpg)
PineTime shipped from June 2021 (to be confirmed) will be flashed with the [new version of the bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader/releases/tag/1.0.0), the [recovery firmware](https://github.com/JF002/InfiniTime/releases/tag/0.14.1) and [InfiniTime 1.0](https://github.com/JF002/InfiniTime/releases/tag/1.0.0).
PineTime shipped from June 2020 (to be confirmed) will be flashed with the [new version of the bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader/releases/tag/1.0.0), the [recovery firmware](https://github.com/JF002/InfiniTime/releases/tag/0.14.1) and [InfiniTime 1.0](https://github.com/JF002/InfiniTime/releases/tag/1.0.0).
The bootloader is easily recognizable with it white pine cone that is progressively drawn in green. It also displays its own version on the bottom (1.0.0 as of now). The bootloader is easily recognizable with it white pine cone that is progressively drawn in green. It also displays its own version on the bottom (1.0.0 as of now).
![Bootloader 1.0](bootloader-1.0.jpg) ![Bootloader 1.0](bootloader-1.0.jpg)
## How to update your PineTime? ## How to update your PineTime?
To update your PineTime, you can use one of the compatible companion applications. Here are the main ones: To update your PineTime, you can use one of the compatible companion applications. Here are the main ones:
- **[Amazfish](https://github.com/piggz/harbour-amazfish)** (Desktop Linux, mobile Linux, SailfishOS, runs on the PinebookPro and the Pinephone) - **[Amazfish](https://github.com/piggz/harbour-amazfish)** (Desktop Linux, mobile Linux, SailfishOS, runs on the PinebookPro and the Pinephone)
@ -45,9 +48,10 @@ To update your PineTime, you can use one of the compatible companion application
- **[Siglo](https://github.com/alexr4535/siglo)** (Linux, GTK based) - **[Siglo](https://github.com/alexr4535/siglo)** (Linux, GTK based)
- **NRFConnect** (closed source, Android & iOS). - **NRFConnect** (closed source, Android & iOS).
See [this page](ota-gadgetbridge-nrfconnect.md) for more info about the OTA procedure using Gadgetbrige and NRFCOnnect. See [this page](ota-gadgetbridge-nrfconnect.md) for more info about the OTA procedure using Gadgetbridge and NRFConnect.
### From InfiniTime 0.7.1 / old bootloader ### From InfiniTime 0.7.1 / old bootloader
If your PineTime is currently running InfiniTime 0.7.1 and the old bootloader, we strongly recommend you update them to more recent version (Bootloader 1.0.0 and InfiniTime 1.0.0 as of now). We also recommend you install the recovery firmware once the bootloader is up-do-date. If your PineTime is currently running InfiniTime 0.7.1 and the old bootloader, we strongly recommend you update them to more recent version (Bootloader 1.0.0 and InfiniTime 1.0.0 as of now). We also recommend you install the recovery firmware once the bootloader is up-do-date.
Using the companion app of your choice, you'll need to apply the OTA procedure for these 3 firmwares in this sequence (failing to follow this specific order might temporarily or permanently brick your device): Using the companion app of your choice, you'll need to apply the OTA procedure for these 3 firmwares in this sequence (failing to follow this specific order might temporarily or permanently brick your device):
@ -59,10 +63,12 @@ Using the companion app of your choice, you'll need to apply the OTA procedure f
You'll find more info about this process in [this wiki page](https://wiki.pine64.org/wiki/Upgrade_PineTime_to_InfiniTime_1.0.0). You can also see the procedure in video [here](https://video.codingfield.com/videos/watch/831077c5-16f3-47b4-9b2b-c4bbfecc6529) and [here (from Amazfish)](https://video.codingfield.com/videos/watch/f7bffb3d-a6a1-43c4-8f01-f4aeff4adf9e) You'll find more info about this process in [this wiki page](https://wiki.pine64.org/wiki/Upgrade_PineTime_to_InfiniTime_1.0.0). You can also see the procedure in video [here](https://video.codingfield.com/videos/watch/831077c5-16f3-47b4-9b2b-c4bbfecc6529) and [here (from Amazfish)](https://video.codingfield.com/videos/watch/f7bffb3d-a6a1-43c4-8f01-f4aeff4adf9e)
### From version > 1.0 ### From version > 1.0
If you are already running the new "1.0.0" bootloader, all you have to do is update your version of InfiniTime when it'll be available. We'll write specific instructions when (if) we release a new version of the bootloader. If you are already running the new "1.0.0" bootloader, all you have to do is update your version of InfiniTime when it'll be available. We'll write specific instructions when (if) we release a new version of the bootloader.
### Firmware validation ### Firmware validation
The bootloader requires a (manual) validation of the firmware. If the watch reset with an updated firmware that was not validated, the bootloader will consider it as non-functionning and will revert to the previous version of the firmware. This is a safety feature to prevent bricking your device with a faulty firmware.
The bootloader requires a (manual) validation of the firmware. If the watch reset with an updated firmware that was not validated, the bootloader will consider it as non-functioning and will revert to the previous version of the firmware. This is a safety feature to prevent bricking your device with a faulty firmware.
You can validate your updated firmware on InfiniTime >= 1.0 by following this simple procedure: You can validate your updated firmware on InfiniTime >= 1.0 by following this simple procedure:
@ -74,7 +80,9 @@ You can validate your updated firmware on InfiniTime >= 1.0 by following this si
- **Reset** to reset the watch and revert to the previously running version of the firmware - **Reset** to reset the watch and revert to the previously running version of the firmware
## InfiniTime 1.0 quick user guide ## InfiniTime 1.0 quick user guide
### Setting the time ### Setting the time
By default, InfiniTime starts on the digital watchface. It'll probably display the epoch time (1 Jan 1970, 00:00). The time will be automatically synchronized once you connect on of the companion app to your PineTime using BLE connectivity. InfiniTime does not provide any way to manually set the time for now. By default, InfiniTime starts on the digital watchface. It'll probably display the epoch time (1 Jan 1970, 00:00). The time will be automatically synchronized once you connect on of the companion app to your PineTime using BLE connectivity. InfiniTime does not provide any way to manually set the time for now.
### Navigation in the menu ### Navigation in the menu
@ -109,4 +117,3 @@ However, you can enable 2 functionalities using the push button:
- Push the button until the pine cone is drawn in **red** to load the recovery firmware. This recovery firmware only provides BLE connectivity and OTA functionality. - Push the button until the pine cone is drawn in **red** to load the recovery firmware. This recovery firmware only provides BLE connectivity and OTA functionality.
More info about the bootloader in [its project page](https://github.com/JF002/pinetime-mcuboot-bootloader/blob/master/README.md). More info about the bootloader in [its project page](https://github.com/JF002/pinetime-mcuboot-bootloader/blob/master/README.md).

5
hooks/README.md Normal file
View file

@ -0,0 +1,5 @@
# Git hooks
This directory contains Git hooks that simplify contributing to this repository.
You can install them by copying the files into `.git/hooks` in the repository folder or by running `git config --local core.hooksPath hooks`

25
hooks/pre-commit Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
if clang-format --version | grep -q 'version 11\.'; then
CLANG_FORMAT_EXECUTABLE="clang-format"
else
CLANG_FORMAT_EXECUTABLE="clang-format-11"
fi
if ! command -v $CLANG_FORMAT_EXECUTABLE &> /dev/null
then
echo $CLANG_FORMAT_EXECUTABLE does not exist, make sure to install it
exit 1
fi
for FILE in $(git diff --cached --name-only)
do
if [[ "$FILE" =~ src/[A-Za-z0-9\ \-]+*\.(c|h|cpp|cc)$ ]]; then
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
git add -- $FILE
elif [[ "$FILE" =~ src/(components|displayapp|drivers|heartratetask|logging|systemtask)/.*\.(c|h|cpp|cc)$ ]]; then
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
git add -- $FILE
fi
done

View file

@ -166,6 +166,13 @@ set(NIMBLE_SRC
libs/mynewt-nimble/nimble/host/util/src/addr.c libs/mynewt-nimble/nimble/host/util/src/addr.c
) )
set(LITTLEFS_SRC
libs/littlefs/lfs_util.h
libs/littlefs/lfs.h
libs/littlefs/lfs_util.c
libs/littlefs/lfs.c
)
set(LVGL_SRC set(LVGL_SRC
libs/lv_conf.h libs/lv_conf.h
libs/lvgl/lvgl.h libs/lvgl/lvgl.h
@ -467,6 +474,7 @@ list(APPEND SOURCE_FILES
components/motor/MotorController.cpp components/motor/MotorController.cpp
components/settings/Settings.cpp components/settings/Settings.cpp
components/timer/TimerController.cpp components/timer/TimerController.cpp
components/fs/FS.cpp
drivers/Cst816s.cpp drivers/Cst816s.cpp
FreeRTOS/port.c FreeRTOS/port.c
FreeRTOS/port_cmsis_systick.c FreeRTOS/port_cmsis_systick.c
@ -545,6 +553,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/heartrate/Biquad.cpp components/heartrate/Biquad.cpp
components/heartrate/Ptagc.cpp components/heartrate/Ptagc.cpp
components/motor/MotorController.cpp components/motor/MotorController.cpp
components/fs/FS.cpp
) )
list(APPEND RECOVERYLOADER_SOURCE_FILES list(APPEND RECOVERYLOADER_SOURCE_FILES
@ -803,13 +812,25 @@ target_compile_options(lvgl PRIVATE
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -x assembler-with-cpp> $<$<COMPILE_LANGUAGE:ASM>: -MP -MD -x assembler-with-cpp>
) )
# LITTLEFS_SRC
add_library(littlefs STATIC ${LITTLEFS_SRC})
target_include_directories(littlefs SYSTEM PUBLIC . ../)
target_include_directories(littlefs SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(littlefs PRIVATE
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wno-unused-function -Og -g3>
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Wno-unused-function -Os>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wno-unused-function -Og -g3 -fno-rtti>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Wno-unused-function -Os -fno-rtti>
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -x assembler-with-cpp>
)
# Build autonomous binary (without support for bootloader) # Build autonomous binary (without support for bootloader)
set(EXECUTABLE_NAME "pinetime-app") set(EXECUTABLE_NAME "pinetime-app")
set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
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) target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs)
target_compile_options(${EXECUTABLE_NAME} PUBLIC target_compile_options(${EXECUTABLE_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os>
@ -838,7 +859,7 @@ set(IMAGE_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERSION_
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) target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs)
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
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
@ -874,7 +895,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) target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs)
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
@ -904,7 +925,7 @@ set(EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-${
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin) set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
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) target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs)
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

View file

@ -77,7 +77,7 @@
#define configENABLE_BACKWARD_COMPATIBILITY 1 #define configENABLE_BACKWARD_COMPATIBILITY 1
/* Hook function related definitions. */ /* Hook function related definitions. */
#define configUSE_IDLE_HOOK 1 #define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0 #define configUSE_TICK_HOOK 0
#define configCHECK_FOR_STACK_OVERFLOW 0 #define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_MALLOC_FAILED_HOOK 0 #define configUSE_MALLOC_FAILED_HOOK 0

View file

@ -1,9 +1,7 @@
#include "BatteryController.h" #include "BatteryController.h"
#include <hal/nrf_gpio.h> #include <hal/nrf_gpio.h>
#include <nrfx_saadc.h> #include <nrfx_saadc.h>
#include <libraries/log/nrf_log.h>
#include <algorithm> #include <algorithm>
#include <math.h>
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
@ -18,7 +16,6 @@ void Battery::Init() {
} }
void Battery::Update() { void Battery::Update() {
isCharging = !nrf_gpio_pin_read(chargingPin); isCharging = !nrf_gpio_pin_read(chargingPin);
isPowerPresent = !nrf_gpio_pin_read(powerPresentPin); isPowerPresent = !nrf_gpio_pin_read(powerPresentPin);
@ -33,13 +30,13 @@ void Battery::Update() {
nrfx_saadc_sample(); nrfx_saadc_sample();
} }
void Battery::adcCallbackStatic(nrfx_saadc_evt_t const* event) { void Battery::AdcCallbackStatic(nrfx_saadc_evt_t const* event) {
instance->SaadcEventHandler(event); instance->SaadcEventHandler(event);
} }
void Battery::SaadcInit() { void Battery::SaadcInit() {
nrfx_saadc_config_t adcConfig = NRFX_SAADC_DEFAULT_CONFIG; nrfx_saadc_config_t adcConfig = NRFX_SAADC_DEFAULT_CONFIG;
APP_ERROR_CHECK(nrfx_saadc_init(&adcConfig, adcCallbackStatic)); APP_ERROR_CHECK(nrfx_saadc_init(&adcConfig, AdcCallbackStatic));
nrf_saadc_channel_config_t adcChannelConfig = {.resistor_p = NRF_SAADC_RESISTOR_DISABLED, nrf_saadc_channel_config_t adcChannelConfig = {.resistor_p = NRF_SAADC_RESISTOR_DISABLED,
.resistor_n = NRF_SAADC_RESISTOR_DISABLED, .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
@ -55,7 +52,6 @@ void Battery::SaadcInit() {
} }
void Battery::SaadcEventHandler(nrfx_saadc_evt_t const* p_event) { void Battery::SaadcEventHandler(nrfx_saadc_evt_t const* p_event) {
const uint16_t battery_max = 4180; // maximum voltage of battery ( max charging voltage is 4.21 ) const uint16_t battery_max = 4180; // maximum voltage of battery ( max charging voltage is 4.21 )
const uint16_t battery_min = 3200; // minimum voltage of battery before shutdown ( depends on the battery ) const uint16_t battery_min = 3200; // minimum voltage of battery before shutdown ( depends on the battery )
@ -69,13 +65,10 @@ void Battery::SaadcEventHandler(nrfx_saadc_evt_t const* p_event) {
// reference_voltage is 0.6V // reference_voltage is 0.6V
// p_event->data.done.p_buffer[0] = (adc_voltage / reference_voltage) * 1024 // p_event->data.done.p_buffer[0] = (adc_voltage / reference_voltage) * 1024
voltage = p_event->data.done.p_buffer[0] * 6000 / 1024; voltage = p_event->data.done.p_buffer[0] * 6000 / 1024;
percentRemaining = (voltage - battery_min) * 100 / (battery_max - battery_min); percentRemaining = (voltage - battery_min) * 100 / (battery_max - battery_min);
percentRemaining = std::max(percentRemaining, 0); percentRemaining = std::max(percentRemaining, 0);
percentRemaining = std::min(percentRemaining, 100); percentRemaining = std::min(percentRemaining, 100);
percentRemainingBuffer.Insert(percentRemaining);
percentRemainingBuffer.insert(percentRemaining);
samples++; samples++;
if (samples > percentRemainingSamples) { if (samples > percentRemainingSamples) {

View file

@ -19,7 +19,7 @@ namespace Pinetime {
insert member function overwrites the next data to the current insert member function overwrites the next data to the current
HEAD and moves the HEAD to the newly inserted value. HEAD and moves the HEAD to the newly inserted value.
*/ */
void insert(const int num) { void Insert(const uint8_t num) {
head %= cap; head %= cap;
arr[head++] = num; arr[head++] = num;
if (sz != cap) { if (sz != cap) {
@ -27,13 +27,13 @@ namespace Pinetime {
} }
} }
int GetAverage() const { uint8_t GetAverage() const {
int sum = std::accumulate(arr.begin(), arr.end(), 0); int sum = std::accumulate(arr.begin(), arr.end(), 0);
return (sum / sz); return static_cast<uint8_t>(sum / sz);
} }
private: private:
std::array<int, N> arr; /**< internal array used to store the values*/ std::array<uint8_t, N> arr; /**< internal array used to store the values*/
uint8_t sz; /**< The current size of the array.*/ uint8_t sz; /**< The current size of the array.*/
uint8_t cap; /**< Total capacity of the CircBuffer.*/ uint8_t cap; /**< Total capacity of the CircBuffer.*/
uint8_t head; /**< The current head of the CircBuffer*/ uint8_t head; /**< The current head of the CircBuffer*/
@ -46,8 +46,11 @@ namespace Pinetime {
void Init(); void Init();
void Update(); void Update();
int PercentRemaining() const { uint8_t PercentRemaining() const {
return percentRemainingBuffer.GetAverage(); auto avg = percentRemainingBuffer.GetAverage();
avg = std::min(avg, static_cast<uint8_t>(100));
avg = std::max(avg, static_cast<uint8_t>(0));
return avg;
} }
uint16_t Voltage() const { uint16_t Voltage() const {
@ -57,6 +60,7 @@ namespace Pinetime {
bool IsCharging() const { bool IsCharging() const {
return isCharging; return isCharging;
} }
bool IsPowerPresent() const { bool IsPowerPresent() const {
return isPowerPresent; return isPowerPresent;
} }
@ -80,7 +84,7 @@ namespace Pinetime {
void SaadcInit(); void SaadcInit();
void SaadcEventHandler(nrfx_saadc_evt_t const* p_event); void SaadcEventHandler(nrfx_saadc_evt_t const* p_event);
static void adcCallbackStatic(nrfx_saadc_evt_t const* event); static void AdcCallbackStatic(nrfx_saadc_evt_t const* event);
bool isReading = false; bool isReading = false;
uint8_t samples = 0; uint8_t samples = 0;

View file

@ -17,7 +17,7 @@ BatteryInformationService::BatteryInformationService(Controllers::Battery& batte
characteristicDefinition {{.uuid = (ble_uuid_t*) &batteryLevelUuid, characteristicDefinition {{.uuid = (ble_uuid_t*) &batteryLevelUuid,
.access_cb = BatteryInformationServiceCallback, .access_cb = BatteryInformationServiceCallback,
.arg = this, .arg = this,
.flags = BLE_GATT_CHR_F_READ, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
.val_handle = &batteryLevelHandle}, .val_handle = &batteryLevelHandle},
{0}}, {0}},
serviceDefinition { serviceDefinition {
@ -49,3 +49,7 @@ int BatteryInformationService::OnBatteryServiceRequested(uint16_t connectionHand
} }
return 0; return 0;
} }
void BatteryInformationService::NotifyBatteryLevel(uint16_t connectionHandle, uint8_t level) {
auto* om = ble_hs_mbuf_from_flat(&level, 1);
ble_gattc_notify_custom(connectionHandle, batteryLevelHandle, om);
}

View file

@ -17,7 +17,7 @@ namespace Pinetime {
void Init(); void Init();
int OnBatteryServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context); int OnBatteryServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
void NotifyBatteryLevel(uint16_t connectionHandle, uint8_t level);
private: private:
Controllers::Battery& batteryController; Controllers::Battery& batteryController;
static constexpr uint16_t batteryInformationServiceId {0x180F}; static constexpr uint16_t batteryInformationServiceId {0x180F};

View file

@ -266,13 +266,14 @@ int DfuService::ControlPointHandler(uint16_t connectionHandle, os_mbuf* om) {
static_cast<uint8_t>(ErrorCodes::NoError)}; static_cast<uint8_t>(ErrorCodes::NoError)};
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3); notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
} else { } else {
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
NRF_LOG_INFO("Image Error : bad CRC"); NRF_LOG_INFO("Image Error : bad CRC");
uint8_t data[3] {static_cast<uint8_t>(Opcodes::Response), uint8_t data[3] {static_cast<uint8_t>(Opcodes::Response),
static_cast<uint8_t>(Opcodes::ValidateFirmware), static_cast<uint8_t>(Opcodes::ValidateFirmware),
static_cast<uint8_t>(ErrorCodes::CrcError)}; static_cast<uint8_t>(ErrorCodes::CrcError)};
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3); notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
Reset();
} }
return 0; return 0;
@ -283,10 +284,8 @@ int DfuService::ControlPointHandler(uint16_t connectionHandle, os_mbuf* om) {
return 0; return 0;
} }
NRF_LOG_INFO("[DFU] -> Activate image and reset!"); NRF_LOG_INFO("[DFU] -> Activate image and reset!");
bleController.StopFirmwareUpdate();
systemTask.PushMessage(Pinetime::System::Messages::BleFirmwareUpdateFinished);
Reset();
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated); bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated);
Reset();
return 0; return 0;
default: default:
return 0; return 0;
@ -294,6 +293,7 @@ int DfuService::ControlPointHandler(uint16_t connectionHandle, os_mbuf* om) {
} }
void DfuService::OnTimeout() { void DfuService::OnTimeout() {
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
Reset(); Reset();
} }
@ -307,7 +307,6 @@ void DfuService::Reset() {
applicationSize = 0; applicationSize = 0;
expectedCrc = 0; expectedCrc = 0;
notificationManager.Reset(); notificationManager.Reset();
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
bleController.StopFirmwareUpdate(); bleController.StopFirmwareUpdate();
systemTask.PushMessage(Pinetime::System::Messages::BleFirmwareUpdateFinished); systemTask.PushMessage(Pinetime::System::Messages::BleFirmwareUpdateFinished);
} }

View file

@ -235,3 +235,9 @@ void NimbleController::StartDiscovery() {
uint16_t NimbleController::connHandle() { uint16_t NimbleController::connHandle() {
return connectionHandle; return connectionHandle;
} }
void NimbleController::NotifyBatteryLevel(uint8_t level) {
if(connectionHandle != BLE_HS_CONN_HANDLE_NONE) {
batteryInformationService.NotifyBatteryLevel(connectionHandle, level);
}
}

View file

@ -70,6 +70,7 @@ namespace Pinetime {
}; };
uint16_t connHandle(); uint16_t connHandle();
void NotifyBatteryLevel(uint8_t level);
private: private:
static constexpr const char* deviceName = "InfiniTime"; static constexpr const char* deviceName = "InfiniTime";
@ -92,7 +93,7 @@ namespace Pinetime {
HeartRateService heartRateService; HeartRateService heartRateService;
uint8_t addrType; // 1 = Random, 0 = PUBLIC uint8_t addrType; // 1 = Random, 0 = PUBLIC
uint16_t connectionHandle = 0; uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
ble_uuid128_t dfuServiceUuid { ble_uuid128_t dfuServiceUuid {
.u {.type = BLE_UUID_TYPE_128}, .u {.type = BLE_UUID_TYPE_128},

197
src/components/fs/FS.cpp Normal file
View file

@ -0,0 +1,197 @@
#include "FS.h"
#include <cstring>
#include <littlefs/lfs.h>
#include <lvgl/lvgl.h>
using namespace Pinetime::Controllers;
FS::FS(Pinetime::Drivers::SpiNorFlash& driver) :
flashDriver{ driver },
lfsConfig{
.context = this,
.read = SectorRead,
.prog = SectorProg,
.erase = SectorErase,
.sync = SectorSync,
.read_size = 16,
.prog_size = 8,
.block_size = blockSize,
.block_count = size / blockSize,
.block_cycles = 1000u,
.cache_size = 16,
.lookahead_size = 16,
.name_max = 50,
.attr_max = 50,
}
{ }
void FS::Init() {
// try mount
int err = lfs_mount(&lfs, &lfsConfig);
// reformat if we can't mount the filesystem
// this should only happen on the first boot
if (err != LFS_ERR_OK) {
lfs_format(&lfs, &lfsConfig);
err = lfs_mount(&lfs, &lfsConfig);
if (err != LFS_ERR_OK) {
return;
}
}
#ifndef PINETIME_IS_RECOVERY
VerifyResource();
LVGLFileSystemInit();
#endif
}
void FS::VerifyResource() {
// validate the resource metadata
resourcesValid = true;
}
int FS::FileOpen(lfs_file_t* file_p, const char* fileName, const int flags) {
return lfs_file_open(&lfs, file_p, fileName, flags);
}
int FS::FileClose(lfs_file_t* file_p) {
return lfs_file_close(&lfs, file_p);
}
int FS::FileRead(lfs_file_t* file_p, uint8_t* buff, uint32_t size) {
return lfs_file_read(&lfs, file_p, buff, size);
}
int FS::FileWrite(lfs_file_t* file_p, const uint8_t* buff, uint32_t size) {
return lfs_file_write(&lfs, file_p, buff, size);
}
int FS::FileSeek(lfs_file_t* file_p, uint32_t pos) {
return lfs_file_seek(&lfs, file_p, pos, LFS_SEEK_SET);
}
int FS::FileDelete(const char* fileName) {
return lfs_remove(&lfs, fileName);
}
int FS::DirCreate(const char* path) {
return lfs_mkdir(&lfs, path);
}
// Delete directory and all files inside
int FS::DirDelete(const char* path) {
lfs_dir_t lfs_dir;
lfs_info entryInfo;
int err;
err = lfs_dir_open(&lfs, &lfs_dir, path);
if (err) {
return err;
}
while (lfs_dir_read(&lfs, &lfs_dir, &entryInfo)) {
lfs_remove(&lfs, entryInfo.name);
}
lfs_dir_close(&lfs, &lfs_dir);
return LFS_ERR_OK;
}
/*
----------- Interface between littlefs and SpiNorFlash -----------
*/
int FS::SectorSync(const struct lfs_config* c) {
return 0;
}
int FS::SectorErase(const struct lfs_config* c, lfs_block_t block) {
Pinetime::Controllers::FS& lfs = *(static_cast<Pinetime::Controllers::FS*>(c->context));
const size_t address = startAddress + (block * blockSize);
lfs.flashDriver.SectorErase(address);
return lfs.flashDriver.EraseFailed() ? -1 : 0;
}
int FS::SectorProg(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, const void* buffer, lfs_size_t size) {
Pinetime::Controllers::FS& lfs = *(static_cast<Pinetime::Controllers::FS*>(c->context));
const size_t address = startAddress + (block * blockSize) + off;
lfs.flashDriver.Write(address, (uint8_t*) buffer, size);
return lfs.flashDriver.ProgramFailed() ? -1 : 0;
}
int FS::SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, void* buffer, lfs_size_t size) {
Pinetime::Controllers::FS& lfs = *(static_cast<Pinetime::Controllers::FS*>(c->context));
const size_t address = startAddress + (block * blockSize) + off;
lfs.flashDriver.Read(address, static_cast<uint8_t*>(buffer), size);
return 0;
}
/*
----------- LVGL filesystem integration -----------
*/
namespace {
lv_fs_res_t lvglOpen(lv_fs_drv_t* drv, void* file_p, const char* path, lv_fs_mode_t mode) {
lfs_file_t* file = static_cast<lfs_file_t*>(file_p);
FS* filesys = static_cast<FS*>(drv->user_data);
filesys->FileOpen(file, path, LFS_O_RDONLY);
if (file->type == 0) {
return LV_FS_RES_FS_ERR;
}
else {
return LV_FS_RES_OK;
}
}
lv_fs_res_t lvglClose(lv_fs_drv_t* drv, void* file_p) {
FS* filesys = static_cast<FS*>(drv->user_data);
lfs_file_t* file = static_cast<lfs_file_t*>(file_p);
filesys->FileClose(file);
return LV_FS_RES_OK;
}
lv_fs_res_t lvglRead(lv_fs_drv_t* drv, void* file_p, void* buf, uint32_t btr, uint32_t* br) {
FS* filesys = static_cast<FS*>(drv->user_data);
lfs_file_t* file = static_cast<lfs_file_t*>(file_p);
filesys->FileRead(file, static_cast<uint8_t*>(buf), btr);
*br = btr;
return LV_FS_RES_OK;
}
lv_fs_res_t lvglSeek(lv_fs_drv_t* drv, void* file_p, uint32_t pos) {
FS* filesys = static_cast<FS*>(drv->user_data);
lfs_file_t* file = static_cast<lfs_file_t*>(file_p);
filesys->FileSeek(file, pos);
return LV_FS_RES_OK;
}
}
void FS::LVGLFileSystemInit() {
lv_fs_drv_t fs_drv;
lv_fs_drv_init(&fs_drv);
fs_drv.file_size = sizeof(lfs_file_t);
fs_drv.letter = 'F';
fs_drv.open_cb = lvglOpen;
fs_drv.close_cb = lvglClose;
fs_drv.read_cb = lvglRead;
fs_drv.seek_cb = lvglSeek;
fs_drv.user_data = this;
lv_fs_drv_register(&fs_drv);
}

71
src/components/fs/FS.h Normal file
View file

@ -0,0 +1,71 @@
#pragma once
#include <cstdint>
#include "drivers/SpiNorFlash.h"
#include <littlefs/lfs.h>
namespace Pinetime {
namespace Controllers {
class FS {
public:
FS(Pinetime::Drivers::SpiNorFlash&);
void Init();
void LVGLFileSystemInit();
int FileOpen(lfs_file_t* file_p, const char* fileName, const int flags);
int FileClose(lfs_file_t* file_p);
int FileRead(lfs_file_t* file_p, uint8_t* buff, uint32_t size);
int FileWrite(lfs_file_t* file_p, const uint8_t* buff, uint32_t size);
int FileSeek(lfs_file_t* file_p, uint32_t pos);
int FileDelete(const char* fileName);
int DirCreate(const char* path);
int DirDelete(const char* path);
void VerifyResource();
private:
Pinetime::Drivers::SpiNorFlash& flashDriver;
/*
* External Flash MAP (4 MBytes)
*
* 0x000000 +---------------------------------------+
* | Bootloader Assets |
* | 256 KBytes |
* | |
* 0x040000 +---------------------------------------+
* | OTA |
* | 464 KBytes |
* | |
* | |
* | |
* 0x0B4000 +---------------------------------------+
* | File System |
* | |
* | |
* | |
* | |
* 0x400000 +---------------------------------------+
*
*/
static constexpr size_t startAddress = 0x0B4000;
static constexpr size_t size = 0x3C0000;
static constexpr size_t blockSize = 4096;
bool resourcesValid = false;
const struct lfs_config lfsConfig;
lfs_t lfs;
static int SectorSync(const struct lfs_config* c);
static int SectorErase(const struct lfs_config* c, lfs_block_t block);
static int SectorProg(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, const void* buffer, lfs_size_t size);
static int SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, void* buffer, lfs_size_t size);
};
}
}

View file

@ -4,108 +4,44 @@
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
struct SettingsHeader { Settings::Settings(Pinetime::Controllers::FS& fs) : fs {fs} {
uint8_t isActive; // 0xF1 = Block is active, 0xF0 = Block is inactive
uint16_t version; // Current version, to verify if the saved data is for the current Version
};
#define HEADER_SIZE sizeof(SettingsHeader)
Settings::Settings(Pinetime::Drivers::SpiNorFlash& spiNorFlash) : spiNorFlash {spiNorFlash} {
} }
void Settings::Init() { void Settings::Init() {
// Load default settings from Flash // Load default settings from Flash
LoadSettingsFromFlash(); LoadSettingsFromFile();
} }
void Settings::SaveSettings() { void Settings::SaveSettings() {
// verify if is necessary to save // verify if is necessary to save
if (settingsChanged) { if (settingsChanged) {
SaveSettingsToFlash(); SaveSettingsToFile();
} }
settingsChanged = false; settingsChanged = false;
} }
bool Settings::FindHeader() { void Settings::LoadSettingsFromFile() {
SettingsHeader settingsHeader; SettingsData bufferSettings;
uint8_t bufferHead[sizeof(settingsHeader)]; lfs_file_t settingsFile;
for (uint8_t block = 0; block < 10; block++) { if ( fs.FileOpen(&settingsFile, "/settings.dat", LFS_O_RDWR | LFS_O_CREAT) != LFS_ERR_OK) {
return;
spiNorFlash.Read(settingsBaseAddr + (block * 0x1000), bufferHead, sizeof(settingsHeader));
std::memcpy(&settingsHeader, bufferHead, sizeof(settingsHeader));
if (settingsHeader.isActive == 0xF1 && settingsHeader.version == settingsVersion) {
settingsFlashBlock = block;
return true;
}
} }
return false; fs.FileRead(&settingsFile, reinterpret_cast<uint8_t*>(&bufferSettings), sizeof(settings));
} fs.FileClose(&settingsFile);
if ( bufferSettings.version == settingsVersion ) {
void Settings::ReadSettingsData() { settings = bufferSettings;
uint8_t bufferSettings[sizeof(settings)];
spiNorFlash.Read(settingsBaseAddr + (settingsFlashBlock * 0x1000) + HEADER_SIZE, bufferSettings, sizeof(settings));
std::memcpy(&settings, bufferSettings, sizeof(settings));
}
void Settings::EraseBlock() {
spiNorFlash.SectorErase(settingsBaseAddr + (settingsFlashBlock * 0x1000));
}
void Settings::SetHeader(bool state) {
SettingsHeader settingsHeader;
uint8_t bufferHead[sizeof(settingsHeader)];
settingsHeader.isActive = state ? 0xF1 : 0xF0;
settingsHeader.version = settingsVersion;
std::memcpy(bufferHead, &settingsHeader, sizeof(settingsHeader));
spiNorFlash.Write(settingsBaseAddr + (settingsFlashBlock * 0x1000), bufferHead, sizeof(settingsHeader));
}
void Settings::SaveSettingsData() {
uint8_t bufferSettings[sizeof(settings)];
std::memcpy(bufferSettings, &settings, sizeof(settings));
spiNorFlash.Write(settingsBaseAddr + (settingsFlashBlock * 0x1000) + HEADER_SIZE, bufferSettings, sizeof(settings));
}
void Settings::LoadSettingsFromFlash() {
if (settingsFlashBlock == 99) {
// Find current Block, if can't find use default settings and set block to 0 ans save !
if (FindHeader()) {
ReadSettingsData();
} else {
SaveSettingsToFlash();
}
} else {
// Read Settings from flash...
// never used :)
ReadSettingsData();
} }
} }
void Settings::SaveSettingsToFlash() { void Settings::SaveSettingsToFile() {
lfs_file_t settingsFile;
// calculate where to save... if ( fs.FileOpen(&settingsFile, "/settings.dat", LFS_O_RDWR | LFS_O_CREAT) != LFS_ERR_OK) {
// mark current to inactive return;
// erase the new location and save
// set settingsFlashBlock
// if first time hever, only saves to block 0 and set settingsFlashBlock
if (settingsFlashBlock != 99) {
SetHeader(false);
} }
fs.FileWrite(&settingsFile, reinterpret_cast<uint8_t*>(&settings), sizeof(settings));
settingsFlashBlock++; fs.FileClose(&settingsFile);
if (settingsFlashBlock > 9)
settingsFlashBlock = 0;
EraseBlock();
SetHeader(true);
SaveSettingsData();
} }

View file

@ -1,26 +1,32 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <bitset>
#include "components/datetime/DateTimeController.h" #include "components/datetime/DateTimeController.h"
#include "components/brightness/BrightnessController.h" #include "components/brightness/BrightnessController.h"
#include "drivers/SpiNorFlash.h" #include "components/fs/FS.h"
#include "drivers/Cst816s.h" #include "drivers/Cst816s.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
class Settings { class Settings {
public: public:
enum class ClockType { H24, H12 }; enum class ClockType : uint8_t { H24, H12 };
enum class Vibration { ON, OFF }; enum class Vibration : uint8_t { ON, OFF };
enum class WakeUpMode { None, SingleTap, DoubleTap, RaiseWrist }; enum class WakeUpMode : uint8_t {
SingleTap = 0,
DoubleTap = 1,
RaiseWrist = 2,
};
Settings(Pinetime::Drivers::SpiNorFlash& spiNorFlash); Settings(Pinetime::Controllers::FS& fs);
void Init(); void Init();
void SaveSettings(); void SaveSettings();
void SetClockFace(uint8_t face) { void SetClockFace(uint8_t face) {
if (face != settings.clockFace) if (face != settings.clockFace) {
settingsChanged = true; settingsChanged = true;
}
settings.clockFace = face; settings.clockFace = face;
}; };
uint8_t GetClockFace() const { uint8_t GetClockFace() const {
@ -42,8 +48,9 @@ namespace Pinetime {
}; };
void SetClockType(ClockType clocktype) { void SetClockType(ClockType clocktype) {
if (clocktype != settings.clockType) if (clocktype != settings.clockType) {
settingsChanged = true; settingsChanged = true;
}
settings.clockType = clocktype; settings.clockType = clocktype;
}; };
ClockType GetClockType() const { ClockType GetClockType() const {
@ -51,8 +58,9 @@ namespace Pinetime {
}; };
void SetVibrationStatus(Vibration status) { void SetVibrationStatus(Vibration status) {
if (status != settings.vibrationStatus) if (status != settings.vibrationStatus) {
settingsChanged = true; settingsChanged = true;
}
settings.vibrationStatus = status; settings.vibrationStatus = status;
}; };
Vibration GetVibrationStatus() const { Vibration GetVibrationStatus() const {
@ -60,26 +68,47 @@ namespace Pinetime {
}; };
void SetScreenTimeOut(uint32_t timeout) { void SetScreenTimeOut(uint32_t timeout) {
if (timeout != settings.screenTimeOut) if (timeout != settings.screenTimeOut) {
settingsChanged = true; settingsChanged = true;
}
settings.screenTimeOut = timeout; settings.screenTimeOut = timeout;
}; };
uint32_t GetScreenTimeOut() const { uint32_t GetScreenTimeOut() const {
return settings.screenTimeOut; return settings.screenTimeOut;
}; };
void setWakeUpMode(WakeUpMode wakeUp) { void setWakeUpMode(WakeUpMode wakeUp, bool enabled) {
if (wakeUp != settings.wakeUpMode) if (!isWakeUpModeOn(wakeUp)) {
settingsChanged = true; settingsChanged = true;
settings.wakeUpMode = wakeUp; }
}; settings.wakeUpMode.set(static_cast<size_t>(wakeUp), enabled);
WakeUpMode getWakeUpMode() const { // Handle special behavior
return settings.wakeUpMode; if (enabled) {
switch (wakeUp) {
case WakeUpMode::SingleTap:
settings.wakeUpMode.set(static_cast<size_t>(WakeUpMode::DoubleTap), false);
break;
case WakeUpMode::DoubleTap:
settings.wakeUpMode.set(static_cast<size_t>(WakeUpMode::SingleTap), false);
break;
case WakeUpMode::RaiseWrist:
break;
}
}
}; };
std::bitset<3> getWakeUpModes() const {
return settings.wakeUpMode;
}
bool isWakeUpModeOn(const WakeUpMode mode) const {
return getWakeUpModes()[static_cast<size_t>(mode)];
}
void SetBrightness(Controllers::BrightnessController::Levels level) { void SetBrightness(Controllers::BrightnessController::Levels level) {
if (level != settings.brightLevel) if (level != settings.brightLevel) {
settingsChanged = true; settingsChanged = true;
}
settings.brightLevel = level; settings.brightLevel = level;
}; };
Controllers::BrightnessController::Levels GetBrightness() const { Controllers::BrightnessController::Levels GetBrightness() const {
@ -87,26 +116,30 @@ namespace Pinetime {
}; };
void SetStepsGoal( uint32_t goal ) { void SetStepsGoal( uint32_t goal ) {
if ( goal != settings.stepsGoal ) if ( goal != settings.stepsGoal ) {
settingsChanged = true; settingsChanged = true;
}
settings.stepsGoal = goal; settings.stepsGoal = goal;
}; };
uint32_t GetStepsGoal() const { return settings.stepsGoal; }; uint32_t GetStepsGoal() const { return settings.stepsGoal; };
private: private:
Pinetime::Drivers::SpiNorFlash& spiNorFlash; Pinetime::Controllers::FS& fs;
static constexpr uint32_t settingsVersion = 0x0001;
struct SettingsData { struct SettingsData {
uint32_t version = settingsVersion;
uint32_t stepsGoal = 10000;
uint32_t screenTimeOut = 15000;
ClockType clockType = ClockType::H24; ClockType clockType = ClockType::H24;
Vibration vibrationStatus = Vibration::ON; Vibration vibrationStatus = Vibration::ON;
uint8_t clockFace = 0; uint8_t clockFace = 0;
uint32_t stepsGoal = 10000; std::bitset<3> wakeUpMode {0};
uint32_t screenTimeOut = 15000;
WakeUpMode wakeUpMode = WakeUpMode::None;
Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium; Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
}; };
@ -117,20 +150,8 @@ namespace Pinetime {
uint8_t appMenu = 0; uint8_t appMenu = 0;
uint8_t settingsMenu = 0; uint8_t settingsMenu = 0;
// There are 10 blocks of reserved flash to save settings void LoadSettingsFromFile();
// to minimize wear, the recording is done in a rotating way by the 10 blocks void SaveSettingsToFile();
uint8_t settingsFlashBlock = 99; // default to indicate it needs to find the active block
static constexpr uint32_t settingsBaseAddr = 0x3F6000; // Flash Settings Location
static constexpr uint16_t settingsVersion = 0x0100; // Flash Settings Version
bool FindHeader();
void ReadSettingsData();
void EraseBlock();
void SetHeader(bool state);
void SaveSettingsData();
void LoadSettingsFromFlash();
void SaveSettingsToFlash();
}; };
} }
} }

View file

@ -45,6 +45,8 @@
#include "displayapp/screens/settings/SettingSetDate.h" #include "displayapp/screens/settings/SettingSetDate.h"
#include "displayapp/screens/settings/SettingSetTime.h" #include "displayapp/screens/settings/SettingSetTime.h"
#include "libs/lv_conf.h"
using namespace Pinetime::Applications; using namespace Pinetime::Applications;
using namespace Pinetime::Applications::Display; using namespace Pinetime::Applications::Display;
@ -52,6 +54,31 @@ namespace {
static inline bool in_isr(void) { static inline bool in_isr(void) {
return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0; return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0;
} }
TouchEvents Convert(Pinetime::Drivers::Cst816S::TouchInfos info) {
if (info.isTouch) {
switch (info.gesture) {
case Pinetime::Drivers::Cst816S::Gestures::SingleTap:
return TouchEvents::Tap;
case Pinetime::Drivers::Cst816S::Gestures::LongPress:
return TouchEvents::LongTap;
case Pinetime::Drivers::Cst816S::Gestures::DoubleTap:
return TouchEvents::DoubleTap;
case Pinetime::Drivers::Cst816S::Gestures::SlideRight:
return TouchEvents::SwipeRight;
case Pinetime::Drivers::Cst816S::Gestures::SlideLeft:
return TouchEvents::SwipeLeft;
case Pinetime::Drivers::Cst816S::Gestures::SlideDown:
return TouchEvents::SwipeDown;
case Pinetime::Drivers::Cst816S::Gestures::SlideUp:
return TouchEvents::SwipeUp;
case Pinetime::Drivers::Cst816S::Gestures::None:
default:
return TouchEvents::None;
}
}
return TouchEvents::None;
}
} }
DisplayApp::DisplayApp(Drivers::St7789& lcd, DisplayApp::DisplayApp(Drivers::St7789& lcd,
@ -116,6 +143,7 @@ uint32_t count = 0;
bool toggle = true; bool toggle = true;
void DisplayApp::Refresh() { void DisplayApp::Refresh() {
TickType_t queueTimeout; TickType_t queueTimeout;
TickType_t delta;
switch (state) { switch (state) {
case States::Idle: case States::Idle:
IdleState(); IdleState();
@ -123,7 +151,11 @@ void DisplayApp::Refresh() {
break; break;
case States::Running: case States::Running:
RunningState(); RunningState();
queueTimeout = 20; delta = xTaskGetTickCount() - lastWakeTime;
if (delta > LV_DISP_DEF_REFR_PERIOD) {
delta = LV_DISP_DEF_REFR_PERIOD;
}
queueTimeout = LV_DISP_DEF_REFR_PERIOD - delta;
break; break;
default: default:
queueTimeout = portMAX_DELAY; queueTimeout = portMAX_DELAY;
@ -131,10 +163,19 @@ void DisplayApp::Refresh() {
} }
Messages msg; Messages msg;
if (xQueueReceive(msgQueue, &msg, queueTimeout)) { bool messageReceived = xQueueReceive(msgQueue, &msg, queueTimeout);
lastWakeTime = xTaskGetTickCount();
if (messageReceived) {
switch (msg) { switch (msg) {
case Messages::GoToSleep: case Messages::DimScreen:
// Backup brightness is the brightness to return to after dimming or sleeping
brightnessController.Backup(); brightnessController.Backup();
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
break;
case Messages::RestoreBrightness:
brightnessController.Restore();
break;
case Messages::GoToSleep:
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
brightnessController.Lower(); brightnessController.Lower();
vTaskDelay(100); vTaskDelay(100);
@ -163,16 +204,21 @@ void DisplayApp::Refresh() {
break; break;
case Messages::TimerDone: case Messages::TimerDone:
if (currentApp == Apps::Timer) { if (currentApp == Apps::Timer) {
auto *timer = static_cast<Screens::Timer*>(currentScreen.get()); auto* timer = static_cast<Screens::Timer*>(currentScreen.get());
timer->setDone(); timer->setDone();
} else { } else {
LoadApp(Apps::Timer, DisplayApp::FullRefreshDirections::Down); LoadApp(Apps::Timer, DisplayApp::FullRefreshDirections::Down);
} }
break; break;
case Messages::TouchEvent: { case Messages::TouchEvent: {
if (state != States::Running) if (state != States::Running) {
break; break;
auto gesture = OnTouchEvent(); }
auto info = touchPanel.GetTouchInfo();
auto gesture = Convert(info);
if (gesture == TouchEvents::None) {
break;
}
if (!currentScreen->OnTouchEvent(gesture)) { if (!currentScreen->OnTouchEvent(gesture)) {
if (currentApp == Apps::Clock) { if (currentApp == Apps::Clock) {
switch (gesture) { switch (gesture) {
@ -193,6 +239,12 @@ void DisplayApp::Refresh() {
} }
} else if (returnTouchEvent == gesture) { } else if (returnTouchEvent == gesture) {
LoadApp(returnToApp, returnDirection); LoadApp(returnToApp, returnDirection);
brightnessController.Set(settingsController.GetBrightness());
brightnessController.Backup();
} else if (touchMode == TouchModes::Gestures) {
if (gesture == TouchEvents::Tap) {
lvgl.SetNewTapEvent(info.x, info.y);
}
} }
} }
} break; } break;
@ -202,6 +254,8 @@ void DisplayApp::Refresh() {
} else { } else {
if (!currentScreen->OnButtonPushed()) { if (!currentScreen->OnButtonPushed()) {
LoadApp(returnToApp, returnDirection); LoadApp(returnToApp, returnDirection);
brightnessController.Set(settingsController.GetBrightness());
brightnessController.Backup();
} }
} }
break; break;
@ -216,7 +270,7 @@ void DisplayApp::Refresh() {
} }
} }
if(nextApp != Apps::None) { if (nextApp != Apps::None) {
LoadApp(nextApp, nextDirection); LoadApp(nextApp, nextDirection);
nextApp = Apps::None; nextApp = Apps::None;
} }
@ -279,6 +333,7 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
break; break;
case Apps::FirmwareUpdate: case Apps::FirmwareUpdate:
currentScreen = std::make_unique<Screens::FirmwareUpdate>(this, bleController); currentScreen = std::make_unique<Screens::FirmwareUpdate>(this, bleController);
ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::None);
break; break;
case Apps::Notifications: case Apps::Notifications:
@ -338,8 +393,8 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown); ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
break; break;
case Apps::SysInfo: case Apps::SysInfo:
currentScreen = currentScreen = std::make_unique<Screens::SystemInfo>(
std::make_unique<Screens::SystemInfo>(this, dateTimeController, batteryController, brightnessController, bleController, watchdog, motionController); this, dateTimeController, batteryController, brightnessController, bleController, watchdog, motionController);
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown); ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
break; break;
case Apps::FlashLight: case Apps::FlashLight:
@ -384,7 +439,7 @@ void DisplayApp::IdleState() {
} }
void DisplayApp::PushMessage(Messages msg) { void DisplayApp::PushMessage(Messages msg) {
if(in_isr()) { if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken; BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE; xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken); xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
@ -396,35 +451,6 @@ void DisplayApp::PushMessage(Messages msg) {
} }
} }
TouchEvents DisplayApp::OnTouchEvent() {
auto info = touchPanel.GetTouchInfo();
if (info.isTouch) {
switch (info.gesture) {
case Pinetime::Drivers::Cst816S::Gestures::SingleTap:
if (touchMode == TouchModes::Gestures) {
lvgl.SetNewTapEvent(info.x, info.y);
}
return TouchEvents::Tap;
case Pinetime::Drivers::Cst816S::Gestures::LongPress:
return TouchEvents::LongTap;
case Pinetime::Drivers::Cst816S::Gestures::DoubleTap:
return TouchEvents::DoubleTap;
case Pinetime::Drivers::Cst816S::Gestures::SlideRight:
return TouchEvents::SwipeRight;
case Pinetime::Drivers::Cst816S::Gestures::SlideLeft:
return TouchEvents::SwipeLeft;
case Pinetime::Drivers::Cst816S::Gestures::SlideDown:
return TouchEvents::SwipeDown;
case Pinetime::Drivers::Cst816S::Gestures::SlideUp:
return TouchEvents::SwipeUp;
case Pinetime::Drivers::Cst816S::Gestures::None:
default:
return TouchEvents::None;
}
}
return TouchEvents::None;
}
void DisplayApp::SetFullRefresh(DisplayApp::FullRefreshDirections direction) { void DisplayApp::SetFullRefresh(DisplayApp::FullRefreshDirections direction) {
switch (direction) { switch (direction) {
case DisplayApp::FullRefreshDirections::Down: case DisplayApp::FullRefreshDirections::Down:
@ -455,7 +481,7 @@ void DisplayApp::SetTouchMode(DisplayApp::TouchModes mode) {
} }
void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) { void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
if(systemTask != nullptr) if (systemTask != nullptr)
systemTask->PushMessage(message); systemTask->PushMessage(message);
} }

View file

@ -102,7 +102,6 @@ namespace Pinetime {
TouchModes touchMode = TouchModes::Gestures; TouchModes touchMode = TouchModes::Gestures;
TouchEvents OnTouchEvent();
void RunningState(); void RunningState();
void IdleState(); void IdleState();
static void Process(void* instance); static void Process(void* instance);
@ -114,6 +113,7 @@ namespace Pinetime {
Apps nextApp = Apps::None; Apps nextApp = Apps::None;
DisplayApp::FullRefreshDirections nextDirection; DisplayApp::FullRefreshDirections nextDirection;
TickType_t lastWakeTime;
}; };
} }
} }

View file

@ -13,7 +13,9 @@ namespace Pinetime {
NewNotification, NewNotification,
TimerDone, TimerDone,
BleFirmwareUpdateStarted, BleFirmwareUpdateStarted,
UpdateTimeOut UpdateTimeOut,
DimScreen,
RestoreBrightness
}; };
} }
} }

View file

@ -13,7 +13,7 @@
* Do not enable font compression and horizontal subpixel hinting * Do not enable font compression and horizontal subpixel hinting
* Load the file `JetBrainsMono-Bold.tff` (use the file in this repo to ensure the version matches) and specify the following range : `0x20-0x7f, 0x410-0x44f` * Load the file `JetBrainsMono-Bold.tff` (use the file in this repo to ensure the version matches) and specify the following range : `0x20-0x7f, 0x410-0x44f`
* Add a 2nd font, load the file `FontAwesome5-Solid+Brands+Regular.woff` and specify the following * Add a 2nd font, load the file `FontAwesome5-Solid+Brands+Regular.woff` and specify the following
range : `0xf293, 0xf294, 0xf244, 0xf240, 0xf242, 0xf243, 0xf241, 0xf54b, 0xf21e, 0xf1e6, 0xf54b, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf069, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf029, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569` range : `0xf293, 0xf294, 0xf244, 0xf240, 0xf242, 0xf243, 0xf241, 0xf54b, 0xf21e, 0xf1e6, 0xf54b, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf069, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf029, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf201, 0xf06e, 0xf015`
* Click on Convert, and download the file `jetbrains_mono_bold_20.c` and copy it in `src/DisplayApp/Fonts` * Click on Convert, and download the file `jetbrains_mono_bold_20.c` and copy it in `src/DisplayApp/Fonts`
* Add the font .c file path to src/CMakeLists.txt * Add the font .c file path to src/CMakeLists.txt
* Add an LV_FONT_DECLARE line in src/libs/lv_conf.h * Add an LV_FONT_DECLARE line in src/libs/lv_conf.h

View file

@ -734,6 +734,15 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xff, 0x7, 0xef, 0xf0, 0x10, 0xff, 0x0, 0x3, 0xff, 0x7, 0xef, 0xf0, 0x10, 0xff, 0x0, 0x3,
0xc0, 0x0, 0xc0, 0x0,
/* U+F015 "" */
0x0, 0x38, 0xe0, 0x0, 0xf9, 0xc0, 0x3, 0xfb,
0x80, 0x1e, 0x3f, 0x0, 0x79, 0x3e, 0x1, 0xe7,
0x3c, 0xf, 0x9f, 0xbc, 0x3c, 0xff, 0x9e, 0xf3,
0xff, 0x9e, 0xcf, 0xff, 0x98, 0x3f, 0xff, 0x80,
0x7f, 0xff, 0x0, 0xfc, 0x7e, 0x1, 0xf8, 0xfc,
0x3, 0xf1, 0xf8, 0x7, 0xe3, 0xf0, 0xf, 0xc7,
0xe0,
/* U+F017 "" */ /* U+F017 "" */
0x3, 0xf8, 0x1, 0xff, 0xc0, 0x7f, 0xfc, 0x1f, 0x3, 0xf8, 0x1, 0xff, 0xc0, 0x7f, 0xfc, 0x1f,
0xff, 0xc7, 0xf1, 0xfc, 0xfe, 0x3f, 0x9f, 0xc7, 0xff, 0xc7, 0xf1, 0xfc, 0xfe, 0x3f, 0x9f, 0xc7,
@ -823,6 +832,14 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xdf, 0x9e, 0x38, 0xf3, 0x7, 0x6, 0x0, 0xe0, 0xdf, 0x9e, 0x38, 0xf3, 0x7, 0x6, 0x0, 0xe0,
0x0, 0x1c, 0x0, 0x3, 0x80, 0x0, 0x70, 0x0, 0x0, 0x1c, 0x0, 0x3, 0x80, 0x0, 0x70, 0x0,
/* U+F06E "" */
0x0, 0xfe, 0x0, 0xf, 0xff, 0x80, 0x3e, 0xf,
0x80, 0xf8, 0xf, 0x83, 0xe3, 0x8f, 0x8f, 0x87,
0x8f, 0xbf, 0x1f, 0x9f, 0xfe, 0xff, 0x3f, 0xfd,
0xfe, 0x7e, 0xf9, 0xf8, 0xf8, 0xf9, 0xe3, 0xe0,
0xf8, 0xf, 0x80, 0xf8, 0x3e, 0x0, 0xff, 0xf0,
0x0, 0x3f, 0x80, 0x0,
/* U+F095 "" */ /* U+F095 "" */
0x0, 0x0, 0x0, 0x0, 0x3e, 0x0, 0x7, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x3e, 0x0, 0x7, 0xf0,
0x0, 0x7f, 0x0, 0x7, 0xf0, 0x0, 0xff, 0x0, 0x0, 0x7f, 0x0, 0x7, 0xf0, 0x0, 0xff, 0x0,
@ -861,6 +878,13 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x1, 0xf8, 0x0, 0x9f, 0xc0, 0xf, 0xfc, 0x0, 0x1, 0xf8, 0x0, 0x9f, 0xc0, 0xf, 0xfc, 0x0,
0x7f, 0xc0, 0x7, 0xf8, 0x0, 0x1f, 0x0, 0x0, 0x7f, 0xc0, 0x7, 0xf8, 0x0, 0x1f, 0x0, 0x0,
/* U+F201 "" */
0x40, 0x0, 0x7, 0x0, 0x0, 0x38, 0x1, 0xf9,
0xc0, 0x7, 0xce, 0x18, 0x1e, 0x71, 0xe1, 0xf3,
0x9f, 0x9d, 0x9d, 0xff, 0xc4, 0xe6, 0x7c, 0x7,
0x1, 0xc0, 0x38, 0x0, 0x1, 0xc0, 0x0, 0xe,
0x0, 0x0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xe0,
/* U+F21E "" */ /* U+F21E "" */
0x1e, 0x7, 0x83, 0xf9, 0xfe, 0x7f, 0xff, 0xef, 0x1e, 0x7, 0x83, 0xf9, 0xfe, 0x7f, 0xff, 0xef,
0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfc,
@ -1182,42 +1206,45 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 2484, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, {.bitmap_index = 2484, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 2498, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, {.bitmap_index = 2498, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 2511, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 2511, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 2561, .adv_w = 320, .box_w = 19, .box_h = 20, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 2561, .adv_w = 360, .box_w = 23, .box_h = 17, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 2609, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 2610, .adv_w = 320, .box_w = 19, .box_h = 20, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 2659, .adv_w = 240, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, {.bitmap_index = 2658, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 2688, .adv_w = 360, .box_w = 23, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 2708, .adv_w = 240, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 2743, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, {.bitmap_index = 2737, .adv_w = 360, .box_w = 23, .box_h = 19, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2782, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, {.bitmap_index = 2792, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 2825, .adv_w = 280, .box_w = 13, .box_h = 17, .ofs_x = 2, .ofs_y = -1}, {.bitmap_index = 2831, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 2853, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 2874, .adv_w = 280, .box_w = 13, .box_h = 17, .ofs_x = 2, .ofs_y = -1},
{.bitmap_index = 2901, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, {.bitmap_index = 2902, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 2940, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, {.bitmap_index = 2950, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 2979, .adv_w = 280, .box_w = 13, .box_h = 17, .ofs_x = 2, .ofs_y = -1}, {.bitmap_index = 2989, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 3007, .adv_w = 320, .box_w = 19, .box_h = 20, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 3028, .adv_w = 280, .box_w = 13, .box_h = 17, .ofs_x = 2, .ofs_y = -1},
{.bitmap_index = 3055, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 3056, .adv_w = 320, .box_w = 19, .box_h = 20, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 3108, .adv_w = 120, .box_w = 8, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3104, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 3127, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 3148, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 3177, .adv_w = 240, .box_w = 15, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3201, .adv_w = 120, .box_w = 8, .box_h = 19, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3213, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3220, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 3261, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, {.bitmap_index = 3270, .adv_w = 240, .box_w = 15, .box_h = 19, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3304, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1}, {.bitmap_index = 3306, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3342, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1}, {.bitmap_index = 3354, .adv_w = 320, .box_w = 21, .box_h = 15, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 3380, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1}, {.bitmap_index = 3394, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 3418, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1}, {.bitmap_index = 3437, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 3456, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1}, {.bitmap_index = 3475, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 3494, .adv_w = 240, .box_w = 15, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3513, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 3530, .adv_w = 280, .box_w = 15, .box_h = 20, .ofs_x = 1, .ofs_y = -3}, {.bitmap_index = 3551, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 3568, .adv_w = 200, .box_w = 11, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 3589, .adv_w = 400, .box_w = 25, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 3597, .adv_w = 280, .box_w = 16, .box_h = 19, .ofs_x = 1, .ofs_y = -2}, {.bitmap_index = 3627, .adv_w = 240, .box_w = 15, .box_h = 19, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3635, .adv_w = 400, .box_w = 25, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 3663, .adv_w = 280, .box_w = 15, .box_h = 20, .ofs_x = 1, .ofs_y = -3},
{.bitmap_index = 3701, .adv_w = 360, .box_w = 23, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, {.bitmap_index = 3701, .adv_w = 200, .box_w = 11, .box_h = 21, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 3750, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3730, .adv_w = 280, .box_w = 16, .box_h = 19, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3800, .adv_w = 400, .box_w = 25, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3768, .adv_w = 400, .box_w = 25, .box_h = 21, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 3860, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 3834, .adv_w = 360, .box_w = 23, .box_h = 17, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 3913, .adv_w = 360, .box_w = 23, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, {.bitmap_index = 3883, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3974, .adv_w = 360, .box_w = 22, .box_h = 20, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3933, .adv_w = 400, .box_w = 25, .box_h = 19, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4029, .adv_w = 360, .box_w = 22, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 3993, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 4082, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0} {.bitmap_index = 4046, .adv_w = 360, .box_w = 23, .box_h = 21, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 4107, .adv_w = 360, .box_w = 22, .box_h = 20, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4162, .adv_w = 360, .box_w = 22, .box_h = 19, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4215, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}
}; };
/*--------------------- /*---------------------
@ -1225,11 +1252,11 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
*--------------------*/ *--------------------*/
static const uint16_t unicode_list_2[] = { static const uint16_t unicode_list_2[] = {
0x0, 0x16, 0x23, 0x26, 0x27, 0x28, 0x39, 0x47, 0x0, 0x14, 0x16, 0x23, 0x26, 0x27, 0x28, 0x39,
0x4a, 0x4b, 0x4c, 0x50, 0x68, 0x94, 0x128, 0x184, 0x47, 0x4a, 0x4b, 0x4c, 0x50, 0x68, 0x6d, 0x94,
0x1e5, 0x1fb, 0x21d, 0x23f, 0x240, 0x241, 0x242, 0x243, 0x128, 0x184, 0x1e5, 0x1fb, 0x200, 0x21d, 0x23f, 0x240,
0x251, 0x292, 0x293, 0x2f1, 0x3dc, 0x3fc, 0x45c, 0x54a, 0x241, 0x242, 0x243, 0x251, 0x292, 0x293, 0x2f1, 0x3dc,
0x55f, 0x568, 0x59e, 0x59f, 0x6a8 0x3fc, 0x45c, 0x54a, 0x55f, 0x568, 0x59e, 0x59f, 0x6a8
}; };
/*Collect the unicode lists and glyph_id offsets*/ /*Collect the unicode lists and glyph_id offsets*/
@ -1245,7 +1272,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
}, },
{ {
.range_start = 61441, .range_length = 1705, .glyph_id_start = 160, .range_start = 61441, .range_length = 1705, .glyph_id_start = 160,
.unicode_list = unicode_list_2, .glyph_id_ofs_list = NULL, .list_length = 37, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY .unicode_list = unicode_list_2, .glyph_id_ofs_list = NULL, .list_length = 40, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
} }
}; };

View file

@ -62,7 +62,7 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen2() {
{Symbols::paintbrush, Apps::Paint}, {Symbols::paintbrush, Apps::Paint},
{Symbols::paddle, Apps::Paddle}, {Symbols::paddle, Apps::Paddle},
{"2", Apps::Twos}, {"2", Apps::Twos},
{"M", Apps::Motion}, {Symbols::chartLine, Apps::Motion},
{Symbols::drum, Apps::Metronome}, {Symbols::drum, Apps::Metronome},
{"", Apps::None}, {"", Apps::None},
}}; }};

View file

@ -1,9 +1,10 @@
#include <cstdint>
#include "BatteryIcon.h" #include "BatteryIcon.h"
#include "Symbols.h" #include "Symbols.h"
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
const char* BatteryIcon::GetBatteryIcon(int batteryPercent) { const char* BatteryIcon::GetBatteryIcon(uint8_t batteryPercent) {
if (batteryPercent > 90) if (batteryPercent > 90)
return Symbols::batteryFull; return Symbols::batteryFull;
if (batteryPercent > 75) if (batteryPercent > 75)

View file

@ -6,7 +6,7 @@ namespace Pinetime {
class BatteryIcon { class BatteryIcon {
public: public:
static const char* GetUnknownIcon(); static const char* GetUnknownIcon();
static const char* GetBatteryIcon(int batteryPercent); static const char* GetBatteryIcon(uint8_t batteryPercent);
static const char* GetPlugIcon(bool isCharging); static const char* GetPlugIcon(bool isCharging);
}; };
} }

View file

@ -9,11 +9,6 @@ static void lv_update_task(struct _lv_task_t* task) {
user_data->UpdateScreen(); user_data->UpdateScreen();
} }
static void lv_anim_task(struct _lv_task_t* task) {
auto user_data = static_cast<BatteryInfo*>(task->user_data);
user_data->UpdateAnim();
}
BatteryInfo::BatteryInfo(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Battery& batteryController) BatteryInfo::BatteryInfo(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Battery& batteryController)
: Screen(app), batteryController {batteryController} { : Screen(app), batteryController {batteryController} {
@ -24,12 +19,12 @@ BatteryInfo::BatteryInfo(Pinetime::Applications::DisplayApp* app, Pinetime::Cont
lv_obj_set_size(charging_bar, 200, 15); lv_obj_set_size(charging_bar, 200, 15);
lv_bar_set_range(charging_bar, 0, 100); lv_bar_set_range(charging_bar, 0, 100);
lv_obj_align(charging_bar, nullptr, LV_ALIGN_CENTER, 0, 10); lv_obj_align(charging_bar, nullptr, LV_ALIGN_CENTER, 0, 10);
lv_bar_set_anim_time(charging_bar, 2000); lv_bar_set_anim_time(charging_bar, 1000);
lv_obj_set_style_local_radius(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE); lv_obj_set_style_local_radius(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, lv_color_hex(0x222222)); lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, lv_color_hex(0x222222));
lv_obj_set_style_local_bg_opa(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100); lv_obj_set_style_local_bg_opa(charging_bar, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_OPA_100);
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, lv_color_hex(0xFF0000)); lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, lv_color_hex(0xFF0000));
lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_OFF); lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON);
status = lv_label_create(lv_scr_act(), nullptr); status = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(status, "Reading Battery status"); lv_label_set_text_static(status, "Reading Battery status");
@ -38,11 +33,7 @@ BatteryInfo::BatteryInfo(Pinetime::Applications::DisplayApp* app, Pinetime::Cont
percent = lv_label_create(lv_scr_act(), nullptr); percent = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); lv_obj_set_style_local_text_font(percent, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76);
if (batteryPercent >= 0) { lv_label_set_text_fmt(percent, "%02i%%", batteryPercent);
lv_label_set_text_fmt(percent, "%02i%%", batteryPercent);
} else {
lv_label_set_text(percent, "--%");
}
lv_label_set_align(percent, LV_LABEL_ALIGN_LEFT); lv_label_set_align(percent, LV_LABEL_ALIGN_LEFT);
lv_obj_align(percent, nullptr, LV_ALIGN_CENTER, 0, -60); lv_obj_align(percent, nullptr, LV_ALIGN_CENTER, 0, -60);
@ -58,40 +49,15 @@ BatteryInfo::BatteryInfo(Pinetime::Applications::DisplayApp* app, Pinetime::Cont
lv_obj_set_pos(backgroundLabel, 0, 0); lv_obj_set_pos(backgroundLabel, 0, 0);
lv_label_set_text_static(backgroundLabel, ""); lv_label_set_text_static(backgroundLabel, "");
taskUpdate = lv_task_create(lv_update_task, 500000, LV_TASK_PRIO_LOW, this); taskUpdate = lv_task_create(lv_update_task, 5000, LV_TASK_PRIO_LOW, this);
taskAnim = lv_task_create(lv_anim_task, 1000, LV_TASK_PRIO_LOW, this);
UpdateScreen(); UpdateScreen();
} }
BatteryInfo::~BatteryInfo() { BatteryInfo::~BatteryInfo() {
lv_task_del(taskUpdate); lv_task_del(taskUpdate);
lv_task_del(taskAnim);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
void BatteryInfo::UpdateAnim() {
batteryPercent = batteryController.PercentRemaining();
if (batteryPercent >= 0) {
if (batteryController.IsCharging() and batteryPercent < 100) {
animation += 1;
if (animation >= 100) {
animation = 0;
}
} else {
if (animation > batteryPercent) {
animation--;
}
if (animation < batteryPercent) {
animation++;
}
}
lv_bar_set_value(charging_bar, animation, LV_ANIM_OFF);
}
}
void BatteryInfo::UpdateScreen() { void BatteryInfo::UpdateScreen() {
batteryController.Update(); batteryController.Update();
@ -99,33 +65,27 @@ void BatteryInfo::UpdateScreen() {
batteryPercent = batteryController.PercentRemaining(); batteryPercent = batteryController.PercentRemaining();
batteryVoltage = batteryController.Voltage(); batteryVoltage = batteryController.Voltage();
if (batteryPercent >= 0) { if (batteryController.IsCharging() and batteryPercent < 100) {
if (batteryController.IsCharging() and batteryPercent < 100) { lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED);
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_RED); lv_label_set_text_static(status, "Charging");
lv_label_set_text_static(status, "Battery charging"); } else if (batteryPercent == 100) {
} else if (batteryPercent == 100) { lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE);
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_BLUE); lv_label_set_text_static(status, "Fully charged");
lv_label_set_text_static(status, "Battery is fully charged"); } else if (batteryPercent < 10) {
} else if (batteryPercent < 10) { lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_YELLOW);
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_YELLOW); lv_label_set_text_static(status, "Battery low");
lv_label_set_text_static(status, "Battery is low");
} else {
lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_GREEN);
lv_label_set_text_static(status, "Battery discharging");
}
lv_label_set_text_fmt(percent, "%02i%%", batteryPercent);
} else { } else {
lv_label_set_text_static(status, "Reading Battery status"); lv_obj_set_style_local_bg_color(charging_bar, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_GREEN);
lv_label_set_text(percent, "--%"); lv_label_set_text_static(status, "Discharging");
} }
lv_label_set_text_fmt(percent, "%02i%%", batteryPercent);
lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20); lv_obj_align(status, charging_bar, LV_ALIGN_OUT_BOTTOM_MID, 0, 20);
lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10); lv_label_set_text_fmt(voltage, "%1i.%02i volts", batteryVoltage / 1000, batteryVoltage % 1000 / 10);
lv_bar_set_value(charging_bar, batteryPercent, LV_ANIM_ON);
} }
bool BatteryInfo::Refresh() { bool BatteryInfo::Refresh() {
return running; return running;
} }

View file

@ -22,7 +22,6 @@ namespace Pinetime {
bool Refresh() override; bool Refresh() override;
void UpdateScreen(); void UpdateScreen();
void UpdateAnim();
private: private:
Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Battery& batteryController;
@ -33,10 +32,8 @@ namespace Pinetime {
lv_obj_t* status; lv_obj_t* status;
lv_task_t* taskUpdate; lv_task_t* taskUpdate;
lv_task_t* taskAnim;
int8_t animation = 0; uint8_t batteryPercent = 0;
int8_t batteryPercent = -1;
uint16_t batteryVoltage = 0; uint16_t batteryVoltage = 0;
}; };
} }

View file

@ -27,9 +27,10 @@ FirmwareUpdate::FirmwareUpdate(Pinetime::Applications::DisplayApp* app, Pinetime
lv_bar_set_value(bar1, 0, LV_ANIM_OFF); lv_bar_set_value(bar1, 0, LV_ANIM_OFF);
percentLabel = lv_label_create(lv_scr_act(), nullptr); percentLabel = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text(percentLabel, ""); lv_label_set_text(percentLabel, "Waiting...");
lv_obj_set_auto_realign(percentLabel, true); lv_obj_set_auto_realign(percentLabel, true);
lv_obj_align(percentLabel, bar1, LV_ALIGN_OUT_TOP_MID, 0, 60); lv_obj_align(percentLabel, bar1, LV_ALIGN_OUT_TOP_MID, 0, 60);
startTime = xTaskGetTickCount();
} }
FirmwareUpdate::~FirmwareUpdate() { FirmwareUpdate::~FirmwareUpdate() {
@ -40,26 +41,42 @@ bool FirmwareUpdate::Refresh() {
switch (bleController.State()) { switch (bleController.State()) {
default: default:
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Idle: case Pinetime::Controllers::Ble::FirmwareUpdateStates::Idle:
// This condition makes sure that the app is exited if somehow it got
// launched without a firmware update. This should never happen.
if (state != States::Error) {
if (xTaskGetTickCount() - startTime > (60 * 1024)) {
UpdateError();
state = States::Error;
}
} else if (xTaskGetTickCount() - startTime > (5 * 1024)) {
running = false;
}
break;
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Running: case Pinetime::Controllers::Ble::FirmwareUpdateStates::Running:
if (state != States::Running) if (state != States::Running)
state = States::Running; state = States::Running;
return DisplayProgression(); DisplayProgression();
break;
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated: case Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated:
if (state != States::Validated) { if (state != States::Validated) {
UpdateValidated(); UpdateValidated();
state = States::Validated; state = States::Validated;
} }
return running; break;
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Error: case Pinetime::Controllers::Ble::FirmwareUpdateStates::Error:
if (state != States::Error) { if (state != States::Error) {
UpdateError(); UpdateError();
state = States::Error; state = States::Error;
} }
return running; if (xTaskGetTickCount() - startTime > (5 * 1024)) {
running = false;
}
break;
} }
return running;
} }
bool FirmwareUpdate::DisplayProgression() const { void FirmwareUpdate::DisplayProgression() const {
float current = bleController.FirmwareUpdateCurrentBytes() / 1024.0f; float current = bleController.FirmwareUpdateCurrentBytes() / 1024.0f;
float total = bleController.FirmwareUpdateTotalBytes() / 1024.0f; float total = bleController.FirmwareUpdateTotalBytes() / 1024.0f;
int16_t pc = (current / total) * 100.0f; int16_t pc = (current / total) * 100.0f;
@ -67,7 +84,6 @@ bool FirmwareUpdate::DisplayProgression() const {
lv_label_set_text(percentLabel, percentStr); lv_label_set_text(percentLabel, percentStr);
lv_bar_set_value(bar1, pc, LV_ANIM_OFF); lv_bar_set_value(bar1, pc, LV_ANIM_OFF);
return running;
} }
void FirmwareUpdate::UpdateValidated() { void FirmwareUpdate::UpdateValidated() {
@ -78,4 +94,9 @@ void FirmwareUpdate::UpdateValidated() {
void FirmwareUpdate::UpdateError() { void FirmwareUpdate::UpdateError() {
lv_label_set_recolor(percentLabel, true); lv_label_set_recolor(percentLabel, true);
lv_label_set_text(percentLabel, "#ff0000 Error!#"); lv_label_set_text(percentLabel, "#ff0000 Error!#");
startTime = xTaskGetTickCount();
}
bool FirmwareUpdate::OnButtonPushed() {
return true;
} }

View file

@ -2,6 +2,7 @@
#include "Screen.h" #include "Screen.h"
#include <lvgl/src/lv_core/lv_obj.h> #include <lvgl/src/lv_core/lv_obj.h>
#include "FreeRTOS.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@ -25,13 +26,17 @@ namespace Pinetime {
lv_obj_t* titleLabel; lv_obj_t* titleLabel;
mutable char percentStr[10]; mutable char percentStr[10];
States state; States state = States::Idle;
bool DisplayProgression() const; void DisplayProgression() const;
bool OnButtonPushed() override;
void UpdateValidated(); void UpdateValidated();
void UpdateError(); void UpdateError();
TickType_t startTime;
}; };
} }
} }

View file

@ -70,5 +70,5 @@ bool FlashLight::Refresh() {
} }
bool FlashLight::OnTouchEvent(Pinetime::Applications::TouchEvents event) { bool FlashLight::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
return true; return false;
} }

View file

@ -15,8 +15,6 @@ namespace Pinetime {
bool Refresh() override; bool Refresh() override;
private: private:
bool running = true;
lv_obj_t* labelText = nullptr; lv_obj_t* labelText = nullptr;
lv_point_t pageIndicatorBasePoints[2]; lv_point_t pageIndicatorBasePoints[2];
lv_point_t pageIndicatorPoints[2]; lv_point_t pageIndicatorPoints[2];

View file

@ -36,7 +36,6 @@ Motion::Motion(Pinetime::Applications::DisplayApp* app, Controllers::MotionContr
labelStep = lv_label_create(lv_scr_act(), NULL); labelStep = lv_label_create(lv_scr_act(), NULL);
lv_obj_align(labelStep, chart, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); lv_obj_align(labelStep, chart, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
lv_label_set_text(labelStep, "Steps ---"); lv_label_set_text(labelStep, "Steps ---");
} }
Motion::~Motion() { Motion::~Motion() {
@ -50,13 +49,12 @@ bool Motion::Refresh() {
lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps()); lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
lv_label_set_text_fmt(label, "X #FF0000 %d# Y #008000 %d# Z #FFFF00 %d#", motionController.X() / 0x10, motionController.Y() / 0x10, motionController.Z() / 0x10); lv_label_set_text_fmt(label,
"X #FF0000 %d# Y #008000 %d# Z #FFFF00 %d#",
motionController.X() / 0x10,
motionController.Y() / 0x10,
motionController.Z() / 0x10);
lv_obj_align(label, NULL, LV_ALIGN_IN_TOP_MID, 0, 10); lv_obj_align(label, NULL, LV_ALIGN_IN_TOP_MID, 0, 10);
return running; return running;
} }
bool Motion::OnButtonPushed() {
running = false;
return true;
}

View file

@ -18,7 +18,6 @@ namespace Pinetime {
~Motion() override; ~Motion() override;
bool Refresh() override; bool Refresh() override;
bool OnButtonPushed() override;
private: private:
Controllers::MotionController& motionController; Controllers::MotionController& motionController;
@ -29,9 +28,6 @@ namespace Pinetime {
lv_obj_t* label; lv_obj_t* label;
lv_obj_t* labelStep; lv_obj_t* labelStep;
static constexpr uint8_t nbStepsBufferSize = 9;
char nbStepsBuffer[nbStepsBufferSize + 1];
bool running = true;
}; };
} }
} }

View file

@ -119,7 +119,6 @@ Music::Music(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Mus
constexpr int8_t MIDDLE_OFFSET = -25; constexpr int8_t MIDDLE_OFFSET = -25;
txtArtist = lv_label_create(lv_scr_act(), nullptr); txtArtist = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(txtArtist, LV_LABEL_LONG_SROLL_CIRC); lv_label_set_long_mode(txtArtist, LV_LABEL_LONG_SROLL_CIRC);
lv_label_set_anim_speed(txtArtist, 1);
lv_obj_align(txtArtist, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 1 * FONT_HEIGHT); lv_obj_align(txtArtist, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 1 * FONT_HEIGHT);
lv_label_set_align(txtArtist, LV_ALIGN_IN_LEFT_MID); lv_label_set_align(txtArtist, LV_ALIGN_IN_LEFT_MID);
lv_obj_set_width(txtArtist, LV_HOR_RES - 12); lv_obj_set_width(txtArtist, LV_HOR_RES - 12);
@ -127,7 +126,6 @@ Music::Music(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Mus
txtTrack = lv_label_create(lv_scr_act(), nullptr); txtTrack = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(txtTrack, LV_LABEL_LONG_SROLL_CIRC); lv_label_set_long_mode(txtTrack, LV_LABEL_LONG_SROLL_CIRC);
lv_label_set_anim_speed(txtTrack, 1);
lv_obj_align(txtTrack, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 2 * FONT_HEIGHT + LINE_PAD); lv_obj_align(txtTrack, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 2 * FONT_HEIGHT + LINE_PAD);
lv_label_set_align(txtTrack, LV_ALIGN_IN_LEFT_MID); lv_label_set_align(txtTrack, LV_ALIGN_IN_LEFT_MID);

View file

@ -184,7 +184,6 @@ Notifications::NotificationItem::NotificationItem(const char* title,
} }
lv_label_set_text(alert_type, title); lv_label_set_text(alert_type, title);
lv_label_set_long_mode(alert_type, LV_LABEL_LONG_SROLL_CIRC); lv_label_set_long_mode(alert_type, LV_LABEL_LONG_SROLL_CIRC);
lv_label_set_anim_speed(alert_type, 3);
lv_obj_set_width(alert_type, 180); lv_obj_set_width(alert_type, 180);
lv_obj_align(alert_type, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 16); lv_obj_align(alert_type, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 16);

View file

@ -179,8 +179,8 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_align(stepGauge, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, 0); lv_obj_align(stepGauge, sidebar, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
lv_gauge_set_scale(stepGauge, 360, 11, 0); lv_gauge_set_scale(stepGauge, 360, 11, 0);
lv_gauge_set_angle_offset(stepGauge, 180); lv_gauge_set_angle_offset(stepGauge, 180);
lv_gauge_set_critical_value(stepGauge, (settingsController.GetStepsGoal() / 100)); lv_gauge_set_critical_value(stepGauge, 100);
lv_gauge_set_range(stepGauge, 0, (settingsController.GetStepsGoal() / 100)); lv_gauge_set_range(stepGauge, 0, 100);
lv_gauge_set_value(stepGauge, 0, 0); lv_gauge_set_value(stepGauge, 0, 0);
lv_obj_set_style_local_pad_right(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, 3); lv_obj_set_style_local_pad_right(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, 3);
@ -264,23 +264,23 @@ bool PineTimeStyle::Refresh() {
char hoursChar[3]; char hoursChar[3];
char ampmChar[5]; char ampmChar[5];
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
sprintf(hoursChar, "%02d", hour); sprintf(hoursChar, "%02d", hour);
} else { } else {
if (hour == 0 && hour != 12) { if (hour == 0 && hour != 12) {
hour = 12; hour = 12;
sprintf(ampmChar, "A\nM"); sprintf(ampmChar, "A\nM");
} else if (hour == 12 && hour != 0) { } else if (hour == 12 && hour != 0) {
hour = 12; hour = 12;
sprintf(ampmChar, "P\nM"); sprintf(ampmChar, "P\nM");
} else if (hour < 12 && hour != 0) { } else if (hour < 12 && hour != 0) {
sprintf(ampmChar, "A\nM"); sprintf(ampmChar, "A\nM");
} else if (hour > 12 && hour != 0) { } else if (hour > 12 && hour != 0) {
hour = hour - 12; hour = hour - 12;
sprintf(ampmChar, "P\nM"); sprintf(ampmChar, "P\nM");
}
sprintf(hoursChar, "%02d", hour);
} }
sprintf(hoursChar, "%02d", hour);
}
if (hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] || if (hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] ||
minutesChar[1] != displayedChar[3]) { minutesChar[1] != displayedChar[3]) {
@ -292,9 +292,9 @@ bool PineTimeStyle::Refresh() {
char hourStr[3]; char hourStr[3];
char minStr[3]; char minStr[3];
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_label_set_text(timeAMPM, ampmChar); lv_label_set_text(timeAMPM, ampmChar);
} }
/* Display the time as 2 pairs of digits */ /* Display the time as 2 pairs of digits */
sprintf(hourStr, "%c%c", hoursChar[0], hoursChar[1]); sprintf(hourStr, "%c%c", hoursChar[0], hoursChar[1]);
@ -328,7 +328,7 @@ bool PineTimeStyle::Refresh() {
stepCount = motionController.NbSteps(); stepCount = motionController.NbSteps();
motionSensorOk = motionController.IsSensorOk(); motionSensorOk = motionController.IsSensorOk();
if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) { if (stepCount.IsUpdated() || motionSensorOk.IsUpdated()) {
lv_gauge_set_value(stepGauge, 0, (stepCount.Get() / 100)); lv_gauge_set_value(stepGauge, 0, (stepCount.Get() / (settingsController.GetStepsGoal() / 100)));
lv_obj_realign(stepGauge); lv_obj_realign(stepGauge);
if (stepCount.Get() > settingsController.GetStepsGoal()) { if (stepCount.Get() > settingsController.GetStepsGoal()) {
lv_obj_set_style_local_line_color(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); lv_obj_set_style_local_line_color(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);

View file

@ -42,7 +42,7 @@ namespace Pinetime {
Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown; Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
uint8_t currentDay = 0; uint8_t currentDay = 0;
DirtyValue<int> batteryPercentRemaining {}; DirtyValue<uint8_t> batteryPercentRemaining {};
DirtyValue<bool> bleState {}; DirtyValue<bool> bleState {};
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
DirtyValue<bool> motionSensorOk {}; DirtyValue<bool> motionSensorOk {};

View file

@ -13,8 +13,12 @@ namespace Pinetime {
DirtyValue() = default; // Use NSDMI DirtyValue() = default; // Use NSDMI
explicit DirtyValue(T const& v) : value {v} { explicit DirtyValue(T const& v) : value {v} {
} // Use MIL and const-lvalue-ref } // Use MIL and const-lvalue-ref
bool IsUpdated() const { bool IsUpdated() {
return isUpdated; if (this->isUpdated) {
this->isUpdated = false;
return true;
}
return false;
} }
T const& Get() { T const& Get() {
this->isUpdated = false; this->isUpdated = false;

View file

@ -5,13 +5,10 @@
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
Steps::Steps( Steps::Steps(Pinetime::Applications::DisplayApp* app,
Pinetime::Applications::DisplayApp *app, Controllers::MotionController& motionController,
Controllers::MotionController& motionController, Controllers::Settings& settingsController)
Controllers::Settings &settingsController) : Screen(app), motionController {motionController}, settingsController {settingsController} {
: Screen(app),
motionController{motionController},
settingsController{settingsController} {
stepsArc = lv_arc_create(lv_scr_act(), nullptr); stepsArc = lv_arc_create(lv_scr_act(), nullptr);
@ -34,12 +31,12 @@ Steps::Steps(
lv_label_set_text_fmt(lSteps, "%li", stepsCount); lv_label_set_text_fmt(lSteps, "%li", stepsCount);
lv_obj_align(lSteps, nullptr, LV_ALIGN_CENTER, 0, -20); lv_obj_align(lSteps, nullptr, LV_ALIGN_CENTER, 0, -20);
lv_obj_t * lstepsL = lv_label_create(lv_scr_act(), nullptr); lv_obj_t* lstepsL = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(lstepsL, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111)); lv_obj_set_style_local_text_color(lstepsL, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x111111));
lv_label_set_text_static(lstepsL, "Steps"); lv_label_set_text_static(lstepsL, "Steps");
lv_obj_align(lstepsL, lSteps, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); lv_obj_align(lstepsL, lSteps, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
lv_obj_t * lstepsGoal = lv_label_create(lv_scr_act(), nullptr); lv_obj_t* lstepsGoal = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(lstepsGoal, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN); lv_obj_set_style_local_text_color(lstepsGoal, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN);
lv_label_set_text_fmt(lstepsGoal, "Goal\n%lu", settingsController.GetStepsGoal()); lv_label_set_text_fmt(lstepsGoal, "Goal\n%lu", settingsController.GetStepsGoal());
lv_label_set_align(lstepsGoal, LV_LABEL_ALIGN_CENTER); lv_label_set_align(lstepsGoal, LV_LABEL_ALIGN_CENTER);

View file

@ -41,6 +41,9 @@ namespace Pinetime {
static constexpr const char* hourGlass = "\xEF\x89\x92"; static constexpr const char* hourGlass = "\xEF\x89\x92";
static constexpr const char* lapsFlag = "\xEF\x80\xA4"; static constexpr const char* lapsFlag = "\xEF\x80\xA4";
static constexpr const char* drum = "\xEF\x95\xA9"; static constexpr const char* drum = "\xEF\x95\xA9";
static constexpr const char* chartLine = "\xEF\x88\x81";
static constexpr const char* eye = "\xEF\x81\xAE";
static constexpr const char* home = "\xEF\x80\x95";
// lv_font_sys_48.c // lv_font_sys_48.c
static constexpr const char* settings = "\xEE\xA4\x82"; // e902 static constexpr const char* settings = "\xEE\xA4\x82"; // e902

View file

@ -20,6 +20,8 @@ namespace {
return "BMA421"; return "BMA421";
case Pinetime::Controllers::MotionController::DeviceTypes::BMA425: case Pinetime::Controllers::MotionController::DeviceTypes::BMA425:
return "BMA425"; return "BMA425";
case Pinetime::Controllers::MotionController::DeviceTypes::Unknown:
return "???";
} }
return "???"; return "???";
} }
@ -70,11 +72,6 @@ bool SystemInfo::Refresh() {
return running; return running;
} }
bool SystemInfo::OnButtonPushed() {
running = false;
return true;
}
bool SystemInfo::OnTouchEvent(Pinetime::Applications::TouchEvents event) { bool SystemInfo::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
return screens.OnTouchEvent(event); return screens.OnTouchEvent(event);
} }
@ -103,7 +100,7 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen1() {
} }
std::unique_ptr<Screen> SystemInfo::CreateScreen2() { std::unique_ptr<Screen> SystemInfo::CreateScreen2() {
auto batteryPercent = static_cast<uint8_t>(batteryController.PercentRemaining()); auto batteryPercent = batteryController.PercentRemaining();
auto resetReason = [this]() { auto resetReason = [this]() {
switch (watchdog.ResetReason()) { switch (watchdog.ResetReason()) {
case Drivers::Watchdog::ResetReasons::Watchdog: case Drivers::Watchdog::ResetReasons::Watchdog:
@ -212,28 +209,50 @@ bool SystemInfo::sortById(const TaskStatus_t& lhs, const TaskStatus_t& rhs) {
std::unique_ptr<Screen> SystemInfo::CreateScreen4() { std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
TaskStatus_t tasksStatus[7]; TaskStatus_t tasksStatus[7];
lv_obj_t* infoTask = lv_table_create(lv_scr_act(), NULL); lv_obj_t* infoTask = lv_table_create(lv_scr_act(), NULL);
lv_table_set_col_cnt(infoTask, 3); lv_table_set_col_cnt(infoTask, 4);
lv_table_set_row_cnt(infoTask, 8); lv_table_set_row_cnt(infoTask, 8);
lv_obj_set_pos(infoTask, 10, 10); lv_obj_set_pos(infoTask, 0, 10);
lv_table_set_cell_value(infoTask, 0, 0, "#"); lv_table_set_cell_value(infoTask, 0, 0, "#");
lv_table_set_col_width(infoTask, 0, 50); lv_table_set_col_width(infoTask, 0, 30);
lv_table_set_cell_value(infoTask, 0, 1, "Task"); lv_table_set_cell_value(infoTask, 0, 1, "S"); // State
lv_table_set_col_width(infoTask, 1, 80); lv_table_set_col_width(infoTask, 1, 30);
lv_table_set_cell_value(infoTask, 0, 2, "Free"); lv_table_set_cell_value(infoTask, 0, 2, "Task");
lv_table_set_col_width(infoTask, 2, 90); lv_table_set_col_width(infoTask, 2, 80);
lv_table_set_cell_value(infoTask, 0, 3, "Free");
lv_table_set_col_width(infoTask, 3, 90);
auto nb = uxTaskGetSystemState(tasksStatus, 7, nullptr); auto nb = uxTaskGetSystemState(tasksStatus, 7, nullptr);
std::sort(tasksStatus, tasksStatus + nb, sortById); std::sort(tasksStatus, tasksStatus + nb, sortById);
for (uint8_t i = 0; i < nb; i++) { for (uint8_t i = 0; i < nb; i++) {
lv_table_set_cell_value(infoTask, i + 1, 0, std::to_string(tasksStatus[i].xTaskNumber).c_str()); lv_table_set_cell_value(infoTask, i + 1, 0, std::to_string(tasksStatus[i].xTaskNumber).c_str());
lv_table_set_cell_value(infoTask, i + 1, 1, tasksStatus[i].pcTaskName); char state[2] = {0};
switch (tasksStatus[i].eCurrentState) {
case eReady:
case eRunning:
state[0] = 'R';
break;
case eBlocked:
state[0] = 'B';
break;
case eSuspended:
state[0] = 'S';
break;
case eDeleted:
state[0] = 'D';
break;
default:
state[0] = 'I'; // Invalid
break;
}
lv_table_set_cell_value(infoTask, i + 1, 1, state);
lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName);
if (tasksStatus[i].usStackHighWaterMark < 20) { if (tasksStatus[i].usStackHighWaterMark < 20) {
std::string str1 = std::to_string(tasksStatus[i].usStackHighWaterMark) + " low"; std::string str1 = std::to_string(tasksStatus[i].usStackHighWaterMark) + " low";
lv_table_set_cell_value(infoTask, i + 1, 2, str1.c_str()); lv_table_set_cell_value(infoTask, i + 1, 3, str1.c_str());
} else { } else {
lv_table_set_cell_value(infoTask, i + 1, 2, std::to_string(tasksStatus[i].usStackHighWaterMark).c_str()); lv_table_set_cell_value(infoTask, i + 1, 3, std::to_string(tasksStatus[i].usStackHighWaterMark).c_str());
} }
} }
return std::make_unique<Screens::Label>(3, 5, app, infoTask); return std::make_unique<Screens::Label>(3, 5, app, infoTask);

View file

@ -31,12 +31,9 @@ namespace Pinetime {
Pinetime::Controllers::MotionController& motionController); Pinetime::Controllers::MotionController& motionController);
~SystemInfo() override; ~SystemInfo() override;
bool Refresh() override; bool Refresh() override;
bool OnButtonPushed() override;
bool OnTouchEvent(TouchEvents event) override; bool OnTouchEvent(TouchEvents event) override;
private: private:
bool running = true;
Pinetime::Controllers::DateTime& dateTimeController; Pinetime::Controllers::DateTime& dateTimeController;
Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Battery& batteryController;
Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::BrightnessController& brightnessController;

View file

@ -6,15 +6,17 @@ using namespace Pinetime::Applications::Screens;
namespace { namespace {
static void lv_update_task(struct _lv_task_t* task) { static void lv_update_task(struct _lv_task_t* task) {
auto user_data = static_cast<Tile*>(task->user_data); auto* user_data = static_cast<Tile*>(task->user_data);
user_data->UpdateScreen(); user_data->UpdateScreen();
} }
static void event_handler(lv_obj_t* obj, lv_event_t event) { static void event_handler(lv_obj_t* obj, lv_event_t event) {
if (event != LV_EVENT_VALUE_CHANGED) return;
Tile* screen = static_cast<Tile*>(obj->user_data); Tile* screen = static_cast<Tile*>(obj->user_data);
uint32_t* eventDataPtr = (uint32_t*) lv_event_get_data(); auto* eventDataPtr = (uint32_t*) lv_event_get_data();
uint32_t eventData = *eventDataPtr; uint32_t eventData = *eventDataPtr;
screen->OnObjectEvent(obj, event, eventData); screen->OnValueChangedEvent(obj, eventData);
} }
} }
@ -107,7 +109,7 @@ Tile::Tile(uint8_t screenID,
lv_obj_set_pos(backgroundLabel, 0, 0); lv_obj_set_pos(backgroundLabel, 0, 0);
lv_label_set_text_static(backgroundLabel, ""); lv_label_set_text_static(backgroundLabel, "");
taskUpdate = lv_task_create(lv_update_task, 500000, LV_TASK_PRIO_MID, this); taskUpdate = lv_task_create(lv_update_task, 5000, LV_TASK_PRIO_MID, this);
} }
Tile::~Tile() { Tile::~Tile() {
@ -124,9 +126,9 @@ bool Tile::Refresh() {
return running; return running;
} }
void Tile::OnObjectEvent(lv_obj_t* obj, lv_event_t event, uint32_t buttonId) { void Tile::OnValueChangedEvent(lv_obj_t* obj, uint32_t buttonId) {
if (event == LV_EVENT_VALUE_CHANGED) { if(obj != btnm1) return;
app->StartApp(apps[buttonId], DisplayApp::FullRefreshDirections::Up);
running = false; app->StartApp(apps[buttonId], DisplayApp::FullRefreshDirections::Up);
} running = false;
} }

View file

@ -32,7 +32,7 @@ namespace Pinetime {
bool Refresh() override; bool Refresh() override;
void UpdateScreen(); void UpdateScreen();
void OnObjectEvent(lv_obj_t* obj, lv_event_t event, uint32_t buttonId); void OnValueChangedEvent(lv_obj_t* obj, uint32_t buttonId);
private: private:
Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Battery& batteryController;

View file

@ -5,27 +5,44 @@
#include "Symbols.h" #include "Symbols.h"
#include "NotificationIcon.h" #include "NotificationIcon.h"
#include <cmath>
LV_IMG_DECLARE(bg_clock); LV_IMG_DECLARE(bg_clock);
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
#define HOUR_LENGTH 70 namespace {
#define MINUTE_LENGTH 90
#define SECOND_LENGTH 110
#define PI 3.14159265358979323846
// ## constexpr auto HOUR_LENGTH = 70;
static int16_t coordinate_x_relocate(int16_t x) { constexpr auto MINUTE_LENGTH = 90;
return ((x) + LV_HOR_RES / 2); constexpr auto SECOND_LENGTH = 110;
// sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
int16_t cosine(int16_t angle) {
return _lv_trigo_sin(angle + 90);
} }
// ## int16_t sine(int16_t angle) {
static int16_t coordinate_y_relocate(int16_t y) { return _lv_trigo_sin(angle);
return (((y) -LV_HOR_RES / 2) < 0) ? (0 - ((y) -LV_HOR_RES / 2)) : ((y) -LV_HOR_RES / 2);
} }
int16_t coordinate_x_relocate(int16_t x) {
return (x + LV_HOR_RES / 2);
}
int16_t coordinate_y_relocate(int16_t y) {
return std::abs(y - LV_HOR_RES / 2);
}
lv_point_t coordinate_relocate(int16_t radius, int16_t angle) {
return lv_point_t{
.x = coordinate_x_relocate(radius * static_cast<int32_t>(sine(angle)) / LV_TRIG_SCALE),
.y = coordinate_y_relocate(radius * static_cast<int32_t>(cosine(angle)) / LV_TRIG_SCALE)
};
}
} // namespace
WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app, WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app,
Controllers::DateTime& dateTimeController, Controllers::DateTime& dateTimeController,
Controllers::Battery& batteryController, Controllers::Battery& batteryController,
@ -123,15 +140,12 @@ void WatchFaceAnalog::UpdateClock() {
second = dateTimeController.Seconds(); second = dateTimeController.Seconds();
if (sMinute != minute) { if (sMinute != minute) {
minute_point[0].x = coordinate_x_relocate(30 * sin(minute * 6 * PI / 180)); auto const angle = minute * 6;
minute_point[0].y = coordinate_y_relocate(30 * cos(minute * 6 * PI / 180)); minute_point[0] = coordinate_relocate(30, angle);
minute_point[1].x = coordinate_x_relocate(MINUTE_LENGTH * sin(minute * 6 * PI / 180)); minute_point[1] = coordinate_relocate(MINUTE_LENGTH, angle);
minute_point[1].y = coordinate_y_relocate(MINUTE_LENGTH * cos(minute * 6 * PI / 180));
minute_point_trace[0].x = coordinate_x_relocate(5 * sin(minute * 6 * PI / 180)); minute_point_trace[0] = coordinate_relocate(5, angle);
minute_point_trace[0].y = coordinate_y_relocate(5 * cos(minute * 6 * PI / 180)); minute_point_trace[1] = coordinate_relocate(31, angle);
minute_point_trace[1].x = coordinate_x_relocate(31 * sin(minute * 6 * PI / 180));
minute_point_trace[1].y = coordinate_y_relocate(31 * cos(minute * 6 * PI / 180));
lv_line_set_points(minute_body, minute_point, 2); lv_line_set_points(minute_body, minute_point, 2);
lv_line_set_points(minute_body_trace, minute_point_trace, 2); lv_line_set_points(minute_body_trace, minute_point_trace, 2);
@ -140,15 +154,13 @@ void WatchFaceAnalog::UpdateClock() {
if (sHour != hour || sMinute != minute) { if (sHour != hour || sMinute != minute) {
sHour = hour; sHour = hour;
sMinute = minute; sMinute = minute;
hour_point[0].x = coordinate_x_relocate(30 * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180)); auto const angle = (hour * 30 + minute / 2);
hour_point[0].y = coordinate_y_relocate(30 * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
hour_point[1].x = coordinate_x_relocate(HOUR_LENGTH * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
hour_point[1].y = coordinate_y_relocate(HOUR_LENGTH * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
hour_point_trace[0].x = coordinate_x_relocate(5 * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180)); hour_point[0] = coordinate_relocate(30, angle);
hour_point_trace[0].y = coordinate_y_relocate(5 * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180)); hour_point[1] = coordinate_relocate(HOUR_LENGTH, angle);
hour_point_trace[1].x = coordinate_x_relocate(31 * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
hour_point_trace[1].y = coordinate_y_relocate(31 * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180)); hour_point_trace[0] = coordinate_relocate(5, angle);
hour_point_trace[1] = coordinate_relocate(31, angle);
lv_line_set_points(hour_body, hour_point, 2); lv_line_set_points(hour_body, hour_point, 2);
lv_line_set_points(hour_body_trace, hour_point_trace, 2); lv_line_set_points(hour_body_trace, hour_point_trace, 2);
@ -156,16 +168,15 @@ void WatchFaceAnalog::UpdateClock() {
if (sSecond != second) { if (sSecond != second) {
sSecond = second; sSecond = second;
second_point[0].x = coordinate_x_relocate(20 * sin((180 + second * 6) * PI / 180)); auto const angle = second * 6;
second_point[0].y = coordinate_y_relocate(20 * cos((180 + second * 6) * PI / 180));
second_point[1].x = coordinate_x_relocate(SECOND_LENGTH * sin(second * 6 * PI / 180)); second_point[0] = coordinate_relocate(-20, angle);
second_point[1].y = coordinate_y_relocate(SECOND_LENGTH * cos(second * 6 * PI / 180)); second_point[1] = coordinate_relocate(SECOND_LENGTH, angle);
lv_line_set_points(second_body, second_point, 2); lv_line_set_points(second_body, second_point, 2);
} }
} }
bool WatchFaceAnalog::Refresh() { bool WatchFaceAnalog::Refresh() {
batteryPercentRemaining = batteryController.PercentRemaining(); batteryPercentRemaining = batteryController.PercentRemaining();
if (batteryPercentRemaining.IsUpdated()) { if (batteryPercentRemaining.IsUpdated()) {
auto batteryPercent = batteryPercentRemaining.Get(); auto batteryPercent = batteryPercentRemaining.Get();

View file

@ -48,7 +48,7 @@ namespace Pinetime {
Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown; Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
uint8_t currentDay = 0; uint8_t currentDay = 0;
DirtyValue<float> batteryPercentRemaining {0}; DirtyValue<uint8_t> batteryPercentRemaining {0};
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime; DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
DirtyValue<bool> notificationState {false}; DirtyValue<bool> notificationState {false};

View file

@ -45,7 +45,7 @@ namespace Pinetime {
Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown; Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
uint8_t currentDay = 0; uint8_t currentDay = 0;
DirtyValue<int> batteryPercentRemaining {}; DirtyValue<uint8_t> batteryPercentRemaining {};
DirtyValue<bool> bleState {}; DirtyValue<bool> bleState {};
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {}; DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
DirtyValue<bool> motionSensorOk {}; DirtyValue<bool> motionSensorOk {};

View file

@ -27,7 +27,7 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
batteryController {batteryController}, batteryController {batteryController},
dateTimeController {dateTimeController}, dateTimeController {dateTimeController},
brightness {brightness}, brightness {brightness},
motorController{motorController}, motorController {motorController},
settingsController {settingsController} { settingsController {settingsController} {
// Time // Time
@ -110,7 +110,7 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app,
lv_obj_set_pos(backgroundLabel, 0, 0); lv_obj_set_pos(backgroundLabel, 0, 0);
lv_label_set_text_static(backgroundLabel, ""); lv_label_set_text_static(backgroundLabel, "");
taskUpdate = lv_task_create(lv_update_task, 500000, LV_TASK_PRIO_MID, this); taskUpdate = lv_task_create(lv_update_task, 5000, LV_TASK_PRIO_MID, this);
} }
QuickSettings::~QuickSettings() { QuickSettings::~QuickSettings() {
@ -154,17 +154,6 @@ void QuickSettings::OnButtonEvent(lv_obj_t* object, lv_event_t event) {
} }
} }
bool QuickSettings::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
switch (event) {
case Pinetime::Applications::TouchEvents::SwipeLeft:
running = false;
return false;
default:
return true;
}
}
bool QuickSettings::Refresh() { bool QuickSettings::Refresh() {
return running; return running;
} }

View file

@ -29,7 +29,6 @@ namespace Pinetime {
bool Refresh() override; bool Refresh() override;
bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override;
void OnButtonEvent(lv_obj_t* object, lv_event_t event); void OnButtonEvent(lv_obj_t* object, lv_event_t event);
void UpdateScreen(); void UpdateScreen();

View file

@ -16,7 +16,7 @@ namespace {
SettingWakeUp::SettingWakeUp(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) SettingWakeUp::SettingWakeUp(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: Screen(app), settingsController {settingsController} { : Screen(app), settingsController {settingsController} {
ignoringEvents = false;
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
@ -36,24 +36,16 @@ SettingWakeUp::SettingWakeUp(Pinetime::Applications::DisplayApp* app, Pinetime::
lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr); lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_label_set_text_static(icon, Symbols::clock); lv_label_set_text_static(icon, Symbols::eye);
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
optionsTotal = 0; optionsTotal = 0;
cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr); cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text_static(cbOption[optionsTotal], " None");
cbOption[optionsTotal]->user_data = this;
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
if (settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::None) {
lv_checkbox_set_checked(cbOption[optionsTotal], true);
}
optionsTotal++;
cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text_static(cbOption[optionsTotal], " Single Tap"); lv_checkbox_set_text_static(cbOption[optionsTotal], " Single Tap");
cbOption[optionsTotal]->user_data = this; cbOption[optionsTotal]->user_data = this;
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler); lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
if (settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::SingleTap) { if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)) {
lv_checkbox_set_checked(cbOption[optionsTotal], true); lv_checkbox_set_checked(cbOption[optionsTotal], true);
} }
optionsTotal++; optionsTotal++;
@ -61,7 +53,7 @@ SettingWakeUp::SettingWakeUp(Pinetime::Applications::DisplayApp* app, Pinetime::
lv_checkbox_set_text_static(cbOption[optionsTotal], " Double Tap"); lv_checkbox_set_text_static(cbOption[optionsTotal], " Double Tap");
cbOption[optionsTotal]->user_data = this; cbOption[optionsTotal]->user_data = this;
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler); lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
if (settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::DoubleTap) { if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
lv_checkbox_set_checked(cbOption[optionsTotal], true); lv_checkbox_set_checked(cbOption[optionsTotal], true);
} }
optionsTotal++; optionsTotal++;
@ -69,7 +61,7 @@ SettingWakeUp::SettingWakeUp(Pinetime::Applications::DisplayApp* app, Pinetime::
lv_checkbox_set_text_static(cbOption[optionsTotal], " Raise Wrist"); lv_checkbox_set_text_static(cbOption[optionsTotal], " Raise Wrist");
cbOption[optionsTotal]->user_data = this; cbOption[optionsTotal]->user_data = this;
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler); lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
if (settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) { if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist)) {
lv_checkbox_set_checked(cbOption[optionsTotal], true); lv_checkbox_set_checked(cbOption[optionsTotal], true);
} }
optionsTotal++; optionsTotal++;
@ -85,27 +77,31 @@ bool SettingWakeUp::Refresh() {
} }
void SettingWakeUp::UpdateSelected(lv_obj_t* object, lv_event_t event) { void SettingWakeUp::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) { using WakeUpMode = Pinetime::Controllers::Settings::WakeUpMode;
for (int i = 0; i < optionsTotal; i++) { if (event == LV_EVENT_VALUE_CHANGED && !ignoringEvents) {
if (object == cbOption[i]) { ignoringEvents = true;
lv_checkbox_set_checked(cbOption[i], true);
if (i == 0) { // Find the index of the checkbox that triggered the event
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::None); int index = 0;
}; for (; index < optionsTotal; ++index) {
if (i == 1) { if (cbOption[index] == object) {
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::SingleTap); break;
};
if (i == 2) {
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap);
};
if (i == 3) {
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist);
};
} else {
lv_checkbox_set_checked(cbOption[i], false);
} }
} }
// Toggle needed wakeup mode
auto mode = static_cast<WakeUpMode>(index);
auto currentState = settingsController.isWakeUpModeOn(mode);
settingsController.setWakeUpMode(mode, !currentState);
// Update checkbox according to current wakeup modes.
// This is needed because we can have extra logic when setting or unsetting wakeup modes,
// for example, when setting SingleTap, DoubleTap is unset and vice versa.
auto modes = settingsController.getWakeUpModes();
for (int i = 0; i < optionsTotal; ++i) {
lv_checkbox_set_checked(cbOption[i], modes[i]);
}
ignoringEvents = false;
} }
} }

View file

@ -22,6 +22,11 @@ namespace Pinetime {
Controllers::Settings& settingsController; Controllers::Settings& settingsController;
uint8_t optionsTotal; uint8_t optionsTotal;
lv_obj_t* cbOption[4]; lv_obj_t* cbOption[4];
// When UpdateSelected is called, it uses lv_checkbox_set_checked,
// which can cause extra events to be fired,
// which might trigger UpdateSelected again, causing a loop.
// This variable is used as a mutex to prevent that.
bool ignoringEvents;
}; };
} }
} }

View file

@ -36,7 +36,7 @@ SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, Pine
lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr); lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_label_set_text_static(icon, Symbols::clock); lv_label_set_text_static(icon, Symbols::home);
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);

View file

@ -44,9 +44,9 @@ std::unique_ptr<Screen> Settings::CreateScreen1() {
std::array<Screens::List::Applications, 4> applications {{ std::array<Screens::List::Applications, 4> applications {{
{Symbols::sun, "Display", Apps::SettingDisplay}, {Symbols::sun, "Display", Apps::SettingDisplay},
{Symbols::clock, "Wake Up", Apps::SettingWakeUp}, {Symbols::eye, "Wake Up", Apps::SettingWakeUp},
{Symbols::clock, "Time format", Apps::SettingTimeFormat}, {Symbols::clock, "Time format", Apps::SettingTimeFormat},
{Symbols::clock, "Watch face", Apps::SettingWatchFace}, {Symbols::home, "Watch face", Apps::SettingWatchFace},
}}; }};
return std::make_unique<Screens::List>(0, 3, app, settingsController, applications); return std::make_unique<Screens::List>(0, 3, app, settingsController, applications);

1
src/libs/littlefs Submodule

@ -0,0 +1 @@
Subproject commit ead50807f1ca3fdf2da00b77a0ce02651ded2d13

View file

@ -42,7 +42,7 @@
/* Default display refresh period. /* Default display refresh period.
* Can be changed in the display driver (`lv_disp_drv_t`).*/ * Can be changed in the display driver (`lv_disp_drv_t`).*/
#define LV_DISP_DEF_REFR_PERIOD 30 /*[ms]*/ #define LV_DISP_DEF_REFR_PERIOD 20 /*[ms]*/
/* Dot Per Inch: used to initialize default sizes. /* Dot Per Inch: used to initialize default sizes.
* E.g. a button with width = LV_DPI / 2 -> half inch wide * E.g. a button with width = LV_DPI / 2 -> half inch wide
@ -112,7 +112,7 @@ typedef int16_t lv_coord_t;
* Can be changed in the Input device driver (`lv_indev_drv_t`)*/ * Can be changed in the Input device driver (`lv_indev_drv_t`)*/
/* Input device read period in milliseconds */ /* Input device read period in milliseconds */
#define LV_INDEV_DEF_READ_PERIOD 30 #define LV_INDEV_DEF_READ_PERIOD 20
/* Drag threshold in pixels */ /* Drag threshold in pixels */
#define LV_INDEV_DEF_DRAG_LIMIT 10 #define LV_INDEV_DEF_DRAG_LIMIT 10
@ -128,7 +128,6 @@ typedef int16_t lv_coord_t;
* Time between `LV_EVENT_LONG_PRESSED_REPEAT */ * Time between `LV_EVENT_LONG_PRESSED_REPEAT */
#define LV_INDEV_DEF_LONG_PRESS_REP_TIME 100 #define LV_INDEV_DEF_LONG_PRESS_REP_TIME 100
/* Gesture threshold in pixels */ /* Gesture threshold in pixels */
#define LV_INDEV_DEF_GESTURE_LIMIT 50 #define LV_INDEV_DEF_GESTURE_LIMIT 50
@ -204,7 +203,7 @@ e.g. "stm32f769xx.h" or "stm32f429xx.h" */
/* 1: Enable file system (might be required for images */ /* 1: Enable file system (might be required for images */
// TODO: Enable FS // TODO: Enable FS
#define LV_USE_FILESYSTEM 0 #define LV_USE_FILESYSTEM 1
#if LV_USE_FILESYSTEM #if LV_USE_FILESYSTEM
/*Declare the type of the user data of file system drivers (can be e.g. `void *`, `int`, `struct`)*/ /*Declare the type of the user data of file system drivers (can be e.g. `void *`, `int`, `struct`)*/
typedef void * lv_fs_drv_user_data_t; typedef void * lv_fs_drv_user_data_t;
@ -236,7 +235,7 @@ typedef void * lv_fs_drv_user_data_t;
* With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. * With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
* However the opened images might consume additional RAM. * However the opened images might consume additional RAM.
* LV_IMG_CACHE_DEF_SIZE must be >= 1 */ * LV_IMG_CACHE_DEF_SIZE must be >= 1 */
#define LV_IMG_CACHE_DEF_SIZE 1 #define LV_IMG_CACHE_DEF_SIZE 6
/*Declare the type of the user data of image decoder (can be e.g. `void *`, `int`, `struct`)*/ /*Declare the type of the user data of image decoder (can be e.g. `void *`, `int`, `struct`)*/
typedef void* lv_img_decoder_user_data_t; typedef void* lv_img_decoder_user_data_t;
@ -293,10 +292,11 @@ typedef void* lv_img_decoder_user_data_t;
/* 1: use a custom tick source. /* 1: use a custom tick source.
* It removes the need to manually update the tick with `lv_tick_inc`) */ * It removes the need to manually update the tick with `lv_tick_inc`) */
#define LV_TICK_CUSTOM 0 #define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM == 1 #if LV_TICK_CUSTOM == 1
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/ #define LV_TICK_CUSTOM_INCLUDE "FreeRTOS.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/ uint32_t xTaskGetTickCount(); /*Forward declare to avoid compiler warning*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (xTaskGetTickCount()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/ #endif /*LV_TICK_CUSTOM*/
typedef void* lv_disp_drv_user_data_t; /*Type of user data in the display driver*/ typedef void* lv_disp_drv_user_data_t; /*Type of user data in the display driver*/

View file

@ -35,6 +35,7 @@
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "components/datetime/DateTimeController.h" #include "components/datetime/DateTimeController.h"
#include "components/heartrate/HeartRateController.h" #include "components/heartrate/HeartRateController.h"
#include "components/fs/FS.h"
#include "drivers/Spi.h" #include "drivers/Spi.h"
#include "drivers/SpiMaster.h" #include "drivers/SpiMaster.h"
#include "drivers/SpiNorFlash.h" #include "drivers/SpiNorFlash.h"
@ -108,10 +109,6 @@ void ble_manager_set_ble_disconnection_callback(void (*disconnection)());
static constexpr uint8_t pinTouchIrq = 28; static constexpr uint8_t pinTouchIrq = 28;
static constexpr uint8_t pinPowerPresentIrq = 19; static constexpr uint8_t pinPowerPresentIrq = 19;
Pinetime::Controllers::Settings settingsController {spiNorFlash};
Pinetime::Controllers::MotorController motorController {settingsController};
Pinetime::Controllers::HeartRateController heartRateController; Pinetime::Controllers::HeartRateController heartRateController;
Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController); Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController);
@ -122,6 +119,11 @@ Pinetime::Controllers::NotificationManager notificationManager;
Pinetime::Controllers::MotionController motionController; Pinetime::Controllers::MotionController motionController;
Pinetime::Controllers::TimerController timerController; Pinetime::Controllers::TimerController timerController;
Pinetime::Controllers::FS fs {spiNorFlash};
Pinetime::Controllers::Settings settingsController {fs};
Pinetime::Controllers::MotorController motorController {settingsController};
Pinetime::Applications::DisplayApp displayApp(lcd, Pinetime::Applications::DisplayApp displayApp(lcd,
lvgl, lvgl,
touchPanel, touchPanel,
@ -155,7 +157,8 @@ Pinetime::System::SystemTask systemTask(spi,
settingsController, settingsController,
heartRateController, heartRateController,
displayApp, displayApp,
heartRateApp); heartRateApp,
fs);
void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
if (pin == pinTouchIrq) { if (pin == pinTouchIrq) {
@ -175,13 +178,6 @@ void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
} }
extern "C" {
void vApplicationIdleHook(void) {
if (!isFactory)
lv_tick_inc(1);
}
}
void DebounceTimerChargeCallback(TimerHandle_t xTimer) { void DebounceTimerChargeCallback(TimerHandle_t xTimer) {
xTimerStop(xTimer, 0); xTimerStop(xTimer, 0);
systemTask.PushMessage(Pinetime::System::Messages::OnChargingEvent); systemTask.PushMessage(Pinetime::System::Messages::OnChargingEvent);

View file

@ -33,6 +33,13 @@ namespace {
} }
} }
void DimTimerCallback(TimerHandle_t xTimer) {
NRF_LOG_INFO("DimTimerCallback");
auto sysTask = static_cast<SystemTask*>(pvTimerGetTimerID(xTimer));
sysTask->OnDim();
}
void IdleTimerCallback(TimerHandle_t xTimer) { void IdleTimerCallback(TimerHandle_t xTimer) {
NRF_LOG_INFO("IdleTimerCallback"); NRF_LOG_INFO("IdleTimerCallback");
@ -59,7 +66,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
Controllers::Settings& settingsController, Controllers::Settings& settingsController,
Pinetime::Controllers::HeartRateController& heartRateController, Pinetime::Controllers::HeartRateController& heartRateController,
Pinetime::Applications::DisplayApp& displayApp, Pinetime::Applications::DisplayApp& displayApp,
Pinetime::Applications::HeartRateTask& heartRateApp) Pinetime::Applications::HeartRateTask& heartRateApp,
Pinetime::Controllers::FS& fs)
: spi {spi}, : spi {spi},
lcd {lcd}, lcd {lcd},
spiNorFlash {spiNorFlash}, spiNorFlash {spiNorFlash},
@ -77,10 +85,11 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
motionSensor {motionSensor}, motionSensor {motionSensor},
settingsController {settingsController}, settingsController {settingsController},
heartRateController{heartRateController}, heartRateController{heartRateController},
nimbleController(*this, bleController, dateTimeController, notificationManager, batteryController, spiNorFlash, heartRateController),
motionController{motionController}, motionController{motionController},
displayApp{displayApp}, displayApp{displayApp},
heartRateApp(heartRateApp) { heartRateApp(heartRateApp),
fs{fs},
nimbleController(*this, bleController, dateTimeController, notificationManager, batteryController, spiNorFlash, heartRateController) {
} }
@ -107,9 +116,11 @@ void SystemTask::Work() {
spi.Init(); spi.Init();
spiNorFlash.Init(); spiNorFlash.Init();
spiNorFlash.Wakeup(); spiNorFlash.Wakeup();
fs.Init();
nimbleController.Init(); nimbleController.Init();
nimbleController.StartAdvertising(); nimbleController.StartAdvertising();
brightnessController.Init();
lcd.Init(); lcd.Init();
twiMaster.Init(); twiMaster.Init();
@ -174,8 +185,9 @@ void SystemTask::Work() {
nrf_gpio_cfg_sense_input(pinPowerPresentIrq, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); nrf_gpio_cfg_sense_input(pinPowerPresentIrq, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH);
} }
idleTimer = xTimerCreate("idleTimer", pdMS_TO_TICKS(settingsController.GetScreenTimeOut()), pdFALSE, this, IdleTimerCallback); idleTimer = xTimerCreate("idleTimer", pdMS_TO_TICKS(2000), pdFALSE, this, IdleTimerCallback);
xTimerStart(idleTimer, 0); dimTimer = xTimerCreate("dimTimer", pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000), pdFALSE, this, DimTimerCallback);
xTimerStart(dimTimer, 0);
// Suppress endless loop diagnostic // Suppress endless loop diagnostic
#pragma clang diagnostic push #pragma clang diagnostic push
@ -193,25 +205,29 @@ void SystemTask::Work() {
Messages message = static_cast<Messages>(msg); Messages message = static_cast<Messages>(msg);
switch (message) { switch (message) {
case Messages::EnableSleeping: case Messages::EnableSleeping:
doNotGoToSleep = false; // Make sure that exiting an app doesn't enable sleeping,
// if the exiting was caused by a firmware update
if (!bleController.IsFirmwareUpdating()) {
doNotGoToSleep = false;
}
break; break;
case Messages::DisableSleeping: case Messages::DisableSleeping:
doNotGoToSleep = true; doNotGoToSleep = true;
break; break;
case Messages::UpdateTimeOut: case Messages::UpdateTimeOut:
xTimerChangePeriod(idleTimer, pdMS_TO_TICKS(settingsController.GetScreenTimeOut()), 0); xTimerChangePeriod(dimTimer, pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000), 0);
break; break;
case Messages::GoToRunning: case Messages::GoToRunning:
spi.Wakeup(); spi.Wakeup();
twiMaster.Wakeup(); twiMaster.Wakeup();
// Double Tap needs the touch screen to be in normal mode // Double Tap needs the touch screen to be in normal mode
if (settingsController.getWakeUpMode() != Pinetime::Controllers::Settings::WakeUpMode::DoubleTap) { if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
touchPanel.Wakeup(); touchPanel.Wakeup();
} }
nimbleController.StartAdvertising(); nimbleController.StartAdvertising();
xTimerStart(idleTimer, 0); xTimerStart(dimTimer, 0);
spiNorFlash.Wakeup(); spiNorFlash.Wakeup();
lcd.Wakeup(); lcd.Wakeup();
@ -221,15 +237,16 @@ void SystemTask::Work() {
isSleeping = false; isSleeping = false;
isWakingUp = false; isWakingUp = false;
isDimmed = false;
break; break;
case Messages::TouchWakeUp: { case Messages::TouchWakeUp: {
twiMaster.Wakeup(); twiMaster.Wakeup();
auto touchInfo = touchPanel.GetTouchInfo(); auto touchInfo = touchPanel.GetTouchInfo();
twiMaster.Sleep(); twiMaster.Sleep();
if (touchInfo.isTouch and ((touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::DoubleTap and if (touchInfo.isTouch and ((touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::DoubleTap and
settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::DoubleTap) or settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) or
(touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::SingleTap and (touchInfo.gesture == Pinetime::Drivers::Cst816S::Gestures::SingleTap and
settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::SingleTap))) { settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)))) {
GoToRunning(); GoToRunning();
} }
} break; } break;
@ -237,6 +254,7 @@ void SystemTask::Work() {
isGoingToSleep = true; isGoingToSleep = true;
NRF_LOG_INFO("[systemtask] Going to sleep"); NRF_LOG_INFO("[systemtask] Going to sleep");
xTimerStop(idleTimer, 0); xTimerStop(idleTimer, 0);
xTimerStop(dimTimer, 0);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep); displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep); heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
break; break;
@ -270,16 +288,19 @@ void SystemTask::Work() {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted); displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
break; break;
case Messages::BleFirmwareUpdateFinished: case Messages::BleFirmwareUpdateFinished:
doNotGoToSleep = false; if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) {
xTimerStart(idleTimer, 0);
if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated)
NVIC_SystemReset(); NVIC_SystemReset();
}
doNotGoToSleep = false;
xTimerStart(dimTimer, 0);
break; break;
case Messages::OnTouchEvent: case Messages::OnTouchEvent:
ReloadIdleTimer(); ReloadIdleTimer();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
break; break;
case Messages::OnButtonEvent: case Messages::OnButtonEvent:
ReloadIdleTimer(); ReloadIdleTimer();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ButtonPushed);
break; break;
case Messages::OnDisplayTaskSleeping: case Messages::OnDisplayTaskSleeping:
if (BootloaderVersion::IsValid()) { if (BootloaderVersion::IsValid()) {
@ -291,7 +312,7 @@ void SystemTask::Work() {
spi.Sleep(); spi.Sleep();
// Double Tap needs the touch screen to be in normal mode // Double Tap needs the touch screen to be in normal mode
if (settingsController.getWakeUpMode() != Pinetime::Controllers::Settings::WakeUpMode::DoubleTap) { if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
touchPanel.Sleep(); touchPanel.Sleep();
} }
twiMaster.Sleep(); twiMaster.Sleep();
@ -325,6 +346,11 @@ void SystemTask::Work() {
} }
} }
if (xTaskGetTickCount() - batteryNotificationTick > batteryNotificationPeriod) {
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
batteryNotificationTick = xTaskGetTickCount();
}
monitor.Process(); monitor.Process();
uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG); uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG);
dateTimeController.UpdateTime(systick_counter); dateTimeController.UpdateTime(systick_counter);
@ -338,7 +364,7 @@ void SystemTask::UpdateMotion() {
if (isGoingToSleep or isWakingUp) if (isGoingToSleep or isWakingUp)
return; return;
if (isSleeping && settingsController.getWakeUpMode() != Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) if (isSleeping && !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist))
return; return;
if (isSleeping) if (isSleeping)
@ -366,7 +392,6 @@ void SystemTask::OnButtonPushed() {
if (!isSleeping) { if (!isSleeping) {
NRF_LOG_INFO("[systemtask] Button pushed"); NRF_LOG_INFO("[systemtask] Button pushed");
PushMessage(Messages::OnButtonEvent); PushMessage(Messages::OnButtonEvent);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ButtonPushed);
} else { } else {
if (!isWakingUp) { if (!isWakingUp) {
NRF_LOG_INFO("[systemtask] Button pushed, waking up"); NRF_LOG_INFO("[systemtask] Button pushed, waking up");
@ -387,12 +412,11 @@ void SystemTask::OnTouchEvent() {
return; return;
if (!isSleeping) { if (!isSleeping) {
PushMessage(Messages::OnTouchEvent); PushMessage(Messages::OnTouchEvent);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
} else if (!isWakingUp) { } else if (!isWakingUp) {
if (settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::None or if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or
settingsController.getWakeUpMode() == Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
return; PushMessage(Messages::TouchWakeUp);
PushMessage(Messages::TouchWakeUp); }
} }
} }
@ -415,6 +439,15 @@ void SystemTask::PushMessage(System::Messages msg) {
} }
} }
void SystemTask::OnDim() {
if (doNotGoToSleep)
return;
NRF_LOG_INFO("Dim timeout -> Dim screen")
displayApp.PushMessage(Pinetime::Applications::Display::Messages::DimScreen);
xTimerStart(idleTimer, 0);
isDimmed = true;
}
void SystemTask::OnIdle() { void SystemTask::OnIdle() {
if (doNotGoToSleep) if (doNotGoToSleep)
return; return;
@ -422,8 +455,13 @@ void SystemTask::OnIdle() {
PushMessage(Messages::GoToSleep); PushMessage(Messages::GoToSleep);
} }
void SystemTask::ReloadIdleTimer() const { void SystemTask::ReloadIdleTimer() {
if (isSleeping || isGoingToSleep) if (isSleeping || isGoingToSleep)
return; return;
xTimerReset(idleTimer, 0); if (isDimmed) {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
isDimmed = false;
}
xTimerReset(dimTimer, 0);
xTimerStop(idleTimer, 0);
} }

View file

@ -16,6 +16,8 @@
#include "components/ble/NotificationManager.h" #include "components/ble/NotificationManager.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "components/timer/TimerController.h" #include "components/timer/TimerController.h"
#include "components/fs/FS.h"
#ifdef PINETIME_IS_RECOVERY #ifdef PINETIME_IS_RECOVERY
#include "displayapp/DisplayAppRecovery.h" #include "displayapp/DisplayAppRecovery.h"
#include "displayapp/DummyLittleVgl.h" #include "displayapp/DummyLittleVgl.h"
@ -59,7 +61,8 @@ namespace Pinetime {
Controllers::Settings& settingsController, Controllers::Settings& settingsController,
Pinetime::Controllers::HeartRateController& heartRateController, Pinetime::Controllers::HeartRateController& heartRateController,
Pinetime::Applications::DisplayApp& displayApp, Pinetime::Applications::DisplayApp& displayApp,
Pinetime::Applications::HeartRateTask& heartRateApp); Pinetime::Applications::HeartRateTask& heartRateApp,
Pinetime::Controllers::FS& fs);
void Start(); void Start();
void PushMessage(Messages msg); void PushMessage(Messages msg);
@ -68,6 +71,7 @@ namespace Pinetime {
void OnTouchEvent(); void OnTouchEvent();
void OnIdle(); void OnIdle();
void OnDim();
Pinetime::Controllers::NimbleController& nimble() { Pinetime::Controllers::NimbleController& nimble() {
return nimbleController; return nimbleController;
@ -96,6 +100,7 @@ namespace Pinetime {
std::atomic<bool> isSleeping {false}; std::atomic<bool> isSleeping {false};
std::atomic<bool> isGoingToSleep {false}; std::atomic<bool> isGoingToSleep {false};
std::atomic<bool> isWakingUp {false}; std::atomic<bool> isWakingUp {false};
std::atomic<bool> isDimmed {false};
Pinetime::Drivers::Watchdog& watchdog; Pinetime::Drivers::Watchdog& watchdog;
Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Controllers::MotorController& motorController; Pinetime::Controllers::MotorController& motorController;
@ -103,13 +108,12 @@ namespace Pinetime {
Pinetime::Drivers::Bma421& motionSensor; Pinetime::Drivers::Bma421& motionSensor;
Pinetime::Controllers::Settings& settingsController; Pinetime::Controllers::Settings& settingsController;
Pinetime::Controllers::HeartRateController& heartRateController; Pinetime::Controllers::HeartRateController& heartRateController;
Pinetime::Controllers::NimbleController nimbleController;
Controllers::BrightnessController brightnessController;
Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::MotionController& motionController;
Pinetime::Applications::DisplayApp& displayApp; Pinetime::Applications::DisplayApp& displayApp;
Pinetime::Applications::HeartRateTask& heartRateApp; Pinetime::Applications::HeartRateTask& heartRateApp;
Pinetime::Controllers::FS& fs;
Pinetime::Controllers::NimbleController nimbleController;
static constexpr uint8_t pinSpiSck = 2; static constexpr uint8_t pinSpiSck = 2;
static constexpr uint8_t pinSpiMosi = 3; static constexpr uint8_t pinSpiMosi = 3;
@ -122,15 +126,18 @@ namespace Pinetime {
static void Process(void* instance); static void Process(void* instance);
void Work(); void Work();
void ReloadIdleTimer() const; void ReloadIdleTimer();
bool isBleDiscoveryTimerRunning = false; bool isBleDiscoveryTimerRunning = false;
uint8_t bleDiscoveryTimer = 0; uint8_t bleDiscoveryTimer = 0;
TimerHandle_t dimTimer;
TimerHandle_t idleTimer; TimerHandle_t idleTimer;
bool doNotGoToSleep = false; bool doNotGoToSleep = false;
void GoToRunning(); void GoToRunning();
void UpdateMotion(); void UpdateMotion();
bool stepCounterMustBeReset = false; bool stepCounterMustBeReset = false;
static constexpr TickType_t batteryNotificationPeriod = 1000 * 60 * 10; // 1 tick ~= 1ms. 1ms * 60 * 10 = 10 minutes
TickType_t batteryNotificationTick = 0;
#if configUSE_TRACE_FACILITY == 1 #if configUSE_TRACE_FACILITY == 1
SystemMonitor<FreeRtosMonitor> monitor; SystemMonitor<FreeRtosMonitor> monitor;