Merge branch 'develop'
# Conflicts: # doc/buildAndProgram.md
This commit is contained in:
commit
f6d0ec49e6
@ -1,6 +1,7 @@
|
||||
Checks: '*,
|
||||
-altera-unroll-loops,
|
||||
-llvmlibc-callee-namespace,
|
||||
-llvmlibc-restrict-system-libc-headers,
|
||||
-llvm-header-guard,
|
||||
-llvm-namespace-comment,
|
||||
-google-build-using-namespace,
|
||||
@ -9,6 +10,7 @@ Checks: '*,
|
||||
-fuchsia-statically-constructed-objects,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-cstyle-cast,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
|
@ -55,7 +55,7 @@ RUN unzip -q /tmp/nRF5_SDK_15.3.0_59ac345 -d /opt
|
||||
RUN rm /tmp/nRF5_SDK_15.3.0_59ac345
|
||||
# McuBoot
|
||||
# RUN bash -c "source /opt/build.sh; GetMcuBoot;"
|
||||
RUN git clone https://github.com/JuulLabs-OSS/mcuboot.git
|
||||
RUN git clone https://github.com/mcu-tools/mcuboot.git
|
||||
RUN pip3 install -r ./mcuboot/scripts/requirements.txt
|
||||
|
||||
RUN adduser infinitime
|
||||
|
@ -43,7 +43,7 @@ GetGcc() {
|
||||
}
|
||||
|
||||
GetMcuBoot() {
|
||||
git clone https://github.com/JuulLabs-OSS/mcuboot.git "$TOOLS_DIR/mcuboot"
|
||||
git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot"
|
||||
pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt"
|
||||
}
|
||||
|
||||
|
58
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thanks for taking the time to fill out this bug report!**
|
||||
*Please, before opening a bug report, check if similar issues already exist. In that case, use those issues to provide your feedback instead.*
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
options:
|
||||
- label: I searched for similar bug reports and found none was relevant.
|
||||
required: true
|
||||
- type: input
|
||||
id: desc-brief
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: A one-line description of the bug.
|
||||
placeholder: "Ex. I woke up as a Kafkian insect this morning."
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: desc-expected
|
||||
attributes:
|
||||
label: What should happen instead?
|
||||
description: The behaviour you were expecting to see.
|
||||
placeholder: "Ex. I was expecting to wake up as a human."
|
||||
- type: textarea
|
||||
id: desc-steps
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: "How do you trigger this bug? Please walk us through it step by step."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: desc-long
|
||||
attributes:
|
||||
label: More details?
|
||||
description: Give us more details about the bug and any personal attempts you made to fix it.
|
||||
placeholder: Tell us more!
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: |
|
||||
What [version of the firmware](https://github.com/JF002/InfiniTime/blob/develop/doc/gettingStarted/gettingStarted-1.0.md#how-to-check-the-version-of-infinitime-and-the-bootloader) are you running?
|
||||
If you are running an older version, please consider [updating to the latest firmware](https://github.com/JF002/InfiniTime/blob/develop/doc/gettingStarted/gettingStarted-1.0.md#how-to-update-your-pinetime).
|
||||
If you are running directly from git, specify the branch or the commit hash directly.
|
||||
placeholder: "Ex. v1.6.0 or develop or fc922b60"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: companion-app
|
||||
attributes:
|
||||
label: Companion app
|
||||
description: Which companion app are you using (if relevant)?
|
||||
placeholder: "Ex. Gadgetbridge v0.60.0, Siglo v0.9.4"
|
40
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
Normal file
40
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Feature Request
|
||||
description: File a feature request
|
||||
labels: ["feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thanks for taking the time to fill out this feature request!**
|
||||
*Please, before opening a feature request, check if similar issues already exist. In that case, use those issues to provide your feedback instead.*
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
options:
|
||||
- label: I searched for similar feature request and found none was relevant.
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Note:** keep in mind that, while InfiniTime is usable, it is still under heavy development and as such it is continuously evolving.
|
||||
Some features you want to see implemented might not be compatible with the current state of the project, or might not even be suitable to include *in the firmware* of the watch.
|
||||
- type: input
|
||||
id: desc-brief
|
||||
attributes:
|
||||
label: Pitch us your idea!
|
||||
description: A one-line elevator pitch of the feature you'd like to see implemented.
|
||||
placeholder: "Ex. My dog wants InfiniTime on its smart collar."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: desc-long
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Give us a detailed description of the feature you are proposing. Mockups or a description of the possible use cases are highly appreciated.
|
||||
Tell us why this should be included in the firmware.
|
||||
placeholder: "Ex. Here is a drawing of my dog wearing an InfiniTime collar and smiling."
|
||||
- type: markdown
|
||||
id: companion-app
|
||||
attributes:
|
||||
value: |
|
||||
If this requires features missing from other software (for example a companion app), please take care of opening any relevant feature request over there as well.
|
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@ -153,6 +153,14 @@ jobs:
|
||||
name: pinetime-app.out
|
||||
path: build/src/pinetime-app*.out
|
||||
|
||||
#########################################################################################
|
||||
# Make but don't Upload the Recovery Firmware to be sure it builds correctly
|
||||
|
||||
- name: Make pinetime-recovery
|
||||
run: |
|
||||
cd build
|
||||
make pinetime-recovery
|
||||
|
||||
#########################################################################################
|
||||
# Finish
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(pinetime VERSION 1.6.0 LANGUAGES C CXX ASM)
|
||||
project(pinetime VERSION 1.7.0 LANGUAGES C CXX ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# InfiniTime
|
||||
|
||||
[](https://github.com/JF002/InfiniTime/actions)
|
||||
[](https://github.com/InfiniTimeOrg/InfiniTime/actions)
|
||||
|
||||

|
||||
|
||||
@ -65,14 +65,15 @@ As of now, here is the list of achievements of this project:
|
||||
* [Amazfish](https://openrepos.net/content/piggz/amazfish) (on SailfishOS and Linux)
|
||||
* [Siglo](https://github.com/alexr4535/siglo) (on Linux)
|
||||
* **[Experimental]** [WebBLEWatch](https://hubmartin.github.io/WebBLEWatch/) Synchronize time directly from your web browser. [video](https://youtu.be/IakiuhVDdrY)
|
||||
* **[Experimental]** [Infini-iOS](https://github.com/xan-m/Infini-iOS) (on iOS)
|
||||
* **[Experimental]** [InfiniLink](https://github.com/xan-m/InfiniLink) (on iOS)
|
||||
- OTA (Over-the-air) update via BLE
|
||||
- [Bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader) based on [MCUBoot](https://juullabs-oss.github.io/mcuboot/)
|
||||
- [Bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader) based on [MCUBoot](https://www.mcuboot.com)
|
||||
|
||||
## Documentation
|
||||
|
||||
### Develop
|
||||
|
||||
- [Rough structure of the code](doc/code/Intro.md)
|
||||
- [How to implement an application](doc/code/Apps.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)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# About this bootloader
|
||||
The [bootloader](https://github.com/lupyuen/pinetime-rust-mynewt/tree/master/libs/pinetime_boot/src) is mostly developed by [Lup Yuen](https://github.com/lupyuen). It is based on [MCUBoot](https://juullabs-oss.github.io/mcuboot/) and [Mynewt](https://mynewt.apache.org/).
|
||||
The [bootloader](https://github.com/lupyuen/pinetime-rust-mynewt/tree/master/libs/pinetime_boot/src) is mostly developed by [Lup Yuen](https://github.com/lupyuen). It is based on [MCUBoot](https://www.mcuboot.com) and [Mynewt](https://mynewt.apache.org/).
|
||||
|
||||
The goal of this project is to provide a common bootloader for multiple (all?) Pinetime projects. It allows to upgrade the current bootloader and even replace the current application by another one that supports the same bootloader.
|
||||
|
||||
@ -86,7 +86,7 @@ make pinetime-mcuboot-app
|
||||
|
||||
The binary is located in *<build directory>/src/pinetime-mcuboot-app.bin*.
|
||||
|
||||
It must me converted into a MCUBoot image using *imgtool.py* from [MCUBoot](https://github.com/JuulLabs-OSS/mcuboot/tree/master/scripts). Simply checkout the project and run the script <mcuboot root>/scripts/imgtool.py with the following command line:
|
||||
It must me converted into a MCUBoot image using *imgtool.py* from [MCUBoot](https://github.com/mcu-tools/mcuboot/tree/master/scripts). Simply checkout the project and run the script <mcuboot root>/scripts/imgtool.py with the following command line:
|
||||
|
||||
`
|
||||
imgtool.py create --align 4 --version 1.0.0 --header-size 32 --slot-size 475136 --pad-header <build directory>/src/pinetime-mcuboot-app.bin image.bin
|
||||
|
@ -6,7 +6,7 @@ The PineTime is equipped with the following memories:
|
||||
|
||||
Note that the NRF52832 cannot execute code stored in the external flash : we need to store the whole firmware in the internal flash memory, and use the external one to store graphicals assets, fonts...
|
||||
|
||||
This document describes how the RAM and Flash memories are used in InfiniTime and how to analyze and monitor their usage. It was written in the context of [this memory analysis effort](https://github.com/JF002/InfiniTime/issues/313).
|
||||
This document describes how the RAM and Flash memories are used in InfiniTime and how to analyze and monitor their usage. It was written in the context of [this memory analysis effort](https://github.com/InfiniTimeOrg/InfiniTime/issues/313).
|
||||
|
||||
## Code sections
|
||||
A binary is composed of multiple sections. Most of the time, these sections are : .text, .rodata, .data and .bss but more sections can be defined in the linker script.
|
||||
@ -38,7 +38,7 @@ In this analysis, I used [Linkermapviz](https://github.com/PromyLOPh/linkermapvi
|
||||
|
||||
Using this tool, you can easily see the size of each symbol relative to the other one, and check what is using most of the space,...
|
||||
|
||||
Also, as Linkermapviz is written in Python, you can easily modify it to adapt it to your firmware, export data in another format,... For example, [I modified it to parse the contents of the MAP file and export it in a CSV file](https://github.com/JF002/InfiniTime/issues/313#issuecomment-842338620). I could later on open this file in LibreOffice Calc and use sort/filter functionality to search for specific symbols in specific files...
|
||||
Also, as Linkermapviz is written in Python, you can easily modify it to adapt it to your firmware, export data in another format,... For example, [I modified it to parse the contents of the MAP file and export it in a CSV file](https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620). I could later on open this file in LibreOffice Calc and use sort/filter functionality to search for specific symbols in specific files...
|
||||
|
||||
### Puncover
|
||||
[Puncover](https://github.com/HBehrens/puncover) is another useful tools that analyses the binary file generated by the compiler (the .out file that contains all debug information). It provides valuable information about the symbols (data and code): name, position, size, max stack of each functions, callers, callees...
|
||||
@ -69,8 +69,8 @@ This way, you can easily check what needs to be optimized : we should find a way
|
||||
It's always a good idea to check the flash memory space when working on the project : this way, you can easily check that your developments are using a reasonable amount of space.
|
||||
|
||||
### Links
|
||||
- Analysis with linkermapviz : https://github.com/JF002/InfiniTime/issues/313#issuecomment-842338620
|
||||
- Analysis with Puncover : https://github.com/JF002/InfiniTime/issues/313#issuecomment-847311392
|
||||
- Analysis with linkermapviz : https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620
|
||||
- Analysis with Puncover : https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-847311392
|
||||
|
||||
## RAM
|
||||
RAM memory contains all the data that can be modified at run-time: variables, stack, heap...
|
||||
@ -93,7 +93,7 @@ int main() {
|
||||
In Infinitime 1.1, the biggest buffers are the buffers allocated for LVGL (14KB) and the one for FreeRTOS (16KB). Nimble also allocated 9KB of RAM.
|
||||
|
||||
### Stack
|
||||
The stack will be used for everything except tasks, which have their own stack allocated by FreeRTOS. The stack is 8192B and is allocated in the [linker script](https://github.com/JF002/InfiniTime/blob/develop/nrf_common.ld#L148).
|
||||
The stack will be used for everything except tasks, which have their own stack allocated by FreeRTOS. The stack is 8192B and is allocated in the [linker script](https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/nrf_common.ld#L148).
|
||||
An easy way to monitor its usage is by filling the section with a known pattern at boot time, then use the firmware and dump the memory. You can then check the maximum stack usage by checking the address from the beginning of the stack that were overwritten.
|
||||
|
||||
#### Fill the stack section by a known pattern:
|
||||
@ -197,10 +197,10 @@ On the following dump, the maximum stack usage is 520 bytes (0xFFFF - 0xFDF8):
|
||||
According to my experimentations, we don't use the stack that much, and 8192 bytes is probably way too big for InfiniTime!
|
||||
|
||||
#### Links
|
||||
- https://github.com/JF002/InfiniTime/issues/313#issuecomment-851035070
|
||||
- https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-851035070
|
||||
|
||||
### Heap
|
||||
The heap is declared in the [linker script](https://github.com/JF002/InfiniTime/blob/develop/nrf_common.ld#L136) and its current size is 8192 bytes. The heap is used for dynamic memory allocation(`malloc()`, `new`...).
|
||||
The heap is declared in the [linker script](https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/nrf_common.ld#L136) and its current size is 8192 bytes. The heap is used for dynamic memory allocation(`malloc()`, `new`...).
|
||||
|
||||
Heap monitoring is not easy, but it seems that we can use the following code to know the current usage of the heap:
|
||||
|
||||
@ -239,7 +239,7 @@ Using this technique, I was able to trace all malloc calls at boot (boot -> digi
|
||||
- hr task = 304
|
||||
|
||||
#### Links
|
||||
- https://github.com/JF002/InfiniTime/issues/313#issuecomment-851035625
|
||||
- https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-851035625
|
||||
- https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-1-calculating-stack-size/
|
||||
- https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-2-properly-allocating-stacks/
|
||||
- https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-3-avoiding-heap-errors/
|
||||
@ -263,7 +263,7 @@ For example a simple lv_label needs **~140 bytes** of memory.
|
||||
|
||||
I tried to monitor this max value while going through all the apps of InfiniTime 1.1 : the max value I've seen is **5660 bytes**. It means that we could probably **reduce the size of the buffer from 14KB to 6 - 10 KB** (we have to take the fragmentation of the memory into account).
|
||||
### Links
|
||||
- https://github.com/JF002/InfiniTime/issues/313#issuecomment-850890064
|
||||
- https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-850890064
|
||||
|
||||
|
||||
## FreeRTOS heap and task stack
|
||||
|
17
doc/MotionService.md
Normal file
17
doc/MotionService.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Motion Service
|
||||
## Introduction
|
||||
The motion service exposes step count and raw X/Y/Z motion value as READ and NOTIFY characteristics.
|
||||
|
||||
## Service
|
||||
The service UUID is **00020000-78fc-48fe-8e23-433b3a1942d0**
|
||||
|
||||
## Characteristics
|
||||
### Step count (UUID 00020001-78fc-48fe-8e23-433b3a1942d0)
|
||||
The current number of steps represented as a single `uint32_t` (4 bytes) value.
|
||||
|
||||
### Raw motion values (UUID 00020002-78fc-48fe-8e23-433b3a1942d0)
|
||||
The current raw motion values. This is a 3 `int16_t` array:
|
||||
|
||||
- [0] : X
|
||||
- [1] : Y
|
||||
- [2] : Z
|
@ -9,19 +9,19 @@ manDist (string) - Manouvre Distance, the distance to the upcoming change
|
||||
progress (uint8) - Percent complete of total route, value 0-100
|
||||
|
||||
## Service
|
||||
The service UUID is c7e60001-78fc-48fe-8e23-433b3a1942d0
|
||||
The service UUID is 00010000-78fc-48fe-8e23-433b3a1942d0
|
||||
|
||||
## Characteristics
|
||||
## Flags (UUID c7e60002-78fc-48fe-8e23-433b3a1942d0)
|
||||
## Flags (UUID 00010001-78fc-48fe-8e23-433b3a1942d0)
|
||||
All included icons are from pure-maps, which provides the actual routing from the client. The icon names ultimately come from the mapbox project "direction-icons", See https://github.com/rinigus/pure-maps/tree/master/qml/icons/navigation See the end of this document for the full lsit of supported icon names.
|
||||
|
||||
## Narrative (UUID c7e60003-78fc-48fe-8e23-433b3a1942d0)
|
||||
## Narrative (UUID 00010002-78fc-48fe-8e23-433b3a1942d0)
|
||||
This is a client supplied string describing the upcoming instruction such as "At the roundabout take the first exit".
|
||||
|
||||
## Man Dist (UUID c7e60004-78fc-48fe-8e23-433b3a1942d0)
|
||||
## Man Dist (UUID 00010003-78fc-48fe-8e23-433b3a1942d0)
|
||||
This is a short string describing the distance to the upcoming instruction such as "50 m".
|
||||
|
||||
## Progress (UUID c7e60001=5-78fc-48fe-8e23-433b3a1942d0)
|
||||
## Progress (UUID 00010004-78fc-48fe-8e23-433b3a1942d0)
|
||||
The percent complete in a uint8. The watch displays this as an overall progress in a progress bar.
|
||||
|
||||
## Full icon list
|
||||
|
261
doc/ble.md
261
doc/ble.md
@ -2,23 +2,59 @@
|
||||
## Introduction
|
||||
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 in this document reflects my current knowledge and understanding of the BLE stack. This information might be erroneous or incomplete. Feel free to submit a PR if you think you can improve it.
|
||||
|
||||
---
|
||||
|
||||
### Table of Contents
|
||||
|
||||
- [BLE Connection](#ble-connection)
|
||||
- [BLE UUIDs](#ble-uuids)
|
||||
- [BLE Services](#ble-services)
|
||||
- [CTS](#cts)
|
||||
- [ANS](#ans)
|
||||
- [Getting Information](#getting-information)
|
||||
- [Firmware Version](#firmware-version)
|
||||
- [Battery Level](#battery-level)
|
||||
- [Heart Rate](#heart-rate)
|
||||
- [Notifications](#notifications)
|
||||
- [New Alert](#new-alert)
|
||||
- [Notification Event](#notification-event)
|
||||
- [Firmware Upgrades](#firmware-upgrades)
|
||||
- [Step one](#step-one)
|
||||
- [Step two](#step-two)
|
||||
- [Step three](#step-three)
|
||||
- [Step four](#step-four)
|
||||
- [Step five](#step-five)
|
||||
- [Step six](#step-six)
|
||||
- [Step seven](#step-seven)
|
||||
- [Step eight](#step-eight)
|
||||
- [Step nine](#step-nine)
|
||||
- [Music Control](#music-control)
|
||||
- [Events](#events)
|
||||
- [Status](#status)
|
||||
- [Artist, Track, and Album](#artist-track-and-album)
|
||||
- [Time](#time)
|
||||
|
||||
---
|
||||
|
||||
## BLE Connection
|
||||
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.
|
||||
When starting, the firmware starts 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, 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,...
|
||||
A companion application (running on a PC, Raspberry Pi, smartphone, etc.) which receives this advertising packet can request a connection to the device. This connection procedure allows the 2 devices to negotiate communication parameters, security keys, etc.
|
||||
|
||||
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.
|
||||
|
||||
If **CTS** is detected, it'll request the current time to the companion application. If **ANS** is detected, it will listen to new notifications coming from the companion application.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## BLE UUIDs
|
||||
When possible, InfiniTime tries to implement BLE services defined by the BLE specification.
|
||||
|
||||
When the service does not exist in the BLE specification, InfiniTime implement custom services. As all BLE services, custom services are identified by a UUID. Here is how to define the UUID of custom services in InfiniTime:
|
||||
When the service does not exist in the BLE specification, InfiniTime implements custom services. Custom services are identified by a UUID, as are all BLE services. Here is how to define the UUID of custom services in InfiniTime:
|
||||
|
||||
```
|
||||
- Base UUID : xxxxxxxx-78fc-48fe-8e23-433b3a1942d0
|
||||
@ -29,14 +65,21 @@ When the service does not exist in the BLE specification, InfiniTime implement c
|
||||
The following custom services are implemented in InfiniTime:
|
||||
|
||||
- Since InfiniTime 0.8:
|
||||
```
|
||||
* Music Service : 00000000-78fc-48fe-8e23-433b3a1942d0
|
||||
```
|
||||
|
||||
* Music Service : 00000000-78fc-48fe-8e23-433b3a1942d0
|
||||
|
||||
|
||||
- Since InfiniTime 0.11:
|
||||
```
|
||||
* Navigation Service : 00010000-78fc-48fe-8e23-433b3a1942d0
|
||||
```
|
||||
* [Navigation Service](NavigationService.md) : 00010000-78fc-48fe-8e23-433b3a1942d0
|
||||
|
||||
|
||||
- Since InfiniTime 0.13
|
||||
* Call characteristic (extension to the Alert Notification Service): 00020001-78fc-48fe-8e23-433b3a1942d0
|
||||
|
||||
|
||||
- Since InfiniTime 1.7:
|
||||
* [Motion Service](MotionService.md) : 00030000-78fc-48fe-8e23-433b3a1942d0
|
||||
|
||||
---
|
||||
|
||||
## BLE services
|
||||
[List of standard BLE services](https://www.bluetooth.com/specifications/gatt/services/)
|
||||
@ -49,3 +92,197 @@ The following custom services are implemented in InfiniTime:
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Getting Information
|
||||
|
||||
The InfiniTime firmware exposes some information about itself through BLE. The BLE characteristic UUIDs for this information are as follows:
|
||||
|
||||
- Firmware Version: `00002a26-0000-1000-8000-00805f9b34fb`
|
||||
- Battery Level: `00002a19-0000-1000-8000-00805f9b34fb`
|
||||
- Heart Rate: `00002a37-0000-1000-8000-00805f9b34fb`
|
||||
|
||||
#### Firmware Version
|
||||
|
||||
Reading a value from the firmware version characteristic will yield a UTF-8 encoded string containing the version of InfiniTime being run on the device. Example: `1.6.0`.
|
||||
|
||||
#### Battery Level
|
||||
|
||||
Reading from the battery level characteristic yields a single byte of data. This byte can be converted to an unsigned 8-bit integer which will be the battery percentage. This characteristic allows notify for updates as the value changes.
|
||||
|
||||
#### Heart Rate
|
||||
|
||||
Reading from the heart rate characteristic yields two bytes of data. I am not sure of the function of the first byte. It appears to always be zero. The second byte can be converted to an unsigned 8-bit integer which is the current heart rate. This characteristic also allows notify for updates as the value changes.
|
||||
|
||||
---
|
||||
|
||||
### Notifications
|
||||
|
||||
InfiniTime uses the Alert Notification Service (ANS) for notifications. The relevant UUIDs are as follows:
|
||||
|
||||
- New Alert: `00002a46-0000-1000-8000-00805f9b34fb`
|
||||
- Notification Event: `00020001-78fc-48fe-8e23-433b3a1942d0`
|
||||
|
||||
#### New Alert
|
||||
|
||||
The new alert characteristic allows sending new notifications to InfiniTime. It requires the following format:
|
||||
|
||||
```
|
||||
<category><amount>\x00<\x00-separated data>
|
||||
```
|
||||
|
||||
For example, here is what a normal notification looks like in Golang (language of `itd`):
|
||||
|
||||
```go
|
||||
// \x00 is the category for simple alert, and there is one new notifcation, hence \x01.
|
||||
"\x00\x01\x00Test Title\x00Test Body"
|
||||
```
|
||||
|
||||
A call notification looks like so:
|
||||
|
||||
```go
|
||||
// \x03 is the category for calls, and there is one new call notifcation, hence \x01.
|
||||
"\x03\x01\x00Mary"
|
||||
```
|
||||
|
||||
The `\x00` stands for hexadecimal `00` which means null.
|
||||
|
||||
Here is the list of categories and commands:
|
||||
|
||||
- Simple Alert: `0`
|
||||
- Email: `1`
|
||||
- News: `2`
|
||||
- Call Notification: `3`
|
||||
- Missed Call: `4`
|
||||
- SMS/MMS: `5`
|
||||
- Voicemail: `6`
|
||||
- Schedule: `7`
|
||||
- High Prioritized Alert: `8`
|
||||
- Instant Message: `9`
|
||||
- All Alerts: `0xFF`
|
||||
|
||||
These lists and information were retrieved from the following pages in the Nordic docs:
|
||||
|
||||
- [Alert Notification Service Client](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v12.2.0%2Fgroup__ble__ans__c.html)
|
||||
- [Alert Notification Application](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v13.0.0%2Fble_sdk_app_alert_notification.html)
|
||||
|
||||
#### Notification Event
|
||||
|
||||
A call notification in InfiniTime contains three buttons. Decline, Accept, and Mute. The notification event characteristic contains the button tapped by the user on a call notification. This characteristic only allows notify, **not** read.
|
||||
|
||||
Enabling notifications from this characteristic, you get a single byte whenever the user taps a button on the call notification. This byte is an unsigned 8-bit integer that signifies one of the buttons. The numbers are as follows:
|
||||
|
||||
- 0: Declined
|
||||
- 1: Accepted
|
||||
- 2: Muted
|
||||
|
||||
---
|
||||
|
||||
### Firmware Upgrades
|
||||
|
||||
Firmware upgrades in InfiniTime are probably the most complex of the BLE operations. It is a nine step process requiring multiple commands be sent to multiple characteristics. The relevant UUIDs are as follows:
|
||||
|
||||
- Control Point: `00001531-1212-efde-1523-785feabcd123`
|
||||
- Packet: `00001532-1212-efde-1523-785feabcd123`
|
||||
|
||||
A DFU upgrade archive for InfiniTime consists of multiple files. The most important being the .bin and .dat files. The first is the actual firmware, while the second is a packet that initializes DFU. Both are needed for a DFU upgrade.
|
||||
|
||||
The first thing to do is to enable notifications on the control point characteristic. This will be needed for verifying that the proper responses are being sent back from InfiniTime.
|
||||
|
||||
#### Step one
|
||||
|
||||
For the first step, write `0x01`, `0x04` to the control point characteristic. This will signal InfiniTime that a DFU upgrade is to be started.
|
||||
|
||||
#### Step two
|
||||
|
||||
In step two, send the total size in bytes of the firmware file to the packet characteristic. This value should be an unsigned 32-bit integer encoded as little-endian. In front of this integer should be 8 null bytes. This is because there are three items that can be updated and each 4 bytes is for one of those. The last four are for the InfiniTime application, so those are the ones that need to be set.
|
||||
|
||||
#### Step three
|
||||
|
||||
Before running step three, wait for a response from the control point. This response should be `0x10`, `0x01`, `0x01` which indicates a successful DFU start. In step three, send `0x02`, `0x00` to the control point. This will signal InfiniTime to expect the init packet on the packet characteristic.
|
||||
|
||||
#### Step four
|
||||
|
||||
The previous step prepared InfiniTime for this one. In this step, send the contents of the .dat init packet file to the packet characteristic. After this, send `0x02`, `0x01` indicating that the packet has been sent.
|
||||
|
||||
#### Step five
|
||||
|
||||
Before running this step, wait to receive `0x10`, `0x02`, `0x01` which indicates that the packet has been received. During this step, send the packet receipt interval to the control point. The firmware file will be sent in segments of 20 bytes each. The packet receipt interval indicates how many segments should be received before sending a receipt containing the amount of bytes received so that it can be confirmed to be the same as the amount sent. This is very useful for detecting packet loss. `itd` uses `0x08`, `0x0A` which indicates 10 segments.
|
||||
|
||||
#### Step six
|
||||
|
||||
In step six, write `0x03` to the control point, indicating that the firmware will be sent next on the packet characteristic.
|
||||
|
||||
#### Step seven
|
||||
|
||||
This step is the most difficult. Here, the actual firmware is sent to InfiniTime.
|
||||
|
||||
As mentioned before, the firmware file must be split up into segments of 20 bytes each and sent to the packet characteristic one by one. Every 10 segments (or whatever you have set the interval to), check for a response starting with `0x11`. The rest of the response will be the amount of bytes received encoded as a little-endian unsigned 32-bit integer. Confirm that this matches the amount of bytes sent, and then continue sending more segments.
|
||||
|
||||
#### Step eight
|
||||
|
||||
Before running this step, wait to receive `0x10`, `0x03`, `0x01` which indicates a successful receipt of the firmware image. In this step, write `0x04` to the control point to signal InfiniTime to validate the image it has received.
|
||||
|
||||
#### Step nine
|
||||
|
||||
Before running this step, wait to receive `0x10`, `0x04`, `0x01` which indicates that the image has been validated. In this step, send `0x05` to the control point as a command with no response. This signals InfiniTime to activate the new firmware and reboot.
|
||||
|
||||
Once all of these steps are complete, the DFU is complete. Don't forget to validate the firmware in the settings.
|
||||
|
||||
---
|
||||
|
||||
### Music Control
|
||||
|
||||
InfiniTime contains a music controller app which is meant to control the music playback and volume through the companion.
|
||||
|
||||
The following UUIDs are relevant to this:
|
||||
|
||||
- Events: `00000001-78fc-48fe-8e23-433b3a1942d0`
|
||||
- Status: `00000002-78fc-48fe-8e23-433b3a1942d0`
|
||||
- Artist: `00000003-78fc-48fe-8e23-433b3a1942d0`
|
||||
- Track: `00000004-78fc-48fe-8e23-433b3a1942d0`
|
||||
- Album: `00000005-78fc-48fe-8e23-433b3a1942d0`
|
||||
|
||||
#### Events
|
||||
|
||||
The events characteristic is meant to respond to user input in the music controller app.
|
||||
|
||||
Enabling notifications on this characteristic gives you a single byte upon any event. This byte can be converted to an unsigned 8-bit integer which corresponds to each possible event. Here are the events:
|
||||
|
||||
- App Opened: `0xe0`
|
||||
- Play: `0x00`
|
||||
- Pause: `0x01`
|
||||
- Next: `0x03`
|
||||
- Previous: `0x04`
|
||||
- Volume up: `0x05`
|
||||
- Volume down: `0x06`
|
||||
|
||||
#### Status
|
||||
|
||||
The status characteristic allows setting the playing status of music. Send `0x01` to the status characteristic for playing, and `0x00` for paused.
|
||||
|
||||
#### Artist, Track, and Album
|
||||
|
||||
These characteristics all work the same way. Simply send a UTF-8 encoded string to the relevant characteristic in order to set the value in the app.
|
||||
|
||||
---
|
||||
|
||||
### Time
|
||||
|
||||
InfiniTime allows setting its time via the Current Time Service (CTS)
|
||||
|
||||
The UUID for the current time characteristic is: `00002a2b-0000-1000-8000-00805f9b34fb`
|
||||
|
||||
This characteristic expects a particular format:
|
||||
|
||||
- Year (`uint16`)
|
||||
- Month (`uint8`)
|
||||
- Day (`uint8`)
|
||||
- Hour (`uint8`)
|
||||
- Minute (`uint8`)
|
||||
- Second (`uint8`)
|
||||
- Weekday (`uint8`)
|
||||
- Microsecond divided by `1e6*256` (`uint8`)
|
||||
- Binary 0001 (`uint8`)
|
||||
|
||||
Write all of these together, encoded as little-endian, to the current time characteristic.
|
@ -3,13 +3,20 @@
|
||||
To build this project, you'll need:
|
||||
- A cross-compiler : [ARM-GCC (9-2020-q2-update)](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads/9-2020-q2-update)
|
||||
- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip)
|
||||
- The `cbor` and `intelhex` modules for Python 3
|
||||
- The Python 3 modules `cbor`, `intelhex`, `click` and `cryptography` modules for the `mcuboot` tool (see [requirements.txt](../tools/mcuboot/requirements.txt))
|
||||
- To to keep the system clean a python virtual environment (`venv`) can be used to install the python modules into
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
python -m pip install wheel
|
||||
python -m pip install -r tools/mcuboot/requirements.txt
|
||||
```
|
||||
- A reasonably recent version of CMake (I use 3.16.5)
|
||||
|
||||
## Build steps
|
||||
### Clone the repo
|
||||
```
|
||||
git clone https://github.com/JF002/InfiniTime.git
|
||||
git clone https://github.com/InfiniTimeOrg/InfiniTime.git
|
||||
cd InfiniTime
|
||||
git submodule update --init
|
||||
mkdir build
|
||||
@ -31,9 +38,9 @@ CMake configures the project according to variables you specify the command line
|
||||
**WATCH_COLMI_P8**|Use pin configuration for Colmi P8 watch|`-DWATCH_COLMI_P8=1`
|
||||
|
||||
####(**) 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/InfiniTimeOrg/InfiniTime/releases) new versions of InfiniTime.
|
||||
|
||||
The *Debug* mode disables all optimizations, which makes the code easier to debug. However, the binary size will likely be too big to fit in the internal flash memory. If you want to build and debug a *Debug* binary, you'll need to disable some parts of the code. For example, the icons for the **Navigation** app use a lot of memory space. You can comment the content of `m_iconMap` in the [Navigation](https://github.com/JF002/InfiniTime/blob/develop/src/displayapp/screens/Navigation.h#L148) application to free some memory.
|
||||
The *Debug* mode disables all optimizations, which makes the code easier to debug. However, the binary size will likely be too big to fit in the internal flash memory. If you want to build and debug a *Debug* binary, you'll need to disable some parts of the code. For example, the icons for the **Navigation** app use a lot of memory space. You can comment the content of `m_iconMap` in the [Navigation](https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/src/displayapp/screens/Navigation.h#L148) application to free some memory.
|
||||
|
||||
####(**) Note about **BUILD_DFU**:
|
||||
DFU files are the files you'll need to install your build of InfiniTime using OTA (over-the-air) mecanism. To generate the DFU file, the Python tool [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil) is needed on your system. Check that this tool is properly installed before enabling this option.
|
||||
@ -223,4 +230,34 @@ Loading section .sec6, size 0x10000 lma 0x30000
|
||||
Loading section .sec7, size 0xdf08 lma 0x40000
|
||||
Start address 0x0, load size 314200
|
||||
Transfer rate: 45 KB/sec, 969 bytes/write.
|
||||
```
|
||||
```
|
||||
|
||||
### How to generate files needed by the factory
|
||||
These files are needed by the Pine64 factory to flash InfiniTime as the default firmware on the PineTimes.
|
||||
|
||||
Two files are needed: an **HEX (.hex)** file that contains the content of the internal flash memory (bootloader + InfiniTime) and a **binary (.bin)** file that contains the content of the external flash memory (recovery firmware).
|
||||
|
||||
#### merged-internal.hex
|
||||
First, convert the bootloader to hex:
|
||||
```
|
||||
<ARM TOOLCHAIN>/bin/arm-none-eabi-objcopy -I binary -O ihex ./bootloader.bin ./bootloader.hex
|
||||
```
|
||||
where `bootloader.bin` is the [last stable version](https://github.com/JF002/pinetime-mcuboot-bootloader/releases) of the [bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader).
|
||||
|
||||
Then, convert the MCUBoot image of InfiniTime:
|
||||
```
|
||||
<ARM TOOLCHAIN>/bin/arm-none-eabi-objcopy -I binary -O ihex --change-addresses 0x8000 ./pinetime-mcuboot-app-image-1.6.0.bin ./pinetime-mcuboot-app-image-1.6.0.hex
|
||||
```
|
||||
where `pinetime-mcuboot-app-image-1.6.0.bin` is [the bin of the last MCUBoot image](https://github.com/InfiniTimeOrg/InfiniTime/releases) of [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime).
|
||||
|
||||
Pay attention to the parameter `--change-addresses 0x8000`. It's needed to ensure the image will be flashed at the offset expected by the bootloader (0x8000).
|
||||
|
||||
Finally, merge them together with **mergehex**:
|
||||
```
|
||||
/opt/mergehex/mergehex -m ./bootloader.hex ./pinetime-mcuboot-app-image-1.6.0.hex -o merged-internal.hex
|
||||
```
|
||||
|
||||
This file must be flashed at offset **0x00** of the internal memory of the NRF52832.
|
||||
|
||||
#### spinor.bin
|
||||
This file is the MCUBoot image of the last stable version of the recovery firmware. It must be flashed at offset **0x00** of the external SPINOR flash memory.
|
@ -11,7 +11,9 @@ Based on Ubuntu 18.04 with the following build dependencies:
|
||||
|
||||
## Run a container to build the project
|
||||
|
||||
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.
|
||||
|
||||
Before continuing, make sure you first build the image as indicated in the [Build the image](#build-the-image) section, or check the [Using the image from Docker Hub](#using-the-image-from-docker-hub) section if you prefer to use a pre-made image.
|
||||
|
||||
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**:
|
||||
|
||||
@ -28,10 +30,10 @@ docker run --rm -it -v $(pwd):/sources infinitime-build /opt/build.sh pinetime-a
|
||||
|
||||
The image is built using 1000:1000 for the user id and group id. If this is different to your user or group ids (run `id -u` and `id -g` to find out what your id values are if you are unsure), you will need to override them via the `--user` parameter in order to prevent permission errors with the output files (and the cmake build cache).
|
||||
|
||||
Running with this image is the same as above, you just specify the ids to `docker run`
|
||||
Running with this image is the same as above, you just specify the ids to `docker run`:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v $(pwd):/sources --user $(id -u):$(id -g) pfeerick/infinitime-build
|
||||
docker run --rm -it -v $(pwd):/sources --user $(id -u):$(id -g) infinitime-build
|
||||
```
|
||||
|
||||
Or you can specify your user id and group id (by number, not by name) directly:
|
||||
@ -42,7 +44,7 @@ docker run --rm -it -v $(pwd):/sources --user 1234:1234 infinitime-build
|
||||
|
||||
## Using the image from Docker Hub
|
||||
|
||||
The image is avaiable via Docker Hub for both the amd64 and arm64v8 architectures at [pfeerick/infinitime-build](https://hub.docker.com/repository/docker/pfeerick/infinitime-build).
|
||||
The image is available via Docker Hub for both the amd64 and arm64v8 architectures at [pfeerick/infinitime-build](https://hub.docker.com/r/pfeerick/infinitime-build).
|
||||
|
||||
It can be pulled (downloaded) using the following command:
|
||||
|
||||
|
@ -36,7 +36,17 @@ Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemNam
|
||||
|
||||
More documentation is available in the [readme in .devcontainer](.devcontainer/readme.md)
|
||||
|
||||
|
||||
### DevContainer on Ubuntu
|
||||
To use the DevContainer configuration on Ubuntu based systems two changes need to be made:
|
||||
|
||||
1. Modify the file ``.devcontainer/devcontainer.json`` and add the argument ``"--net=host"`` to the ``"runArgs"`` parameter making the line look like this:
|
||||
`` "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined", "--net=host"],
|
||||
``
|
||||
2. Modify the file ``.vscode/launch.json`` and change the argument of ``"gdbTarget"`` to ``"127.0.0.1:3333"``, making the line look like:
|
||||
``"gdbTarget": "127.0.0.1:3333",``
|
||||
3. To start debugging launch openocd on your host system with the appropriate configuration, for example with a stlink-v2 the command is:
|
||||
``openocd -f interface/stlink.cfg -f target/nrf52.cfg``. This launches openocd with the default ports ``3333``, ``4444`` and ``6666``.
|
||||
4. In VsCode go to the Debug pane on the left of the screen and select the configuration ``Debug - Openocd docker Remote`` and hit the play button on the left.
|
||||
|
||||
|
||||
|
||||
|
106
doc/code/Apps.md
Normal file
106
doc/code/Apps.md
Normal file
@ -0,0 +1,106 @@
|
||||
# Apps
|
||||
This page will teach you:
|
||||
- what apps in InfiniTime are
|
||||
- how to implement your own app
|
||||
|
||||
## Theory
|
||||
Apps are the things you can launch from the app selection you get by swiping up.
|
||||
At the moment, settings and even the app launcher itself or the clock are implemented very similarly, this might change in the future though.
|
||||
Every app in InfiniTime is it's own class.
|
||||
An instance of the class is created when the app is launched and destroyed when the user exits the app.
|
||||
They run inside the "displayapp" task (briefly discussed [here](./Intro.md)).
|
||||
Apps are responsible for everything drawn on the screen when they are running.
|
||||
By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected.
|
||||
|
||||
## Interface
|
||||
Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`.
|
||||
The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen.
|
||||
Other parameters should be references to controllers that the app needs.
|
||||
A destructor is needed to clean up LVGL and restore any changes (for example re-enable sleeping).
|
||||
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
|
||||
If an app only needs to display some text and do something upon a touch screen button press,
|
||||
it does not need to override any of these functions, as LVGL can also handle touch events for you.
|
||||
If you have any doubts, you can always look at how the other apps are doing things.
|
||||
|
||||
### Continuous updating
|
||||
If your app needs to be updated continuously, yo can do so by overriding the `Refresh()` function in your class
|
||||
and calling `lv_task_create` inside the constructor.
|
||||
An example call could look like this: <br>
|
||||
`taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);` <br>
|
||||
With `taskRefresh` being a member variable of your class and of type `lv_task_t*`.
|
||||
Remember to delete the task again using `lv_task_del`.
|
||||
The function `RefreshTaskCallback` is inherited from screen and just calls your `Refresh` function.
|
||||
|
||||
### Apps with multiple screens
|
||||
InfiniTime provides a mini-library in [displayapp/screens/ScreenList.h](/src/displayapp/screens/ScreenList.h)
|
||||
which makes it relatively easy to add multiple screens to your app.
|
||||
To use it, #include it in the header file of your app and add a ScreenList member to your class.
|
||||
The template argument should be the number of screens you need.
|
||||
You will also need to add `CreateScreen` functions that return `std::unique_ptr<Screen>`
|
||||
to your class, one for every screen you have.
|
||||
There are still some things left to to that I won't cover here.
|
||||
To figure them out, have a look at the "apps" ApplicationList, Settings and SystemInfo.
|
||||
|
||||
|
||||
## Creating your own app
|
||||
A minimal app could look like this: <br>
|
||||
MyApp.h:
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class MyApp : public Screen {
|
||||
public:
|
||||
MyApp(DisplayApp* app);
|
||||
~MyApp() override;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
MyApp.cpp:
|
||||
```cpp
|
||||
#include "MyApp.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
MyApp::MyApp(DisplayApp* app) : Screen(app) {
|
||||
lv_obj_t* title = lv_label_create(lv_scr_act(), NULL);
|
||||
lv_label_set_text_static(title, "My test application");
|
||||
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(title, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
|
||||
}
|
||||
|
||||
MyApp::~MyApp() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
```
|
||||
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/)
|
||||
or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a setting app.
|
||||
|
||||
Now we have our very own app, but InfiniTime does not know about it yet.
|
||||
The first step is to include your MyApp.cpp (or any new cpp files for that matter)
|
||||
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
|
||||
The next step to making it launchable is to give your app an id.
|
||||
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
|
||||
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
|
||||
Now, go to the function `DisplayApp::LoadApp` and add another case to the switch statement.
|
||||
The case will be the id you gave your app earlier.
|
||||
If your app needs any additional arguments, this is the place to pass them.
|
||||
|
||||
If you want your app to be launched from the regular app launcher, go to [displayapp/screens/ApplicationList.cpp](/src/displayapp/screens/ApplicationList.cpp).
|
||||
Add your app to one of the `CreateScreen` functions, or add another `CreateScreen` function if there are no empty spaces for your app. <br>
|
||||
If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.cpp](/src/displayapp/screens/settings/Settings.cpp).
|
||||
|
||||
You should now be able to [build](../buildAndProgram.md) the firmware
|
||||
and flash it to your PineTime. Yay!
|
||||
|
||||
Please remember to pay attention to the [UI guidelines](../ui_guidelines.md)
|
||||
when designing an app that you want to include in mainstream InfiniTime.
|
42
doc/code/Intro.md
Normal file
42
doc/code/Intro.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Introduction to the code
|
||||
This page is meant to guide you through the source code, so you can find the relevant files for what you're working on.
|
||||
|
||||
## FreeRTOS
|
||||
Infinitime is based on FreeRTOS, a real-time operating system.
|
||||
FreeRTOS provides several quality of life abstractions (for example easy software timers)
|
||||
and most importantly supports multiple tasks.
|
||||
If you want to read up on real-time operating systems, you can look [here](https://www.freertos.org/implementation/a00002.html) and [here](https://www.freertos.org/features.html).
|
||||
The main "process" creates at least one task and then starts the FreeRTOS task scheduler.
|
||||
This main "process" is the standard main() function inside [main.cpp](/src/main.cpp).
|
||||
The task scheduler is responsible for giving every task enough cpu time.
|
||||
As there is only one core on the SoC of the PineTime, real concurrency is impossible and the scheduler has to swap tasks in and out to emulate it.
|
||||
|
||||
### Tasks
|
||||
Tasks are created by calling `xTaskCreate` and passing a function with the signature `void functionName(void*)`.
|
||||
For more info on task creation see the [FreeRTOS Documentation](https://www.freertos.org/a00125.html).
|
||||
In our case, main calls `systemTask.Start()`, which creates the **"MAIN" task**.
|
||||
The function running inside that task is `SystemTask::Work()`.
|
||||
You may also see this task being referred to as the **work task**.
|
||||
Both functions are located inside [systemtask/SystemTask.cpp](/src/systemtask/SystemTask.cpp). `SystemTask::Work()` initializes all the driver and controller objects.
|
||||
It also starts the **task "displayapp"**, which is responsible for launching and running apps, controlling the screen and handling touch events (or forwarding them to the active app).
|
||||
You can find the "displayapp" task inside [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
|
||||
There are also other tasks that are responsible for Bluetooth ("ll" and "ble" inside [libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c](/src/libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c))
|
||||
and periodic tasks like heartrate measurements ([heartratetask/HeartRateTask.cpp](/src/heartratetask/HeartRateTask.cpp)). <br>
|
||||
While it is possible for you to create your own task when you need it, it is recommended to just add functionality to `SystemTask::Work()` if possible.
|
||||
If you absolutely need to create another task, try to guess how much [stack space](https://www.freertos.org/FAQMem.html#StackSize) (in words/4-byte packets)
|
||||
it will need instead of just typing in a large-ish number.
|
||||
You can use the define `configMINIMAL_STACK_SIZE` which is currently set to 120 words.
|
||||
|
||||
## Controllers
|
||||
Controllers in InfiniTime are singleton objects that can provide access to certain resources to apps.
|
||||
Some of them interface with drivers, others are the driver for the resource.
|
||||
The resources provided don't have to be hardware-based.
|
||||
They are declared in main.cpp and initialized in [systemtask/SystemTask.cpp](/src/systemtask/SystemTask.cpp).
|
||||
Some controllers can be passed by reference to apps that need access to the resource (for example vibration motor).
|
||||
They reside in [components/](/src/components/) inside their own subfolder.
|
||||
|
||||
## Apps
|
||||
For more detail see the [Apps page](./Apps.md)
|
||||
|
||||
## Bluetooth
|
||||
Header files with short documentation for the functions are inside [libs/mynewt-nimble/nimble/host/include/host/](/src/libs/mynewt-nimble/nimble/host/include/host/).
|
@ -2,7 +2,7 @@
|
||||
|
||||
## Report bugs
|
||||
|
||||
Have you found a bug in the firmware? [Create an issue on Github](https://github.com/JF002/InfiniTime/issues) explaining the bug, how to reproduce it, the version of the firmware you use...
|
||||
Have you found a bug in the firmware? [Create an issue on Github](https://github.com/InfiniTimeOrg/InfiniTime/issues) explaining the bug, how to reproduce it, the version of the firmware you use...
|
||||
|
||||
## Write and improve documentation
|
||||
|
||||
@ -18,18 +18,18 @@ You want to fix a bug, add a cool new functionality or improve the code? See *Ho
|
||||
|
||||
The Pinetime is a cool open source project that deserves to be known. Talk about it around you, on social networks, on your blog,... and let people know that we are working on an open source firmware for a smartwatch!
|
||||
|
||||
# How to submit a pull request ?
|
||||
# How to submit a pull request?
|
||||
|
||||
## TL;DR
|
||||
|
||||
- Create a branch from develop;
|
||||
- Work on a single subject in this branch. Create multiple branches/pulls-requests if you want to work on multiple subjects (bugs, features,...);
|
||||
- Test your modifications on the actual hardware;
|
||||
- Check the code formatting against our coding conventions and [clang-format](../.clang-format) and [clang-tidy](../.clang-tidy);
|
||||
- Clean your code and remove files that are not needed;
|
||||
- Write documentation related to your new feature if applicable;
|
||||
- Create a pull request and write a great description about it : what does your PR do, why, how,... Add pictures and video if possible;
|
||||
- Wait for someone to review your PR and take part in the review process;
|
||||
- Create a branch from develop
|
||||
- Work on a single subject in this branch. Create multiple branches/pulls-requests if you want to work on multiple subjects (bugs, features,...)
|
||||
- Test your modifications on the actual hardware
|
||||
- Check the code formatting against our coding conventions and [clang-format](../.clang-format) and [clang-tidy](../.clang-tidy)
|
||||
- Clean your code and remove files that are not needed
|
||||
- Write documentation related to your new feature if applicable
|
||||
- Create a pull request and write a great description about it: what does your PR do, why, how,... Add pictures and video if possible
|
||||
- Wait for someone to review your PR and take part in the review process
|
||||
- Your PR will eventually be merged :)
|
||||
|
||||
Your contributions are more than welcome!
|
||||
@ -94,3 +94,10 @@ If there are no preconfigured rules for your IDE, you can use one of the existin
|
||||
- **Includes** :
|
||||
- files from the project : `#include "relative/path/to/the/file.h"`
|
||||
- external files and std : `#include <file.h>`
|
||||
- Only use [primary spellings for operators and tokens](https://en.cppreference.com/w/cpp/language/operator_alternative)
|
||||
- Use auto sparingly. Don't use auto for [fundamental/built-in types](https://en.cppreference.com/w/cpp/language/types) and [fixed width integer types](https://en.cppreference.com/w/cpp/types/integer), except when initializing with a cast to avoid duplicating the type name.
|
||||
- Examples:
|
||||
- `auto* app = static_cast<DisplayApp*>(instance);`
|
||||
- `auto number = static_cast<uint8_t>(variable);`
|
||||
- `uint8_t returnValue = MyFunction();`
|
||||
- Use nullptr instead of NULL
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Using the releases
|
||||
For each new *stable* version of IniniTime, a [release note](https://github.com/JF002/InfiniTime/releases) is created. It contains a description of the main changes in the release and some files you can use to flash the firmware to your Pinetime.
|
||||
For each new *stable* version of IniniTime, a [release note](https://github.com/InfiniTimeOrg/InfiniTime/releases) is created. It contains a description of the main changes in the release and some files you can use to flash the firmware to your Pinetime.
|
||||
|
||||
This page describes the files from the release notes and how to use them.
|
||||
|
||||
**NOTE :** the files included in different Releases could be different. This page describes the release notes of [version 0.7.1](https://github.com/JF002/InfiniTime/releases/tag/0.7.1), which is the version that is pre-programmed for the last batches of pinetimes but will be replaced with [1.0.0](https://github.com/jF002/infiniTime/releases/tag/1.0.0) around june 2021.
|
||||
**NOTE :** the files included in different Releases could be different. This page describes the release notes of [version 0.7.1](https://github.com/InfiniTimeOrg/InfiniTime/releases/tag/0.7.1), which is the version that is pre-programmed for the last batches of pinetimes but will be replaced with [1.0.0](https://github.com/jF002/infiniTime/releases/tag/1.0.0) around june 2021.
|
||||
|
||||
## Files included in the release notes
|
||||
|
||||
|
@ -8,15 +8,15 @@ You might have already seen these words by reading the announcement, release not
|
||||
|
||||
Basically, a **firmware** is just a software running on the embedded hardware of a device, the PineTime in this case.
|
||||
**InfiniTime** is based on 3 distinct **firmwares**:
|
||||
- **[InfiniTime](https://github.com/JF002/InfiniTime)** itself, this is the *application firmware* running on the PineTime. This is the main firmware which provides most of the functionalities you'll use on a daily basis : bluetooth low-energy (BLE) connectivity, applications, watchfaces,...
|
||||
- **[InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)** itself, this is the *application firmware* running on the PineTime. This is the main firmware which provides most of the functionalities you'll use on a daily basis : bluetooth low-energy (BLE) connectivity, applications, watchfaces,...
|
||||
- **[The bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader)** is responsible for safely applying **updates** of the *application firmware*, reverting them in case of issues and load the recovery firmware when requested.
|
||||
- **The recovery firmware** is a specific *application firmware* than can be loaded by the bootloader on user request. This firmware can be useful in case of serious issue, when the main application firmware cannot perform an OTA update correctly. Currently, this recovery firmware is based on [InfiniTime 0.14.1](https://github.com/JF002/InfiniTime/releases/tag/0.14.1).
|
||||
- **The recovery firmware** is a specific *application firmware* than can be loaded by the bootloader on user request. This firmware can be useful in case of serious issue, when the main application firmware cannot perform an OTA update correctly. Currently, this recovery firmware is based on [InfiniTime 0.14.1](https://github.com/InfiniTimeOrg/InfiniTime/releases/tag/0.14.1).
|
||||
|
||||
**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**evice **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?
|
||||
|
||||
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/InfiniTimeOrg/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 successful OTA or a manual reset - long push on the button).
|
||||
|
||||
@ -33,7 +33,7 @@ And for version >= 1.0 :
|
||||
|
||||

|
||||
|
||||
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 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/InfiniTimeOrg/InfiniTime/releases/tag/0.14.1) and [InfiniTime 1.0](https://github.com/InfiniTimeOrg/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).
|
||||
|
||||
@ -56,9 +56,9 @@ If your PineTime is currently running InfiniTime 0.7.1 and the old bootloader, w
|
||||
|
||||
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):
|
||||
|
||||
1. Flash the latest version of InfiniTime. The file to upload is named **pinetime-mcuboot-app-dfu-x.y.z.zip**. Here is the link to [InfiniTime 1.0](https://github.com/JF002/InfiniTime/releases/download/1.0.0/pinetime-mcuboot-app-dfu-1.0.0.zip).
|
||||
1. Flash the latest version of InfiniTime. The file to upload is named **pinetime-mcuboot-app-dfu-x.y.z.zip**. Here is the link to [InfiniTime 1.0](https://github.com/InfiniTimeOrg/InfiniTime/releases/download/1.0.0/pinetime-mcuboot-app-dfu-1.0.0.zip).
|
||||
2. Update the bootloader by applying the OTA procedure with the file named [**reloader-mcuboot.zip** from the repo of the bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader/releases/download/1.0.0/reloader-mcuboot.zip).
|
||||
3. Install the recovery firmware by applying the OTA procedure with the file named [**pinetime-mcuboot-recovery-loader-dfu-0.14.1.zip** from the version 0.14.1 of InfiniTime](https://github.com/JF002/InfiniTime/releases/download/0.14.1/pinetime-mcuboot-recovery-loader-dfu-0.14.1.zip).
|
||||
3. Install the recovery firmware by applying the OTA procedure with the file named [**pinetime-mcuboot-recovery-loader-dfu-0.14.1.zip** from the version 0.14.1 of InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime/releases/download/0.14.1/pinetime-mcuboot-recovery-loader-dfu-0.14.1.zip).
|
||||
|
||||
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)
|
||||
|
||||
|
@ -8,7 +8,7 @@ If you just want to flash or upgrade InfiniTime on your PineTime, this page is f
|
||||
- [How to flash InfiniTime using the SWD interface](#how-to-flash-infinitime-using-the-swd-interface)
|
||||
|
||||
## InfiniTime releases and versions
|
||||
All releases of InfiniTime are available on the [release page of the GitHub repo](https://github.com/JF002/InfiniTime/releases).
|
||||
All releases of InfiniTime are available on the [release page of the GitHub repo](https://github.com/InfiniTimeOrg/InfiniTime/releases).
|
||||
|
||||
Versions that are tagged as **RELEASE CANDIDATE** are pre-release versions, that are available for testing before actually releasing a new stable version. If you want to help us debug the project and provide stable versions to other user, you can use them. If you want stable and tested version, you should not flash these release candidate version.
|
||||
|
||||
|
@ -43,7 +43,7 @@ GetGcc() {
|
||||
}
|
||||
|
||||
GetMcuBoot() {
|
||||
git clone https://github.com/JuulLabs-OSS/mcuboot.git "$TOOLS_DIR/mcuboot"
|
||||
git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot"
|
||||
pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt"
|
||||
}
|
||||
|
||||
|
10
src/BootErrors.h
Normal file
10
src/BootErrors.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pinetime {
|
||||
namespace System {
|
||||
enum class BootErrors {
|
||||
None,
|
||||
TouchController,
|
||||
};
|
||||
}
|
||||
}
|
@ -421,6 +421,7 @@ list(APPEND SOURCE_FILES
|
||||
displayapp/screens/BatteryInfo.cpp
|
||||
displayapp/screens/Steps.cpp
|
||||
displayapp/screens/Timer.cpp
|
||||
displayapp/screens/Error.cpp
|
||||
displayapp/screens/Alarm.cpp
|
||||
displayapp/Colors.cpp
|
||||
|
||||
@ -433,6 +434,8 @@ list(APPEND SOURCE_FILES
|
||||
displayapp/screens/settings/SettingDisplay.cpp
|
||||
displayapp/screens/settings/SettingSteps.cpp
|
||||
displayapp/screens/settings/SettingPineTimeStyle.cpp
|
||||
displayapp/screens/settings/SettingSetDate.cpp
|
||||
displayapp/screens/settings/SettingSetTime.cpp
|
||||
|
||||
## Watch faces
|
||||
displayapp/icons/bg_clock.c
|
||||
@ -474,6 +477,7 @@ list(APPEND SOURCE_FILES
|
||||
components/ble/ImmediateAlertService.cpp
|
||||
components/ble/ServiceDiscovery.cpp
|
||||
components/ble/HeartRateService.cpp
|
||||
components/ble/MotionService.cpp
|
||||
components/firmwarevalidator/FirmwareValidator.cpp
|
||||
components/motor/MotorController.cpp
|
||||
components/settings/Settings.cpp
|
||||
@ -503,6 +507,7 @@ list(APPEND SOURCE_FILES
|
||||
components/heartrate/Ptagc.cpp
|
||||
components/heartrate/HeartRateController.cpp
|
||||
|
||||
buttonhandler/ButtonHandler.cpp
|
||||
touchhandler/TouchHandler.cpp
|
||||
)
|
||||
|
||||
@ -542,6 +547,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||
components/ble/ServiceDiscovery.cpp
|
||||
components/ble/NavigationService.cpp
|
||||
components/ble/HeartRateService.cpp
|
||||
components/ble/MotionService.cpp
|
||||
components/firmwarevalidator/FirmwareValidator.cpp
|
||||
components/settings/Settings.cpp
|
||||
components/timer/TimerController.cpp
|
||||
@ -562,6 +568,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||
components/heartrate/Ptagc.cpp
|
||||
components/motor/MotorController.cpp
|
||||
components/fs/FS.cpp
|
||||
buttonhandler/ButtonHandler.cpp
|
||||
touchhandler/TouchHandler.cpp
|
||||
)
|
||||
|
||||
@ -649,6 +656,7 @@ set(INCLUDE_FILES
|
||||
components/ble/ServiceDiscovery.h
|
||||
components/ble/BleClient.h
|
||||
components/ble/HeartRateService.h
|
||||
components/ble/MotionService.h
|
||||
components/settings/Settings.h
|
||||
components/timer/TimerController.h
|
||||
components/alarm/AlarmController.h
|
||||
@ -675,6 +683,7 @@ set(INCLUDE_FILES
|
||||
components/heartrate/Ptagc.h
|
||||
components/heartrate/HeartRateController.h
|
||||
components/motor/MotorController.h
|
||||
buttonhandler/ButtonHandler.h
|
||||
touchhandler/TouchHandler.h
|
||||
)
|
||||
|
||||
|
7
src/buttonhandler/ButtonActions.h
Normal file
7
src/buttonhandler/ButtonActions.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
enum class ButtonActions { None, Click, DoubleClick, LongPress, LongerPress };
|
||||
}
|
||||
}
|
78
src/buttonhandler/ButtonHandler.cpp
Normal file
78
src/buttonhandler/ButtonHandler.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "ButtonHandler.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
void ButtonTimerCallback(TimerHandle_t xTimer) {
|
||||
auto* sysTask = static_cast<Pinetime::System::SystemTask*>(pvTimerGetTimerID(xTimer));
|
||||
sysTask->PushMessage(Pinetime::System::Messages::HandleButtonTimerEvent);
|
||||
}
|
||||
|
||||
void ButtonHandler::Init(Pinetime::System::SystemTask* systemTask) {
|
||||
buttonTimer = xTimerCreate("buttonTimer", 0, pdFALSE, systemTask, ButtonTimerCallback);
|
||||
}
|
||||
|
||||
ButtonActions ButtonHandler::HandleEvent(Events event) {
|
||||
static constexpr TickType_t doubleClickTime = pdMS_TO_TICKS(200);
|
||||
static constexpr TickType_t longPressTime = pdMS_TO_TICKS(400);
|
||||
static constexpr TickType_t longerPressTime = pdMS_TO_TICKS(2000);
|
||||
|
||||
if (event == Events::Press) {
|
||||
buttonPressed = true;
|
||||
} else if (event == Events::Release) {
|
||||
releaseTime = xTaskGetTickCount();
|
||||
buttonPressed = false;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case States::Idle:
|
||||
if (event == Events::Press) {
|
||||
xTimerChangePeriod(buttonTimer, doubleClickTime, 0);
|
||||
xTimerStart(buttonTimer, 0);
|
||||
state = States::Pressed;
|
||||
}
|
||||
break;
|
||||
case States::Pressed:
|
||||
if (event == Events::Press) {
|
||||
if (xTaskGetTickCount() - releaseTime < doubleClickTime) {
|
||||
xTimerStop(buttonTimer, 0);
|
||||
state = States::Idle;
|
||||
return ButtonActions::DoubleClick;
|
||||
}
|
||||
} else if (event == Events::Release) {
|
||||
xTimerChangePeriod(buttonTimer, doubleClickTime, 0);
|
||||
xTimerStart(buttonTimer, 0);
|
||||
} else if (event == Events::Timer) {
|
||||
if (buttonPressed) {
|
||||
xTimerChangePeriod(buttonTimer, longPressTime - doubleClickTime, 0);
|
||||
xTimerStart(buttonTimer, 0);
|
||||
state = States::Holding;
|
||||
} else {
|
||||
state = States::Idle;
|
||||
return ButtonActions::Click;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case States::Holding:
|
||||
if (event == Events::Release) {
|
||||
xTimerStop(buttonTimer, 0);
|
||||
state = States::Idle;
|
||||
return ButtonActions::Click;
|
||||
} else if (event == Events::Timer) {
|
||||
xTimerChangePeriod(buttonTimer, longerPressTime - longPressTime - doubleClickTime, 0);
|
||||
xTimerStart(buttonTimer, 0);
|
||||
state = States::LongHeld;
|
||||
return ButtonActions::LongPress;
|
||||
}
|
||||
break;
|
||||
case States::LongHeld:
|
||||
if (event == Events::Release) {
|
||||
xTimerStop(buttonTimer, 0);
|
||||
state = States::Idle;
|
||||
} else if (event == Events::Timer) {
|
||||
state = States::Idle;
|
||||
return ButtonActions::LongerPress;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return ButtonActions::None;
|
||||
}
|
24
src/buttonhandler/ButtonHandler.h
Normal file
24
src/buttonhandler/ButtonHandler.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "ButtonActions.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include <FreeRTOS.h>
|
||||
#include <timers.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class ButtonHandler {
|
||||
public:
|
||||
enum class Events : uint8_t { Press, Release, Timer };
|
||||
void Init(Pinetime::System::SystemTask* systemTask);
|
||||
ButtonActions HandleEvent(Events event);
|
||||
|
||||
private:
|
||||
enum class States : uint8_t { Idle, Pressed, Holding, LongHeld };
|
||||
TickType_t releaseTime = 0;
|
||||
TimerHandle_t buttonTimer;
|
||||
bool buttonPressed = false;
|
||||
States state = States::Idle;
|
||||
};
|
||||
}
|
||||
}
|
@ -13,10 +13,20 @@ Battery::Battery() {
|
||||
nrf_gpio_cfg_input(PinMap::Charging, static_cast<nrf_gpio_pin_pull_t> GPIO_PIN_CNF_PULL_Disabled);
|
||||
}
|
||||
|
||||
void Battery::Update() {
|
||||
void Battery::ReadPowerState() {
|
||||
isCharging = !nrf_gpio_pin_read(PinMap::Charging);
|
||||
isPowerPresent = !nrf_gpio_pin_read(PinMap::PowerPresent);
|
||||
|
||||
if (isPowerPresent && !isCharging) {
|
||||
isFull = true;
|
||||
} else if (!isPowerPresent) {
|
||||
isFull = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Battery::MeasureVoltage() {
|
||||
ReadPowerState();
|
||||
|
||||
if (isReading) {
|
||||
return;
|
||||
}
|
||||
@ -63,18 +73,23 @@ void Battery::SaadcEventHandler(nrfx_saadc_evt_t const* p_event) {
|
||||
// p_event->data.done.p_buffer[0] = (adc_voltage / reference_voltage) * 1024
|
||||
voltage = p_event->data.done.p_buffer[0] * (8 * 600) / 1024;
|
||||
|
||||
if (voltage > battery_max) {
|
||||
percentRemaining = 100;
|
||||
uint8_t newPercent;
|
||||
if (isFull) {
|
||||
newPercent = 100;
|
||||
} else if (voltage < battery_min) {
|
||||
percentRemaining = 0;
|
||||
newPercent = 0;
|
||||
} else {
|
||||
percentRemaining = (voltage - battery_min) * 100 / (battery_max - battery_min);
|
||||
newPercent = std::min((voltage - battery_min) * 100 / (battery_max - battery_min), isCharging ? 99 : 100);
|
||||
}
|
||||
|
||||
if ((isPowerPresent && newPercent > percentRemaining) || (!isPowerPresent && newPercent < percentRemaining) || firstMeasurement) {
|
||||
firstMeasurement = false;
|
||||
percentRemaining = newPercent;
|
||||
systemTask->PushMessage(System::Messages::BatteryPercentageUpdated);
|
||||
}
|
||||
|
||||
nrfx_saadc_uninit();
|
||||
isReading = false;
|
||||
|
||||
systemTask->PushMessage(System::Messages::BatteryMeasurementDone);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,8 @@ namespace Pinetime {
|
||||
public:
|
||||
Battery();
|
||||
|
||||
void Update();
|
||||
void ReadPowerState();
|
||||
void MeasureVoltage();
|
||||
void Register(System::SystemTask* systemTask);
|
||||
|
||||
uint8_t PercentRemaining() const {
|
||||
@ -22,7 +23,9 @@ namespace Pinetime {
|
||||
}
|
||||
|
||||
bool IsCharging() const {
|
||||
return isCharging;
|
||||
// isCharging will go up and down when fully charged
|
||||
// isFull makes sure this returns false while fully charged.
|
||||
return isCharging && !isFull;
|
||||
}
|
||||
|
||||
bool IsPowerPresent() const {
|
||||
@ -37,8 +40,10 @@ namespace Pinetime {
|
||||
uint16_t voltage = 0;
|
||||
uint8_t percentRemaining = 0;
|
||||
|
||||
bool isFull = false;
|
||||
bool isCharging = false;
|
||||
bool isPowerPresent = false;
|
||||
bool firstMeasurement = true;
|
||||
|
||||
void SaadcInit();
|
||||
|
||||
|
@ -55,7 +55,7 @@ bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const
|
||||
return true;
|
||||
}
|
||||
|
||||
if (service != nullptr && ble_uuid_cmp(((ble_uuid_t*) &ansServiceUuid), &service->uuid.u) == 0) {
|
||||
if (service != nullptr && ble_uuid_cmp(&ansServiceUuid.u, &service->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS discovered : 0x%x - 0x%x", service->start_handle, service->end_handle);
|
||||
ansStartHandle = service->start_handle;
|
||||
ansEndHandle = service->end_handle;
|
||||
@ -80,21 +80,21 @@ int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connection
|
||||
} else
|
||||
onServiceDiscovered(connectionHandle);
|
||||
} else {
|
||||
if (characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*) &supportedNewAlertCategoryUuid), &characteristic->uuid.u) == 0) {
|
||||
if (characteristic != nullptr && ble_uuid_cmp(&supportedNewAlertCategoryUuid.u, &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : supportedNewAlertCategoryUuid");
|
||||
supportedNewAlertCategoryHandle = characteristic->val_handle;
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*) &supportedUnreadAlertCategoryUuid), &characteristic->uuid.u) == 0) {
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(&supportedUnreadAlertCategoryUuid.u, &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : supportedUnreadAlertCategoryUuid");
|
||||
supportedUnreadAlertCategoryHandle = characteristic->val_handle;
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*) &newAlertUuid), &characteristic->uuid.u) == 0) {
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(&newAlertUuid.u, &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : newAlertUuid");
|
||||
newAlertHandle = characteristic->val_handle;
|
||||
newAlertDefHandle = characteristic->def_handle;
|
||||
isCharacteristicDiscovered = true;
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*) &unreadAlertStatusUuid), &characteristic->uuid.u) == 0) {
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(&unreadAlertStatusUuid.u, &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : unreadAlertStatusUuid");
|
||||
unreadAlertStatusHandle = characteristic->val_handle;
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*) &controlPointUuid), &characteristic->uuid.u) == 0) {
|
||||
} else if (characteristic != nullptr && ble_uuid_cmp(&controlPointUuid.u, &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : controlPointUuid");
|
||||
controlPointHandle = characteristic->val_handle;
|
||||
} else
|
||||
@ -119,7 +119,7 @@ int AlertNotificationClient::OnDescriptorDiscoveryEventCallback(uint16_t connect
|
||||
uint16_t characteristicValueHandle,
|
||||
const ble_gatt_dsc* descriptor) {
|
||||
if (error->status == 0) {
|
||||
if (characteristicValueHandle == newAlertHandle && ble_uuid_cmp(((ble_uuid_t*) &newAlertUuid), &descriptor->uuid.u)) {
|
||||
if (characteristicValueHandle == newAlertHandle && ble_uuid_cmp(&newAlertUuid.u, &descriptor->uuid.u)) {
|
||||
if (newAlertDescriptorHandle == 0) {
|
||||
NRF_LOG_INFO("ANS Descriptor discovered : %d", descriptor->handle);
|
||||
newAlertDescriptorHandle = descriptor->handle;
|
||||
|
@ -26,11 +26,8 @@ void AlertNotificationService::Init() {
|
||||
}
|
||||
|
||||
AlertNotificationService::AlertNotificationService(System::SystemTask& systemTask, NotificationManager& notificationManager)
|
||||
: characteristicDefinition {{.uuid = (ble_uuid_t*) &ansCharUuid,
|
||||
.access_cb = AlertNotificationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE},
|
||||
{.uuid = (ble_uuid_t*) ¬ificationEventUuid,
|
||||
: characteristicDefinition {{.uuid = &ansCharUuid.u, .access_cb = AlertNotificationCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE},
|
||||
{.uuid = ¬ificationEventUuid.u,
|
||||
.access_cb = AlertNotificationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||
@ -39,7 +36,7 @@ AlertNotificationService::AlertNotificationService(System::SystemTask& systemTas
|
||||
serviceDefinition {
|
||||
{/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t*) &ansUuid,
|
||||
.uuid = &ansUuid.u,
|
||||
.characteristics = characteristicDefinition},
|
||||
{0},
|
||||
},
|
||||
@ -123,4 +120,4 @@ void AlertNotificationService::MuteIncomingCall() {
|
||||
}
|
||||
|
||||
ble_gattc_notify_custom(connectionHandle, eventHandle, om);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ int BatteryInformationServiceCallback(uint16_t conn_handle, uint16_t attr_handle
|
||||
|
||||
BatteryInformationService::BatteryInformationService(Controllers::Battery& batteryController)
|
||||
: batteryController {batteryController},
|
||||
characteristicDefinition {{.uuid = (ble_uuid_t*) &batteryLevelUuid,
|
||||
characteristicDefinition {{.uuid = &batteryLevelUuid.u,
|
||||
.access_cb = BatteryInformationServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
|
||||
@ -23,7 +23,7 @@ BatteryInformationService::BatteryInformationService(Controllers::Battery& batte
|
||||
serviceDefinition {
|
||||
{/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t*) &batteryInformationServiceUuid,
|
||||
.uuid = &batteryInformationServiceUuid.u,
|
||||
.characteristics = characteristicDefinition},
|
||||
{0},
|
||||
} {
|
||||
@ -43,7 +43,7 @@ int BatteryInformationService::OnBatteryServiceRequested(uint16_t connectionHand
|
||||
ble_gatt_access_ctxt* context) {
|
||||
if (attributeHandle == batteryLevelHandle) {
|
||||
NRF_LOG_INFO("BATTERY : handle = %d", batteryLevelHandle);
|
||||
static uint8_t batteryValue = batteryController.PercentRemaining();
|
||||
uint8_t batteryValue = batteryController.PercentRemaining();
|
||||
int res = os_mbuf_append(context->om, &batteryValue, 1);
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ bool CurrentTimeClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_ga
|
||||
return true;
|
||||
}
|
||||
|
||||
if (service != nullptr && ble_uuid_cmp(((ble_uuid_t*) &ctsServiceUuid), &service->uuid.u) == 0) {
|
||||
if (service != nullptr && ble_uuid_cmp(&ctsServiceUuid.u, &service->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("CTS discovered : 0x%x - 0x%x", service->start_handle, service->end_handle);
|
||||
isDiscovered = true;
|
||||
ctsStartHandle = service->start_handle;
|
||||
@ -72,7 +72,7 @@ int CurrentTimeClient::OnCharacteristicDiscoveryEvent(uint16_t conn_handle,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*) ¤tTimeCharacteristicUuid), &characteristic->uuid.u) == 0) {
|
||||
if (characteristic != nullptr && ble_uuid_cmp(¤tTimeCharacteristicUuid.u, &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("CTS Characteristic discovered : 0x%x", characteristic->val_handle);
|
||||
isCharacteristicDiscovered = true;
|
||||
currentTimeHandle = characteristic->val_handle;
|
||||
|
@ -53,7 +53,7 @@ int CurrentTimeService::OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handl
|
||||
}
|
||||
|
||||
CurrentTimeService::CurrentTimeService(DateTime& dateTimeController)
|
||||
: characteristicDefinition {{.uuid = (ble_uuid_t*) &ctChrUuid,
|
||||
: characteristicDefinition {{.uuid = &ctChrUuid.u,
|
||||
.access_cb = CTSCallback,
|
||||
|
||||
.arg = this,
|
||||
@ -62,7 +62,7 @@ CurrentTimeService::CurrentTimeService(DateTime& dateTimeController)
|
||||
serviceDefinition {
|
||||
{/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t*) &ctsUuid,
|
||||
.uuid = &ctsUuid.u,
|
||||
.characteristics = characteristicDefinition},
|
||||
{0},
|
||||
},
|
||||
|
@ -56,37 +56,37 @@ int DeviceInformationService::OnDeviceInfoRequested(uint16_t conn_handle, uint16
|
||||
|
||||
DeviceInformationService::DeviceInformationService()
|
||||
: characteristicDefinition {{
|
||||
.uuid = (ble_uuid_t*) &manufacturerNameUuid,
|
||||
.uuid = &manufacturerNameUuid.u,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t*) &modelNumberUuid,
|
||||
.uuid = &modelNumberUuid.u,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t*) &serialNumberUuid,
|
||||
.uuid = &serialNumberUuid.u,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t*) &fwRevisionUuid,
|
||||
.uuid = &fwRevisionUuid.u,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t*) &hwRevisionUuid,
|
||||
.uuid = &hwRevisionUuid.u,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t*) &swRevisionUuid,
|
||||
.uuid = &swRevisionUuid.u,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
@ -95,7 +95,7 @@ DeviceInformationService::DeviceInformationService()
|
||||
serviceDefinition {
|
||||
{/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t*) &deviceInfoUuid,
|
||||
.uuid = &deviceInfoUuid.u,
|
||||
.characteristics = characteristicDefinition},
|
||||
{0},
|
||||
} {
|
||||
|
@ -33,21 +33,21 @@ DfuService::DfuService(Pinetime::System::SystemTask& systemTask,
|
||||
bleController {bleController},
|
||||
dfuImage {spiNorFlash},
|
||||
characteristicDefinition {{
|
||||
.uuid = (ble_uuid_t*) &packetCharacteristicUuid,
|
||||
.uuid = &packetCharacteristicUuid.u,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
|
||||
.val_handle = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t*) &controlPointCharacteristicUuid,
|
||||
.uuid = &controlPointCharacteristicUuid.u,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t*) &revisionCharacteristicUuid,
|
||||
.uuid = &revisionCharacteristicUuid.u,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
@ -60,7 +60,7 @@ DfuService::DfuService(Pinetime::System::SystemTask& systemTask,
|
||||
serviceDefinition {
|
||||
{/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t*) &serviceUuid,
|
||||
.uuid = &serviceUuid.u,
|
||||
.characteristics = characteristicDefinition},
|
||||
{0},
|
||||
} {
|
||||
@ -81,9 +81,9 @@ int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandl
|
||||
xTimerStart(timeoutTimer, 0);
|
||||
}
|
||||
|
||||
ble_gatts_find_chr((ble_uuid_t*) &serviceUuid, (ble_uuid_t*) &packetCharacteristicUuid, nullptr, &packetCharacteristicHandle);
|
||||
ble_gatts_find_chr((ble_uuid_t*) &serviceUuid, (ble_uuid_t*) &controlPointCharacteristicUuid, nullptr, &controlPointCharacteristicHandle);
|
||||
ble_gatts_find_chr((ble_uuid_t*) &serviceUuid, (ble_uuid_t*) &revisionCharacteristicUuid, nullptr, &revisionCharacteristicHandle);
|
||||
ble_gatts_find_chr(&serviceUuid.u, &packetCharacteristicUuid.u, nullptr, &packetCharacteristicHandle);
|
||||
ble_gatts_find_chr(&serviceUuid.u, &controlPointCharacteristicUuid.u, nullptr, &controlPointCharacteristicHandle);
|
||||
ble_gatts_find_chr(&serviceUuid.u, &revisionCharacteristicUuid.u, nullptr, &revisionCharacteristicHandle);
|
||||
|
||||
if (attributeHandle == packetCharacteristicHandle) {
|
||||
if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
|
||||
@ -164,10 +164,10 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) {
|
||||
|
||||
if ((nbPacketReceived % nbPacketsToNotify) == 0 && bytesReceived != applicationSize) {
|
||||
uint8_t data[5] {static_cast<uint8_t>(Opcodes::PacketReceiptNotification),
|
||||
(uint8_t) (bytesReceived & 0x000000FFu),
|
||||
(uint8_t) (bytesReceived >> 8u),
|
||||
(uint8_t) (bytesReceived >> 16u),
|
||||
(uint8_t) (bytesReceived >> 24u)};
|
||||
(uint8_t)(bytesReceived & 0x000000FFu),
|
||||
(uint8_t)(bytesReceived >> 8u),
|
||||
(uint8_t)(bytesReceived >> 16u),
|
||||
(uint8_t)(bytesReceived >> 24u)};
|
||||
NRF_LOG_INFO("[DFU] -> Send packet notification: %d bytes received", bytesReceived);
|
||||
notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 5);
|
||||
}
|
||||
@ -422,9 +422,9 @@ uint16_t DfuService::DfuImage::ComputeCrc(uint8_t const* p_data, uint32_t size,
|
||||
uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc;
|
||||
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
crc = (uint8_t) (crc >> 8) | (crc << 8);
|
||||
crc = (uint8_t)(crc >> 8) | (crc << 8);
|
||||
crc ^= p_data[i];
|
||||
crc ^= (uint8_t) (crc & 0xFF) >> 4;
|
||||
crc ^= (uint8_t)(crc & 0xFF) >> 4;
|
||||
crc ^= (crc << 8) << 4;
|
||||
crc ^= ((crc & 0xFF) << 4) << 1;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ constexpr ble_uuid16_t HeartRateService::heartRateServiceUuid;
|
||||
constexpr ble_uuid16_t HeartRateService::heartRateMeasurementUuid;
|
||||
|
||||
namespace {
|
||||
int HeartRateServiceServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
int HeartRateServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
auto* heartRateService = static_cast<HeartRateService*>(arg);
|
||||
return heartRateService->OnHeartRateRequested(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
@ -18,8 +18,8 @@ namespace {
|
||||
HeartRateService::HeartRateService(Pinetime::System::SystemTask& system, Controllers::HeartRateController& heartRateController)
|
||||
: system {system},
|
||||
heartRateController {heartRateController},
|
||||
characteristicDefinition {{.uuid = (ble_uuid_t*) &heartRateMeasurementUuid,
|
||||
.access_cb = HeartRateServiceServiceCallback,
|
||||
characteristicDefinition {{.uuid = &heartRateMeasurementUuid.u,
|
||||
.access_cb = HeartRateServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = &heartRateMeasurementHandle},
|
||||
@ -27,7 +27,7 @@ HeartRateService::HeartRateService(Pinetime::System::SystemTask& system, Control
|
||||
serviceDefinition {
|
||||
{/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t*) &heartRateServiceUuid,
|
||||
.uuid = &heartRateServiceUuid.u,
|
||||
.characteristics = characteristicDefinition},
|
||||
{0},
|
||||
} {
|
||||
@ -56,6 +56,8 @@ int HeartRateService::OnHeartRateRequested(uint16_t connectionHandle, uint16_t a
|
||||
}
|
||||
|
||||
void HeartRateService::OnNewHeartRateValue(uint8_t heartRateValue) {
|
||||
if(!heartRateMeasurementNotificationEnable) return;
|
||||
|
||||
uint8_t buffer[2] = {0, heartRateController.HeartRate()}; // [0] = flags, [1] = hr value
|
||||
auto* om = ble_hs_mbuf_from_flat(buffer, 2);
|
||||
|
||||
@ -67,3 +69,13 @@ void HeartRateService::OnNewHeartRateValue(uint8_t heartRateValue) {
|
||||
|
||||
ble_gattc_notify_custom(connectionHandle, heartRateMeasurementHandle, om);
|
||||
}
|
||||
|
||||
void HeartRateService::SubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle) {
|
||||
if(attributeHandle == heartRateMeasurementHandle)
|
||||
heartRateMeasurementNotificationEnable = true;
|
||||
}
|
||||
|
||||
void HeartRateService::UnsubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle) {
|
||||
if(attributeHandle == heartRateMeasurementHandle)
|
||||
heartRateMeasurementNotificationEnable = false;
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||
#define max
|
||||
#include <host/ble_gap.h>
|
||||
#include <atomic>
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
@ -18,6 +19,9 @@ namespace Pinetime {
|
||||
int OnHeartRateRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
|
||||
void OnNewHeartRateValue(uint8_t hearRateValue);
|
||||
|
||||
void SubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle);
|
||||
void UnsubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle);
|
||||
|
||||
private:
|
||||
Pinetime::System::SystemTask& system;
|
||||
Controllers::HeartRateController& heartRateController;
|
||||
@ -28,10 +32,11 @@ namespace Pinetime {
|
||||
|
||||
static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId};
|
||||
|
||||
struct ble_gatt_chr_def characteristicDefinition[3];
|
||||
struct ble_gatt_chr_def characteristicDefinition[2];
|
||||
struct ble_gatt_svc_def serviceDefinition[2];
|
||||
|
||||
uint16_t heartRateMeasurementHandle;
|
||||
std::atomic_bool heartRateMeasurementNotificationEnable {false};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ ImmediateAlertService::ImmediateAlertService(Pinetime::System::SystemTask& syste
|
||||
Pinetime::Controllers::NotificationManager& notificationManager)
|
||||
: systemTask {systemTask},
|
||||
notificationManager {notificationManager},
|
||||
characteristicDefinition {{.uuid = (ble_uuid_t*) &alertLevelUuid,
|
||||
characteristicDefinition {{.uuid = &alertLevelUuid.u,
|
||||
.access_cb = AlertLevelCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
|
||||
@ -41,7 +41,7 @@ ImmediateAlertService::ImmediateAlertService(Pinetime::System::SystemTask& syste
|
||||
serviceDefinition {
|
||||
{/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t*) &immediateAlertServiceUuid,
|
||||
.uuid = &immediateAlertServiceUuid.u,
|
||||
.characteristics = characteristicDefinition},
|
||||
{0},
|
||||
} {
|
||||
@ -72,4 +72,4 @@ int ImmediateAlertService::OnAlertLevelChanged(uint16_t connectionHandle, uint16
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
124
src/components/ble/MotionService.cpp
Normal file
124
src/components/ble/MotionService.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include "MotionService.h"
|
||||
#include "components/motion//MotionController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
namespace {
|
||||
// 0002yyxx-78fc-48fe-8e23-433b3a1942d0
|
||||
constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
|
||||
return ble_uuid128_t{
|
||||
.u = {.type = BLE_UUID_TYPE_128},
|
||||
.value = { 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, x, y, 0x03, 0x00 }
|
||||
};
|
||||
}
|
||||
|
||||
// 00020000-78fc-48fe-8e23-433b3a1942d0
|
||||
constexpr ble_uuid128_t BaseUuid() {
|
||||
return CharUuid(0x00, 0x00);
|
||||
}
|
||||
|
||||
constexpr ble_uuid128_t motionServiceUuid {BaseUuid()};
|
||||
constexpr ble_uuid128_t stepCountCharUuid {CharUuid(0x01, 0x00)};
|
||||
constexpr ble_uuid128_t motionValuesCharUuid {CharUuid(0x02, 0x00)};
|
||||
|
||||
int MotionServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
auto* motionService = static_cast<MotionService*>(arg);
|
||||
return motionService->OnStepCountRequested(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Refactoring - remove dependency to SystemTask
|
||||
MotionService::MotionService(Pinetime::System::SystemTask& system, Controllers::MotionController& motionController)
|
||||
: system {system},
|
||||
motionController {motionController},
|
||||
characteristicDefinition {{.uuid = &stepCountCharUuid.u,
|
||||
.access_cb = MotionServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = &stepCountHandle},
|
||||
{.uuid = &motionValuesCharUuid.u,
|
||||
.access_cb = MotionServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = &motionValuesHandle},
|
||||
{0}},
|
||||
serviceDefinition {
|
||||
{
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = &motionServiceUuid.u,
|
||||
.characteristics = characteristicDefinition
|
||||
},
|
||||
{0},
|
||||
} {
|
||||
// TODO refactor to prevent this loop dependency (service depends on controller and controller depends on service)
|
||||
motionController.SetService(this);
|
||||
}
|
||||
|
||||
void MotionService::Init() {
|
||||
int res = 0;
|
||||
res = ble_gatts_count_cfg(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
|
||||
res = ble_gatts_add_svcs(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
int MotionService::OnStepCountRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
|
||||
if (attributeHandle == stepCountHandle) {
|
||||
NRF_LOG_INFO("Motion-stepcount : handle = %d", stepCountHandle);
|
||||
uint32_t buffer = motionController.NbSteps();
|
||||
|
||||
int res = os_mbuf_append(context->om, &buffer, 4);
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
} else if(attributeHandle == motionValuesHandle) {
|
||||
int16_t buffer[3] = { motionController.X(), motionController.Y(), motionController.Z() };
|
||||
|
||||
int res = os_mbuf_append(context->om, buffer, 3 * sizeof(int16_t));
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MotionService::OnNewStepCountValue(uint32_t stepCount) {
|
||||
if(!stepCountNoficationEnabled) return;
|
||||
|
||||
uint32_t buffer = stepCount;
|
||||
auto* om = ble_hs_mbuf_from_flat(&buffer, 4);
|
||||
|
||||
uint16_t connectionHandle = system.nimble().connHandle();
|
||||
|
||||
if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ble_gattc_notify_custom(connectionHandle, stepCountHandle, om);
|
||||
}
|
||||
void MotionService::OnNewMotionValues(int16_t x, int16_t y, int16_t z) {
|
||||
if(!motionValuesNoficationEnabled) return;
|
||||
|
||||
int16_t buffer[3] = { motionController.X(), motionController.Y(), motionController.Z() };
|
||||
auto* om = ble_hs_mbuf_from_flat(buffer, 3 * sizeof(int16_t));
|
||||
|
||||
uint16_t connectionHandle = system.nimble().connHandle();
|
||||
|
||||
if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ble_gattc_notify_custom(connectionHandle, motionValuesHandle, om);
|
||||
}
|
||||
|
||||
void MotionService::SubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle) {
|
||||
if(attributeHandle == stepCountHandle)
|
||||
stepCountNoficationEnabled = true;
|
||||
else if(attributeHandle == motionValuesHandle)
|
||||
motionValuesNoficationEnabled = true;
|
||||
}
|
||||
|
||||
void MotionService::UnsubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle) {
|
||||
if(attributeHandle == stepCountHandle)
|
||||
stepCountNoficationEnabled = false;
|
||||
else if(attributeHandle == motionValuesHandle)
|
||||
motionValuesNoficationEnabled = false;
|
||||
}
|
39
src/components/ble/MotionService.h
Normal file
39
src/components/ble/MotionService.h
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||
#define max
|
||||
#include <host/ble_gap.h>
|
||||
#include <atomic>
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
namespace Pinetime {
|
||||
namespace System {
|
||||
class SystemTask;
|
||||
}
|
||||
namespace Controllers {
|
||||
class MotionController;
|
||||
class MotionService {
|
||||
public:
|
||||
MotionService(Pinetime::System::SystemTask& system, Controllers::MotionController& motionController);
|
||||
void Init();
|
||||
int OnStepCountRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
|
||||
void OnNewStepCountValue(uint32_t stepCount);
|
||||
void OnNewMotionValues(int16_t x, int16_t y, int16_t z);
|
||||
|
||||
void SubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle);
|
||||
void UnsubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle);
|
||||
|
||||
private:
|
||||
Pinetime::System::SystemTask& system;
|
||||
Controllers::MotionController& motionController;
|
||||
|
||||
struct ble_gatt_chr_def characteristicDefinition[3];
|
||||
struct ble_gatt_svc_def serviceDefinition[2];
|
||||
|
||||
uint16_t stepCountHandle;
|
||||
uint16_t motionValuesHandle;
|
||||
std::atomic_bool stepCountNoficationEnabled {false};
|
||||
std::atomic_bool motionValuesNoficationEnabled {false};
|
||||
};
|
||||
}
|
||||
}
|
@ -20,54 +20,45 @@
|
||||
|
||||
#include "systemtask/SystemTask.h"
|
||||
|
||||
int NAVCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
auto navService = static_cast<Pinetime::Controllers::NavigationService*>(arg);
|
||||
return navService->OnCommand(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
namespace {
|
||||
// 0001yyxx-78fc-48fe-8e23-433b3a1942d0
|
||||
constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
|
||||
return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
|
||||
.value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, x, y, 0x01, 0x00}};
|
||||
}
|
||||
|
||||
// 00010000-78fc-48fe-8e23-433b3a1942d0
|
||||
constexpr ble_uuid128_t BaseUuid() {
|
||||
return CharUuid(0x00, 0x00);
|
||||
}
|
||||
|
||||
constexpr ble_uuid128_t navUuid {BaseUuid()};
|
||||
|
||||
constexpr ble_uuid128_t navFlagCharUuid {CharUuid(0x01, 0x00)};
|
||||
constexpr ble_uuid128_t navNarrativeCharUuid {CharUuid(0x02, 0x00)};
|
||||
constexpr ble_uuid128_t navManDistCharUuid {CharUuid(0x03, 0x00)};
|
||||
constexpr ble_uuid128_t navProgressCharUuid {CharUuid(0x04, 0x00)};
|
||||
|
||||
int NAVCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
auto navService = static_cast<Pinetime::Controllers::NavigationService*>(arg);
|
||||
return navService->OnCommand(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Pinetime::Controllers::NavigationService::NavigationService(Pinetime::System::SystemTask& system) : m_system(system) {
|
||||
navUuid.value[14] = navId[0];
|
||||
navUuid.value[15] = navId[1];
|
||||
|
||||
navFlagCharUuid.value[12] = navFlagCharId[0];
|
||||
navFlagCharUuid.value[13] = navFlagCharId[1];
|
||||
navFlagCharUuid.value[14] = navId[0];
|
||||
navFlagCharUuid.value[15] = navId[1];
|
||||
|
||||
navNarrativeCharUuid.value[12] = navNarrativeCharId[0];
|
||||
navNarrativeCharUuid.value[13] = navNarrativeCharId[1];
|
||||
navNarrativeCharUuid.value[14] = navId[0];
|
||||
navNarrativeCharUuid.value[15] = navId[1];
|
||||
|
||||
navManDistCharUuid.value[12] = navManDistCharId[0];
|
||||
navManDistCharUuid.value[13] = navManDistCharId[1];
|
||||
navManDistCharUuid.value[14] = navId[0];
|
||||
navManDistCharUuid.value[15] = navId[1];
|
||||
|
||||
navProgressCharUuid.value[12] = navProgressCharId[0];
|
||||
navProgressCharUuid.value[13] = navProgressCharId[1];
|
||||
navProgressCharUuid.value[14] = navId[0];
|
||||
navProgressCharUuid.value[15] = navId[1];
|
||||
|
||||
characteristicDefinition[0] = {
|
||||
.uuid = (ble_uuid_t*) (&navFlagCharUuid), .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
.uuid = &navFlagCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
|
||||
characteristicDefinition[1] = {.uuid = (ble_uuid_t*) (&navNarrativeCharUuid),
|
||||
.access_cb = NAVCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
characteristicDefinition[2] = {.uuid = (ble_uuid_t*) (&navManDistCharUuid),
|
||||
.access_cb = NAVCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
characteristicDefinition[3] = {.uuid = (ble_uuid_t*) (&navProgressCharUuid),
|
||||
.access_cb = NAVCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
characteristicDefinition[1] = {
|
||||
.uuid = &navNarrativeCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
characteristicDefinition[2] = {
|
||||
.uuid = &navManDistCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
characteristicDefinition[3] = {
|
||||
.uuid = &navProgressCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
|
||||
|
||||
characteristicDefinition[4] = {0};
|
||||
|
||||
serviceDefinition[0] = {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = (ble_uuid_t*) &navUuid, .characteristics = characteristicDefinition};
|
||||
serviceDefinition[0] = {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &navUuid.u, .characteristics = characteristicDefinition};
|
||||
serviceDefinition[1] = {0};
|
||||
|
||||
m_progress = 0;
|
||||
@ -90,13 +81,13 @@ int Pinetime::Controllers::NavigationService::OnCommand(uint16_t conn_handle, ui
|
||||
data[notifSize] = '\0';
|
||||
os_mbuf_copydata(ctxt->om, 0, notifSize, data);
|
||||
char* s = (char*) &data[0];
|
||||
if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t*) &navFlagCharUuid) == 0) {
|
||||
if (ble_uuid_cmp(ctxt->chr->uuid, &navFlagCharUuid.u) == 0) {
|
||||
m_flag = s;
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t*) &navNarrativeCharUuid) == 0) {
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, &navNarrativeCharUuid.u) == 0) {
|
||||
m_narrative = s;
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t*) &navManDistCharUuid) == 0) {
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, &navManDistCharUuid.u) == 0) {
|
||||
m_manDist = s;
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t*) &navProgressCharUuid) == 0) {
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, &navProgressCharUuid.u) == 0) {
|
||||
m_progress = data[0];
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,6 @@
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
// c7e60000-78fc-48fe-8e23-433b3a1942d0
|
||||
#define NAVIGATION_SERVICE_UUID_BASE \
|
||||
{ 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x00, 0x00, 0x00, 0x00 }
|
||||
|
||||
namespace Pinetime {
|
||||
namespace System {
|
||||
class SystemTask;
|
||||
@ -53,19 +49,6 @@ namespace Pinetime {
|
||||
int getProgress();
|
||||
|
||||
private:
|
||||
static constexpr uint8_t navId[2] = {0x01, 0x00};
|
||||
static constexpr uint8_t navFlagCharId[2] = {0x01, 0x00};
|
||||
static constexpr uint8_t navNarrativeCharId[2] = {0x02, 0x00};
|
||||
static constexpr uint8_t navManDistCharId[2] = {0x03, 0x00};
|
||||
static constexpr uint8_t navProgressCharId[2] = {0x04, 0x00};
|
||||
|
||||
ble_uuid128_t navUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = NAVIGATION_SERVICE_UUID_BASE};
|
||||
|
||||
ble_uuid128_t navFlagCharUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = NAVIGATION_SERVICE_UUID_BASE};
|
||||
ble_uuid128_t navNarrativeCharUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = NAVIGATION_SERVICE_UUID_BASE};
|
||||
ble_uuid128_t navManDistCharUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = NAVIGATION_SERVICE_UUID_BASE};
|
||||
ble_uuid128_t navProgressCharUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = NAVIGATION_SERVICE_UUID_BASE};
|
||||
|
||||
struct ble_gatt_chr_def characteristicDefinition[5];
|
||||
struct ble_gatt_svc_def serviceDefinition[2];
|
||||
|
||||
|
@ -23,7 +23,8 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||
Pinetime::Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Battery& batteryController,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
|
||||
Controllers::HeartRateController& heartRateController)
|
||||
Controllers::HeartRateController& heartRateController,
|
||||
Controllers::MotionController& motionController)
|
||||
: systemTask {systemTask},
|
||||
bleController {bleController},
|
||||
dateTimeController {dateTimeController},
|
||||
@ -39,6 +40,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||
batteryInformationService {batteryController},
|
||||
immediateAlertService {systemTask, notificationManager},
|
||||
heartRateService {systemTask, heartRateController},
|
||||
motionService{systemTask, motionController},
|
||||
serviceDiscovery({¤tTimeClient, &alertNotificationClient}) {
|
||||
}
|
||||
|
||||
@ -81,6 +83,7 @@ void NimbleController::Init() {
|
||||
batteryInformationService.Init();
|
||||
immediateAlertService.Init();
|
||||
heartRateService.Init();
|
||||
motionService.Init();
|
||||
|
||||
int rc;
|
||||
rc = ble_hs_util_ensure_addr(0);
|
||||
@ -215,6 +218,19 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
|
||||
event->subscribe.prev_notify,
|
||||
event->subscribe.cur_notify,
|
||||
event->subscribe.prev_indicate);
|
||||
|
||||
if(event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
|
||||
heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
|
||||
motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
|
||||
}
|
||||
else if(event->subscribe.prev_notify == 0 && event->subscribe.cur_notify == 1) {
|
||||
heartRateService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
|
||||
motionService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
|
||||
}
|
||||
else if(event->subscribe.prev_notify == 1 && event->subscribe.cur_notify == 0) {
|
||||
heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
|
||||
motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "NavigationService.h"
|
||||
#include "ServiceDiscovery.h"
|
||||
#include "HeartRateService.h"
|
||||
#include "MotionService.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Drivers {
|
||||
@ -43,7 +44,8 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Battery& batteryController,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
|
||||
Controllers::HeartRateController& heartRateController);
|
||||
Controllers::HeartRateController& heartRateController,
|
||||
Controllers::MotionController& motionController);
|
||||
void Init();
|
||||
void StartAdvertising();
|
||||
int OnGAPEvent(ble_gap_event* event);
|
||||
@ -95,6 +97,7 @@ namespace Pinetime {
|
||||
BatteryInformationService batteryInformationService;
|
||||
ImmediateAlertService immediateAlertService;
|
||||
HeartRateService heartRateService;
|
||||
MotionService motionService;
|
||||
|
||||
uint8_t addrType; // 1 = Random, 0 = PUBLIC
|
||||
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
|
||||
|
@ -5,6 +5,12 @@
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
namespace {
|
||||
char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
|
||||
char const* MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
|
||||
char const* MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
}
|
||||
|
||||
void DateTime::SetCurrentTime(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> t) {
|
||||
this->currentDateTime = t;
|
||||
UpdateTime(previousSystickCounter); // Update internal state without updating the time
|
||||
@ -80,48 +86,18 @@ void DateTime::UpdateTime(uint32_t systickCounter) {
|
||||
}
|
||||
|
||||
const char* DateTime::MonthShortToString() {
|
||||
return DateTime::MonthsString[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
const char* DateTime::MonthShortToStringLow() {
|
||||
return DateTime::MonthsStringLow[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
const char* DateTime::MonthsToStringLow() {
|
||||
return DateTime::MonthsLow[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
const char* DateTime::DayOfWeekToString() {
|
||||
return DateTime::DaysString[static_cast<uint8_t>(dayOfWeek)];
|
||||
return MonthsString[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
const char* DateTime::DayOfWeekShortToString() {
|
||||
return DateTime::DaysStringShort[static_cast<uint8_t>(dayOfWeek)];
|
||||
return DaysStringShort[static_cast<uint8_t>(dayOfWeek)];
|
||||
}
|
||||
|
||||
const char* DateTime::DayOfWeekToStringLow() {
|
||||
return DateTime::DaysStringLow[static_cast<uint8_t>(dayOfWeek)];
|
||||
}
|
||||
|
||||
const char* DateTime::DayOfWeekShortToStringLow() {
|
||||
return DateTime::DaysStringShortLow[static_cast<uint8_t>(dayOfWeek)];
|
||||
const char* DateTime::MonthShortToStringLow(Months month) {
|
||||
return MonthsStringLow[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
|
||||
this->systemTask = systemTask;
|
||||
}
|
||||
|
||||
char const* DateTime::DaysStringLow[] = {"--", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
|
||||
|
||||
char const* DateTime::DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||
|
||||
char const* DateTime::DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
|
||||
|
||||
char const* DateTime::DaysString[] = {"--", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"};
|
||||
|
||||
char const* DateTime::MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
|
||||
|
||||
char const* DateTime::MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
|
||||
char const* DateTime::MonthsLow[] = {
|
||||
"--", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
|
@ -59,12 +59,8 @@ namespace Pinetime {
|
||||
}
|
||||
|
||||
const char* MonthShortToString();
|
||||
const char* MonthShortToStringLow();
|
||||
const char* MonthsToStringLow();
|
||||
const char* DayOfWeekToString();
|
||||
const char* DayOfWeekShortToString();
|
||||
const char* DayOfWeekToStringLow();
|
||||
const char* DayOfWeekShortToStringLow();
|
||||
static const char* MonthShortToStringLow(Months month);
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
|
||||
return currentDateTime;
|
||||
@ -91,14 +87,6 @@ namespace Pinetime {
|
||||
|
||||
bool isMidnightAlreadyNotified = false;
|
||||
System::SystemTask* systemTask = nullptr;
|
||||
|
||||
static char const* DaysString[];
|
||||
static char const* DaysStringShort[];
|
||||
static char const* DaysStringLow[];
|
||||
static char const* DaysStringShortLow[];
|
||||
static char const* MonthsString[];
|
||||
static char const* MonthsStringLow[];
|
||||
static char const* MonthsLow[];
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ namespace Pinetime {
|
||||
*
|
||||
*/
|
||||
static constexpr size_t startAddress = 0x0B4000;
|
||||
static constexpr size_t size = 0x3C0000;
|
||||
static constexpr size_t size = 0x34C000;
|
||||
static constexpr size_t blockSize = 4096;
|
||||
|
||||
bool resourcesValid = false;
|
||||
|
@ -3,6 +3,14 @@
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps) {
|
||||
if (this->nbSteps != nbSteps && service != nullptr) {
|
||||
service->OnNewStepCountValue(nbSteps);
|
||||
}
|
||||
|
||||
if(service != nullptr && (this->x != x || this->y != y || this->z != z)) {
|
||||
service->OnNewMotionValues(x, y, z);
|
||||
}
|
||||
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
this->z = z;
|
||||
@ -41,3 +49,6 @@ void MotionController::Init(Pinetime::Drivers::Bma421::DeviceTypes types) {
|
||||
default: this->deviceType = DeviceTypes::Unknown; break;
|
||||
}
|
||||
}
|
||||
void MotionController::SetService(Pinetime::Controllers::MotionService* service) {
|
||||
this->service = service;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <drivers/Bma421.h>
|
||||
#include <components/ble/MotionService.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
@ -39,6 +40,7 @@ namespace Pinetime {
|
||||
}
|
||||
|
||||
void Init(Pinetime::Drivers::Bma421::DeviceTypes types);
|
||||
void SetService(Pinetime::Controllers::MotionService* service);
|
||||
|
||||
private:
|
||||
uint32_t nbSteps;
|
||||
@ -48,6 +50,7 @@ namespace Pinetime {
|
||||
int16_t lastYForWakeUp = 0;
|
||||
bool isSensorOk = false;
|
||||
DeviceTypes deviceType = DeviceTypes::Unknown;
|
||||
Pinetime::Controllers::MotionService* service = nullptr;
|
||||
};
|
||||
}
|
||||
}
|
@ -32,7 +32,10 @@ namespace Pinetime {
|
||||
SettingDisplay,
|
||||
SettingWakeUp,
|
||||
SettingSteps,
|
||||
SettingPineTimeStyle
|
||||
SettingPineTimeStyle,
|
||||
SettingSetDate,
|
||||
SettingSetTime,
|
||||
Error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "displayapp/screens/FlashLight.h"
|
||||
#include "displayapp/screens/BatteryInfo.h"
|
||||
#include "displayapp/screens/Steps.h"
|
||||
#include "displayapp/screens/Error.h"
|
||||
|
||||
#include "drivers/Cst816s.h"
|
||||
#include "drivers/St7789.h"
|
||||
@ -44,6 +45,8 @@
|
||||
#include "displayapp/screens/settings/SettingDisplay.h"
|
||||
#include "displayapp/screens/settings/SettingSteps.h"
|
||||
#include "displayapp/screens/settings/SettingPineTimeStyle.h"
|
||||
#include "displayapp/screens/settings/SettingSetDate.h"
|
||||
#include "displayapp/screens/settings/SettingSetTime.h"
|
||||
|
||||
#include "libs/lv_conf.h"
|
||||
|
||||
@ -110,11 +113,16 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
||||
touchHandler {touchHandler} {
|
||||
}
|
||||
|
||||
void DisplayApp::Start() {
|
||||
void DisplayApp::Start(System::BootErrors error) {
|
||||
msgQueue = xQueueCreate(queueSize, itemSize);
|
||||
|
||||
// Start clock when smartwatch boots
|
||||
LoadApp(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||
bootError = error;
|
||||
|
||||
if (error == System::BootErrors::TouchController) {
|
||||
LoadApp(Apps::Error, DisplayApp::FullRefreshDirections::None);
|
||||
} else {
|
||||
LoadApp(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||
}
|
||||
|
||||
if (pdPASS != xTaskCreate(DisplayApp::Process, "displayapp", 800, this, 0, &taskHandle)) {
|
||||
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
|
||||
@ -141,19 +149,15 @@ void DisplayApp::InitHw() {
|
||||
|
||||
void DisplayApp::Refresh() {
|
||||
TickType_t queueTimeout;
|
||||
TickType_t delta;
|
||||
switch (state) {
|
||||
case States::Idle:
|
||||
IdleState();
|
||||
queueTimeout = portMAX_DELAY;
|
||||
break;
|
||||
case States::Running:
|
||||
RunningState();
|
||||
delta = xTaskGetTickCount() - lastWakeTime;
|
||||
if (delta > LV_DISP_DEF_REFR_PERIOD) {
|
||||
delta = LV_DISP_DEF_REFR_PERIOD;
|
||||
if (!currentScreen->IsRunning()) {
|
||||
LoadApp(returnToApp, returnDirection);
|
||||
}
|
||||
queueTimeout = LV_DISP_DEF_REFR_PERIOD - delta;
|
||||
queueTimeout = lv_task_handler();
|
||||
break;
|
||||
default:
|
||||
queueTimeout = portMAX_DELAY;
|
||||
@ -161,9 +165,7 @@ void DisplayApp::Refresh() {
|
||||
}
|
||||
|
||||
Messages msg;
|
||||
bool messageReceived = xQueueReceive(msgQueue, &msg, queueTimeout);
|
||||
lastWakeTime = xTaskGetTickCount();
|
||||
if (messageReceived) {
|
||||
if (xQueueReceive(msgQueue, &msg, queueTimeout)) {
|
||||
switch (msg) {
|
||||
case Messages::DimScreen:
|
||||
// Backup brightness is the brightness to return to after dimming or sleeping
|
||||
@ -258,6 +260,20 @@ void DisplayApp::Refresh() {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Messages::ButtonLongPressed:
|
||||
if (currentApp != Apps::Clock) {
|
||||
LoadApp(Apps::Clock, DisplayApp::FullRefreshDirections::Down);
|
||||
}
|
||||
break;
|
||||
case Messages::ButtonLongerPressed:
|
||||
// Create reboot app and open it instead
|
||||
LoadApp(Apps::SysInfo, DisplayApp::FullRefreshDirections::Up);
|
||||
break;
|
||||
case Messages::ButtonDoubleClicked:
|
||||
if (currentApp != Apps::Notifications && currentApp != Apps::NotificationsPreview) {
|
||||
LoadApp(Apps::Notifications, DisplayApp::FullRefreshDirections::Down);
|
||||
}
|
||||
break;
|
||||
|
||||
case Messages::BleFirmwareUpdateStarted:
|
||||
LoadApp(Apps::FirmwareUpdate, DisplayApp::FullRefreshDirections::Down);
|
||||
@ -279,13 +295,6 @@ void DisplayApp::Refresh() {
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayApp::RunningState() {
|
||||
if (!currentScreen->IsRunning()) {
|
||||
LoadApp(returnToApp, returnDirection);
|
||||
}
|
||||
lv_task_handler();
|
||||
}
|
||||
|
||||
void DisplayApp::StartApp(Apps app, DisplayApp::FullRefreshDirections direction) {
|
||||
nextApp = app;
|
||||
nextDirection = direction;
|
||||
@ -322,6 +331,11 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
|
||||
motionController);
|
||||
break;
|
||||
|
||||
case Apps::Error:
|
||||
currentScreen = std::make_unique<Screens::Error>(this, bootError);
|
||||
ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::None);
|
||||
break;
|
||||
|
||||
case Apps::FirmwareValidation:
|
||||
currentScreen = std::make_unique<Screens::FirmwareValidation>(this, validator);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
@ -378,6 +392,14 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
|
||||
currentScreen = std::make_unique<Screens::SettingSteps>(this, settingsController);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
break;
|
||||
case Apps::SettingSetDate:
|
||||
currentScreen = std::make_unique<Screens::SettingSetDate>(this, dateTimeController);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
break;
|
||||
case Apps::SettingSetTime:
|
||||
currentScreen = std::make_unique<Screens::SettingSetTime>(this, dateTimeController);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
break;
|
||||
case Apps::SettingPineTimeStyle:
|
||||
currentScreen = std::make_unique<Screens::SettingPineTimeStyle>(this, settingsController);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
@ -388,12 +410,12 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
|
||||
break;
|
||||
case Apps::SysInfo:
|
||||
currentScreen = std::make_unique<Screens::SystemInfo>(
|
||||
this, dateTimeController, batteryController, brightnessController, bleController, watchdog, motionController);
|
||||
this, dateTimeController, batteryController, brightnessController, bleController, watchdog, motionController, touchPanel);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
break;
|
||||
case Apps::FlashLight:
|
||||
currentScreen = std::make_unique<Screens::FlashLight>(this, *systemTask, brightnessController);
|
||||
ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::None);
|
||||
ReturnApp(Apps::QuickSettings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
break;
|
||||
case Apps::StopWatch:
|
||||
currentScreen = std::make_unique<Screens::StopWatch>(this, *systemTask);
|
||||
@ -430,9 +452,6 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
|
||||
currentApp = app;
|
||||
}
|
||||
|
||||
void DisplayApp::IdleState() {
|
||||
}
|
||||
|
||||
void DisplayApp::PushMessage(Messages msg) {
|
||||
if (in_isr()) {
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "touchhandler/TouchHandler.h"
|
||||
|
||||
#include "Messages.h"
|
||||
#include "BootErrors.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
||||
@ -61,7 +62,7 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::TimerController& timerController,
|
||||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler);
|
||||
void Start();
|
||||
void Start(System::BootErrors error);
|
||||
void PushMessage(Display::Messages msg);
|
||||
|
||||
void StartApp(Apps app, DisplayApp::FullRefreshDirections direction);
|
||||
@ -107,8 +108,6 @@ namespace Pinetime {
|
||||
TouchEvents returnTouchEvent = TouchEvents::None;
|
||||
|
||||
TouchEvents GetGesture();
|
||||
void RunningState();
|
||||
void IdleState();
|
||||
static void Process(void* instance);
|
||||
void InitHw();
|
||||
void Refresh();
|
||||
@ -118,7 +117,7 @@ namespace Pinetime {
|
||||
|
||||
Apps nextApp = Apps::None;
|
||||
DisplayApp::FullRefreshDirections nextDirection;
|
||||
TickType_t lastWakeTime;
|
||||
System::BootErrors bootError;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <date/date.h>
|
||||
#include <drivers/Watchdog.h>
|
||||
#include <components/motor/MotorController.h>
|
||||
#include "BootErrors.h"
|
||||
#include "TouchEvents.h"
|
||||
#include "Apps.h"
|
||||
#include "Messages.h"
|
||||
@ -58,6 +59,7 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler);
|
||||
void Start();
|
||||
void Start(Pinetime::System::BootErrors){ Start(); };
|
||||
void PushMessage(Pinetime::Applications::Display::Messages msg);
|
||||
void Register(Pinetime::System::SystemTask* systemTask);
|
||||
|
||||
|
@ -9,6 +9,9 @@ namespace Pinetime {
|
||||
UpdateBleConnection,
|
||||
TouchEvent,
|
||||
ButtonPushed,
|
||||
ButtonLongPressed,
|
||||
ButtonLongerPressed,
|
||||
ButtonDoubleClicked,
|
||||
NewNotification,
|
||||
TimerDone,
|
||||
BleFirmwareUpdateStarted,
|
||||
|
@ -120,10 +120,7 @@ void Alarm::OnButtonEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
return;
|
||||
}
|
||||
if (obj == btnMessage) {
|
||||
lv_obj_del(txtMessage);
|
||||
lv_obj_del(btnMessage);
|
||||
txtMessage = nullptr;
|
||||
btnMessage = nullptr;
|
||||
HideInfo();
|
||||
return;
|
||||
}
|
||||
// If any other button was pressed, disable the alarm
|
||||
@ -174,6 +171,14 @@ void Alarm::OnButtonEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Alarm::OnButtonPushed() {
|
||||
if (txtMessage != nullptr && btnMessage != nullptr) {
|
||||
HideInfo();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Alarm::UpdateAlarmTime() {
|
||||
lv_label_set_text_fmt(time, "%02d:%02d", alarmHours, alarmMinutes);
|
||||
alarmController.SetAlarmTime(alarmHours, alarmMinutes);
|
||||
@ -224,6 +229,12 @@ void Alarm::ShowInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
void Alarm::HideInfo() {
|
||||
lv_obj_del(btnMessage);
|
||||
txtMessage = nullptr;
|
||||
btnMessage = nullptr;
|
||||
}
|
||||
|
||||
void Alarm::SetRecurButtonState() {
|
||||
using Pinetime::Controllers::AlarmController;
|
||||
switch (alarmController.Recurrence()) {
|
||||
|
@ -31,6 +31,7 @@ namespace Pinetime {
|
||||
~Alarm() override;
|
||||
void SetAlerting();
|
||||
void OnButtonEvent(lv_obj_t* obj, lv_event_t event);
|
||||
bool OnButtonPushed() override;
|
||||
|
||||
private:
|
||||
bool running;
|
||||
@ -39,13 +40,16 @@ namespace Pinetime {
|
||||
Controllers::AlarmController& alarmController;
|
||||
|
||||
lv_obj_t *time, *btnEnable, *txtEnable, *btnMinutesUp, *btnMinutesDown, *btnHoursUp, *btnHoursDown, *txtMinUp, *txtMinDown,
|
||||
*txtHrUp, *txtHrDown, *btnRecur, *txtRecur, *btnMessage, *txtMessage, *btnInfo, *txtInfo;
|
||||
*txtHrUp, *txtHrDown, *btnRecur, *txtRecur, *btnInfo, *txtInfo;
|
||||
lv_obj_t* txtMessage = nullptr;
|
||||
lv_obj_t* btnMessage = nullptr;
|
||||
|
||||
enum class EnableButtonState { On, Off, Alerting };
|
||||
void SetEnableButtonState();
|
||||
void SetRecurButtonState();
|
||||
void SetAlarm();
|
||||
void ShowInfo();
|
||||
void HideInfo();
|
||||
void ToggleRecurrence();
|
||||
void UpdateAlarmTime();
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ void BatteryInfo::Refresh() {
|
||||
batteryPercent = batteryController.PercentRemaining();
|
||||
batteryVoltage = batteryController.Voltage();
|
||||
|
||||
if (batteryController.IsCharging() and batteryPercent < 100) {
|
||||
if (batteryController.IsCharging()) {
|
||||
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");
|
||||
} else if (batteryPercent == 100) {
|
||||
|
50
src/displayapp/screens/Error.cpp
Normal file
50
src/displayapp/screens/Error.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include "Error.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
void ButtonEventCallback(lv_obj_t* obj, lv_event_t /*event*/) {
|
||||
auto* errorScreen = static_cast<Error*>(obj->user_data);
|
||||
errorScreen->ButtonEventHandler();
|
||||
}
|
||||
}
|
||||
|
||||
Error::Error(Pinetime::Applications::DisplayApp* app, System::BootErrors error)
|
||||
: Screen(app) {
|
||||
|
||||
lv_obj_t* warningLabel = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(warningLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
|
||||
lv_label_set_text_static(warningLabel, "Warning");
|
||||
lv_obj_align(warningLabel, nullptr, LV_ALIGN_IN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t* causeLabel = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_long_mode(causeLabel, LV_LABEL_LONG_BREAK);
|
||||
lv_obj_set_width(causeLabel, LV_HOR_RES);
|
||||
lv_obj_align(causeLabel, warningLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
|
||||
|
||||
if (error == System::BootErrors::TouchController) {
|
||||
lv_label_set_text_static(causeLabel, "Touch controller error detected.");
|
||||
}
|
||||
|
||||
lv_obj_t* tipLabel = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_long_mode(tipLabel, LV_LABEL_LONG_BREAK);
|
||||
lv_obj_set_width(tipLabel, LV_HOR_RES);
|
||||
lv_label_set_text_static(tipLabel, "If you encounter problems and your device is under warranty, contact the devices seller.");
|
||||
lv_obj_align(tipLabel, causeLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
|
||||
|
||||
btnOk = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnOk->user_data = this;
|
||||
lv_obj_set_event_cb(btnOk, ButtonEventCallback);
|
||||
lv_obj_set_size(btnOk, LV_HOR_RES, 50);
|
||||
lv_obj_align(btnOk, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_local_value_str(btnOk, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Proceed");
|
||||
lv_obj_set_style_local_bg_color(btnOk, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
|
||||
}
|
||||
|
||||
void Error::ButtonEventHandler() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
Error::~Error() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
21
src/displayapp/screens/Error.h
Normal file
21
src/displayapp/screens/Error.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Screen.h"
|
||||
#include "BootErrors.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Error : public Screen {
|
||||
public:
|
||||
Error(DisplayApp* app, System::BootErrors error);
|
||||
~Error() override;
|
||||
|
||||
void ButtonEventHandler();
|
||||
private:
|
||||
lv_obj_t* btnOk;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -5,30 +5,41 @@
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
static void event_handler(lv_obj_t* obj, lv_event_t event) {
|
||||
FlashLight* screen = static_cast<FlashLight*>(obj->user_data);
|
||||
void event_handler(lv_obj_t* obj, lv_event_t event) {
|
||||
auto* screen = static_cast<FlashLight*>(obj->user_data);
|
||||
screen->OnClickEvent(obj, event);
|
||||
}
|
||||
}
|
||||
|
||||
FlashLight::FlashLight(Pinetime::Applications::DisplayApp* app,
|
||||
System::SystemTask& systemTask,
|
||||
Controllers::BrightnessController& brightness)
|
||||
Controllers::BrightnessController& brightnessController)
|
||||
: Screen(app),
|
||||
systemTask {systemTask},
|
||||
brightness {brightness}
|
||||
brightnessController {brightnessController}
|
||||
|
||||
{
|
||||
brightness.Backup();
|
||||
brightness.Set(Controllers::BrightnessController::Levels::High);
|
||||
// Set the background
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFFFFFF));
|
||||
brightnessController.Backup();
|
||||
|
||||
flashLight = lv_label_create(lv_scr_act(), NULL);
|
||||
lv_obj_set_style_local_text_color(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
|
||||
brightnessLevel = brightnessController.Level();
|
||||
|
||||
flashLight = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48);
|
||||
lv_label_set_text_static(flashLight, Symbols::highlight);
|
||||
lv_obj_align(flashLight, NULL, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_align(flashLight, nullptr, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
for (auto & i : indicators) {
|
||||
i = lv_obj_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_size(i, 15, 10);
|
||||
lv_obj_set_style_local_border_width(i, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 2);
|
||||
}
|
||||
|
||||
lv_obj_align(indicators[1], flashLight, LV_ALIGN_OUT_BOTTOM_MID, 0, 5);
|
||||
lv_obj_align(indicators[0], indicators[1], LV_ALIGN_OUT_LEFT_MID, -8, 0);
|
||||
lv_obj_align(indicators[2], indicators[1], LV_ALIGN_OUT_RIGHT_MID, 8, 0);
|
||||
|
||||
SetIndicators();
|
||||
SetColors();
|
||||
|
||||
backgroundAction = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_long_mode(backgroundAction, LV_LABEL_LONG_CROP);
|
||||
@ -44,27 +55,80 @@ FlashLight::FlashLight(Pinetime::Applications::DisplayApp* app,
|
||||
|
||||
FlashLight::~FlashLight() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
|
||||
brightness.Restore();
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
|
||||
brightnessController.Restore();
|
||||
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
|
||||
}
|
||||
|
||||
void FlashLight::OnClickEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
if (obj == backgroundAction) {
|
||||
if (event == LV_EVENT_CLICKED) {
|
||||
isOn = !isOn;
|
||||
|
||||
if (isOn) {
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFFFFFF));
|
||||
lv_obj_set_style_local_text_color(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
|
||||
} else {
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
|
||||
lv_obj_set_style_local_text_color(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFFFFFF));
|
||||
}
|
||||
void FlashLight::SetColors() {
|
||||
if (isOn) {
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||
lv_obj_set_style_local_text_color(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
|
||||
for (auto & i : indicators) {
|
||||
lv_obj_set_style_local_bg_color(i, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
|
||||
lv_obj_set_style_local_bg_color(i, LV_OBJ_PART_MAIN, LV_STATE_DISABLED, LV_COLOR_WHITE);
|
||||
lv_obj_set_style_local_border_color(i, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
|
||||
}
|
||||
} else {
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
|
||||
lv_obj_set_style_local_text_color(flashLight, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||
for (auto & i : indicators) {
|
||||
lv_obj_set_style_local_bg_color(i, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||
lv_obj_set_style_local_bg_color(i, LV_OBJ_PART_MAIN, LV_STATE_DISABLED, LV_COLOR_BLACK);
|
||||
lv_obj_set_style_local_border_color(i, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlashLight::SetIndicators() {
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
if (brightnessLevel == BrightnessController::Levels::High) {
|
||||
lv_obj_set_state(indicators[1], LV_STATE_DEFAULT);
|
||||
lv_obj_set_state(indicators[2], LV_STATE_DEFAULT);
|
||||
} else if (brightnessLevel == BrightnessController::Levels::Medium) {
|
||||
lv_obj_set_state(indicators[1], LV_STATE_DEFAULT);
|
||||
lv_obj_set_state(indicators[2], LV_STATE_DISABLED);
|
||||
} else {
|
||||
lv_obj_set_state(indicators[1], LV_STATE_DISABLED);
|
||||
lv_obj_set_state(indicators[2], LV_STATE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
void FlashLight::OnClickEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
if (obj == backgroundAction && event == LV_EVENT_CLICKED) {
|
||||
isOn = !isOn;
|
||||
SetColors();
|
||||
}
|
||||
}
|
||||
|
||||
bool FlashLight::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
if (event == TouchEvents::SwipeLeft) {
|
||||
if (brightnessLevel == BrightnessController::Levels::High) {
|
||||
brightnessLevel = BrightnessController::Levels::Medium;
|
||||
brightnessController.Set(brightnessLevel);
|
||||
SetIndicators();
|
||||
} else if (brightnessLevel == BrightnessController::Levels::Medium) {
|
||||
brightnessLevel = BrightnessController::Levels::Low;
|
||||
brightnessController.Set(brightnessLevel);
|
||||
SetIndicators();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event == TouchEvents::SwipeRight) {
|
||||
if (brightnessLevel == BrightnessController::Levels::Low) {
|
||||
brightnessLevel = BrightnessController::Levels::Medium;
|
||||
brightnessController.Set(brightnessLevel);
|
||||
SetIndicators();
|
||||
} else if (brightnessLevel == BrightnessController::Levels::Medium) {
|
||||
brightnessLevel = BrightnessController::Levels::High;
|
||||
brightnessController.Set(brightnessLevel);
|
||||
SetIndicators();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "Screen.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "components/brightness/BrightnessController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include <cstdint>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
namespace Pinetime {
|
||||
|
||||
@ -20,12 +20,18 @@ namespace Pinetime {
|
||||
void OnClickEvent(lv_obj_t* obj, lv_event_t event);
|
||||
|
||||
private:
|
||||
void SetIndicators();
|
||||
void SetColors();
|
||||
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Controllers::BrightnessController& brightness;
|
||||
Controllers::BrightnessController& brightnessController;
|
||||
|
||||
Controllers::BrightnessController::Levels brightnessLevel;
|
||||
|
||||
lv_obj_t* flashLight;
|
||||
lv_obj_t* backgroundAction;
|
||||
bool isOn = true;
|
||||
lv_obj_t* indicators[3];
|
||||
bool isOn = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ void Paddle::Refresh() {
|
||||
dy *= -1;
|
||||
}
|
||||
|
||||
// checks if it has touched the side (left side)
|
||||
if (ballX >= LV_VER_RES - ballSize - 1) {
|
||||
// checks if it has touched the side (right side)
|
||||
if (ballX >= LV_HOR_RES - ballSize - 1) {
|
||||
dx *= -1;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file is part of the Infinitime distribution (https://github.com/JF002/Infinitime).
|
||||
* This file is part of the Infinitime distribution (https://github.com/InfiniTimeOrg/Infinitime).
|
||||
* Copyright (c) 2021 Kieran Cawthray.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -100,10 +100,7 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
|
||||
lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
|
||||
lv_label_set_text(batteryIcon, Symbols::batteryFull);
|
||||
lv_obj_align(batteryIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 2);
|
||||
|
||||
batteryPlug = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
|
||||
lv_obj_align(batteryPlug, sidebar, LV_ALIGN_IN_TOP_MID, 0, 2);
|
||||
lv_obj_set_auto_realign(batteryIcon, true);
|
||||
|
||||
bleIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
|
||||
@ -205,18 +202,24 @@ PineTimeStyle::~PineTimeStyle() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
void PineTimeStyle::SetBatteryIcon() {
|
||||
auto batteryPercent = batteryPercentRemaining.Get();
|
||||
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
|
||||
}
|
||||
|
||||
void PineTimeStyle::Refresh() {
|
||||
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||
if (batteryPercentRemaining.IsUpdated()) {
|
||||
auto batteryPercent = batteryPercentRemaining.Get();
|
||||
if (batteryController.IsCharging()) {
|
||||
auto isCharging = batteryController.IsCharging() || batteryController.IsPowerPresent();
|
||||
lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging));
|
||||
lv_obj_realign(batteryPlug);
|
||||
lv_label_set_text(batteryIcon, "");
|
||||
isCharging = batteryController.IsCharging();
|
||||
if (isCharging.IsUpdated()) {
|
||||
if (isCharging.Get()) {
|
||||
lv_label_set_text(batteryIcon, Symbols::plug);
|
||||
} else {
|
||||
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
|
||||
lv_label_set_text(batteryPlug, "");
|
||||
SetBatteryIcon();
|
||||
}
|
||||
}
|
||||
if (!isCharging.Get()) {
|
||||
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||
if (batteryPercentRemaining.IsUpdated()) {
|
||||
SetBatteryIcon();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ namespace Pinetime {
|
||||
uint8_t currentDay = 0;
|
||||
|
||||
DirtyValue<uint8_t> batteryPercentRemaining {};
|
||||
DirtyValue<bool> isCharging {};
|
||||
DirtyValue<bool> bleState {};
|
||||
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
|
||||
DirtyValue<bool> motionSensorOk {};
|
||||
@ -58,7 +59,6 @@ namespace Pinetime {
|
||||
lv_obj_t* backgroundLabel;
|
||||
lv_obj_t* batteryIcon;
|
||||
lv_obj_t* bleIcon;
|
||||
lv_obj_t* batteryPlug;
|
||||
lv_obj_t* calendarOuter;
|
||||
lv_obj_t* calendarInner;
|
||||
lv_obj_t* calendarBar1;
|
||||
@ -76,6 +76,8 @@ namespace Pinetime {
|
||||
Controllers::Settings& settingsController;
|
||||
Controllers::MotionController& motionController;
|
||||
|
||||
void SetBatteryIcon();
|
||||
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::Ble& bleController,
|
||||
Pinetime::Drivers::WatchdogView& watchdog,
|
||||
Pinetime::Controllers::MotionController& motionController)
|
||||
Pinetime::Controllers::MotionController& motionController,
|
||||
Pinetime::Drivers::Cst816S& touchPanel)
|
||||
: Screen(app),
|
||||
dateTimeController {dateTimeController},
|
||||
batteryController {batteryController},
|
||||
@ -41,6 +42,7 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
|
||||
bleController {bleController},
|
||||
watchdog {watchdog},
|
||||
motionController{motionController},
|
||||
touchPanel{touchPanel},
|
||||
screens {app,
|
||||
0,
|
||||
{[this]() -> std::unique_ptr<Screen> {
|
||||
@ -141,7 +143,8 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen2() {
|
||||
"#444444 Battery# %d%%/%03imV\n"
|
||||
"#444444 Backlight# %s\n"
|
||||
"#444444 Last reset# %s\n"
|
||||
"#444444 Accel.# %s\n",
|
||||
"#444444 Accel.# %s\n"
|
||||
"#444444 Touch.# %x.%x.%x\n",
|
||||
dateTimeController.Day(),
|
||||
static_cast<uint8_t>(dateTimeController.Month()),
|
||||
dateTimeController.Year(),
|
||||
@ -156,7 +159,10 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen2() {
|
||||
batteryController.Voltage(),
|
||||
brightnessController.ToString(),
|
||||
resetReason,
|
||||
ToString(motionController.DeviceType()));
|
||||
ToString(motionController.DeviceType()),
|
||||
touchPanel.GetChipId(),
|
||||
touchPanel.GetVendorId(),
|
||||
touchPanel.GetFwVersion());
|
||||
lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
|
||||
return std::make_unique<Screens::Label>(1, 5, app, label);
|
||||
}
|
||||
@ -200,11 +206,14 @@ bool SystemInfo::sortById(const TaskStatus_t& lhs, const TaskStatus_t& rhs) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
||||
TaskStatus_t tasksStatus[10];
|
||||
lv_obj_t* infoTask = lv_table_create(lv_scr_act(), NULL);
|
||||
static constexpr uint8_t maxTaskCount = 9;
|
||||
TaskStatus_t tasksStatus[maxTaskCount];
|
||||
|
||||
lv_obj_t* infoTask = lv_table_create(lv_scr_act(), nullptr);
|
||||
lv_table_set_col_cnt(infoTask, 4);
|
||||
lv_table_set_row_cnt(infoTask, 8);
|
||||
lv_obj_set_pos(infoTask, 0, 10);
|
||||
lv_table_set_row_cnt(infoTask, maxTaskCount + 1);
|
||||
lv_obj_set_style_local_pad_all(infoTask, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, 0);
|
||||
lv_obj_set_style_local_border_color(infoTask, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_GRAY);
|
||||
|
||||
lv_table_set_cell_value(infoTask, 0, 0, "#");
|
||||
lv_table_set_col_width(infoTask, 0, 30);
|
||||
@ -215,38 +224,40 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
||||
lv_table_set_cell_value(infoTask, 0, 3, "Free");
|
||||
lv_table_set_col_width(infoTask, 3, 90);
|
||||
|
||||
auto nb = uxTaskGetSystemState(tasksStatus, sizeof(tasksStatus) / sizeof(tasksStatus[0]), nullptr);
|
||||
auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr);
|
||||
std::sort(tasksStatus, tasksStatus + nb, sortById);
|
||||
for (uint8_t i = 0; i < nb && i < 7; i++) {
|
||||
for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) {
|
||||
char buffer[7] = {0};
|
||||
|
||||
lv_table_set_cell_value(infoTask, i + 1, 0, std::to_string(tasksStatus[i].xTaskNumber).c_str());
|
||||
char state[2] = {0};
|
||||
sprintf(buffer, "%lu", tasksStatus[i].xTaskNumber);
|
||||
lv_table_set_cell_value(infoTask, i + 1, 0, buffer);
|
||||
switch (tasksStatus[i].eCurrentState) {
|
||||
case eReady:
|
||||
case eRunning:
|
||||
state[0] = 'R';
|
||||
buffer[0] = 'R';
|
||||
break;
|
||||
case eBlocked:
|
||||
state[0] = 'B';
|
||||
buffer[0] = 'B';
|
||||
break;
|
||||
case eSuspended:
|
||||
state[0] = 'S';
|
||||
buffer[0] = 'S';
|
||||
break;
|
||||
case eDeleted:
|
||||
state[0] = 'D';
|
||||
buffer[0] = 'D';
|
||||
break;
|
||||
default:
|
||||
state[0] = 'I'; // Invalid
|
||||
buffer[0] = 'I'; // Invalid
|
||||
break;
|
||||
}
|
||||
lv_table_set_cell_value(infoTask, i + 1, 1, state);
|
||||
buffer[1] = '\0';
|
||||
lv_table_set_cell_value(infoTask, i + 1, 1, buffer);
|
||||
lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName);
|
||||
if (tasksStatus[i].usStackHighWaterMark < 20) {
|
||||
std::string str1 = std::to_string(tasksStatus[i].usStackHighWaterMark) + " low";
|
||||
lv_table_set_cell_value(infoTask, i + 1, 3, str1.c_str());
|
||||
sprintf(buffer, "%d low", tasksStatus[i].usStackHighWaterMark);
|
||||
} else {
|
||||
lv_table_set_cell_value(infoTask, i + 1, 3, std::to_string(tasksStatus[i].usStackHighWaterMark).c_str());
|
||||
sprintf(buffer, "%d", tasksStatus[i].usStackHighWaterMark);
|
||||
}
|
||||
lv_table_set_cell_value(infoTask, i + 1, 3, buffer);
|
||||
}
|
||||
return std::make_unique<Screens::Label>(3, 5, app, infoTask);
|
||||
}
|
||||
@ -261,7 +272,8 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen5() {
|
||||
"Public License v3\n"
|
||||
"#444444 Source code#\n"
|
||||
"#FFFF00 https://github.com/#\n"
|
||||
"#FFFF00 JF002/InfiniTime#");
|
||||
"#FFFF00 InfiniTimeOrg/#\n"
|
||||
"#FFFF00 InfiniTime#");
|
||||
lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
|
||||
return std::make_unique<Screens::Label>(4, 5, app, label);
|
||||
|
@ -28,7 +28,8 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::Ble& bleController,
|
||||
Pinetime::Drivers::WatchdogView& watchdog,
|
||||
Pinetime::Controllers::MotionController& motionController);
|
||||
Pinetime::Controllers::MotionController& motionController,
|
||||
Pinetime::Drivers::Cst816S& touchPanel);
|
||||
~SystemInfo() override;
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
|
||||
@ -39,6 +40,7 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::Ble& bleController;
|
||||
Pinetime::Drivers::WatchdogView& watchdog;
|
||||
Pinetime::Controllers::MotionController& motionController;
|
||||
Pinetime::Drivers::Cst816S& touchPanel;
|
||||
|
||||
ScreenList<5> screens;
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
#include "Twos.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <string>
|
||||
#include <charconv>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
@ -265,7 +265,9 @@ void Twos::updateGridDisplay(Tile grid[][4]) {
|
||||
for (int row = 0; row < 4; row++) {
|
||||
for (int col = 0; col < 4; col++) {
|
||||
if (grid[row][col].value) {
|
||||
lv_table_set_cell_value(gridDisplay, row, col, (std::to_string(grid[row][col].value)).c_str());
|
||||
char buffer[7];
|
||||
sprintf(buffer, "%d", grid[row][col].value);
|
||||
lv_table_set_cell_value(gridDisplay, row, col, buffer);
|
||||
} else {
|
||||
lv_table_set_cell_value(gridDisplay, row, col, "");
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp* app,
|
||||
batteryIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text(batteryIcon, Symbols::batteryHalf);
|
||||
lv_obj_align(batteryIcon, NULL, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
|
||||
lv_obj_set_auto_realign(batteryIcon, true);
|
||||
|
||||
notificationIcon = lv_label_create(lv_scr_act(), NULL);
|
||||
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00));
|
||||
@ -176,11 +177,31 @@ void WatchFaceAnalog::UpdateClock() {
|
||||
}
|
||||
}
|
||||
|
||||
void WatchFaceAnalog::SetBatteryIcon() {
|
||||
auto batteryPercent = batteryPercentRemaining.Get();
|
||||
if (batteryPercent == 100) {
|
||||
lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
||||
} else {
|
||||
lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||
}
|
||||
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
|
||||
}
|
||||
|
||||
void WatchFaceAnalog::Refresh() {
|
||||
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||
if (batteryPercentRemaining.IsUpdated()) {
|
||||
auto batteryPercent = batteryPercentRemaining.Get();
|
||||
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
|
||||
isCharging = batteryController.IsCharging();
|
||||
if (isCharging.IsUpdated()) {
|
||||
if (isCharging.Get()) {
|
||||
lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
|
||||
lv_label_set_text(batteryIcon, Symbols::plug);
|
||||
} else {
|
||||
SetBatteryIcon();
|
||||
}
|
||||
}
|
||||
if (!isCharging.Get()) {
|
||||
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||
if (batteryPercentRemaining.IsUpdated()) {
|
||||
SetBatteryIcon();
|
||||
}
|
||||
}
|
||||
|
||||
notificationState = notificationManager.AreNewNotificationsAvailable();
|
||||
|
@ -49,6 +49,7 @@ namespace Pinetime {
|
||||
uint8_t currentDay = 0;
|
||||
|
||||
DirtyValue<uint8_t> batteryPercentRemaining {0};
|
||||
DirtyValue<bool> isCharging {};
|
||||
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
|
||||
DirtyValue<bool> notificationState {false};
|
||||
|
||||
@ -81,6 +82,7 @@ namespace Pinetime {
|
||||
Controllers::Settings& settingsController;
|
||||
|
||||
void UpdateClock();
|
||||
void SetBatteryIcon();
|
||||
|
||||
lv_task_t* taskRefresh;
|
||||
};
|
||||
|
@ -102,12 +102,20 @@ WatchFaceDigital::~WatchFaceDigital() {
|
||||
}
|
||||
|
||||
void WatchFaceDigital::Refresh() {
|
||||
powerPresent = batteryController.IsPowerPresent();
|
||||
if (powerPresent.IsUpdated()) {
|
||||
lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(powerPresent.Get()));
|
||||
}
|
||||
|
||||
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||
if (batteryPercentRemaining.IsUpdated()) {
|
||||
auto batteryPercent = batteryPercentRemaining.Get();
|
||||
if (batteryPercent == 100) {
|
||||
lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
||||
} else {
|
||||
lv_obj_set_style_local_text_color(batteryIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
|
||||
}
|
||||
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
|
||||
auto isCharging = batteryController.IsCharging() or batteryController.IsPowerPresent();
|
||||
lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging));
|
||||
}
|
||||
|
||||
bleState = bleController.IsConnected();
|
||||
|
@ -44,6 +44,7 @@ namespace Pinetime {
|
||||
uint8_t currentDay = 0;
|
||||
|
||||
DirtyValue<uint8_t> batteryPercentRemaining {};
|
||||
DirtyValue<bool> powerPresent {};
|
||||
DirtyValue<bool> bleState {};
|
||||
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
|
||||
DirtyValue<bool> motionSensorOk {};
|
||||
|
@ -131,7 +131,7 @@ void QuickSettings::OnButtonEvent(lv_obj_t* object, lv_event_t event) {
|
||||
if (object == btn2 && event == LV_EVENT_CLICKED) {
|
||||
|
||||
running = false;
|
||||
app->StartApp(Apps::FlashLight, DisplayApp::FullRefreshDirections::None);
|
||||
app->StartApp(Apps::FlashLight, DisplayApp::FullRefreshDirections::Up);
|
||||
|
||||
} else if (object == btn1 && event == LV_EVENT_CLICKED) {
|
||||
|
||||
|
198
src/displayapp/screens/settings/SettingSetDate.cpp
Normal file
198
src/displayapp/screens/settings/SettingSetDate.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
#include "SettingSetDate.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <hal/nrf_rtc.h>
|
||||
#include <nrf_log.h>
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
constexpr int16_t POS_X_DAY = -72;
|
||||
constexpr int16_t POS_X_MONTH = 0;
|
||||
constexpr int16_t POS_X_YEAR = 72;
|
||||
constexpr int16_t POS_Y_PLUS = -50;
|
||||
constexpr int16_t POS_Y_TEXT = -6;
|
||||
constexpr int16_t POS_Y_MINUS = 40;
|
||||
|
||||
void event_handler(lv_obj_t * obj, lv_event_t event) {
|
||||
auto* screen = static_cast<SettingSetDate *>(obj->user_data);
|
||||
screen->HandleButtonPress(obj, event);
|
||||
}
|
||||
}
|
||||
|
||||
SettingSetDate::SettingSetDate(Pinetime::Applications::DisplayApp *app, Pinetime::Controllers::DateTime &dateTimeController) :
|
||||
Screen(app),
|
||||
dateTimeController {dateTimeController} {
|
||||
lv_obj_t * title = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(title, "Set current date");
|
||||
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 15, 15);
|
||||
|
||||
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_label_set_text_static(icon, Symbols::clock);
|
||||
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
|
||||
|
||||
dayValue = static_cast<int>(dateTimeController.Day());
|
||||
lblDay = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_fmt(lblDay, "%d", dayValue);
|
||||
lv_label_set_align(lblDay, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblDay, lv_scr_act(), LV_ALIGN_CENTER, POS_X_DAY, POS_Y_TEXT);
|
||||
lv_obj_set_auto_realign(lblDay, true);
|
||||
|
||||
monthValue = static_cast<int>(dateTimeController.Month());
|
||||
lblMonth = lv_label_create(lv_scr_act(), nullptr);
|
||||
UpdateMonthLabel();
|
||||
lv_label_set_align(lblMonth, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblMonth, lv_scr_act(), LV_ALIGN_CENTER, POS_X_MONTH, POS_Y_TEXT);
|
||||
lv_obj_set_auto_realign(lblMonth, true);
|
||||
|
||||
yearValue = static_cast<int>(dateTimeController.Year());
|
||||
if (yearValue < 2021)
|
||||
yearValue = 2021;
|
||||
lblYear = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_fmt(lblYear, "%d", yearValue);
|
||||
lv_label_set_align(lblYear, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblYear, lv_scr_act(), LV_ALIGN_CENTER, POS_X_YEAR, POS_Y_TEXT);
|
||||
lv_obj_set_auto_realign(lblYear, true);
|
||||
|
||||
btnDayPlus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnDayPlus->user_data = this;
|
||||
lv_obj_set_size(btnDayPlus, 50, 40);
|
||||
lv_obj_align(btnDayPlus, lv_scr_act(), LV_ALIGN_CENTER, POS_X_DAY, POS_Y_PLUS);
|
||||
lv_obj_set_style_local_value_str(btnDayPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+");
|
||||
lv_obj_set_event_cb(btnDayPlus, event_handler);
|
||||
|
||||
btnDayMinus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnDayMinus->user_data = this;
|
||||
lv_obj_set_size(btnDayMinus, 50, 40);
|
||||
lv_obj_align(btnDayMinus, lv_scr_act(), LV_ALIGN_CENTER, POS_X_DAY, POS_Y_MINUS);
|
||||
lv_obj_set_style_local_value_str(btnDayMinus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "-");
|
||||
lv_obj_set_event_cb(btnDayMinus, event_handler);
|
||||
|
||||
btnMonthPlus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnMonthPlus->user_data = this;
|
||||
lv_obj_set_size(btnMonthPlus, 50, 40);
|
||||
lv_obj_align(btnMonthPlus, lv_scr_act(), LV_ALIGN_CENTER, POS_X_MONTH, POS_Y_PLUS);
|
||||
lv_obj_set_style_local_value_str(btnMonthPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+");
|
||||
lv_obj_set_event_cb(btnMonthPlus, event_handler);
|
||||
|
||||
btnMonthMinus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnMonthMinus->user_data = this;
|
||||
lv_obj_set_size(btnMonthMinus, 50, 40);
|
||||
lv_obj_align(btnMonthMinus, lv_scr_act(), LV_ALIGN_CENTER, POS_X_MONTH, POS_Y_MINUS);
|
||||
lv_obj_set_style_local_value_str(btnMonthMinus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "-");
|
||||
lv_obj_set_event_cb(btnMonthMinus, event_handler);
|
||||
|
||||
btnYearPlus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnYearPlus->user_data = this;
|
||||
lv_obj_set_size(btnYearPlus, 50, 40);
|
||||
lv_obj_align(btnYearPlus, lv_scr_act(), LV_ALIGN_CENTER, POS_X_YEAR, POS_Y_PLUS);
|
||||
lv_obj_set_style_local_value_str(btnYearPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+");
|
||||
lv_obj_set_event_cb(btnYearPlus, event_handler);
|
||||
|
||||
btnYearMinus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnYearMinus->user_data = this;
|
||||
lv_obj_set_size(btnYearMinus, 50, 40);
|
||||
lv_obj_align(btnYearMinus, lv_scr_act(), LV_ALIGN_CENTER, POS_X_YEAR, POS_Y_MINUS);
|
||||
lv_obj_set_style_local_value_str(btnYearMinus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "-");
|
||||
lv_obj_set_event_cb(btnYearMinus, event_handler);
|
||||
|
||||
btnSetTime = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnSetTime->user_data = this;
|
||||
lv_obj_set_size(btnSetTime, 120, 48);
|
||||
lv_obj_align(btnSetTime, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_local_value_str(btnSetTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Set");
|
||||
lv_obj_set_event_cb(btnSetTime, event_handler);
|
||||
}
|
||||
|
||||
SettingSetDate::~SettingSetDate() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
void SettingSetDate::HandleButtonPress(lv_obj_t *object, lv_event_t event) {
|
||||
if (event != LV_EVENT_CLICKED)
|
||||
return;
|
||||
|
||||
if (object == btnDayPlus) {
|
||||
dayValue++;
|
||||
if (dayValue > MaximumDayOfMonth())
|
||||
dayValue = 1;
|
||||
lv_label_set_text_fmt(lblDay, "%d", dayValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
} else if (object == btnDayMinus) {
|
||||
dayValue--;
|
||||
if (dayValue < 1)
|
||||
dayValue = MaximumDayOfMonth();
|
||||
lv_label_set_text_fmt(lblDay, "%d", dayValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
} else if (object == btnMonthPlus) {
|
||||
monthValue++;
|
||||
if (monthValue > 12)
|
||||
monthValue = 1;
|
||||
UpdateMonthLabel();
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
CheckDay();
|
||||
} else if (object == btnMonthMinus) {
|
||||
monthValue--;
|
||||
if (monthValue < 1)
|
||||
monthValue = 12;
|
||||
UpdateMonthLabel();
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
CheckDay();
|
||||
} else if (object == btnYearPlus) {
|
||||
yearValue++;
|
||||
lv_label_set_text_fmt(lblYear, "%d", yearValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
CheckDay();
|
||||
} else if (object == btnYearMinus) {
|
||||
yearValue--;
|
||||
lv_label_set_text_fmt(lblYear, "%d", yearValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
CheckDay();
|
||||
} else if (object == btnSetTime) {
|
||||
NRF_LOG_INFO("Setting date (manually) to %04d-%02d-%02d", yearValue, monthValue, dayValue);
|
||||
dateTimeController.SetTime(static_cast<uint16_t>(yearValue),
|
||||
static_cast<uint8_t>(monthValue),
|
||||
static_cast<uint8_t>(dayValue),
|
||||
0,
|
||||
dateTimeController.Hours(),
|
||||
dateTimeController.Minutes(),
|
||||
dateTimeController.Seconds(),
|
||||
nrf_rtc_counter_get(portNRF_RTC_REG));
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
int SettingSetDate::MaximumDayOfMonth() const {
|
||||
switch (monthValue) {
|
||||
case 2:
|
||||
if ((((yearValue % 4) == 0) && ((yearValue % 100) != 0)) || ((yearValue % 400) == 0))
|
||||
return 29;
|
||||
return 28;
|
||||
case 4:
|
||||
case 6:
|
||||
case 9:
|
||||
case 11:
|
||||
return 30;
|
||||
default:
|
||||
return 31;
|
||||
}
|
||||
}
|
||||
|
||||
void SettingSetDate::CheckDay() {
|
||||
int maxDay = MaximumDayOfMonth();
|
||||
if (dayValue > maxDay) {
|
||||
dayValue = maxDay;
|
||||
lv_label_set_text_fmt(lblDay, "%d", dayValue);
|
||||
lv_obj_align(lblDay, lv_scr_act(), LV_ALIGN_CENTER, POS_X_DAY, POS_Y_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingSetDate::UpdateMonthLabel() {
|
||||
lv_label_set_text_static(
|
||||
lblMonth, Pinetime::Controllers::DateTime::MonthShortToStringLow(static_cast<Pinetime::Controllers::DateTime::Months>(monthValue)));
|
||||
}
|
41
src/displayapp/screens/settings/SettingSetDate.h
Normal file
41
src/displayapp/screens/settings/SettingSetDate.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class SettingSetDate : public Screen{
|
||||
public:
|
||||
SettingSetDate(DisplayApp* app, Pinetime::Controllers::DateTime &dateTimeController);
|
||||
~SettingSetDate() override;
|
||||
|
||||
void HandleButtonPress(lv_obj_t *object, lv_event_t event);
|
||||
|
||||
private:
|
||||
Controllers::DateTime& dateTimeController;
|
||||
|
||||
int dayValue;
|
||||
int monthValue;
|
||||
int yearValue;
|
||||
lv_obj_t * lblDay;
|
||||
lv_obj_t * lblMonth;
|
||||
lv_obj_t * lblYear;
|
||||
lv_obj_t * btnDayPlus;
|
||||
lv_obj_t * btnDayMinus;
|
||||
lv_obj_t * btnMonthPlus;
|
||||
lv_obj_t * btnMonthMinus;
|
||||
lv_obj_t * btnYearPlus;
|
||||
lv_obj_t * btnYearMinus;
|
||||
lv_obj_t * btnSetTime;
|
||||
|
||||
int MaximumDayOfMonth() const;
|
||||
void CheckDay();
|
||||
void UpdateMonthLabel();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
154
src/displayapp/screens/settings/SettingSetTime.cpp
Normal file
154
src/displayapp/screens/settings/SettingSetTime.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
#include "SettingSetTime.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <hal/nrf_rtc.h>
|
||||
#include <nrf_log.h>
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
constexpr int16_t POS_X_HOURS = -72;
|
||||
constexpr int16_t POS_X_MINUTES = 0;
|
||||
constexpr int16_t POS_X_SECONDS = 72;
|
||||
constexpr int16_t POS_Y_PLUS = -50;
|
||||
constexpr int16_t POS_Y_TEXT = -6;
|
||||
constexpr int16_t POS_Y_MINUS = 40;
|
||||
constexpr int16_t OFS_Y_COLON = -2;
|
||||
|
||||
void event_handler(lv_obj_t * obj, lv_event_t event) {
|
||||
auto* screen = static_cast<SettingSetTime *>(obj->user_data);
|
||||
screen->HandleButtonPress(obj, event);
|
||||
}
|
||||
}
|
||||
|
||||
SettingSetTime::SettingSetTime(Pinetime::Applications::DisplayApp *app, Pinetime::Controllers::DateTime &dateTimeController) :
|
||||
Screen(app),
|
||||
dateTimeController {dateTimeController} {
|
||||
lv_obj_t * title = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(title, "Set current time");
|
||||
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 15, 15);
|
||||
|
||||
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_label_set_text_static(icon, Symbols::clock);
|
||||
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
|
||||
|
||||
hoursValue = static_cast<int>(dateTimeController.Hours());
|
||||
lblHours = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(lblHours, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||
lv_label_set_text_fmt(lblHours, "%02d", hoursValue);
|
||||
lv_label_set_align(lblHours, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblHours, lv_scr_act(), LV_ALIGN_CENTER, POS_X_HOURS, POS_Y_TEXT);
|
||||
lv_obj_set_auto_realign(lblHours, true);
|
||||
|
||||
lv_obj_t * lblColon1 = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(lblColon1, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||
lv_label_set_text_static(lblColon1, ":");
|
||||
lv_label_set_align(lblColon1, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblColon1, lv_scr_act(), LV_ALIGN_CENTER, (POS_X_HOURS + POS_X_MINUTES) / 2, POS_Y_TEXT + OFS_Y_COLON);
|
||||
|
||||
minutesValue = static_cast<int>(dateTimeController.Minutes());
|
||||
lblMinutes = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(lblMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||
lv_label_set_text_fmt(lblMinutes, "%02d", minutesValue);
|
||||
lv_label_set_align(lblMinutes, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblMinutes, lv_scr_act(), LV_ALIGN_CENTER, POS_X_MINUTES, POS_Y_TEXT);
|
||||
lv_obj_set_auto_realign(lblMinutes, true);
|
||||
|
||||
lv_obj_t * lblColon2 = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(lblColon2, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||
lv_label_set_text_static(lblColon2, ":");
|
||||
lv_label_set_align(lblColon2, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblColon2, lv_scr_act(), LV_ALIGN_CENTER, (POS_X_MINUTES + POS_X_SECONDS) / 2, POS_Y_TEXT + OFS_Y_COLON);
|
||||
|
||||
lv_obj_t * lblSeconds = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(lblSeconds, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||
lv_label_set_text_static(lblSeconds, "00");
|
||||
lv_label_set_align(lblSeconds, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(lblSeconds, lv_scr_act(), LV_ALIGN_CENTER, POS_X_SECONDS, POS_Y_TEXT);
|
||||
|
||||
btnHoursPlus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnHoursPlus->user_data = this;
|
||||
lv_obj_set_size(btnHoursPlus, 50, 40);
|
||||
lv_obj_align(btnHoursPlus, lv_scr_act(), LV_ALIGN_CENTER, -72, -50);
|
||||
lv_obj_set_style_local_value_str(btnHoursPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+");
|
||||
lv_obj_set_event_cb(btnHoursPlus, event_handler);
|
||||
|
||||
btnHoursMinus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnHoursMinus->user_data = this;
|
||||
lv_obj_set_size(btnHoursMinus, 50, 40);
|
||||
lv_obj_align(btnHoursMinus, lv_scr_act(), LV_ALIGN_CENTER, -72, 40);
|
||||
lv_obj_set_style_local_value_str(btnHoursMinus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "-");
|
||||
lv_obj_set_event_cb(btnHoursMinus, event_handler);
|
||||
|
||||
btnMinutesPlus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnMinutesPlus->user_data = this;
|
||||
lv_obj_set_size(btnMinutesPlus, 50, 40);
|
||||
lv_obj_align(btnMinutesPlus, lv_scr_act(), LV_ALIGN_CENTER, 0, -50);
|
||||
lv_obj_set_style_local_value_str(btnMinutesPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+");
|
||||
lv_obj_set_event_cb(btnMinutesPlus, event_handler);
|
||||
|
||||
btnMinutesMinus = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnMinutesMinus->user_data = this;
|
||||
lv_obj_set_size(btnMinutesMinus, 50, 40);
|
||||
lv_obj_align(btnMinutesMinus, lv_scr_act(), LV_ALIGN_CENTER, 0, 40);
|
||||
lv_obj_set_style_local_value_str(btnMinutesMinus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "-");
|
||||
lv_obj_set_event_cb(btnMinutesMinus, event_handler);
|
||||
|
||||
btnSetTime = lv_btn_create(lv_scr_act(), nullptr);
|
||||
btnSetTime->user_data = this;
|
||||
lv_obj_set_size(btnSetTime, 120, 48);
|
||||
lv_obj_align(btnSetTime, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_local_value_str(btnSetTime, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "Set");
|
||||
lv_obj_set_event_cb(btnSetTime, event_handler);
|
||||
}
|
||||
|
||||
SettingSetTime::~SettingSetTime() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
void SettingSetTime::HandleButtonPress(lv_obj_t *object, lv_event_t event) {
|
||||
if (event != LV_EVENT_CLICKED)
|
||||
return;
|
||||
|
||||
if (object == btnHoursPlus) {
|
||||
hoursValue++;
|
||||
if (hoursValue > 23)
|
||||
hoursValue = 0;
|
||||
lv_label_set_text_fmt(lblHours, "%02d", hoursValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
} else if (object == btnHoursMinus) {
|
||||
hoursValue--;
|
||||
if (hoursValue < 0)
|
||||
hoursValue = 23;
|
||||
lv_label_set_text_fmt(lblHours, "%02d", hoursValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
} else if (object == btnMinutesPlus) {
|
||||
minutesValue++;
|
||||
if (minutesValue > 59)
|
||||
minutesValue = 0;
|
||||
lv_label_set_text_fmt(lblMinutes, "%02d", minutesValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
} else if (object == btnMinutesMinus) {
|
||||
minutesValue--;
|
||||
if (minutesValue < 0)
|
||||
minutesValue = 59;
|
||||
lv_label_set_text_fmt(lblMinutes, "%02d", minutesValue);
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_RELEASED);
|
||||
} else if (object == btnSetTime) {
|
||||
NRF_LOG_INFO("Setting time (manually) to %02d:%02d:00", hoursValue, minutesValue);
|
||||
dateTimeController.SetTime(dateTimeController.Year(),
|
||||
static_cast<uint8_t>(dateTimeController.Month()),
|
||||
dateTimeController.Day(),
|
||||
static_cast<uint8_t>(dateTimeController.DayOfWeek()),
|
||||
static_cast<uint8_t>(hoursValue),
|
||||
static_cast<uint8_t>(minutesValue),
|
||||
0,
|
||||
nrf_rtc_counter_get(portNRF_RTC_REG));
|
||||
lv_btn_set_state(btnSetTime, LV_BTN_STATE_DISABLED);
|
||||
}
|
||||
}
|
33
src/displayapp/screens/settings/SettingSetTime.h
Normal file
33
src/displayapp/screens/settings/SettingSetTime.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class SettingSetTime : public Screen{
|
||||
public:
|
||||
SettingSetTime(DisplayApp* app, Pinetime::Controllers::DateTime &dateTimeController);
|
||||
~SettingSetTime() override;
|
||||
|
||||
void HandleButtonPress(lv_obj_t *object, lv_event_t event);
|
||||
|
||||
private:
|
||||
Controllers::DateTime& dateTimeController;
|
||||
|
||||
int hoursValue;
|
||||
int minutesValue;
|
||||
lv_obj_t * lblHours;
|
||||
lv_obj_t * lblMinutes;
|
||||
lv_obj_t * btnHoursPlus;
|
||||
lv_obj_t * btnHoursMinus;
|
||||
lv_obj_t * btnMinutesPlus;
|
||||
lv_obj_t * btnMinutesMinus;
|
||||
lv_obj_t * btnSetTime;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -49,9 +49,9 @@ std::unique_ptr<Screen> Settings::CreateScreen2() {
|
||||
|
||||
std::array<Screens::List::Applications, 4> applications {{
|
||||
{Symbols::shoe, "Steps", Apps::SettingSteps},
|
||||
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
|
||||
{Symbols::paintbrush, "PTS Colors", Apps::SettingPineTimeStyle},
|
||||
{Symbols::check, "Firmware", Apps::FirmwareValidation},
|
||||
{Symbols::clock, "Set date", Apps::SettingSetDate},
|
||||
{Symbols::clock, "Set time", Apps::SettingSetTime},
|
||||
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo}
|
||||
}};
|
||||
|
||||
return std::make_unique<Screens::List>(1, 3, app, settingsController, applications);
|
||||
@ -60,10 +60,10 @@ std::unique_ptr<Screen> Settings::CreateScreen2() {
|
||||
std::unique_ptr<Screen> Settings::CreateScreen3() {
|
||||
|
||||
std::array<Screens::List::Applications, 4> applications {{
|
||||
{Symbols::paintbrush, "PTS Colors", Apps::SettingPineTimeStyle},
|
||||
{Symbols::check, "Firmware", Apps::FirmwareValidation},
|
||||
{Symbols::list, "About", Apps::SysInfo},
|
||||
{Symbols::none, "None", Apps::None},
|
||||
{Symbols::none, "None", Apps::None},
|
||||
{Symbols::none, "None", Apps::None},
|
||||
}};
|
||||
|
||||
return std::make_unique<Screens::List>(2, 3, app, settingsController, applications);
|
||||
|
@ -18,10 +18,8 @@ using namespace Pinetime::Drivers;
|
||||
Cst816S::Cst816S(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, twiAddress {twiAddress} {
|
||||
}
|
||||
|
||||
void Cst816S::Init() {
|
||||
bool Cst816S::Init() {
|
||||
nrf_gpio_cfg_output(PinMap::Cst816sReset);
|
||||
nrf_gpio_pin_set(PinMap::Cst816sReset);
|
||||
vTaskDelay(50);
|
||||
nrf_gpio_pin_clear(PinMap::Cst816sReset);
|
||||
vTaskDelay(5);
|
||||
nrf_gpio_pin_set(PinMap::Cst816sReset);
|
||||
@ -34,6 +32,18 @@ void Cst816S::Init() {
|
||||
twiMaster.Read(twiAddress, 0xa7, &dummy, 1);
|
||||
vTaskDelay(5);
|
||||
|
||||
static constexpr uint8_t maxRetries = 3;
|
||||
bool isDeviceOk;
|
||||
uint8_t retries = 0;
|
||||
do {
|
||||
isDeviceOk = CheckDeviceIds();
|
||||
retries++;
|
||||
} while (!isDeviceOk && retries < maxRetries);
|
||||
|
||||
if (!isDeviceOk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
[2] EnConLR - Continuous operation can slide around
|
||||
[1] EnConUD - Slide up and down to enable continuous operation
|
||||
@ -51,10 +61,13 @@ void Cst816S::Init() {
|
||||
*/
|
||||
static constexpr uint8_t irqCtl = 0b01110000;
|
||||
twiMaster.Write(twiAddress, 0xFA, &irqCtl, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Cst816S::TouchInfos Cst816S::GetTouchInfo() {
|
||||
Cst816S::TouchInfos info;
|
||||
uint8_t touchData[7];
|
||||
|
||||
auto ret = twiMaster.Read(twiAddress, 0, touchData, sizeof(touchData));
|
||||
if (ret != TwiMaster::ErrorCodes::NoError) {
|
||||
@ -62,21 +75,35 @@ Cst816S::TouchInfos Cst816S::GetTouchInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
auto nbTouchPoints = touchData[2] & 0x0f;
|
||||
|
||||
auto xHigh = touchData[touchXHighIndex] & 0x0f;
|
||||
auto xLow = touchData[touchXLowIndex];
|
||||
// This can only be 0 or 1
|
||||
uint8_t nbTouchPoints = touchData[touchPointNumIndex] & 0x0f;
|
||||
uint8_t xHigh = touchData[touchXHighIndex] & 0x0f;
|
||||
uint8_t xLow = touchData[touchXLowIndex];
|
||||
uint16_t x = (xHigh << 8) | xLow;
|
||||
|
||||
auto yHigh = touchData[touchYHighIndex] & 0x0f;
|
||||
auto yLow = touchData[touchYLowIndex];
|
||||
uint8_t yHigh = touchData[touchYHighIndex] & 0x0f;
|
||||
uint8_t yLow = touchData[touchYLowIndex];
|
||||
uint16_t y = (yHigh << 8) | yLow;
|
||||
Gestures gesture = static_cast<Gestures>(touchData[gestureIndex]);
|
||||
|
||||
// Validity check
|
||||
if(x >= maxX || y >= maxY ||
|
||||
(gesture != Gestures::None &&
|
||||
gesture != Gestures::SlideDown &&
|
||||
gesture != Gestures::SlideUp &&
|
||||
gesture != Gestures::SlideLeft &&
|
||||
gesture != Gestures::SlideRight &&
|
||||
gesture != Gestures::SingleTap &&
|
||||
gesture != Gestures::DoubleTap &&
|
||||
gesture != Gestures::LongPress)) {
|
||||
info.isValid = false;
|
||||
return info;
|
||||
}
|
||||
|
||||
info.x = x;
|
||||
info.y = y;
|
||||
info.touching = (nbTouchPoints > 0);
|
||||
info.gesture = static_cast<Gestures>(touchData[gestureIndex]);
|
||||
|
||||
info.gesture = gesture;
|
||||
info.isValid = true;
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -94,3 +121,21 @@ void Cst816S::Wakeup() {
|
||||
Init();
|
||||
NRF_LOG_INFO("[TOUCHPANEL] Wakeup");
|
||||
}
|
||||
|
||||
bool Cst816S::CheckDeviceIds() {
|
||||
// There's mixed information about which register contains which information
|
||||
if (twiMaster.Read(twiAddress, 0xA7, &chipId, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
|
||||
chipId = 0xFF;
|
||||
return false;
|
||||
}
|
||||
if (twiMaster.Read(twiAddress, 0xA8, &vendorId, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
|
||||
vendorId = 0xFF;
|
||||
return false;
|
||||
}
|
||||
if (twiMaster.Read(twiAddress, 0xA9, &fwVersion, 1) == TwiMaster::ErrorCodes::TransactionFailed) {
|
||||
fwVersion = 0xFF;
|
||||
return false;
|
||||
}
|
||||
|
||||
return chipId == 0xb4 && vendorId == 0 && fwVersion == 1;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace Pinetime {
|
||||
uint16_t y = 0;
|
||||
Gestures gesture = Gestures::None;
|
||||
bool touching = false;
|
||||
bool isValid = true;
|
||||
bool isValid = false;
|
||||
};
|
||||
|
||||
Cst816S(TwiMaster& twiMaster, uint8_t twiAddress);
|
||||
@ -30,12 +30,23 @@ namespace Pinetime {
|
||||
Cst816S(Cst816S&&) = delete;
|
||||
Cst816S& operator=(Cst816S&&) = delete;
|
||||
|
||||
void Init();
|
||||
bool Init();
|
||||
TouchInfos GetTouchInfo();
|
||||
void Sleep();
|
||||
void Wakeup();
|
||||
|
||||
uint8_t GetChipId() const {
|
||||
return chipId;
|
||||
}
|
||||
uint8_t GetVendorId() const {
|
||||
return vendorId;
|
||||
}
|
||||
uint8_t GetFwVersion() const {
|
||||
return fwVersion;
|
||||
}
|
||||
private:
|
||||
bool CheckDeviceIds();
|
||||
|
||||
// Unused/Unavailable commented out
|
||||
static constexpr uint8_t gestureIndex = 1;
|
||||
static constexpr uint8_t touchPointNumIndex = 2;
|
||||
@ -49,9 +60,15 @@ namespace Pinetime {
|
||||
//static constexpr uint8_t touchXYIndex = 7;
|
||||
//static constexpr uint8_t touchMiscIndex = 8;
|
||||
|
||||
uint8_t touchData[7];
|
||||
static constexpr uint8_t maxX = 240;
|
||||
static constexpr uint8_t maxY = 240;
|
||||
|
||||
TwiMaster& twiMaster;
|
||||
uint8_t twiAddress;
|
||||
|
||||
uint8_t chipId;
|
||||
uint8_t vendorId;
|
||||
uint8_t fwVersion;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -170,16 +170,7 @@ void St7789::Sleep() {
|
||||
|
||||
void St7789::Wakeup() {
|
||||
nrf_gpio_cfg_output(pinDataCommand);
|
||||
// TODO why do we need to reset the controller?
|
||||
HardwareReset();
|
||||
SoftwareReset();
|
||||
SleepOut();
|
||||
ColMod();
|
||||
MemoryDataAccessControl();
|
||||
ColumnAddressSet();
|
||||
RowAddressSet();
|
||||
DisplayInversionOn();
|
||||
NormalModeOn();
|
||||
VerticalScrollStartAddress(verticalScrollingStartAddress);
|
||||
DisplayOn();
|
||||
NRF_LOG_INFO("[LCD] Wakeup")
|
||||
|
23
src/main.cpp
23
src/main.cpp
@ -47,6 +47,7 @@
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "drivers/PinMap.h"
|
||||
#include "touchhandler/TouchHandler.h"
|
||||
#include "buttonhandler/ButtonHandler.h"
|
||||
|
||||
#if NRF_LOG_ENABLED
|
||||
#include "logging/NrfLogger.h"
|
||||
@ -96,8 +97,6 @@ TimerHandle_t debounceTimer;
|
||||
TimerHandle_t debounceChargeTimer;
|
||||
Pinetime::Controllers::Battery batteryController;
|
||||
Pinetime::Controllers::Ble bleController;
|
||||
static constexpr uint8_t pinTouchIrq = Pinetime::PinMap::Cst816sIrq;
|
||||
static constexpr uint8_t pinPowerPresentIrq = Pinetime::PinMap::PowerPresent;
|
||||
|
||||
Pinetime::Controllers::HeartRateController heartRateController;
|
||||
Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController);
|
||||
@ -110,6 +109,7 @@ Pinetime::Controllers::MotionController motionController;
|
||||
Pinetime::Controllers::TimerController timerController;
|
||||
Pinetime::Controllers::AlarmController alarmController {dateTimeController};
|
||||
Pinetime::Controllers::TouchHandler touchHandler(touchPanel, lvgl);
|
||||
Pinetime::Controllers::ButtonHandler buttonHandler;
|
||||
|
||||
Pinetime::Controllers::FS fs {spiNorFlash};
|
||||
Pinetime::Controllers::Settings settingsController {fs};
|
||||
@ -153,7 +153,8 @@ Pinetime::System::SystemTask systemTask(spi,
|
||||
displayApp,
|
||||
heartRateApp,
|
||||
fs,
|
||||
touchHandler);
|
||||
touchHandler,
|
||||
buttonHandler);
|
||||
|
||||
/* Variable Declarations for variables in noinit SRAM
|
||||
Increment NoInit_MagicValue upon adding variables to this area
|
||||
@ -176,11 +177,10 @@ void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action
|
||||
if (pin == Pinetime::PinMap::PowerPresent and action == NRF_GPIOTE_POLARITY_TOGGLE) {
|
||||
xTimerStartFromISR(debounceChargeTimer, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
return;
|
||||
} else if (pin == Pinetime::PinMap::Button) {
|
||||
xTimerStartFromISR(debounceTimer, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
xTimerStartFromISR(debounceTimer, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
void DebounceTimerChargeCallback(TimerHandle_t xTimer) {
|
||||
@ -188,9 +188,8 @@ void DebounceTimerChargeCallback(TimerHandle_t xTimer) {
|
||||
systemTask.PushMessage(Pinetime::System::Messages::OnChargingEvent);
|
||||
}
|
||||
|
||||
void DebounceTimerCallback(TimerHandle_t xTimer) {
|
||||
xTimerStop(xTimer, 0);
|
||||
systemTask.OnButtonPushed();
|
||||
void DebounceTimerCallback(TimerHandle_t /*unused*/) {
|
||||
systemTask.PushMessage(Pinetime::System::Messages::HandleButtonEvent);
|
||||
}
|
||||
|
||||
void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void) {
|
||||
@ -319,8 +318,8 @@ int main(void) {
|
||||
}
|
||||
nrf_gpio_cfg_default(Pinetime::PinMap::TwiScl);
|
||||
|
||||
debounceTimer = xTimerCreate("debounceTimer", 200, pdFALSE, (void*) 0, DebounceTimerCallback);
|
||||
debounceChargeTimer = xTimerCreate("debounceTimerCharge", 200, pdFALSE, (void*) 0, DebounceTimerChargeCallback);
|
||||
debounceTimer = xTimerCreate("debounceTimer", 10, pdFALSE, nullptr, DebounceTimerCallback);
|
||||
debounceChargeTimer = xTimerCreate("debounceTimerCharge", 200, pdFALSE, nullptr, DebounceTimerChargeCallback);
|
||||
|
||||
// retrieve version stored by bootloader
|
||||
Pinetime::BootloaderVersion::SetVersion(NRF_TIMER2->CC[0]);
|
||||
|
@ -15,7 +15,8 @@ namespace Pinetime {
|
||||
BleFirmwareUpdateStarted,
|
||||
BleFirmwareUpdateFinished,
|
||||
OnTouchEvent,
|
||||
OnButtonEvent,
|
||||
HandleButtonEvent,
|
||||
HandleButtonTimerEvent,
|
||||
OnDisplayTaskSleeping,
|
||||
EnableSleeping,
|
||||
DisableSleeping,
|
||||
@ -24,7 +25,7 @@ namespace Pinetime {
|
||||
SetOffAlarm,
|
||||
StopRinging,
|
||||
MeasureBatteryTimerExpired,
|
||||
BatteryMeasurementDone,
|
||||
BatteryPercentageUpdated,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
#include "drivers/Hrs3300.h"
|
||||
#include "drivers/PinMap.h"
|
||||
#include "main.h"
|
||||
|
||||
#include "BootErrors.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@ -76,7 +76,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
|
||||
Pinetime::Applications::DisplayApp& displayApp,
|
||||
Pinetime::Applications::HeartRateTask& heartRateApp,
|
||||
Pinetime::Controllers::FS& fs,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler)
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::ButtonHandler& buttonHandler)
|
||||
: spi {spi},
|
||||
lcd {lcd},
|
||||
spiNorFlash {spiNorFlash},
|
||||
@ -100,7 +101,15 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
|
||||
heartRateApp(heartRateApp),
|
||||
fs {fs},
|
||||
touchHandler {touchHandler},
|
||||
nimbleController(*this, bleController, dateTimeController, notificationManager, batteryController, spiNorFlash, heartRateController) {
|
||||
buttonHandler {buttonHandler},
|
||||
nimbleController(*this,
|
||||
bleController,
|
||||
dateTimeController,
|
||||
notificationManager,
|
||||
batteryController,
|
||||
spiNorFlash,
|
||||
heartRateController,
|
||||
motionController) {
|
||||
}
|
||||
|
||||
void SystemTask::Start() {
|
||||
@ -116,6 +125,8 @@ void SystemTask::Process(void* instance) {
|
||||
}
|
||||
|
||||
void SystemTask::Work() {
|
||||
BootErrors bootError = BootErrors::None;
|
||||
|
||||
watchdog.Setup(7);
|
||||
watchdog.Start();
|
||||
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::Watchdog::ResetReasonToString(watchdog.ResetReason()));
|
||||
@ -133,10 +144,17 @@ void SystemTask::Work() {
|
||||
lcd.Init();
|
||||
|
||||
twiMaster.Init();
|
||||
/*
|
||||
* TODO We disable this warning message until we ensure it won't be displayed
|
||||
* on legitimate PineTime equipped with a compatible touch controller.
|
||||
* (some users reported false positive). See https://github.com/InfiniTimeOrg/InfiniTime/issues/763
|
||||
if (!touchPanel.Init()) {
|
||||
bootError = BootErrors::TouchController;
|
||||
}
|
||||
*/
|
||||
touchPanel.Init();
|
||||
dateTimeController.Register(this);
|
||||
batteryController.Register(this);
|
||||
batteryController.Update();
|
||||
motorController.Init();
|
||||
motionSensor.SoftReset();
|
||||
timerController.Register(this);
|
||||
@ -152,25 +170,29 @@ void SystemTask::Work() {
|
||||
settingsController.Init();
|
||||
|
||||
displayApp.Register(this);
|
||||
displayApp.Start();
|
||||
displayApp.Start(bootError);
|
||||
|
||||
heartRateSensor.Init();
|
||||
heartRateSensor.Disable();
|
||||
heartRateApp.Start();
|
||||
|
||||
nrf_gpio_cfg_sense_input(PinMap::Button, (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pulldown, (nrf_gpio_pin_sense_t) GPIO_PIN_CNF_SENSE_High);
|
||||
buttonHandler.Init(this);
|
||||
|
||||
// Button
|
||||
nrf_gpio_cfg_output(15);
|
||||
nrf_gpio_pin_set(15);
|
||||
|
||||
nrfx_gpiote_in_config_t pinConfig;
|
||||
pinConfig.skip_gpio_setup = true;
|
||||
pinConfig.skip_gpio_setup = false;
|
||||
pinConfig.hi_accuracy = false;
|
||||
pinConfig.is_watcher = false;
|
||||
pinConfig.sense = (nrf_gpiote_polarity_t) NRF_GPIOTE_POLARITY_HITOLO;
|
||||
pinConfig.sense = (nrf_gpiote_polarity_t) NRF_GPIOTE_POLARITY_TOGGLE;
|
||||
pinConfig.pull = (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pulldown;
|
||||
|
||||
nrfx_gpiote_in_init(PinMap::Button, &pinConfig, nrfx_gpiote_evt_handler);
|
||||
nrfx_gpiote_in_event_enable(PinMap::Button, true);
|
||||
|
||||
// Touchscreen
|
||||
nrf_gpio_cfg_sense_input(PinMap::Cst816sIrq, (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pullup, (nrf_gpio_pin_sense_t) GPIO_PIN_CNF_SENSE_Low);
|
||||
|
||||
pinConfig.skip_gpio_setup = true;
|
||||
@ -181,18 +203,16 @@ void SystemTask::Work() {
|
||||
|
||||
nrfx_gpiote_in_init(PinMap::Cst816sIrq, &pinConfig, nrfx_gpiote_evt_handler);
|
||||
|
||||
// Power present
|
||||
pinConfig.sense = NRF_GPIOTE_POLARITY_TOGGLE;
|
||||
pinConfig.pull = NRF_GPIO_PIN_NOPULL;
|
||||
pinConfig.is_watcher = false;
|
||||
pinConfig.hi_accuracy = false;
|
||||
pinConfig.skip_gpio_setup = true;
|
||||
pinConfig.skip_gpio_setup = false;
|
||||
nrfx_gpiote_in_init(PinMap::PowerPresent, &pinConfig, nrfx_gpiote_evt_handler);
|
||||
nrfx_gpiote_in_event_enable(PinMap::PowerPresent, true);
|
||||
|
||||
if (nrf_gpio_pin_read(PinMap::PowerPresent)) {
|
||||
nrf_gpio_cfg_sense_input(PinMap::PowerPresent, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW);
|
||||
} else {
|
||||
nrf_gpio_cfg_sense_input(PinMap::PowerPresent, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH);
|
||||
}
|
||||
batteryController.MeasureVoltage();
|
||||
|
||||
idleTimer = xTimerCreate("idleTimer", pdMS_TO_TICKS(2000), pdFALSE, this, IdleTimerCallback);
|
||||
dimTimer = xTimerCreate("dimTimer", pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000), pdFALSE, this, DimTimerCallback);
|
||||
@ -246,12 +266,13 @@ void SystemTask::Work() {
|
||||
isDimmed = false;
|
||||
break;
|
||||
case Messages::TouchWakeUp: {
|
||||
if(touchHandler.GetNewTouchInfo()) {
|
||||
if (touchHandler.GetNewTouchInfo()) {
|
||||
auto gesture = touchHandler.GestureGet();
|
||||
if (gesture != Pinetime::Drivers::Cst816S::Gestures::None and ((gesture == Pinetime::Drivers::Cst816S::Gestures::DoubleTap and
|
||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) or
|
||||
(gesture == Pinetime::Drivers::Cst816S::Gestures::SingleTap and
|
||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)))) {
|
||||
if (gesture != Pinetime::Drivers::Cst816S::Gestures::None and
|
||||
((gesture == Pinetime::Drivers::Cst816S::Gestures::DoubleTap and
|
||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) or
|
||||
(gesture == Pinetime::Drivers::Cst816S::Gestures::SingleTap and
|
||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)))) {
|
||||
GoToRunning();
|
||||
}
|
||||
}
|
||||
@ -272,6 +293,8 @@ void SystemTask::Work() {
|
||||
if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::ON) {
|
||||
if (isSleeping && !isWakingUp) {
|
||||
GoToRunning();
|
||||
} else {
|
||||
ReloadIdleTimer();
|
||||
}
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
|
||||
}
|
||||
@ -318,10 +341,25 @@ void SystemTask::Work() {
|
||||
ReloadIdleTimer();
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
|
||||
break;
|
||||
case Messages::OnButtonEvent:
|
||||
ReloadIdleTimer();
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ButtonPushed);
|
||||
break;
|
||||
case Messages::HandleButtonEvent: {
|
||||
Controllers::ButtonActions action;
|
||||
if (nrf_gpio_pin_read(Pinetime::PinMap::Button) == 0) {
|
||||
action = buttonHandler.HandleEvent(Controllers::ButtonHandler::Events::Release);
|
||||
} else {
|
||||
action = buttonHandler.HandleEvent(Controllers::ButtonHandler::Events::Press);
|
||||
// This is for faster wakeup, sacrificing special longpress and doubleclick handling while sleeping
|
||||
if (IsSleeping()) {
|
||||
fastWakeUpDone = true;
|
||||
GoToRunning();
|
||||
break;
|
||||
}
|
||||
}
|
||||
HandleButtonAction(action);
|
||||
} break;
|
||||
case Messages::HandleButtonTimerEvent: {
|
||||
auto action = buttonHandler.HandleEvent(Controllers::ButtonHandler::Events::Timer);
|
||||
HandleButtonAction(action);
|
||||
} break;
|
||||
case Messages::OnDisplayTaskSleeping:
|
||||
if (BootloaderVersion::IsValid()) {
|
||||
// First versions of the bootloader do not expose their version and cannot initialize the SPI NOR FLASH
|
||||
@ -345,18 +383,18 @@ void SystemTask::Work() {
|
||||
stepCounterMustBeReset = true;
|
||||
break;
|
||||
case Messages::OnChargingEvent:
|
||||
batteryController.Update();
|
||||
batteryController.ReadPowerState();
|
||||
motorController.RunForDuration(15);
|
||||
ReloadIdleTimer();
|
||||
if (isSleeping && !isWakingUp) {
|
||||
GoToRunning();
|
||||
}
|
||||
break;
|
||||
case Messages::MeasureBatteryTimerExpired:
|
||||
sendBatteryNotification = true;
|
||||
batteryController.Update();
|
||||
batteryController.MeasureVoltage();
|
||||
break;
|
||||
case Messages::BatteryMeasurementDone:
|
||||
if (sendBatteryNotification) {
|
||||
sendBatteryNotification = false;
|
||||
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
|
||||
}
|
||||
case Messages::BatteryPercentageUpdated:
|
||||
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -406,18 +444,36 @@ void SystemTask::UpdateMotion() {
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTask::OnButtonPushed() {
|
||||
if (isGoingToSleep)
|
||||
void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
|
||||
if (IsSleeping()) {
|
||||
return;
|
||||
if (!isSleeping) {
|
||||
NRF_LOG_INFO("[systemtask] Button pushed");
|
||||
PushMessage(Messages::OnButtonEvent);
|
||||
} else {
|
||||
if (!isWakingUp) {
|
||||
NRF_LOG_INFO("[systemtask] Button pushed, waking up");
|
||||
GoToRunning();
|
||||
}
|
||||
}
|
||||
|
||||
ReloadIdleTimer();
|
||||
|
||||
using Actions = Controllers::ButtonActions;
|
||||
|
||||
switch (action) {
|
||||
case Actions::Click:
|
||||
// If the first action after fast wakeup is a click, it should be ignored.
|
||||
if (!fastWakeUpDone && !isGoingToSleep) {
|
||||
displayApp.PushMessage(Applications::Display::Messages::ButtonPushed);
|
||||
}
|
||||
break;
|
||||
case Actions::DoubleClick:
|
||||
displayApp.PushMessage(Applications::Display::Messages::ButtonDoubleClicked);
|
||||
break;
|
||||
case Actions::LongPress:
|
||||
displayApp.PushMessage(Applications::Display::Messages::ButtonLongPressed);
|
||||
break;
|
||||
case Actions::LongerPress:
|
||||
displayApp.PushMessage(Applications::Display::Messages::ButtonLongerPressed);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
fastWakeUpDone = false;
|
||||
}
|
||||
|
||||
void SystemTask::GoToRunning() {
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "components/alarm/AlarmController.h"
|
||||
#include "components/fs/FS.h"
|
||||
#include "touchhandler/TouchHandler.h"
|
||||
#include "buttonhandler/ButtonHandler.h"
|
||||
#include "buttonhandler/ButtonActions.h"
|
||||
|
||||
#ifdef PINETIME_IS_RECOVERY
|
||||
#include "displayapp/DisplayAppRecovery.h"
|
||||
@ -45,6 +47,7 @@ namespace Pinetime {
|
||||
}
|
||||
namespace Controllers {
|
||||
class TouchHandler;
|
||||
class ButtonHandler;
|
||||
}
|
||||
namespace System {
|
||||
class SystemTask {
|
||||
@ -71,12 +74,12 @@ namespace Pinetime {
|
||||
Pinetime::Applications::DisplayApp& displayApp,
|
||||
Pinetime::Applications::HeartRateTask& heartRateApp,
|
||||
Pinetime::Controllers::FS& fs,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler);
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::ButtonHandler& buttonHandler);
|
||||
|
||||
void Start();
|
||||
void PushMessage(Messages msg);
|
||||
|
||||
void OnButtonPushed();
|
||||
void OnTouchEvent();
|
||||
|
||||
void OnIdle();
|
||||
@ -123,6 +126,7 @@ namespace Pinetime {
|
||||
Pinetime::Applications::HeartRateTask& heartRateApp;
|
||||
Pinetime::Controllers::FS& fs;
|
||||
Pinetime::Controllers::TouchHandler& touchHandler;
|
||||
Pinetime::Controllers::ButtonHandler& buttonHandler;
|
||||
Pinetime::Controllers::NimbleController nimbleController;
|
||||
|
||||
static void Process(void* instance);
|
||||
@ -133,14 +137,15 @@ namespace Pinetime {
|
||||
TimerHandle_t dimTimer;
|
||||
TimerHandle_t idleTimer;
|
||||
TimerHandle_t measureBatteryTimer;
|
||||
bool sendBatteryNotification = false;
|
||||
bool doNotGoToSleep = false;
|
||||
|
||||
void HandleButtonAction(Controllers::ButtonActions action);
|
||||
bool fastWakeUpDone = false;
|
||||
|
||||
void GoToRunning();
|
||||
void UpdateMotion();
|
||||
bool stepCounterMustBeReset = false;
|
||||
static constexpr TickType_t batteryMeasurementPeriod = pdMS_TO_TICKS(10 * 60 * 1000);
|
||||
TickType_t lastBatteryNotificationTime = 0;
|
||||
|
||||
#if configUSE_TRACE_FACILITY == 1
|
||||
SystemMonitor<FreeRtosMonitor> monitor;
|
||||
|
Loading…
Reference in New Issue
Block a user