Merge branch 'develop' into master
|
@ -1,12 +1,28 @@
|
||||||
.idea/
|
.idea/
|
||||||
|
# CMake
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
**/CMakeCache.txt
|
|
||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
|
**/CMakeCache.txt
|
||||||
|
cmake_install.cmake
|
||||||
|
Makefile
|
||||||
|
|
||||||
|
# Resulting binary files
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.s
|
||||||
|
*.hex
|
||||||
|
*.bin
|
||||||
|
!bootloader/bootloader-5.0.4.bin
|
||||||
|
*.map
|
||||||
|
*.out
|
||||||
|
pinetime*.cbp
|
||||||
|
|
||||||
|
# InfiniTime's files
|
||||||
core
|
core
|
||||||
sdk
|
sdk
|
||||||
src/Version.h
|
src/Version.h
|
||||||
docker/post_build.sh
|
docker/post_build.sh
|
||||||
|
Testing/Temporary/
|
||||||
|
|
||||||
# Linux
|
# Linux
|
||||||
**/.directory
|
**/.directory
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<Objective-C>
|
||||||
|
<option name="INDENT_NAMESPACE_MEMBERS" value="2" />
|
||||||
|
<option name="INDENT_C_STRUCT_MEMBERS" value="2" />
|
||||||
|
<option name="INDENT_CLASS_MEMBERS" value="2" />
|
||||||
|
<option name="INDENT_INSIDE_CODE_BLOCK" value="2" />
|
||||||
|
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_TEMPLATE_DECLARATION_LT" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_POINTER_IN_DECLARATION" value="false" />
|
||||||
|
<option name="SPACE_AFTER_POINTER_IN_DECLARATION" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_REFERENCE_IN_DECLARATION" value="false" />
|
||||||
|
<option name="SPACE_AFTER_REFERENCE_IN_DECLARATION" value="true" />
|
||||||
|
</Objective-C>
|
||||||
|
<codeStyleSettings language="ObjectiveC">
|
||||||
|
<option name="RIGHT_MARGIN" value="140" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WRAP_ON_TYPING" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="LABEL_INDENT_ABSOLUTE" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
|
@ -1,10 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(pinetime VERSION 0.8.2 LANGUAGES C CXX ASM)
|
project(pinetime VERSION 0.9.0 LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
set(NRF_TARGET "nrf52")
|
set(NRF_TARGET "nrf52")
|
||||||
|
|
||||||
if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
|
if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
|
||||||
message(FATAL_ERROR "The path to the toolchain (arm-non-eabi) must be specified on the command line (add -DARM_NONE_EABI_TOOLCHAIN_PATH=<path>")
|
message(FATAL_ERROR "The path to the toolchain (arm-none-eabi) must be specified on the command line (add -DARM_NONE_EABI_TOOLCHAIN_PATH=<path>")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (NOT NRF5_SDK_PATH)
|
if (NOT NRF5_SDK_PATH)
|
||||||
|
|
|
@ -45,13 +45,14 @@ As of now, here is the list of achievements of this project:
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### Develop
|
### Develop
|
||||||
- [Generate the fonts and symbols](src/DisplayApp/Fonts/Readme.md)
|
- [Generate the fonts and symbols](src/displayapp/fonts/Readme.md)
|
||||||
|
|
||||||
### Build, flash and debug
|
### Build, flash and debug
|
||||||
- [Project branches](doc/branches.md)
|
- [Project branches](doc/branches.md)
|
||||||
- [Versioning](doc/versioning.md)
|
- [Versioning](doc/versioning.md)
|
||||||
- [Files included in the release notes](doc/filesInReleaseNotes.md)
|
- [Files included in the release notes](doc/filesInReleaseNotes.md)
|
||||||
- [Build the project](doc/buildAndProgram.md)
|
- [Build the project](doc/buildAndProgram.md)
|
||||||
|
- [Flash the firmware using OpenOCD and STLinkV2](doc/openOCD.md)
|
||||||
- [Build the project with Docker](doc/buildWithDocker.md)
|
- [Build the project with Docker](doc/buildWithDocker.md)
|
||||||
- [Bootloader, OTA and DFU](./bootloader/README.md)
|
- [Bootloader, OTA and DFU](./bootloader/README.md)
|
||||||
- [Stub using NRF52-DK](./doc/PinetimeStubWithNrf52DK.md)
|
- [Stub using NRF52-DK](./doc/PinetimeStubWithNrf52DK.md)
|
||||||
|
|
|
@ -162,7 +162,7 @@ class NrfBleDfuController(object, metaclass=ABCMeta):
|
||||||
self.ble_conn.sendline('characteristics')
|
self.ble_conn.sendline('characteristics')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.ble_conn.expect([uuid], timeout=2)
|
self.ble_conn.expect([uuid], timeout=10)
|
||||||
handles = re.findall(b'.*handle: (0x....),.*char value handle: (0x....)', self.ble_conn.before)
|
handles = re.findall(b'.*handle: (0x....),.*char value handle: (0x....)', self.ble_conn.before)
|
||||||
(handle, value_handle) = handles[-1]
|
(handle, value_handle) = handles[-1]
|
||||||
except pexpect.TIMEOUT as e:
|
except pexpect.TIMEOUT as e:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Memory analysis
|
# Memory analysis
|
||||||
## FreeRTOS heap and task stack
|
## FreeRTOS heap and task stack
|
||||||
FreeRTOS statically allocate its own heap buffer in a global variable named `ucHeap`. This is an aray of *uint8_t*. Its size is specified by the definition `configTOTAL_HEAP_SIZE` in *FreeRTOSConfig.h*
|
FreeRTOS statically allocate its own heap buffer in a global variable named `ucHeap`. This is an array of *uint8_t*. Its size is specified by the definition `configTOTAL_HEAP_SIZE` in *FreeRTOSConfig.h*
|
||||||
FreeRTOS uses this buffer to allocate memory for tasks stack and all the RTOS object created during runtime (timers, mutexes,...).
|
FreeRTOS uses this buffer to allocate memory for tasks stack and all the RTOS object created during runtime (timers, mutexes,...).
|
||||||
|
|
||||||
The function `xPortGetFreeHeapSize()` returns the amount of memory available in this *ucHeap* buffer. If this value reaches 0, FreeRTOS runs out of memory.
|
The function `xPortGetFreeHeapSize()` returns the amount of memory available in this *ucHeap* buffer. If this value reaches 0, FreeRTOS runs out of memory.
|
||||||
|
|
|
@ -18,7 +18,7 @@ CMake configures the project according to variables you specify the command line
|
||||||
|
|
||||||
Variable | Description | Example|
|
Variable | Description | Example|
|
||||||
----------|-------------|--------|
|
----------|-------------|--------|
|
||||||
**ARM_NONE_EABI_TOOLCHAIN_PATH**|path to the toolchain directory|`-DARM_NONE_EABI_TOOLCHAIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/`|
|
**ARM_NONE_EABI_TOOLCHAIN_PATH**|path to the toolchain directory|`-DARM_NONE_EABI_TOOLCHAIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2020-q2-update/`|
|
||||||
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
|
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
|
||||||
**USE_JLINK, USE_GDB_CLIENT and USE_OPENOCD**|Enable *JLink* mode, *GDB Client* (Black Magic Probe) mode or *OpenOCD* mode (set the one you want to use to `1`)|`-DUSE_JLINK=1`
|
**USE_JLINK, USE_GDB_CLIENT and USE_OPENOCD**|Enable *JLink* mode, *GDB Client* (Black Magic Probe) mode or *OpenOCD* mode (set the one you want to use to `1`)|`-DUSE_JLINK=1`
|
||||||
**CMAKE_BUILD_TYPE**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
|
**CMAKE_BUILD_TYPE**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
|
||||||
|
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
|
@ -22,3 +22,23 @@ Then, you can submit a pull-request for review. Try to describe your pull reques
|
||||||
Other contributors can post comments about the pull request, maybe ask for more info or adjustements in the code.
|
Other contributors can post comments about the pull request, maybe ask for more info or adjustements in the code.
|
||||||
|
|
||||||
Once the pull request is reviewed an accepted, it'll be merge in **develop** and will be released in the next release version of the firmware.
|
Once the pull request is reviewed an accepted, it'll be merge in **develop** and will be released in the next release version of the firmware.
|
||||||
|
|
||||||
|
# Coding convention
|
||||||
|
## Language
|
||||||
|
The language of this project is **C++**, and all new code must be written in C++. (Modern) C++ provides a lot of useful tools and functionalities that are beneficial for embedded software development like `constexpr`, `template` and anything that provides zero-cost abstraction.
|
||||||
|
|
||||||
|
It's OK to include C code if this code comes from another library like FreeRTOS, NimBLE, LVGL or the NRF-SDK.
|
||||||
|
|
||||||
|
## Coding style
|
||||||
|
The most important rule to follow is to try to keep the code as easy to read and maintain as possible.
|
||||||
|
|
||||||
|
- **Identation** : 2 spaces, no tabulation
|
||||||
|
- **Opening brace** at the end of the line
|
||||||
|
- **Naming** : Choose self-describing variable name
|
||||||
|
- **class** : PascalCase
|
||||||
|
- **namespace** : PascalCase
|
||||||
|
- **variable** : camelCase, **no** prefix/suffix ('_', 'm_',...) for class members
|
||||||
|
- **Include guard** : `#pragma once` (no `#ifdef __MODULE__ / #define __MODULE__ / #endif`)
|
||||||
|
- **Includes** :
|
||||||
|
- files from the project : `#include "relative/path/to/the/file.h"`
|
||||||
|
- external files and std : `#include <file.h>`
|
|
@ -42,7 +42,7 @@ This firmware is intended to be used with our [MCUBoot-based bootloader](../boot
|
||||||
|
|
||||||
The following files are not directly usable by the bootloader:
|
The following files are not directly usable by the bootloader:
|
||||||
|
|
||||||
- **pinetime-mcuboot-app.bin** : Output file of GCC containing debug symbols, useful is you want to debug the firmware using GDB.
|
- **pinetime-mcuboot-app.out** : Output file of GCC containing debug symbols, useful is you want to debug the firmware using GDB.
|
||||||
- **pinetime-mcuboot-app.hex** : Firmware in Intel HEX file format.
|
- **pinetime-mcuboot-app.hex** : Firmware in Intel HEX file format.
|
||||||
- **pinetime-mcuboot-app.bin** : Firmware in binary format.
|
- **pinetime-mcuboot-app.bin** : Firmware in binary format.
|
||||||
- **pinetime-mcuboot-app.map** : Map file containing all the symbols, addresses in memory,...
|
- **pinetime-mcuboot-app.map** : Map file containing all the symbols, addresses in memory,...
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
# OpenOCD and STLink
|
||||||
|
OpenOCD (**Open O**n **C**hip **D**ebugger) is an open source tool that interfaces with many SWD/JTAG debugger to provide debugging and *in-system* programming for embedded target devices.
|
||||||
|
|
||||||
|
It supports the **NRF52** (the CPU of the PineTime) and the **STLinkV2**, a cheap SWD debugger.
|
||||||
|
|
||||||
|
It works on X86 computers, as well as ARM/ARM64 computers and SBC (like the RaspberryPi and Pine64 Pinebook Pro) !
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
We will build OpenOCD from sources, as packages from Linux distributions are most of the time outdated and do not support the NRF52 correctly.
|
||||||
|
|
||||||
|
- Fetch the sources from GIT, and build and install it:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://git.code.sf.net/p/openocd/code openocd-code
|
||||||
|
|
||||||
|
cd openocd-code
|
||||||
|
|
||||||
|
./bootstrap
|
||||||
|
./configure --enable-stlink
|
||||||
|
make -j 4
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
- Configure UDEV to allow OpenOCD to open the interface to your STLinkV2:
|
||||||
|
```
|
||||||
|
sudo cp contrib/60-openocd.rules /etc/udev/rules.d/
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
```
|
||||||
|
|
||||||
|
- You can now plug your STLinkV2 in a USB port and run OpenOCD to see if it's working correctly:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ openocd -f interface/stlink.cfg -f target/nrf52.cfg
|
||||||
|
Open On-Chip Debugger 0.10.0+dev-01411-g051e80812-dirty (2020-09-28-20:16)
|
||||||
|
Licensed under GNU GPL v2
|
||||||
|
For bug reports, read
|
||||||
|
http://openocd.org/doc/doxygen/bugs.html
|
||||||
|
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
|
||||||
|
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
|
||||||
|
|
||||||
|
nRF52 device has a CTRL-AP dedicated to recover the device from AP lock.
|
||||||
|
A high level adapter (like a ST-Link) you are currently using cannot access
|
||||||
|
the CTRL-AP so 'nrf52_recover' command will not work.
|
||||||
|
Do not enable UICR APPROTECT.
|
||||||
|
|
||||||
|
Info : Listening on port 6666 for tcl connections
|
||||||
|
Info : Listening on port 4444 for telnet connections
|
||||||
|
Info : clock speed 1000 kHz
|
||||||
|
Info : STLINK V2J34S7 (API v2) VID:PID 0483:3748
|
||||||
|
Info : Target voltage: 3.294340
|
||||||
|
Error: init mode failed (unable to connect to the target)
|
||||||
|
```
|
||||||
|
Ok, OpenOCD is running and it detects my STLinkV2. The last error shows that I've not connected the STLinkV2 to the PineTime.
|
||||||
|
|
||||||
|
## Configuration files
|
||||||
|
OpenOCD is configured using configuration files.
|
||||||
|
First, we need a common configuration file for the project : **openocd-stlink.ocd**:
|
||||||
|
```
|
||||||
|
source [find interface/stlink.cfg]
|
||||||
|
|
||||||
|
gdb_flash_program enable
|
||||||
|
gdb_breakpoint_override hard
|
||||||
|
|
||||||
|
source [find target/nrf52.cfg]
|
||||||
|
```
|
||||||
|
This file specifies to OpenOCD which debugger and target it will be connected to..
|
||||||
|
|
||||||
|
Then, we use various *user files* to use OpenOCD to flash InfiniTime binary files.
|
||||||
|
|
||||||
|
This files flashes the bootloader and the application firmware : **flash_bootloader_app.ocd**:
|
||||||
|
```
|
||||||
|
init
|
||||||
|
|
||||||
|
program <build directory>/bootloader.bin verify 0x00000000
|
||||||
|
program <build directory>/image-0.8.2.bin verify 0x00008000
|
||||||
|
|
||||||
|
reset
|
||||||
|
```
|
||||||
|
|
||||||
|
And this one flashes the graphics flasher (it writes the bootloader graphics into the SPI NOR flash memory) : **flash_graphics.ocd**:
|
||||||
|
```
|
||||||
|
init
|
||||||
|
|
||||||
|
program <build directory>/pinetime-graphics-0.8.2.bin verify 0x00000000
|
||||||
|
|
||||||
|
reset
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
### Flash bootloader and application
|
||||||
|
```
|
||||||
|
openocd -f ./openocd-stlink.cfg -f ./flash_bootloader_app.ocd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flash graphics flasher
|
||||||
|
```
|
||||||
|
openocd -f ./openocd-stlink.cfg -f ./flash_graphics.ocd
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connect the STLinkV2 to the PineTime
|
||||||
|
Here is an example using the pogo pins:
|
||||||
|
![SWD pinout](../images/swd_pinout.jpg)
|
||||||
|
![Pogo pins](../images/pogopins.jpg)
|
||||||
|
|
||||||
|
You can find more information about the SWD wiring [on the wiki](https://wiki.pine64.org/index.php?title=PineTime_devkit_wiring).
|
After Width: | Height: | Size: 1.9 MiB |
After Width: | Height: | Size: 1.4 MiB |
|
@ -0,0 +1,26 @@
|
||||||
|
#include <cstdint>
|
||||||
|
#include "BootloaderVersion.h"
|
||||||
|
|
||||||
|
using namespace Pinetime;
|
||||||
|
|
||||||
|
// NOTE : current bootloader does not export its version to the application firmware.
|
||||||
|
|
||||||
|
uint32_t BootloaderVersion::Major() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BootloaderVersion::Minor() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BootloaderVersion::Patch() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *BootloaderVersion::VersionString() {
|
||||||
|
return "0.0.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BootloaderVersion::IsValid() {
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
class BootloaderVersion {
|
||||||
|
public:
|
||||||
|
static uint32_t Major();
|
||||||
|
static uint32_t Minor();
|
||||||
|
static uint32_t Patch();
|
||||||
|
static const char* VersionString();
|
||||||
|
static bool IsValid();
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,145 +0,0 @@
|
||||||
#include <SystemTask/SystemTask.h>
|
|
||||||
#include "NotificationManager.h"
|
|
||||||
|
|
||||||
#include "AlertNotificationClient.h"
|
|
||||||
|
|
||||||
|
|
||||||
using namespace Pinetime::Controllers;
|
|
||||||
constexpr ble_uuid16_t AlertNotificationClient::ansServiceUuid;
|
|
||||||
|
|
||||||
constexpr ble_uuid16_t AlertNotificationClient::supportedNewAlertCategoryUuid;
|
|
||||||
constexpr ble_uuid16_t AlertNotificationClient::supportedUnreadAlertCategoryUuid ;
|
|
||||||
constexpr ble_uuid16_t AlertNotificationClient::newAlertUuid;
|
|
||||||
constexpr ble_uuid16_t AlertNotificationClient::unreadAlertStatusUuid;
|
|
||||||
constexpr ble_uuid16_t AlertNotificationClient::controlPointUuid;
|
|
||||||
|
|
||||||
int Pinetime::Controllers::NewAlertSubcribeCallback(uint16_t conn_handle,
|
|
||||||
const struct ble_gatt_error *error,
|
|
||||||
struct ble_gatt_attr *attr,
|
|
||||||
void *arg) {
|
|
||||||
auto client = static_cast<AlertNotificationClient*>(arg);
|
|
||||||
return client->OnNewAlertSubcribe(conn_handle, error, attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertNotificationClient::AlertNotificationClient(Pinetime::System::SystemTask& systemTask,
|
|
||||||
Pinetime::Controllers::NotificationManager& notificationManager) :
|
|
||||||
systemTask{systemTask}, notificationManager{notificationManager}{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
|
|
||||||
if(service == nullptr && error->status == BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("ANS Discovery complete");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ansServiceUuid), &service->uuid.u) == 0) {
|
|
||||||
NRF_LOG_INFO("ANS discovered : 0x%x", service->start_handle);
|
|
||||||
ansStartHandle = service->start_handle;
|
|
||||||
ansEndHandle = service->end_handle;
|
|
||||||
isDiscovered = true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
|
||||||
const ble_gatt_chr *characteristic) {
|
|
||||||
if(error->status != 0 && error->status != BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("ANS Characteristic discovery ERROR");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("ANS Characteristic discovery complete");
|
|
||||||
} else {
|
|
||||||
if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&supportedNewAlertCategoryUuid), &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) {
|
|
||||||
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) {
|
|
||||||
NRF_LOG_INFO("ANS Characteristic discovered : newAlertUuid");
|
|
||||||
newAlertHandle = characteristic->val_handle;
|
|
||||||
newAlertDefHandle = characteristic->def_handle;
|
|
||||||
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&unreadAlertStatusUuid), &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) {
|
|
||||||
NRF_LOG_INFO("ANS Characteristic discovered : controlPointUuid");
|
|
||||||
controlPointHandle = characteristic->val_handle;
|
|
||||||
}else
|
|
||||||
NRF_LOG_INFO("ANS Characteristic discovered : 0x%x", characteristic->val_handle);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AlertNotificationClient::OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error *error,
|
|
||||||
ble_gatt_attr *attribute) {
|
|
||||||
if(error->status == 0) {
|
|
||||||
NRF_LOG_INFO("ANS New alert subscribe OK");
|
|
||||||
} else {
|
|
||||||
NRF_LOG_INFO("ANS New alert subscribe ERROR");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AlertNotificationClient::OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
|
||||||
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(newAlertDescriptorHandle == 0) {
|
|
||||||
NRF_LOG_INFO("ANS Descriptor discovered : %d", descriptor->handle);
|
|
||||||
newAlertDescriptorHandle = descriptor->handle;
|
|
||||||
uint8_t value[2];
|
|
||||||
value[0] = 1;
|
|
||||||
value[1] = 0;
|
|
||||||
ble_gattc_write_flat(connectionHandle, newAlertDescriptorHandle, value, sizeof(value), NewAlertSubcribeCallback, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlertNotificationClient::OnNotification(ble_gap_event *event) {
|
|
||||||
if(event->notify_rx.attr_handle == newAlertHandle) {
|
|
||||||
// TODO implement this with more memory safety (and constexpr)
|
|
||||||
static const size_t maxBufferSize{21};
|
|
||||||
static const size_t maxMessageSize{18};
|
|
||||||
size_t bufferSize = min(OS_MBUF_PKTLEN(event->notify_rx.om), maxBufferSize);
|
|
||||||
|
|
||||||
uint8_t data[bufferSize];
|
|
||||||
os_mbuf_copydata(event->notify_rx.om, 0, bufferSize, data);
|
|
||||||
|
|
||||||
char *s = (char *) &data[3];
|
|
||||||
auto messageSize = min(maxMessageSize, (bufferSize-3));
|
|
||||||
|
|
||||||
for (uint i = 0; i < messageSize-1; i++) {
|
|
||||||
if (s[i] == 0x00) {
|
|
||||||
s[i] = 0x0A;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s[messageSize-1] = '\0';
|
|
||||||
|
|
||||||
notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
|
|
||||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AlertNotificationClient::IsDiscovered() const {
|
|
||||||
return isDiscovered;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AlertNotificationClient::StartHandle() const {
|
|
||||||
return ansStartHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AlertNotificationClient::EndHandle() const {
|
|
||||||
return ansEndHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AlertNotificationClient::NewAlerthandle() const {
|
|
||||||
return newAlertHandle;
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
#include <hal/nrf_rtc.h>
|
|
||||||
#include "CurrentTimeClient.h"
|
|
||||||
|
|
||||||
using namespace Pinetime::Controllers;
|
|
||||||
|
|
||||||
constexpr ble_uuid16_t CurrentTimeClient::ctsServiceUuid;
|
|
||||||
constexpr ble_uuid16_t CurrentTimeClient::currentTimeCharacteristicUuid;
|
|
||||||
|
|
||||||
CurrentTimeClient::CurrentTimeClient(DateTime& dateTimeController) : dateTimeController{dateTimeController} {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CurrentTimeClient::Init() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CurrentTimeClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
|
|
||||||
if(service == nullptr && error->status == BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("CTS Discovery complete");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ctsServiceUuid), &service->uuid.u) == 0) {
|
|
||||||
NRF_LOG_INFO("CTS discovered : 0x%x", service->start_handle);
|
|
||||||
isDiscovered = true;
|
|
||||||
ctsStartHandle = service->start_handle;
|
|
||||||
ctsEndHandle = service->end_handle;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CurrentTimeClient::OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
|
|
||||||
const ble_gatt_chr *characteristic) {
|
|
||||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("CTS Characteristic discovery complete");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)¤tTimeCharacteristicUuid), &characteristic->uuid.u) == 0) {
|
|
||||||
NRF_LOG_INFO("CTS Characteristic discovered : 0x%x", characteristic->val_handle);
|
|
||||||
currentTimeHandle = characteristic->val_handle;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CurrentTimeClient::OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute) {
|
|
||||||
if(error->status == 0) {
|
|
||||||
// TODO check that attribute->handle equals the handle discovered in OnCharacteristicDiscoveryEvent
|
|
||||||
CtsData result;
|
|
||||||
os_mbuf_copydata(attribute->om, 0, sizeof(CtsData), &result);
|
|
||||||
NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", result.year,
|
|
||||||
result.month, result.dayofmonth,
|
|
||||||
result.hour, result.minute, result.second);
|
|
||||||
dateTimeController.SetTime(result.year, result.month, result.dayofmonth,
|
|
||||||
0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG));
|
|
||||||
} else {
|
|
||||||
NRF_LOG_INFO("Error retrieving current time: %d", error->status);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CurrentTimeClient::IsDiscovered() const {
|
|
||||||
return isDiscovered;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t CurrentTimeClient::StartHandle() const {
|
|
||||||
return ctsStartHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t CurrentTimeClient::EndHandle() const {
|
|
||||||
return ctsEndHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t CurrentTimeClient::CurrentTimeHandle() const {
|
|
||||||
return currentTimeHandle;
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
#include <SystemTask/SystemTask.h>
|
|
||||||
#include "MusicService.h"
|
|
||||||
|
|
||||||
int MSCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
|
||||||
auto musicService = static_cast<Pinetime::Controllers::MusicService*>(arg);
|
|
||||||
return musicService->OnCommand(conn_handle, attr_handle, ctxt);
|
|
||||||
}
|
|
||||||
|
|
||||||
Pinetime::Controllers::MusicService::MusicService(Pinetime::System::SystemTask &system) : m_system(system)
|
|
||||||
{
|
|
||||||
msUuid.value[11] = msId[0];
|
|
||||||
msUuid.value[12] = msId[1];
|
|
||||||
msEventCharUuid.value[11] = msEventCharId[0];
|
|
||||||
msEventCharUuid.value[12] = msEventCharId[1];
|
|
||||||
msStatusCharUuid.value[11] = msStatusCharId[0];
|
|
||||||
msStatusCharUuid.value[12] = msStatusCharId[1];
|
|
||||||
msTrackCharUuid.value[11] = msTrackCharId[0];
|
|
||||||
msTrackCharUuid.value[12] = msTrackCharId[1];
|
|
||||||
msArtistCharUuid.value[11] = msArtistCharId[0];
|
|
||||||
msArtistCharUuid.value[12] = msArtistCharId[1];
|
|
||||||
msAlbumCharUuid.value[11] = msAlbumCharId[0];
|
|
||||||
msAlbumCharUuid.value[12] = msAlbumCharId[1];
|
|
||||||
|
|
||||||
characteristicDefinition[0] = { .uuid = (ble_uuid_t*)(&msEventCharUuid),
|
|
||||||
.access_cb = MSCallback,
|
|
||||||
.arg = this,
|
|
||||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
|
||||||
.val_handle = &m_eventHandle
|
|
||||||
};
|
|
||||||
characteristicDefinition[1] = { .uuid = (ble_uuid_t*)(&msStatusCharUuid),
|
|
||||||
.access_cb = MSCallback,
|
|
||||||
.arg = this,
|
|
||||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
|
||||||
};
|
|
||||||
characteristicDefinition[2] = { .uuid = (ble_uuid_t*)(&msTrackCharUuid),
|
|
||||||
.access_cb = MSCallback,
|
|
||||||
.arg = this,
|
|
||||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
|
||||||
};
|
|
||||||
characteristicDefinition[3] = { .uuid = (ble_uuid_t*)(&msArtistCharUuid),
|
|
||||||
.access_cb = MSCallback,
|
|
||||||
.arg = this,
|
|
||||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
|
||||||
};
|
|
||||||
characteristicDefinition[4] = { .uuid = (ble_uuid_t*)(&msAlbumCharUuid),
|
|
||||||
.access_cb = MSCallback,
|
|
||||||
.arg = this,
|
|
||||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
|
||||||
};
|
|
||||||
characteristicDefinition[5] = {0};
|
|
||||||
|
|
||||||
serviceDefinition[0] = {
|
|
||||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
|
||||||
.uuid = (ble_uuid_t *) &msUuid,
|
|
||||||
.characteristics = characteristicDefinition
|
|
||||||
};
|
|
||||||
serviceDefinition[1] = {0};
|
|
||||||
|
|
||||||
m_artist = "Waiting for";
|
|
||||||
m_album = "";
|
|
||||||
m_track = "track information...";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Pinetime::Controllers::MusicService::Init()
|
|
||||||
{
|
|
||||||
int res = 0;
|
|
||||||
res = ble_gatts_count_cfg(serviceDefinition);
|
|
||||||
ASSERT(res == 0);
|
|
||||||
|
|
||||||
res = ble_gatts_add_svcs(serviceDefinition);
|
|
||||||
ASSERT(res == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Pinetime::Controllers::MusicService::OnCommand(uint16_t conn_handle, uint16_t attr_handle,
|
|
||||||
struct ble_gatt_access_ctxt *ctxt) {
|
|
||||||
|
|
||||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
|
||||||
size_t notifSize = OS_MBUF_PKTLEN(ctxt->om);
|
|
||||||
uint8_t data[notifSize + 1];
|
|
||||||
data[notifSize] = '\0';
|
|
||||||
os_mbuf_copydata(ctxt->om, 0, notifSize, data);
|
|
||||||
char *s = (char *) &data[0];
|
|
||||||
NRF_LOG_INFO("DATA : %s", s);
|
|
||||||
if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msArtistCharUuid) == 0) {
|
|
||||||
m_artist = s;
|
|
||||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msTrackCharUuid) == 0) {
|
|
||||||
m_track = s;
|
|
||||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msAlbumCharUuid) == 0) {
|
|
||||||
m_album = s;
|
|
||||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msStatusCharUuid) == 0) {
|
|
||||||
m_status = s[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Pinetime::Controllers::MusicService::album()
|
|
||||||
{
|
|
||||||
return m_album;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Pinetime::Controllers::MusicService::artist()
|
|
||||||
{
|
|
||||||
return m_artist;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Pinetime::Controllers::MusicService::track()
|
|
||||||
{
|
|
||||||
return m_track;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char Pinetime::Controllers::MusicService::status()
|
|
||||||
{
|
|
||||||
return m_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Pinetime::Controllers::MusicService::event(char event)
|
|
||||||
{
|
|
||||||
auto *om = ble_hs_mbuf_from_flat(&event, 1);
|
|
||||||
|
|
||||||
uint16_t connectionHandle = m_system.nimble().connHandle();
|
|
||||||
|
|
||||||
if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ble_gattc_notify_custom(connectionHandle, m_eventHandle, om);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <array>
|
|
||||||
#include <host/ble_gap.h>
|
|
||||||
#include <host/ble_uuid.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
//c7e50000-78fc-48fe-8e23-43b37a1942d0
|
|
||||||
#define MUSIC_SERVICE_UUID_BASE {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x00, 0x00, 0xe5, 0xc7}
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace System {
|
|
||||||
class SystemTask;
|
|
||||||
}
|
|
||||||
namespace Controllers {
|
|
||||||
|
|
||||||
class MusicService {
|
|
||||||
public:
|
|
||||||
MusicService(Pinetime::System::SystemTask &system);
|
|
||||||
void Init();
|
|
||||||
int OnCommand(uint16_t conn_handle, uint16_t attr_handle,
|
|
||||||
struct ble_gatt_access_ctxt *ctxt);
|
|
||||||
|
|
||||||
std::string artist();
|
|
||||||
std::string track();
|
|
||||||
std::string album();
|
|
||||||
unsigned char status();
|
|
||||||
|
|
||||||
void event(char event);
|
|
||||||
|
|
||||||
static const char EVENT_MUSIC_OPEN = 0xe0;
|
|
||||||
static const char EVENT_MUSIC_PLAY = 0x00;
|
|
||||||
static const char EVENT_MUSIC_PAUSE = 0x01;
|
|
||||||
static const char EVENT_MUSIC_NEXT = 0x03;
|
|
||||||
static const char EVENT_MUSIC_PREV = 0x04;
|
|
||||||
static const char EVENT_MUSIC_VOLUP = 0x05;
|
|
||||||
static const char EVENT_MUSIC_VOLDOWN = 0x06;
|
|
||||||
static const char STATUS_MUSIC_PAUSED = 0x00;
|
|
||||||
static const char STATUS_MUSIC_PLAYING = 0x01;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr uint8_t msId[2] = {0x00, 0x01};
|
|
||||||
static constexpr uint8_t msEventCharId[2] = {0x00, 0x02};
|
|
||||||
static constexpr uint8_t msStatusCharId[2] = {0x00, 0x03};
|
|
||||||
static constexpr uint8_t msArtistCharId[2] = {0x00, 0x04};
|
|
||||||
static constexpr uint8_t msTrackCharId[2] = {0x00, 0x05};
|
|
||||||
static constexpr uint8_t msAlbumCharId[2] = {0x00, 0x06};
|
|
||||||
|
|
||||||
ble_uuid128_t msUuid {
|
|
||||||
.u = { .type = BLE_UUID_TYPE_128 },
|
|
||||||
.value = MUSIC_SERVICE_UUID_BASE
|
|
||||||
};
|
|
||||||
|
|
||||||
ble_uuid128_t msEventCharUuid {
|
|
||||||
.u = { .type = BLE_UUID_TYPE_128 },
|
|
||||||
.value = MUSIC_SERVICE_UUID_BASE
|
|
||||||
};
|
|
||||||
ble_uuid128_t msStatusCharUuid {
|
|
||||||
.u = { .type = BLE_UUID_TYPE_128 },
|
|
||||||
.value = MUSIC_SERVICE_UUID_BASE
|
|
||||||
};
|
|
||||||
ble_uuid128_t msArtistCharUuid {
|
|
||||||
.u = { .type = BLE_UUID_TYPE_128 },
|
|
||||||
.value = MUSIC_SERVICE_UUID_BASE
|
|
||||||
};
|
|
||||||
ble_uuid128_t msTrackCharUuid {
|
|
||||||
.u = { .type = BLE_UUID_TYPE_128 },
|
|
||||||
.value = MUSIC_SERVICE_UUID_BASE
|
|
||||||
};
|
|
||||||
ble_uuid128_t msAlbumCharUuid {
|
|
||||||
.u = { .type = BLE_UUID_TYPE_128 },
|
|
||||||
.value = MUSIC_SERVICE_UUID_BASE
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ble_gatt_chr_def characteristicDefinition[6];
|
|
||||||
struct ble_gatt_svc_def serviceDefinition[2];
|
|
||||||
|
|
||||||
uint16_t m_eventHandle;
|
|
||||||
|
|
||||||
std::string m_artist;
|
|
||||||
std::string m_album;
|
|
||||||
std::string m_track;
|
|
||||||
|
|
||||||
unsigned char m_status;
|
|
||||||
|
|
||||||
Pinetime::System::SystemTask& m_system;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
#include <cstring>
|
|
||||||
#include "NotificationManager.h"
|
|
||||||
|
|
||||||
using namespace Pinetime::Controllers;
|
|
||||||
|
|
||||||
void NotificationManager::Push(Pinetime::Controllers::NotificationManager::Categories category,
|
|
||||||
const char *message, uint8_t currentMessageSize) {
|
|
||||||
// TODO handle edge cases on read/write index
|
|
||||||
auto checkedSize = std::min(currentMessageSize, uint8_t{18});
|
|
||||||
auto& notif = notifications[writeIndex];
|
|
||||||
std::memcpy(notif.message.data(), message, checkedSize);
|
|
||||||
notif.message[checkedSize] = '\0';
|
|
||||||
notif.category = category;
|
|
||||||
|
|
||||||
writeIndex = (writeIndex + 1 < TotalNbNotifications) ? writeIndex + 1 : 0;
|
|
||||||
if(!empty && writeIndex == readIndex)
|
|
||||||
readIndex = writeIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationManager::Notification Pinetime::Controllers::NotificationManager::Pop() {
|
|
||||||
// TODO handle edge cases on read/write index
|
|
||||||
NotificationManager::Notification notification = notifications[readIndex];
|
|
||||||
|
|
||||||
if(readIndex != writeIndex) {
|
|
||||||
readIndex = (readIndex + 1 < TotalNbNotifications) ? readIndex + 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Check move optimization on return
|
|
||||||
return notification;
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace Controllers {
|
|
||||||
class NotificationManager {
|
|
||||||
public:
|
|
||||||
enum class Categories {Unknown, SimpleAlert, Email, News, IncomingCall, MissedCall, Sms, VoiceMail, Schedule, HighProriotyAlert, InstantMessage };
|
|
||||||
static constexpr uint8_t MessageSize{18};
|
|
||||||
|
|
||||||
struct Notification {
|
|
||||||
std::array<char, MessageSize+1> message;
|
|
||||||
Categories category = Categories::Unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
void Push(Categories category, const char* message, uint8_t messageSize);
|
|
||||||
Notification Pop();
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr uint8_t TotalNbNotifications = 5;
|
|
||||||
std::array<Notification, TotalNbNotifications> notifications;
|
|
||||||
uint8_t readIndex = 0;
|
|
||||||
uint8_t writeIndex = 0;
|
|
||||||
bool empty = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include "Screen.h"
|
|
||||||
#include <bits/unique_ptr.h>
|
|
||||||
#include <libs/lvgl/src/lv_core/lv_style.h>
|
|
||||||
#include <libs/lvgl/src/lv_core/lv_obj.h>
|
|
||||||
#include <drivers/St7789.h>
|
|
||||||
#include <DisplayApp/LittleVgl.h>
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace Applications {
|
|
||||||
namespace Screens {
|
|
||||||
|
|
||||||
class InfiniPaint : public Screen{
|
|
||||||
public:
|
|
||||||
InfiniPaint(DisplayApp* app, Pinetime::Components::LittleVgl& lvgl);
|
|
||||||
~InfiniPaint() override;
|
|
||||||
|
|
||||||
bool Refresh() override;
|
|
||||||
bool OnButtonPushed() override;
|
|
||||||
bool OnTouchEvent(TouchEvents event) override;
|
|
||||||
bool OnTouchEvent(uint16_t x, uint16_t y) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Pinetime::Components::LittleVgl& lvgl;
|
|
||||||
static constexpr uint16_t width = 10;
|
|
||||||
static constexpr uint16_t height = 10;
|
|
||||||
static constexpr uint16_t bufferSize = width*height;
|
|
||||||
lv_color_t b[bufferSize];
|
|
||||||
bool running = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
#include <libs/lvgl/lvgl.h>
|
|
||||||
#include "Music.h"
|
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
|
||||||
extern lv_font_t jetbrains_mono_extrabold_compressed;
|
|
||||||
extern lv_font_t jetbrains_mono_bold_20;
|
|
||||||
|
|
||||||
static void event_handler(lv_obj_t * obj, lv_event_t event)
|
|
||||||
{
|
|
||||||
Music* screen = static_cast<Music *>(obj->user_data);
|
|
||||||
screen->OnObjectEvent(obj, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
Music::Music(Pinetime::Applications::DisplayApp *app, Pinetime::Controllers::MusicService &music) : Screen(app), musicService(music) {
|
|
||||||
lv_obj_t * label;
|
|
||||||
|
|
||||||
btnVolDown = lv_btn_create(lv_scr_act(), NULL);
|
|
||||||
btnVolDown->user_data = this;
|
|
||||||
lv_obj_set_event_cb(btnVolDown, event_handler);
|
|
||||||
lv_obj_align(btnVolDown, NULL, LV_ALIGN_IN_TOP_LEFT, 10, 10);
|
|
||||||
label = lv_label_create(btnVolDown, NULL);
|
|
||||||
lv_label_set_text(label, "v-");
|
|
||||||
|
|
||||||
btnVolUp = lv_btn_create(lv_scr_act(), NULL);
|
|
||||||
btnVolUp->user_data = this;
|
|
||||||
lv_obj_set_event_cb(btnVolUp, event_handler);
|
|
||||||
lv_obj_align(btnVolUp, NULL, LV_ALIGN_IN_TOP_RIGHT, -10, 10);
|
|
||||||
label = lv_label_create(btnVolUp, NULL);
|
|
||||||
lv_label_set_text(label, "v+");
|
|
||||||
|
|
||||||
btnPrev = lv_btn_create(lv_scr_act(), NULL);
|
|
||||||
btnPrev->user_data = this;
|
|
||||||
lv_obj_set_event_cb(btnPrev, event_handler);
|
|
||||||
lv_obj_set_size(btnPrev, LV_HOR_RES / 4, LV_VER_RES / 4);
|
|
||||||
lv_obj_align(btnPrev, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 10,-10);
|
|
||||||
label = lv_label_create(btnPrev, NULL);
|
|
||||||
lv_label_set_text(label, "<<");
|
|
||||||
|
|
||||||
btnPlayPause = lv_btn_create(lv_scr_act(), NULL);
|
|
||||||
btnPlayPause->user_data = this;
|
|
||||||
lv_obj_set_event_cb(btnPlayPause, event_handler);
|
|
||||||
lv_obj_set_size(btnPlayPause, LV_HOR_RES / 4, LV_VER_RES / 4);
|
|
||||||
lv_obj_align(btnPlayPause, NULL, LV_ALIGN_IN_BOTTOM_MID, 0,-10);
|
|
||||||
txtPlayPause = lv_label_create(btnPlayPause, NULL);
|
|
||||||
lv_label_set_text(txtPlayPause, ">");
|
|
||||||
|
|
||||||
btnNext = lv_btn_create(lv_scr_act(), NULL);
|
|
||||||
btnNext->user_data = this;
|
|
||||||
lv_obj_set_event_cb(btnNext, event_handler);
|
|
||||||
lv_obj_set_size(btnNext, LV_HOR_RES / 4, LV_VER_RES / 4);
|
|
||||||
lv_obj_align(btnNext, NULL, LV_ALIGN_IN_BOTTOM_RIGHT, -10,-10);
|
|
||||||
label = lv_label_create(btnNext, NULL);
|
|
||||||
lv_label_set_text(label, ">>");
|
|
||||||
|
|
||||||
txtArtist = lv_label_create(lv_scr_act(), NULL);
|
|
||||||
lv_label_set_long_mode(txtArtist, LV_LABEL_LONG_SROLL);
|
|
||||||
lv_obj_align(txtArtist, NULL, LV_ALIGN_IN_LEFT_MID, 0,-20);
|
|
||||||
lv_label_set_text(txtArtist, "Artist Name");
|
|
||||||
lv_label_set_align(txtArtist, LV_LABEL_ALIGN_CENTER);
|
|
||||||
lv_obj_set_width(txtArtist, LV_HOR_RES);
|
|
||||||
|
|
||||||
txtTrack = lv_label_create(lv_scr_act(), NULL);
|
|
||||||
lv_label_set_long_mode(txtTrack, LV_LABEL_LONG_DOT);
|
|
||||||
lv_obj_align(txtTrack, NULL, LV_ALIGN_IN_LEFT_MID, 0,20);
|
|
||||||
lv_label_set_text(txtTrack, "This is a very long track name");
|
|
||||||
lv_label_set_align(txtTrack, LV_LABEL_ALIGN_CENTER);
|
|
||||||
lv_obj_set_width(txtTrack, LV_HOR_RES);
|
|
||||||
|
|
||||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_OPEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
Music::~Music() {
|
|
||||||
lv_obj_clean(lv_scr_act());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Music::OnButtonPushed() {
|
|
||||||
running = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Music::Refresh() {
|
|
||||||
|
|
||||||
if (m_artist != musicService.artist()) {
|
|
||||||
m_artist = musicService.artist();
|
|
||||||
lv_label_set_text(txtArtist, m_artist.data());
|
|
||||||
}
|
|
||||||
if (m_track != musicService.track()) {
|
|
||||||
m_track = musicService.track();
|
|
||||||
lv_label_set_text(txtTrack, m_track.data());
|
|
||||||
}
|
|
||||||
if (m_album != musicService.album()) {
|
|
||||||
m_album = musicService.album();
|
|
||||||
}
|
|
||||||
if (m_status != musicService.status()) {
|
|
||||||
m_status = musicService.status();
|
|
||||||
}
|
|
||||||
if (m_status == Pinetime::Controllers::MusicService::STATUS_MUSIC_PLAYING) {
|
|
||||||
lv_label_set_text(txtPlayPause, "||");
|
|
||||||
} else {
|
|
||||||
lv_label_set_text(txtPlayPause, ">");
|
|
||||||
}
|
|
||||||
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Music::OnObjectEvent(lv_obj_t* obj, lv_event_t event)
|
|
||||||
{
|
|
||||||
if (event == LV_EVENT_CLICKED) {
|
|
||||||
if (obj == btnVolDown) {
|
|
||||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_VOLDOWN);
|
|
||||||
} else if (obj == btnVolUp) {
|
|
||||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_VOLUP);
|
|
||||||
} else if (obj == btnPrev) {
|
|
||||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_PREV);
|
|
||||||
} else if (obj == btnPlayPause) {
|
|
||||||
if (m_status == Pinetime::Controllers::MusicService::STATUS_MUSIC_PLAYING) {
|
|
||||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_PAUSE);
|
|
||||||
} else {
|
|
||||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_PLAY);
|
|
||||||
}
|
|
||||||
} else if (obj == btnNext) {
|
|
||||||
musicService.event(Controllers::MusicService::EVENT_MUSIC_NEXT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <chrono>
|
|
||||||
#include <Components/Gfx/Gfx.h>
|
|
||||||
#include "Screen.h"
|
|
||||||
#include <bits/unique_ptr.h>
|
|
||||||
#include <libs/lvgl/src/lv_core/lv_style.h>
|
|
||||||
#include <libs/lvgl/src/lv_core/lv_obj.h>
|
|
||||||
#include <Components/Battery/BatteryController.h>
|
|
||||||
#include <Components/Ble/BleController.h>
|
|
||||||
#include "../../Version.h"
|
|
||||||
#include <Components/Ble/MusicService.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace Applications {
|
|
||||||
namespace Screens {
|
|
||||||
|
|
||||||
class Music : public Screen{
|
|
||||||
public:
|
|
||||||
Music(DisplayApp* app, Pinetime::Controllers::MusicService &music);
|
|
||||||
~Music() override;
|
|
||||||
|
|
||||||
bool Refresh() override;
|
|
||||||
bool OnButtonPushed() override;
|
|
||||||
|
|
||||||
void OnObjectEvent(lv_obj_t* obj, lv_event_t event);
|
|
||||||
|
|
||||||
private:
|
|
||||||
lv_obj_t * btnPrev;
|
|
||||||
lv_obj_t * btnPlayPause;
|
|
||||||
lv_obj_t * btnNext;
|
|
||||||
lv_obj_t * btnVolDown;
|
|
||||||
lv_obj_t * btnVolUp;
|
|
||||||
lv_obj_t * txtArtist;
|
|
||||||
lv_obj_t * txtTrack;
|
|
||||||
lv_obj_t * txtPlayPause;
|
|
||||||
|
|
||||||
bool running = true;
|
|
||||||
Pinetime::Controllers::MusicService &musicService;
|
|
||||||
std::string m_artist;
|
|
||||||
std::string m_album;
|
|
||||||
std::string m_track;
|
|
||||||
unsigned char m_status;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include "../TouchEvents.h"
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace Applications {
|
|
||||||
class DisplayApp;
|
|
||||||
namespace Screens {
|
|
||||||
class Screen {
|
|
||||||
public:
|
|
||||||
Screen(DisplayApp* app) : app{app} {}
|
|
||||||
virtual ~Screen() = default;
|
|
||||||
|
|
||||||
// Return false if the app can be closed, true if it must continue to run
|
|
||||||
virtual bool Refresh() = 0;
|
|
||||||
|
|
||||||
// Return false if the button hasn't been handled by the app, true if it has been handled
|
|
||||||
virtual bool OnButtonPushed() { return false; }
|
|
||||||
|
|
||||||
// Return false if the event hasn't been handled by the app, true if it has been handled
|
|
||||||
virtual bool OnTouchEvent(TouchEvents event) { return false; }
|
|
||||||
virtual bool OnTouchEvent(uint16_t x, uint16_t y) { return false; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
DisplayApp* app;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include <drivers/include/nrfx_saadc.h>
|
#include <drivers/include/nrfx_saadc.h>
|
||||||
#include <hal/nrf_gpio.h>
|
#include <hal/nrf_gpio.h>
|
||||||
#include <libraries/log/nrf_log.h>
|
#include <libraries/log/nrf_log.h>
|
||||||
|
#include <algorithm>
|
||||||
#include "BatteryController.h"
|
#include "BatteryController.h"
|
||||||
|
|
||||||
using namespace Pinetime::Controllers;
|
using namespace Pinetime::Controllers;
|
||||||
|
@ -34,7 +35,9 @@ void Battery::Update() {
|
||||||
|
|
||||||
// see https://forum.pine64.org/showthread.php?tid=8147
|
// see https://forum.pine64.org/showthread.php?tid=8147
|
||||||
voltage = (value * 2.0f) / (1024/3.0f);
|
voltage = (value * 2.0f) / (1024/3.0f);
|
||||||
percentRemaining = ((voltage - 3.55)*100)*3.9;
|
percentRemaining = ((voltage - 3.55f)*100.0f)*3.9f;
|
||||||
|
percentRemaining = std::max(percentRemaining, 0.0f);
|
||||||
|
percentRemaining = std::min(percentRemaining, 100.0f);
|
||||||
|
|
||||||
// NRF_LOG_INFO("BATTERY " NRF_LOG_FLOAT_MARKER " %% - " NRF_LOG_FLOAT_MARKER " v", NRF_LOG_FLOAT(percentRemaining), NRF_LOG_FLOAT(voltage));
|
// NRF_LOG_INFO("BATTERY " NRF_LOG_FLOAT_MARKER " %% - " NRF_LOG_FLOAT_MARKER " v", NRF_LOG_FLOAT(percentRemaining), NRF_LOG_FLOAT(voltage));
|
||||||
// NRF_LOG_INFO("POWER Charging : %d - Power : %d", isCharging, isPowerPresent);
|
// NRF_LOG_INFO("POWER Charging : %d - Power : %d", isCharging, isPowerPresent);
|
|
@ -0,0 +1,194 @@
|
||||||
|
#include <systemtask/SystemTask.h>
|
||||||
|
#include "NotificationManager.h"
|
||||||
|
|
||||||
|
#include "AlertNotificationClient.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Controllers;
|
||||||
|
constexpr ble_uuid16_t AlertNotificationClient::ansServiceUuid;
|
||||||
|
constexpr ble_uuid16_t AlertNotificationClient::supportedNewAlertCategoryUuid;
|
||||||
|
constexpr ble_uuid16_t AlertNotificationClient::supportedUnreadAlertCategoryUuid;
|
||||||
|
constexpr ble_uuid16_t AlertNotificationClient::newAlertUuid;
|
||||||
|
constexpr ble_uuid16_t AlertNotificationClient::unreadAlertStatusUuid;
|
||||||
|
constexpr ble_uuid16_t AlertNotificationClient::controlPointUuid;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int
|
||||||
|
OnDiscoveryEventCallback(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service,
|
||||||
|
void *arg) {
|
||||||
|
auto client = static_cast<AlertNotificationClient *>(arg);
|
||||||
|
return client->OnDiscoveryEvent(conn_handle, error, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OnAlertNotificationCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
||||||
|
const struct ble_gatt_chr *chr, void *arg) {
|
||||||
|
auto client = static_cast<AlertNotificationClient *>(arg);
|
||||||
|
return client->OnCharacteristicsDiscoveryEvent(conn_handle, error, chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OnAlertNotificationDescriptorDiscoveryEventCallback(uint16_t conn_handle,
|
||||||
|
const struct ble_gatt_error *error,
|
||||||
|
uint16_t chr_val_handle,
|
||||||
|
const struct ble_gatt_dsc *dsc,
|
||||||
|
void *arg) {
|
||||||
|
auto client = static_cast<AlertNotificationClient *>(arg);
|
||||||
|
return client->OnDescriptorDiscoveryEventCallback(conn_handle, error, chr_val_handle, dsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NewAlertSubcribeCallback(uint16_t conn_handle,
|
||||||
|
const struct ble_gatt_error *error,
|
||||||
|
struct ble_gatt_attr *attr,
|
||||||
|
void *arg) {
|
||||||
|
auto client = static_cast<AlertNotificationClient *>(arg);
|
||||||
|
return client->OnNewAlertSubcribe(conn_handle, error, attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertNotificationClient::AlertNotificationClient(Pinetime::System::SystemTask &systemTask,
|
||||||
|
Pinetime::Controllers::NotificationManager ¬ificationManager) :
|
||||||
|
systemTask{systemTask}, notificationManager{notificationManager} {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||||
|
const ble_gatt_svc *service) {
|
||||||
|
if (service == nullptr && error->status == BLE_HS_EDONE) {
|
||||||
|
if (isDiscovered) {
|
||||||
|
NRF_LOG_INFO("ANS Discovery found, starting characteristics discovery");
|
||||||
|
|
||||||
|
ble_gattc_disc_all_chrs(connectionHandle, ansStartHandle, ansEndHandle,
|
||||||
|
OnAlertNotificationCharacteristicDiscoveredCallback, this);
|
||||||
|
} else {
|
||||||
|
NRF_LOG_INFO("ANS not found");
|
||||||
|
onServiceDiscovered(connectionHandle);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service != nullptr && ble_uuid_cmp(((ble_uuid_t *) &ansServiceUuid), &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;
|
||||||
|
isDiscovered = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||||
|
const ble_gatt_chr *characteristic) {
|
||||||
|
if (error->status != 0 && error->status != BLE_HS_EDONE) {
|
||||||
|
NRF_LOG_INFO("ANS Characteristic discovery ERROR");
|
||||||
|
onServiceDiscovered(connectionHandle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
||||||
|
NRF_LOG_INFO("ANS Characteristic discovery complete");
|
||||||
|
if (isCharacteristicDiscovered) {
|
||||||
|
ble_gattc_disc_all_dscs(connectionHandle,
|
||||||
|
newAlertHandle, ansEndHandle,
|
||||||
|
OnAlertNotificationDescriptorDiscoveryEventCallback, this);
|
||||||
|
} else
|
||||||
|
onServiceDiscovered(connectionHandle);
|
||||||
|
} else {
|
||||||
|
if (characteristic != nullptr &&
|
||||||
|
ble_uuid_cmp(((ble_uuid_t *) &supportedNewAlertCategoryUuid), &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) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
NRF_LOG_INFO("ANS Characteristic discovered : controlPointUuid");
|
||||||
|
controlPointHandle = characteristic->val_handle;
|
||||||
|
} else NRF_LOG_INFO("ANS Characteristic discovered : 0x%x", characteristic->val_handle);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AlertNotificationClient::OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||||
|
ble_gatt_attr *attribute) {
|
||||||
|
if (error->status == 0) {
|
||||||
|
NRF_LOG_INFO("ANS New alert subscribe OK");
|
||||||
|
} else {
|
||||||
|
NRF_LOG_INFO("ANS New alert subscribe ERROR");
|
||||||
|
}
|
||||||
|
onServiceDiscovered(connectionHandle);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AlertNotificationClient::OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||||
|
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 (newAlertDescriptorHandle == 0) {
|
||||||
|
NRF_LOG_INFO("ANS Descriptor discovered : %d", descriptor->handle);
|
||||||
|
newAlertDescriptorHandle = descriptor->handle;
|
||||||
|
isDescriptorFound = true;
|
||||||
|
uint8_t value[2];
|
||||||
|
value[0] = 1;
|
||||||
|
value[1] = 0;
|
||||||
|
ble_gattc_write_flat(connectionHandle, newAlertDescriptorHandle, value, sizeof(value), NewAlertSubcribeCallback, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isDescriptorFound)
|
||||||
|
onServiceDiscovered(connectionHandle);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlertNotificationClient::OnNotification(ble_gap_event *event) {
|
||||||
|
if (event->notify_rx.attr_handle == newAlertHandle) {
|
||||||
|
constexpr size_t stringTerminatorSize = 1; // end of string '\0'
|
||||||
|
constexpr size_t headerSize = 3;
|
||||||
|
const auto maxMessageSize{NotificationManager::MaximumMessageSize()};
|
||||||
|
const auto maxBufferSize{maxMessageSize + headerSize};
|
||||||
|
|
||||||
|
const auto dbgPacketLen = OS_MBUF_PKTLEN(event->notify_rx.om);
|
||||||
|
size_t bufferSize = min(dbgPacketLen + stringTerminatorSize, maxBufferSize);
|
||||||
|
auto messageSize = min(maxMessageSize, (bufferSize - headerSize));
|
||||||
|
|
||||||
|
NotificationManager::Notification notif;
|
||||||
|
os_mbuf_copydata(event->notify_rx.om, headerSize, messageSize - 1, notif.message.data());
|
||||||
|
notif.message[messageSize - 1] = '\0';
|
||||||
|
notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
|
||||||
|
notificationManager.Push(std::move(notif));
|
||||||
|
|
||||||
|
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlertNotificationClient::Reset() {
|
||||||
|
ansStartHandle = 0;
|
||||||
|
ansEndHandle = 0;
|
||||||
|
supportedNewAlertCategoryHandle = 0;
|
||||||
|
supportedUnreadAlertCategoryHandle = 0;
|
||||||
|
newAlertHandle = 0;
|
||||||
|
newAlertDescriptorHandle = 0;
|
||||||
|
newAlertDefHandle = 0;
|
||||||
|
unreadAlertStatusHandle = 0;
|
||||||
|
controlPointHandle = 0;
|
||||||
|
isDiscovered = false;
|
||||||
|
isCharacteristicDiscovered = false;
|
||||||
|
isDescriptorFound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlertNotificationClient::Discover(uint16_t connectionHandle, std::function<void(uint16_t)> onServiceDiscovered) {
|
||||||
|
NRF_LOG_INFO("[ANS] Starting discovery");
|
||||||
|
this->onServiceDiscovered = onServiceDiscovered;
|
||||||
|
ble_gattc_disc_svc_by_uuid(connectionHandle, &ansServiceUuid.u, OnDiscoveryEventCallback, this);
|
||||||
|
}
|
|
@ -3,16 +3,12 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <host/ble_gap.h>
|
#include <host/ble_gap.h>
|
||||||
|
#include "BleClient.h"
|
||||||
|
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
int NewAlertSubcribeCallback(uint16_t conn_handle,
|
class AlertNotificationClient : public BleClient {
|
||||||
const struct ble_gatt_error *error,
|
|
||||||
struct ble_gatt_attr *attr,
|
|
||||||
void *arg);
|
|
||||||
|
|
||||||
class AlertNotificationClient {
|
|
||||||
public:
|
public:
|
||||||
explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask,
|
explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask,
|
||||||
Pinetime::Controllers::NotificationManager ¬ificationManager);
|
Pinetime::Controllers::NotificationManager ¬ificationManager);
|
||||||
|
@ -24,13 +20,9 @@ namespace Pinetime {
|
||||||
int OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
int OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||||
uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
|
uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
|
||||||
void OnNotification(ble_gap_event *event);
|
void OnNotification(ble_gap_event *event);
|
||||||
bool IsDiscovered() const;
|
void Reset();
|
||||||
uint16_t StartHandle() const;
|
void Discover(uint16_t connectionHandle, std::function<void(uint16_t)> lambda) override;
|
||||||
uint16_t EndHandle() const;
|
|
||||||
|
|
||||||
static constexpr const ble_uuid16_t &Uuid() { return ansServiceUuid; }
|
|
||||||
|
|
||||||
uint16_t NewAlerthandle() const;
|
|
||||||
private:
|
private:
|
||||||
static constexpr uint16_t ansServiceId{0x1811};
|
static constexpr uint16_t ansServiceId{0x1811};
|
||||||
static constexpr uint16_t supportedNewAlertCategoryId = 0x2a47;
|
static constexpr uint16_t supportedNewAlertCategoryId = 0x2a47;
|
||||||
|
@ -64,18 +56,21 @@ namespace Pinetime {
|
||||||
.value = controlPointId
|
.value = controlPointId
|
||||||
};
|
};
|
||||||
|
|
||||||
uint16_t ansStartHandle;
|
uint16_t ansStartHandle = 0;
|
||||||
uint16_t ansEndHandle;
|
uint16_t ansEndHandle = 0;
|
||||||
uint16_t supportedNewAlertCategoryHandle;
|
uint16_t supportedNewAlertCategoryHandle = 0;
|
||||||
uint16_t supportedUnreadAlertCategoryHandle;
|
uint16_t supportedUnreadAlertCategoryHandle = 0;
|
||||||
uint16_t newAlertHandle;
|
uint16_t newAlertHandle = 0;
|
||||||
uint16_t newAlertDescriptorHandle = 0;
|
uint16_t newAlertDescriptorHandle = 0;
|
||||||
uint16_t newAlertDefHandle;
|
uint16_t newAlertDefHandle = 0;
|
||||||
uint16_t unreadAlertStatusHandle;
|
uint16_t unreadAlertStatusHandle = 0;
|
||||||
uint16_t controlPointHandle;
|
uint16_t controlPointHandle = 0;
|
||||||
bool isDiscovered = false;
|
bool isDiscovered = false;
|
||||||
Pinetime::System::SystemTask &systemTask;
|
Pinetime::System::SystemTask &systemTask;
|
||||||
Pinetime::Controllers::NotificationManager ¬ificationManager;
|
Pinetime::Controllers::NotificationManager ¬ificationManager;
|
||||||
|
std::function<void(uint16_t)> onServiceDiscovered;
|
||||||
|
bool isCharacteristicDiscovered = false;
|
||||||
|
bool isDescriptorFound = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
#include <hal/nrf_rtc.h>
|
#include <hal/nrf_rtc.h>
|
||||||
#include "NotificationManager.h"
|
#include "NotificationManager.h"
|
||||||
#include <SystemTask/SystemTask.h>
|
#include <systemtask/SystemTask.h>
|
||||||
|
|
||||||
#include "AlertNotificationService.h"
|
#include "AlertNotificationService.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -38,7 +38,7 @@ AlertNotificationService::AlertNotificationService ( System::SystemTask& systemT
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
serviceDefinition{
|
serviceDefinition{
|
||||||
{
|
{
|
||||||
/* Device Information Service */
|
/* Device Information Service */
|
||||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||||
|
@ -48,33 +48,28 @@ AlertNotificationService::AlertNotificationService ( System::SystemTask& systemT
|
||||||
{
|
{
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
}, m_systemTask{systemTask}, m_notificationManager{notificationManager} {
|
}, systemTask{systemTask}, notificationManager{notificationManager} {
|
||||||
}
|
}
|
||||||
|
|
||||||
int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle,
|
int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle,
|
||||||
struct ble_gatt_access_ctxt *ctxt) {
|
struct ble_gatt_access_ctxt *ctxt) {
|
||||||
|
|
||||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||||
// TODO implement this with more memory safety (and constexpr)
|
constexpr size_t stringTerminatorSize = 1; // end of string '\0'
|
||||||
static const size_t maxBufferSize{21};
|
constexpr size_t headerSize = 3;
|
||||||
static const size_t maxMessageSize{18};
|
const auto maxMessageSize {NotificationManager::MaximumMessageSize()};
|
||||||
size_t bufferSize = min(OS_MBUF_PKTLEN(ctxt->om), maxBufferSize);
|
const auto maxBufferSize{maxMessageSize + headerSize};
|
||||||
|
|
||||||
uint8_t data[bufferSize];
|
const auto dbgPacketLen = OS_MBUF_PKTLEN(ctxt->om);
|
||||||
os_mbuf_copydata(ctxt->om, 0, bufferSize, data);
|
size_t bufferSize = min(dbgPacketLen + stringTerminatorSize, maxBufferSize);
|
||||||
|
auto messageSize = min(maxMessageSize, (bufferSize-headerSize));
|
||||||
|
|
||||||
char *s = (char *) &data[3];
|
NotificationManager::Notification notif;
|
||||||
auto messageSize = min(maxMessageSize, (bufferSize-3));
|
os_mbuf_copydata(ctxt->om, headerSize, messageSize-1, notif.message.data());
|
||||||
|
notif.message[messageSize-1] = '\0';
|
||||||
|
notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
|
||||||
|
notificationManager.Push(std::move(notif));
|
||||||
|
|
||||||
for (uint i = 0; i < messageSize-1; i++) {
|
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
||||||
if (s[i] == 0x00) {
|
|
||||||
s[i] = 0x0A;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s[messageSize-1] = '\0';
|
|
||||||
|
|
||||||
m_notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
|
|
||||||
m_systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
|
@ -32,8 +32,8 @@ namespace Pinetime {
|
||||||
struct ble_gatt_chr_def characteristicDefinition[2];
|
struct ble_gatt_chr_def characteristicDefinition[2];
|
||||||
struct ble_gatt_svc_def serviceDefinition[2];
|
struct ble_gatt_svc_def serviceDefinition[2];
|
||||||
|
|
||||||
Pinetime::System::SystemTask &m_systemTask;
|
Pinetime::System::SystemTask &systemTask;
|
||||||
NotificationManager &m_notificationManager;
|
NotificationManager ¬ificationManager;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
#include "BatteryInformationService.h"
|
||||||
|
#include "components/battery/BatteryController.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
|
constexpr ble_uuid16_t BatteryInformationService::batteryInformationServiceUuid;
|
||||||
|
constexpr ble_uuid16_t BatteryInformationService::batteryLevelUuid;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int BatteryInformationServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||||
|
auto* batteryInformationService = static_cast<BatteryInformationService*>(arg);
|
||||||
|
return batteryInformationService->OnBatteryServiceRequested(conn_handle, attr_handle, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
BatteryInformationService::BatteryInformationService(Controllers::Battery& batteryController) :
|
||||||
|
batteryController{batteryController},
|
||||||
|
characteristicDefinition{
|
||||||
|
{
|
||||||
|
.uuid = (ble_uuid_t *) &batteryLevelUuid,
|
||||||
|
.access_cb = BatteryInformationServiceCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_READ,
|
||||||
|
.val_handle = &batteryLevelHandle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
serviceDefinition{
|
||||||
|
{
|
||||||
|
/* Device Information Service */
|
||||||
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||||
|
.uuid = (ble_uuid_t *) &batteryInformationServiceUuid,
|
||||||
|
.characteristics = characteristicDefinition
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0
|
||||||
|
},
|
||||||
|
}{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryInformationService::Init() {
|
||||||
|
int res = 0;
|
||||||
|
res = ble_gatts_count_cfg(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
|
||||||
|
res = ble_gatts_add_svcs(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int BatteryInformationService::OnBatteryServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle,
|
||||||
|
ble_gatt_access_ctxt *context) {
|
||||||
|
if(attributeHandle == batteryLevelHandle) {
|
||||||
|
NRF_LOG_INFO("BATTERY : handle = %d", batteryLevelHandle);
|
||||||
|
static uint8_t batteryValue = batteryController.PercentRemaining();
|
||||||
|
int res = os_mbuf_append(context->om, &batteryValue, 1);
|
||||||
|
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
#include <host/ble_gap.h>
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace System {
|
||||||
|
class SystemTask;
|
||||||
|
}
|
||||||
|
namespace Controllers {
|
||||||
|
class Battery;
|
||||||
|
class BatteryInformationService {
|
||||||
|
public:
|
||||||
|
BatteryInformationService(Controllers::Battery& batteryController);
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
int
|
||||||
|
OnBatteryServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Controllers::Battery& batteryController;
|
||||||
|
static constexpr uint16_t batteryInformationServiceId {0x180F};
|
||||||
|
static constexpr uint16_t batteryLevelId {0x2A19};
|
||||||
|
|
||||||
|
static constexpr ble_uuid16_t batteryInformationServiceUuid {
|
||||||
|
.u {.type = BLE_UUID_TYPE_16},
|
||||||
|
.value = batteryInformationServiceId
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr ble_uuid16_t batteryLevelUuid {
|
||||||
|
.u {.type = BLE_UUID_TYPE_16},
|
||||||
|
.value = batteryLevelId
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ble_gatt_chr_def characteristicDefinition[3];
|
||||||
|
struct ble_gatt_svc_def serviceDefinition[2];
|
||||||
|
|
||||||
|
uint16_t batteryLevelHandle;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace Controllers{
|
||||||
|
class BleClient {
|
||||||
|
public:
|
||||||
|
virtual void Discover(uint16_t connectionHandle, std::function<void(uint16_t)> lambda) = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
#include <hal/nrf_rtc.h>
|
||||||
|
#include "CurrentTimeClient.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
|
constexpr ble_uuid16_t CurrentTimeClient::ctsServiceUuid;
|
||||||
|
constexpr ble_uuid16_t CurrentTimeClient::currentTimeCharacteristicUuid;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int OnDiscoveryEventCallback(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) {
|
||||||
|
auto client = static_cast<CurrentTimeClient *>(arg);
|
||||||
|
return client->OnDiscoveryEvent(conn_handle, error, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OnCurrentTimeCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
||||||
|
const struct ble_gatt_chr *chr, void *arg) {
|
||||||
|
auto client = static_cast<CurrentTimeClient *>(arg);
|
||||||
|
return client->OnCharacteristicDiscoveryEvent(conn_handle, error, chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CurrentTimeReadCallback(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) {
|
||||||
|
auto client = static_cast<CurrentTimeClient *>(arg);
|
||||||
|
return client->OnCurrentTimeReadResult(conn_handle, error, attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentTimeClient::CurrentTimeClient(DateTime &dateTimeController) : dateTimeController{dateTimeController} {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurrentTimeClient::Init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CurrentTimeClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||||
|
const ble_gatt_svc *service) {
|
||||||
|
if (service == nullptr && error->status == BLE_HS_EDONE) {
|
||||||
|
if (isDiscovered) {
|
||||||
|
NRF_LOG_INFO("CTS found, starting characteristics discovery");
|
||||||
|
|
||||||
|
ble_gattc_disc_all_chrs(connectionHandle, ctsStartHandle, ctsEndHandle,
|
||||||
|
OnCurrentTimeCharacteristicDiscoveredCallback, this);
|
||||||
|
} else {
|
||||||
|
NRF_LOG_INFO("CTS not found");
|
||||||
|
onServiceDiscovered(connectionHandle);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service != nullptr && ble_uuid_cmp(((ble_uuid_t *) &ctsServiceUuid), &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;
|
||||||
|
ctsEndHandle = service->end_handle;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CurrentTimeClient::OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
|
||||||
|
const ble_gatt_chr *characteristic) {
|
||||||
|
if (characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
||||||
|
if (isCharacteristicDiscovered) {
|
||||||
|
NRF_LOG_INFO("CTS Characteristic discovery complete, fetching time");
|
||||||
|
ble_gattc_read(conn_handle, currentTimeHandle, CurrentTimeReadCallback, this);
|
||||||
|
} else {
|
||||||
|
NRF_LOG_INFO("CTS Characteristic discovery unsuccessful");
|
||||||
|
onServiceDiscovered(conn_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characteristic != nullptr &&
|
||||||
|
ble_uuid_cmp(((ble_uuid_t *) ¤tTimeCharacteristicUuid), &characteristic->uuid.u) == 0) {
|
||||||
|
NRF_LOG_INFO("CTS Characteristic discovered : 0x%x", characteristic->val_handle);
|
||||||
|
isCharacteristicDiscovered = true;
|
||||||
|
currentTimeHandle = characteristic->val_handle;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CurrentTimeClient::OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error,
|
||||||
|
const ble_gatt_attr *attribute) {
|
||||||
|
if (error->status == 0) {
|
||||||
|
// TODO check that attribute->handle equals the handle discovered in OnCharacteristicDiscoveryEvent
|
||||||
|
CtsData result;
|
||||||
|
os_mbuf_copydata(attribute->om, 0, sizeof(CtsData), &result);
|
||||||
|
NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", result.year,
|
||||||
|
result.month, result.dayofmonth,
|
||||||
|
result.hour, result.minute, result.second);
|
||||||
|
dateTimeController.SetTime(result.year, result.month, result.dayofmonth,
|
||||||
|
0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG));
|
||||||
|
} else {
|
||||||
|
NRF_LOG_INFO("Error retrieving current time: %d", error->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
onServiceDiscovered(conn_handle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurrentTimeClient::Reset() {
|
||||||
|
isDiscovered = false;
|
||||||
|
isCharacteristicDiscovered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurrentTimeClient::Discover(uint16_t connectionHandle, std::function<void(uint16_t)> onServiceDiscovered) {
|
||||||
|
NRF_LOG_INFO("[CTS] Starting discovery");
|
||||||
|
this->onServiceDiscovered = onServiceDiscovered;
|
||||||
|
ble_gattc_disc_svc_by_uuid(connectionHandle, &ctsServiceUuid.u, OnDiscoveryEventCallback, this);
|
||||||
|
}
|
|
@ -1,27 +1,28 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <Components/DateTime/DateTimeController.h>
|
|
||||||
|
#include "components/datetime/DateTimeController.h"
|
||||||
|
#include "BleClient.h"
|
||||||
#include <host/ble_gap.h>
|
#include <host/ble_gap.h>
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
|
|
||||||
class CurrentTimeClient {
|
class CurrentTimeClient : public BleClient {
|
||||||
public:
|
public:
|
||||||
explicit CurrentTimeClient(DateTime& dateTimeController);
|
explicit CurrentTimeClient(DateTime& dateTimeController);
|
||||||
void Init();
|
void Init();
|
||||||
|
void Reset();
|
||||||
bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
|
bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
|
||||||
int OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
|
int OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
|
||||||
const ble_gatt_chr *characteristic);
|
const ble_gatt_chr *characteristic);
|
||||||
int OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute);
|
int OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute);
|
||||||
bool IsDiscovered() const;
|
|
||||||
uint16_t StartHandle() const;
|
|
||||||
uint16_t EndHandle() const;
|
|
||||||
uint16_t CurrentTimeHandle() const;
|
|
||||||
static constexpr const ble_uuid16_t* Uuid() { return &CurrentTimeClient::ctsServiceUuid; }
|
static constexpr const ble_uuid16_t* Uuid() { return &CurrentTimeClient::ctsServiceUuid; }
|
||||||
static constexpr const ble_uuid16_t* CurrentTimeCharacteristicUuid() { return &CurrentTimeClient::currentTimeCharacteristicUuid; }
|
static constexpr const ble_uuid16_t* CurrentTimeCharacteristicUuid() { return &CurrentTimeClient::currentTimeCharacteristicUuid; }
|
||||||
private:
|
void Discover(uint16_t connectionHandle, std::function<void(uint16_t)> lambda) override;
|
||||||
|
|
||||||
|
private:
|
||||||
typedef struct __attribute__((packed)) {
|
typedef struct __attribute__((packed)) {
|
||||||
uint16_t year;
|
uint16_t year;
|
||||||
uint8_t month;
|
uint8_t month;
|
||||||
|
@ -45,11 +46,14 @@ namespace Pinetime {
|
||||||
.value = currentTimeCharacteristicId
|
.value = currentTimeCharacteristicId
|
||||||
};
|
};
|
||||||
|
|
||||||
uint16_t currentTimeHandle;
|
|
||||||
DateTime& dateTimeController;
|
DateTime& dateTimeController;
|
||||||
bool isDiscovered = false;
|
bool isDiscovered = false;
|
||||||
uint16_t ctsStartHandle;
|
uint16_t ctsStartHandle;
|
||||||
uint16_t ctsEndHandle;
|
uint16_t ctsEndHandle;
|
||||||
|
|
||||||
|
bool isCharacteristicDiscovered = false;
|
||||||
|
uint16_t currentTimeHandle;
|
||||||
|
std::function<void(uint16_t)> onServiceDiscovered;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <Components/DateTime/DateTimeController.h>
|
|
||||||
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include <host/ble_gap.h>
|
#include <host/ble_gap.h>
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
|
@ -1,6 +1,7 @@
|
||||||
#include <Components/Ble/BleController.h>
|
|
||||||
#include <SystemTask/SystemTask.h>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "components/ble/BleController.h"
|
||||||
|
#include "systemtask/SystemTask.h"
|
||||||
#include "DfuService.h"
|
#include "DfuService.h"
|
||||||
|
|
||||||
using namespace Pinetime::Controllers;
|
using namespace Pinetime::Controllers;
|
|
@ -0,0 +1,82 @@
|
||||||
|
#include <systemtask/SystemTask.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include "ImmediateAlertService.h"
|
||||||
|
#include "AlertNotificationService.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
|
constexpr ble_uuid16_t ImmediateAlertService::immediateAlertServiceUuid;
|
||||||
|
constexpr ble_uuid16_t ImmediateAlertService::alertLevelUuid;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int AlertLevelCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||||
|
auto *immediateAlertService = static_cast<ImmediateAlertService *>(arg);
|
||||||
|
return immediateAlertService->OnAlertLevelChanged(conn_handle, attr_handle, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ToString(ImmediateAlertService::Levels level) {
|
||||||
|
switch (level) {
|
||||||
|
case ImmediateAlertService::Levels::NoAlert: return "Alert : None";
|
||||||
|
case ImmediateAlertService::Levels::HighAlert: return "Alert : High";
|
||||||
|
case ImmediateAlertService::Levels::MildAlert: return "Alert : Mild";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmediateAlertService::ImmediateAlertService(Pinetime::System::SystemTask &systemTask,
|
||||||
|
Pinetime::Controllers::NotificationManager ¬ificationManager) :
|
||||||
|
systemTask{systemTask},
|
||||||
|
notificationManager{notificationManager},
|
||||||
|
characteristicDefinition{
|
||||||
|
{
|
||||||
|
.uuid = (ble_uuid_t *) &alertLevelUuid,
|
||||||
|
.access_cb = AlertLevelCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
|
||||||
|
.val_handle = &alertLevelHandle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
serviceDefinition{
|
||||||
|
{
|
||||||
|
/* Device Information Service */
|
||||||
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||||
|
.uuid = (ble_uuid_t *) &immediateAlertServiceUuid,
|
||||||
|
.characteristics = characteristicDefinition
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0
|
||||||
|
},
|
||||||
|
}{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImmediateAlertService::Init() {
|
||||||
|
int res = 0;
|
||||||
|
res = ble_gatts_count_cfg(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
|
||||||
|
res = ble_gatts_add_svcs(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ImmediateAlertService::OnAlertLevelChanged(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context) {
|
||||||
|
if(attributeHandle == alertLevelHandle) {
|
||||||
|
if(context->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||||
|
auto alertLevel = static_cast<Levels>(context->om->om_data[0]);
|
||||||
|
auto* alertString = ToString(alertLevel);
|
||||||
|
|
||||||
|
NotificationManager::Notification notif;
|
||||||
|
std::memcpy(notif.message.data(), alertString, strlen(alertString));
|
||||||
|
notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
|
||||||
|
notificationManager.Push(std::move(notif));
|
||||||
|
|
||||||
|
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
#include <host/ble_gap.h>
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace System {
|
||||||
|
class SystemTask;
|
||||||
|
}
|
||||||
|
namespace Controllers {
|
||||||
|
class NotificationManager;
|
||||||
|
class ImmediateAlertService {
|
||||||
|
public:
|
||||||
|
enum class Levels : uint8_t {
|
||||||
|
NoAlert = 0,
|
||||||
|
MildAlert = 1,
|
||||||
|
HighAlert = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
ImmediateAlertService(Pinetime::System::SystemTask &systemTask,
|
||||||
|
Pinetime::Controllers::NotificationManager ¬ificationManager);
|
||||||
|
void Init();
|
||||||
|
int OnAlertLevelChanged(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Pinetime::System::SystemTask& systemTask;
|
||||||
|
NotificationManager& notificationManager;
|
||||||
|
|
||||||
|
static constexpr uint16_t immediateAlertServiceId {0x1802};
|
||||||
|
static constexpr uint16_t alertLevelId {0x2A06};
|
||||||
|
|
||||||
|
static constexpr ble_uuid16_t immediateAlertServiceUuid {
|
||||||
|
.u {.type = BLE_UUID_TYPE_16},
|
||||||
|
.value = immediateAlertServiceId
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr ble_uuid16_t alertLevelUuid {
|
||||||
|
.u {.type = BLE_UUID_TYPE_16},
|
||||||
|
.value = alertLevelId
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ble_gatt_chr_def characteristicDefinition[3];
|
||||||
|
struct ble_gatt_svc_def serviceDefinition[2];
|
||||||
|
|
||||||
|
uint16_t alertLevelHandle;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
/* Copyright (C) 2020 JF, Adam Pigg, Avamander
|
||||||
|
|
||||||
|
This file is part of InfiniTime.
|
||||||
|
|
||||||
|
InfiniTime is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
InfiniTime is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <systemtask/SystemTask.h>
|
||||||
|
#include "MusicService.h"
|
||||||
|
|
||||||
|
int MSCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||||
|
auto musicService = static_cast<Pinetime::Controllers::MusicService *>(arg);
|
||||||
|
return musicService->OnCommand(conn_handle, attr_handle, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pinetime::Controllers::MusicService::MusicService(Pinetime::System::SystemTask &system) : m_system(system) {
|
||||||
|
msUuid.value[11] = msId[0];
|
||||||
|
msUuid.value[12] = msId[1];
|
||||||
|
msEventCharUuid.value[11] = msEventCharId[0];
|
||||||
|
msEventCharUuid.value[12] = msEventCharId[1];
|
||||||
|
msStatusCharUuid.value[11] = msStatusCharId[0];
|
||||||
|
msStatusCharUuid.value[12] = msStatusCharId[1];
|
||||||
|
msTrackCharUuid.value[11] = msTrackCharId[0];
|
||||||
|
msTrackCharUuid.value[12] = msTrackCharId[1];
|
||||||
|
msArtistCharUuid.value[11] = msArtistCharId[0];
|
||||||
|
msArtistCharUuid.value[12] = msArtistCharId[1];
|
||||||
|
msAlbumCharUuid.value[11] = msAlbumCharId[0];
|
||||||
|
msAlbumCharUuid.value[12] = msAlbumCharId[1];
|
||||||
|
msPositionCharUuid.value[11] = msPositionCharId[0];
|
||||||
|
msPositionCharUuid.value[12] = msPositionCharId[1];
|
||||||
|
msTotalLengthCharUuid.value[11] = msTotalLengthCharId[0];
|
||||||
|
msTotalLengthCharUuid.value[12] = msTotalLengthCharId[1];
|
||||||
|
msTrackNumberCharUuid.value[11] = msTrackNumberCharId[0];
|
||||||
|
msTrackNumberCharUuid.value[12] = msTrackNumberCharId[1];
|
||||||
|
msTrackTotalCharUuid.value[11] = msTrackTotalCharId[0];
|
||||||
|
msTrackTotalCharUuid.value[12] = msTrackTotalCharId[1];
|
||||||
|
msPlaybackSpeedCharUuid.value[11] = msPlaybackSpeedCharId[0];
|
||||||
|
msPlaybackSpeedCharUuid.value[12] = msPlaybackSpeedCharId[1];
|
||||||
|
msRepeatCharUuid.value[11] = msRepeatCharId[0];
|
||||||
|
msRepeatCharUuid.value[12] = msRepeatCharId[1];
|
||||||
|
msShuffleCharUuid.value[11] = msShuffleCharId[0];
|
||||||
|
msShuffleCharUuid.value[12] = msShuffleCharId[1];
|
||||||
|
|
||||||
|
characteristicDefinition[0] = {.uuid = (ble_uuid_t *) (&msEventCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||||
|
.val_handle = &eventHandle
|
||||||
|
};
|
||||||
|
characteristicDefinition[1] = {.uuid = (ble_uuid_t *) (&msStatusCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[2] = {.uuid = (ble_uuid_t *) (&msTrackCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[3] = {.uuid = (ble_uuid_t *) (&msArtistCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[4] = {.uuid = (ble_uuid_t *) (&msAlbumCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[5] = {.uuid = (ble_uuid_t *) (&msPositionCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[6] = {.uuid = (ble_uuid_t *) (&msTotalLengthCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[7] = {.uuid = (ble_uuid_t *) (&msTotalLengthCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[8] = {.uuid = (ble_uuid_t *) (&msTrackNumberCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[9] = {.uuid = (ble_uuid_t *) (&msTrackTotalCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[10] = {.uuid = (ble_uuid_t *) (&msPlaybackSpeedCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[11] = {.uuid = (ble_uuid_t *) (&msRepeatCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[12] = {.uuid = (ble_uuid_t *) (&msShuffleCharUuid),
|
||||||
|
.access_cb = MSCallback,
|
||||||
|
.arg = this,
|
||||||
|
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||||
|
};
|
||||||
|
characteristicDefinition[13] = {0};
|
||||||
|
|
||||||
|
serviceDefinition[0] = {
|
||||||
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||||
|
.uuid = (ble_uuid_t *) &msUuid,
|
||||||
|
.characteristics = characteristicDefinition
|
||||||
|
};
|
||||||
|
serviceDefinition[1] = {0};
|
||||||
|
|
||||||
|
artistName = "Waiting for";
|
||||||
|
albumName = "";
|
||||||
|
trackName = "track information...";
|
||||||
|
playing = false;
|
||||||
|
repeat = false;
|
||||||
|
shuffle = false;
|
||||||
|
playbackSpeed = 1.0f;
|
||||||
|
trackProgress = 0;
|
||||||
|
trackLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pinetime::Controllers::MusicService::Init() {
|
||||||
|
int res = 0;
|
||||||
|
res = ble_gatts_count_cfg(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
|
||||||
|
res = ble_gatts_add_svcs(serviceDefinition);
|
||||||
|
ASSERT(res == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Pinetime::Controllers::MusicService::OnCommand(uint16_t conn_handle, uint16_t attr_handle,
|
||||||
|
struct ble_gatt_access_ctxt *ctxt) {
|
||||||
|
|
||||||
|
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||||
|
size_t notifSize = OS_MBUF_PKTLEN(ctxt->om);
|
||||||
|
uint8_t data[notifSize + 1];
|
||||||
|
data[notifSize] = '\0';
|
||||||
|
os_mbuf_copydata(ctxt->om, 0, notifSize, data);
|
||||||
|
char *s = (char *) &data[0];
|
||||||
|
NRF_LOG_INFO("DATA : %s", s);
|
||||||
|
if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msArtistCharUuid) == 0) {
|
||||||
|
artistName = s;
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTrackCharUuid) == 0) {
|
||||||
|
trackName = s;
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msAlbumCharUuid) == 0) {
|
||||||
|
albumName = s;
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msStatusCharUuid) == 0) {
|
||||||
|
playing = s[0];
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msRepeatCharUuid) == 0) {
|
||||||
|
repeat = s[0];
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msShuffleCharUuid) == 0) {
|
||||||
|
shuffle = s[0];
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msPositionCharUuid) == 0) {
|
||||||
|
trackProgress = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTotalLengthCharUuid) == 0) {
|
||||||
|
trackLength = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTrackNumberCharUuid) == 0) {
|
||||||
|
trackNumber = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTrackTotalCharUuid) == 0) {
|
||||||
|
tracksTotal = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
|
||||||
|
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msPlaybackSpeedCharUuid) == 0) {
|
||||||
|
playbackSpeed = static_cast<float>(((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3])) / 100.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Pinetime::Controllers::MusicService::getAlbum() {
|
||||||
|
return albumName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Pinetime::Controllers::MusicService::getArtist() {
|
||||||
|
return artistName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Pinetime::Controllers::MusicService::getTrack() {
|
||||||
|
return trackName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pinetime::Controllers::MusicService::isPlaying() {
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Pinetime::Controllers::MusicService::getPlaybackSpeed() {
|
||||||
|
return playbackSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pinetime::Controllers::MusicService::event(char event) {
|
||||||
|
auto *om = ble_hs_mbuf_from_flat(&event, 1);
|
||||||
|
|
||||||
|
uint16_t connectionHandle = m_system.nimble().connHandle();
|
||||||
|
|
||||||
|
if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ble_gattc_notify_custom(connectionHandle, eventHandle, om);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Pinetime::Controllers::MusicService::getProgress() {
|
||||||
|
return trackProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Pinetime::Controllers::MusicService::getTrackLength() {
|
||||||
|
return trackLength;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
/* Copyright (C) 2020 JF, Adam Pigg, Avamander
|
||||||
|
|
||||||
|
This file is part of InfiniTime.
|
||||||
|
|
||||||
|
InfiniTime is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
InfiniTime is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
#include <host/ble_gap.h>
|
||||||
|
#include <host/ble_uuid.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
//c7e50000-78fc-48fe-8e23-43b37a1942d0
|
||||||
|
#define MUSIC_SERVICE_UUID_BASE {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x00, 0x00, 0xe5, 0xc7}
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace System {
|
||||||
|
class SystemTask;
|
||||||
|
}
|
||||||
|
namespace Controllers {
|
||||||
|
|
||||||
|
class MusicService {
|
||||||
|
public:
|
||||||
|
explicit MusicService(Pinetime::System::SystemTask &system);
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
int OnCommand(uint16_t conn_handle, uint16_t attr_handle,
|
||||||
|
struct ble_gatt_access_ctxt *ctxt);
|
||||||
|
|
||||||
|
void event(char event);
|
||||||
|
|
||||||
|
std::string getArtist();
|
||||||
|
|
||||||
|
std::string getTrack();
|
||||||
|
|
||||||
|
std::string getAlbum();
|
||||||
|
|
||||||
|
int getProgress();
|
||||||
|
|
||||||
|
int getTrackLength();
|
||||||
|
|
||||||
|
float getPlaybackSpeed();
|
||||||
|
|
||||||
|
bool isPlaying();
|
||||||
|
|
||||||
|
static const char EVENT_MUSIC_OPEN = 0xe0;
|
||||||
|
static const char EVENT_MUSIC_PLAY = 0x00;
|
||||||
|
static const char EVENT_MUSIC_PAUSE = 0x01;
|
||||||
|
static const char EVENT_MUSIC_NEXT = 0x03;
|
||||||
|
static const char EVENT_MUSIC_PREV = 0x04;
|
||||||
|
static const char EVENT_MUSIC_VOLUP = 0x05;
|
||||||
|
static const char EVENT_MUSIC_VOLDOWN = 0x06;
|
||||||
|
|
||||||
|
enum MusicStatus {
|
||||||
|
NotPlaying = 0x00,
|
||||||
|
Playing = 0x01
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
static constexpr uint8_t msId[2] = {0x00, 0x01};
|
||||||
|
static constexpr uint8_t msEventCharId[2] = {0x00, 0x02};
|
||||||
|
static constexpr uint8_t msStatusCharId[2] = {0x00, 0x03};
|
||||||
|
static constexpr uint8_t msArtistCharId[2] = {0x00, 0x04};
|
||||||
|
static constexpr uint8_t msTrackCharId[2] = {0x00, 0x05};
|
||||||
|
static constexpr uint8_t msAlbumCharId[2] = {0x00, 0x06};
|
||||||
|
static constexpr uint8_t msPositionCharId[2] = {0x00, 0x07};
|
||||||
|
static constexpr uint8_t msTotalLengthCharId[2] = {0x00, 0x08};
|
||||||
|
static constexpr uint8_t msTrackNumberCharId[2] = {0x00, 0x09};
|
||||||
|
static constexpr uint8_t msTrackTotalCharId[2] = {0x00, 0x0a};
|
||||||
|
static constexpr uint8_t msPlaybackSpeedCharId[2] = {0x00, 0x0b};
|
||||||
|
static constexpr uint8_t msRepeatCharId[2] = {0x00, 0x0c};
|
||||||
|
static constexpr uint8_t msShuffleCharId[2] = {0x00, 0x0d};
|
||||||
|
|
||||||
|
ble_uuid128_t msUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
|
||||||
|
ble_uuid128_t msEventCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msStatusCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msArtistCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msTrackCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msAlbumCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msPositionCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msTotalLengthCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msTrackNumberCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msTrackTotalCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msPlaybackSpeedCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msRepeatCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
ble_uuid128_t msShuffleCharUuid{
|
||||||
|
.u = {.type = BLE_UUID_TYPE_128},
|
||||||
|
.value = MUSIC_SERVICE_UUID_BASE
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ble_gatt_chr_def characteristicDefinition[14];
|
||||||
|
struct ble_gatt_svc_def serviceDefinition[2];
|
||||||
|
|
||||||
|
uint16_t eventHandle;
|
||||||
|
|
||||||
|
std::string artistName;
|
||||||
|
std::string albumName;
|
||||||
|
std::string trackName;
|
||||||
|
|
||||||
|
bool playing;
|
||||||
|
|
||||||
|
int trackProgress;
|
||||||
|
int trackLength;
|
||||||
|
int trackNumber;
|
||||||
|
int tracksTotal;
|
||||||
|
|
||||||
|
float playbackSpeed;
|
||||||
|
|
||||||
|
bool repeat;
|
||||||
|
bool shuffle;
|
||||||
|
|
||||||
|
Pinetime::System::SystemTask &m_system;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include <Components/DateTime/DateTimeController.h>
|
#include <systemtask/SystemTask.h>
|
||||||
|
#include "components/ble/NotificationManager.h"
|
||||||
#include <SystemTask/SystemTask.h>
|
|
||||||
#include <Components/Ble/NotificationManager.h>
|
|
||||||
#include <hal/nrf_rtc.h>
|
#include <hal/nrf_rtc.h>
|
||||||
|
|
||||||
#include "NimbleController.h"
|
#include "NimbleController.h"
|
||||||
#include "MusicService.h"
|
#include "MusicService.h"
|
||||||
#include <services/gatt/ble_svc_gatt.h>
|
#include <services/gatt/ble_svc_gatt.h>
|
||||||
|
@ -14,18 +11,13 @@
|
||||||
#include <host/ble_hs.h>
|
#include <host/ble_hs.h>
|
||||||
#include <host/ble_gap.h>
|
#include <host/ble_gap.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
using namespace Pinetime::Controllers;
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
// TODO I'm not satisfied by how this code looks like (AlertNotificationClient and CurrentTimeClient must
|
|
||||||
// expose too much data, too many callbacks -> NimbleController -> CTS/ANS client.
|
|
||||||
// Let's try to improve this code (and keep it working!)
|
|
||||||
|
|
||||||
NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||||
Pinetime::Controllers::Ble& bleController,
|
Pinetime::Controllers::Ble& bleController,
|
||||||
DateTime& dateTimeController,
|
DateTime& dateTimeController,
|
||||||
Pinetime::Controllers::NotificationManager& notificationManager,
|
Pinetime::Controllers::NotificationManager& notificationManager,
|
||||||
|
Controllers::Battery& batteryController,
|
||||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash) :
|
Pinetime::Drivers::SpiNorFlash& spiNorFlash) :
|
||||||
systemTask{systemTask},
|
systemTask{systemTask},
|
||||||
bleController{bleController},
|
bleController{bleController},
|
||||||
|
@ -37,8 +29,10 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||||
anService{systemTask, notificationManager},
|
anService{systemTask, notificationManager},
|
||||||
alertNotificationClient{systemTask, notificationManager},
|
alertNotificationClient{systemTask, notificationManager},
|
||||||
currentTimeService{dateTimeController},
|
currentTimeService{dateTimeController},
|
||||||
musicService{systemTask} {
|
musicService{systemTask},
|
||||||
|
batteryInformationService{batteryController},
|
||||||
|
immediateAlertService{systemTask, notificationManager},
|
||||||
|
serviceDiscovery({¤tTimeClient, &alertNotificationClient}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int GAPEventCallback(struct ble_gap_event *event, void *arg) {
|
int GAPEventCallback(struct ble_gap_event *event, void *arg) {
|
||||||
|
@ -46,33 +40,6 @@ int GAPEventCallback(struct ble_gap_event *event, void *arg) {
|
||||||
return nimbleController->OnGAPEvent(event);
|
return nimbleController->OnGAPEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
int CurrentTimeCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
|
||||||
const struct ble_gatt_chr *chr, void *arg) {
|
|
||||||
auto client = static_cast<NimbleController*>(arg);
|
|
||||||
return client->OnCTSCharacteristicDiscoveryEvent(conn_handle, error, chr);
|
|
||||||
}
|
|
||||||
|
|
||||||
int AlertNotificationCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
|
||||||
const struct ble_gatt_chr *chr, void *arg) {
|
|
||||||
auto client = static_cast<NimbleController*>(arg);
|
|
||||||
return client->OnANSCharacteristicDiscoveryEvent(conn_handle, error, chr);
|
|
||||||
}
|
|
||||||
|
|
||||||
int CurrentTimeReadCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
|
||||||
struct ble_gatt_attr *attr, void *arg) {
|
|
||||||
auto client = static_cast<NimbleController*>(arg);
|
|
||||||
return client->OnCurrentTimeReadResult(conn_handle, error, attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
int AlertNotificationDescriptorDiscoveryEventCallback(uint16_t conn_handle,
|
|
||||||
const struct ble_gatt_error *error,
|
|
||||||
uint16_t chr_val_handle,
|
|
||||||
const struct ble_gatt_dsc *dsc,
|
|
||||||
void *arg) {
|
|
||||||
auto client = static_cast<NimbleController*>(arg);
|
|
||||||
return client->OnANSDescriptorDiscoveryEventCallback(conn_handle, error, chr_val_handle, dsc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NimbleController::Init() {
|
void NimbleController::Init() {
|
||||||
while (!ble_hs_synced()) {}
|
while (!ble_hs_synced()) {}
|
||||||
|
|
||||||
|
@ -83,10 +50,10 @@ void NimbleController::Init() {
|
||||||
currentTimeClient.Init();
|
currentTimeClient.Init();
|
||||||
currentTimeService.Init();
|
currentTimeService.Init();
|
||||||
musicService.Init();
|
musicService.Init();
|
||||||
|
|
||||||
anService.Init();
|
anService.Init();
|
||||||
|
|
||||||
dfuService.Init();
|
dfuService.Init();
|
||||||
|
batteryInformationService.Init();
|
||||||
|
immediateAlertService.Init();
|
||||||
int res;
|
int res;
|
||||||
res = ble_hs_util_ensure_addr(0);
|
res = ble_hs_util_ensure_addr(0);
|
||||||
ASSERT(res == 0);
|
ASSERT(res == 0);
|
||||||
|
@ -105,7 +72,7 @@ void NimbleController::Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NimbleController::StartAdvertising() {
|
void NimbleController::StartAdvertising() {
|
||||||
if(ble_gap_adv_active()) return;
|
if(bleController.IsConnected() || ble_gap_conn_active() || ble_gap_adv_active()) return;
|
||||||
|
|
||||||
ble_svc_gap_device_name_set(deviceName);
|
ble_svc_gap_device_name_set(deviceName);
|
||||||
|
|
||||||
|
@ -155,15 +122,6 @@ void NimbleController::StartAdvertising() {
|
||||||
// the application has been woken up, for example.
|
// the application has been woken up, for example.
|
||||||
}
|
}
|
||||||
|
|
||||||
int OnAllSvrDisco(uint16_t conn_handle,
|
|
||||||
const struct ble_gatt_error *error,
|
|
||||||
const struct ble_gatt_svc *service,
|
|
||||||
void *arg) {
|
|
||||||
auto nimbleController = static_cast<NimbleController*>(arg);
|
|
||||||
return nimbleController->OnDiscoveryEvent(conn_handle, error, service);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NimbleController::OnGAPEvent(ble_gap_event *event) {
|
int NimbleController::OnGAPEvent(ble_gap_event *event) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||||
|
@ -194,6 +152,8 @@ int NimbleController::OnGAPEvent(ble_gap_event *event) {
|
||||||
NRF_LOG_INFO("disconnect; reason=%d", event->disconnect.reason);
|
NRF_LOG_INFO("disconnect; reason=%d", event->disconnect.reason);
|
||||||
|
|
||||||
/* Connection terminated; resume advertising. */
|
/* Connection terminated; resume advertising. */
|
||||||
|
currentTimeClient.Reset();
|
||||||
|
alertNotificationClient.Reset();
|
||||||
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
|
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
|
||||||
bleController.Disconnect();
|
bleController.Disconnect();
|
||||||
StartAdvertising();
|
StartAdvertising();
|
||||||
|
@ -266,65 +226,8 @@ int NimbleController::OnGAPEvent(ble_gap_event *event) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NimbleController::OnDiscoveryEvent(uint16_t i, const ble_gatt_error *error, const ble_gatt_svc *service) {
|
|
||||||
if(service == nullptr && error->status == BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("Service Discovery complete");
|
|
||||||
if(currentTimeClient.IsDiscovered()) {
|
|
||||||
ble_gattc_disc_all_chrs(connectionHandle, currentTimeClient.StartHandle(), currentTimeClient.EndHandle(),
|
|
||||||
CurrentTimeCharacteristicDiscoveredCallback, this);
|
|
||||||
|
|
||||||
} else if(alertNotificationClient.IsDiscovered()) {
|
|
||||||
ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(), alertNotificationClient.EndHandle(),
|
|
||||||
AlertNotificationCharacteristicDiscoveredCallback, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alertNotificationClient.OnDiscoveryEvent(i, error, service);
|
|
||||||
currentTimeClient.OnDiscoveryEvent(i, error, service);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NimbleController::OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
|
||||||
const ble_gatt_chr *characteristic) {
|
|
||||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("CTS characteristic Discovery complete");
|
|
||||||
ble_gattc_read(connectionHandle, currentTimeClient.CurrentTimeHandle(), CurrentTimeReadCallback, this);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return currentTimeClient.OnCharacteristicDiscoveryEvent(connectionHandle, error, characteristic);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NimbleController::OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
|
||||||
const ble_gatt_chr *characteristic) {
|
|
||||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
|
||||||
NRF_LOG_INFO("ANS characteristic Discovery complete");
|
|
||||||
ble_gattc_disc_all_dscs(connectionHandle,
|
|
||||||
alertNotificationClient.NewAlerthandle(), alertNotificationClient.EndHandle(),
|
|
||||||
AlertNotificationDescriptorDiscoveryEventCallback, this);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return alertNotificationClient.OnCharacteristicsDiscoveryEvent(connectionHandle, error, characteristic);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NimbleController::OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute) {
|
|
||||||
currentTimeClient.OnCurrentTimeReadResult(connectionHandle, error, attribute);
|
|
||||||
|
|
||||||
if (alertNotificationClient.IsDiscovered()) {
|
|
||||||
ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(),
|
|
||||||
alertNotificationClient.EndHandle(),
|
|
||||||
AlertNotificationCharacteristicDiscoveredCallback, this);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NimbleController::OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
|
||||||
uint16_t characteristicValueHandle,
|
|
||||||
const ble_gatt_dsc *descriptor) {
|
|
||||||
return alertNotificationClient.OnDescriptorDiscoveryEventCallback(connectionHandle, error, characteristicValueHandle, descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NimbleController::StartDiscovery() {
|
void NimbleController::StartDiscovery() {
|
||||||
ble_gattc_disc_all_svcs(connectionHandle, OnAllSvrDisco, this);
|
serviceDiscovery.StartDiscovery(connectionHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "AlertNotificationService.h"
|
#include "AlertNotificationService.h"
|
||||||
#include "AlertNotificationClient.h"
|
#include "AlertNotificationClient.h"
|
||||||
#include "DeviceInformationService.h"
|
#include "DeviceInformationService.h"
|
||||||
|
@ -8,6 +9,9 @@
|
||||||
#include "DfuService.h"
|
#include "DfuService.h"
|
||||||
#include "CurrentTimeService.h"
|
#include "CurrentTimeService.h"
|
||||||
#include "MusicService.h"
|
#include "MusicService.h"
|
||||||
|
#include "BatteryInformationService.h"
|
||||||
|
#include "ImmediateAlertService.h"
|
||||||
|
#include "ServiceDiscovery.h"
|
||||||
#include <host/ble_gap.h>
|
#include <host/ble_gap.h>
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
@ -22,7 +26,7 @@ namespace Pinetime {
|
||||||
public:
|
public:
|
||||||
NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController,
|
NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController,
|
||||||
DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager,
|
DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager,
|
||||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
Controllers::Battery& batteryController, Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
||||||
void Init();
|
void Init();
|
||||||
void StartAdvertising();
|
void StartAdvertising();
|
||||||
int OnGAPEvent(ble_gap_event *event);
|
int OnGAPEvent(ble_gap_event *event);
|
||||||
|
@ -57,6 +61,8 @@ namespace Pinetime {
|
||||||
AlertNotificationClient alertNotificationClient;
|
AlertNotificationClient alertNotificationClient;
|
||||||
CurrentTimeService currentTimeService;
|
CurrentTimeService currentTimeService;
|
||||||
MusicService musicService;
|
MusicService musicService;
|
||||||
|
BatteryInformationService batteryInformationService;
|
||||||
|
ImmediateAlertService immediateAlertService;
|
||||||
|
|
||||||
uint8_t addrType; // 1 = Random, 0 = PUBLIC
|
uint8_t addrType; // 1 = Random, 0 = PUBLIC
|
||||||
uint16_t connectionHandle = 0;
|
uint16_t connectionHandle = 0;
|
||||||
|
@ -66,6 +72,8 @@ namespace Pinetime {
|
||||||
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
|
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
|
||||||
0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}
|
0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ServiceDiscovery serviceDiscovery;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "NotificationManager.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
|
constexpr uint8_t NotificationManager::MessageSize;
|
||||||
|
|
||||||
|
|
||||||
|
void NotificationManager::Push(NotificationManager::Notification &¬if) {
|
||||||
|
notif.id = GetNextId();
|
||||||
|
notif.valid = true;
|
||||||
|
notifications[writeIndex] = std::move(notif);
|
||||||
|
writeIndex = (writeIndex + 1 < TotalNbNotifications) ? writeIndex + 1 : 0;
|
||||||
|
if(!empty)
|
||||||
|
readIndex = (readIndex + 1 < TotalNbNotifications) ? readIndex + 1 : 0;
|
||||||
|
else empty = false;
|
||||||
|
|
||||||
|
newNotification = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManager::Notification NotificationManager::GetLastNotification() {
|
||||||
|
NotificationManager::Notification notification = notifications[readIndex];
|
||||||
|
notification.index = 1;
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManager::Notification::Id NotificationManager::GetNextId() {
|
||||||
|
return nextId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManager::Notification NotificationManager::GetNext(NotificationManager::Notification::Id id) {
|
||||||
|
auto currentIterator = std::find_if(notifications.begin(), notifications.end(), [id](const Notification& n){return n.valid && n.id == id;});
|
||||||
|
if(currentIterator == notifications.end() || currentIterator->id != id) return Notification{};
|
||||||
|
|
||||||
|
auto& lastNotification = notifications[readIndex];
|
||||||
|
|
||||||
|
NotificationManager::Notification result;
|
||||||
|
|
||||||
|
if(currentIterator == (notifications.end()-1))
|
||||||
|
result = *(notifications.begin());
|
||||||
|
else
|
||||||
|
result = *(currentIterator+1);
|
||||||
|
|
||||||
|
if(result.id <= id) return {};
|
||||||
|
|
||||||
|
result.index = (lastNotification.id - result.id)+1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManager::Notification NotificationManager::GetPrevious(NotificationManager::Notification::Id id) {
|
||||||
|
auto currentIterator = std::find_if(notifications.begin(), notifications.end(), [id](const Notification& n){return n.valid && n.id == id;});
|
||||||
|
if(currentIterator == notifications.end() || currentIterator->id != id) return Notification{};
|
||||||
|
|
||||||
|
auto& lastNotification = notifications[readIndex];
|
||||||
|
|
||||||
|
NotificationManager::Notification result;
|
||||||
|
|
||||||
|
if(currentIterator == notifications.begin())
|
||||||
|
result = *(notifications.end()-1);
|
||||||
|
else
|
||||||
|
result = *(currentIterator-1);
|
||||||
|
|
||||||
|
if(result.id >= id) return {};
|
||||||
|
|
||||||
|
result.index = (lastNotification.id - result.id)+1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NotificationManager::AreNewNotificationsAvailable() {
|
||||||
|
return newNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NotificationManager::ClearNewNotificationFlag() {
|
||||||
|
return newNotification.exchange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NotificationManager::NbNotifications() const {
|
||||||
|
return std::count_if(notifications.begin(), notifications.end(), [](const Notification& n){ return n.valid;});
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace Controllers {
|
||||||
|
class NotificationManager {
|
||||||
|
public:
|
||||||
|
enum class Categories {Unknown, SimpleAlert, Email, News, IncomingCall, MissedCall, Sms, VoiceMail, Schedule, HighProriotyAlert, InstantMessage };
|
||||||
|
static constexpr uint8_t MessageSize{100};
|
||||||
|
|
||||||
|
struct Notification {
|
||||||
|
using Id = uint8_t;
|
||||||
|
Id id;
|
||||||
|
bool valid = false;
|
||||||
|
uint8_t index;
|
||||||
|
std::array<char, MessageSize+1> message;
|
||||||
|
Categories category = Categories::Unknown;
|
||||||
|
};
|
||||||
|
Notification::Id nextId {0};
|
||||||
|
|
||||||
|
void Push(Notification&& notif);
|
||||||
|
Notification GetLastNotification();
|
||||||
|
Notification GetNext(Notification::Id id);
|
||||||
|
Notification GetPrevious(Notification::Id id);
|
||||||
|
bool ClearNewNotificationFlag();
|
||||||
|
bool AreNewNotificationsAvailable();
|
||||||
|
|
||||||
|
static constexpr uint8_t MaximumMessageSize() { return MessageSize; };
|
||||||
|
size_t NbNotifications() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Notification::Id GetNextId();
|
||||||
|
static constexpr uint8_t TotalNbNotifications = 5;
|
||||||
|
std::array<Notification, TotalNbNotifications> notifications;
|
||||||
|
uint8_t readIndex = 0;
|
||||||
|
uint8_t writeIndex = 0;
|
||||||
|
bool empty = true;
|
||||||
|
std::atomic<bool> newNotification{false};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <libraries/log/nrf_log.h>
|
||||||
|
#include "ServiceDiscovery.h"
|
||||||
|
using namespace Pinetime::Controllers;
|
||||||
|
|
||||||
|
ServiceDiscovery::ServiceDiscovery(std::array<BleClient*, 2>&& clients) : clients{clients} {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServiceDiscovery::StartDiscovery(uint16_t connectionHandle) {
|
||||||
|
NRF_LOG_INFO("[Discovery] Starting discovery");
|
||||||
|
clientIterator = clients.begin();
|
||||||
|
DiscoverNextService(connectionHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServiceDiscovery::OnServiceDiscovered(uint16_t connectionHandle) {
|
||||||
|
clientIterator++;
|
||||||
|
if(clientIterator != clients.end()) {
|
||||||
|
DiscoverNextService(connectionHandle);
|
||||||
|
} else {
|
||||||
|
NRF_LOG_INFO("End of service discovery");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServiceDiscovery::DiscoverNextService(uint16_t connectionHandle) {
|
||||||
|
NRF_LOG_INFO("[Discovery] Discover next service");
|
||||||
|
|
||||||
|
auto discoverNextService = [this](uint16_t connectionHandle){
|
||||||
|
this->OnServiceDiscovered(connectionHandle);
|
||||||
|
};
|
||||||
|
(*clientIterator)->Discover(connectionHandle, discoverNextService);
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include "BleClient.h"
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace Controllers {
|
||||||
|
class ServiceDiscovery {
|
||||||
|
public:
|
||||||
|
ServiceDiscovery(std::array<BleClient*, 2>&& bleClients);
|
||||||
|
|
||||||
|
void StartDiscovery(uint16_t connectionHandle);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
BleClient** clientIterator;
|
||||||
|
std::array<BleClient*, 2> clients;
|
||||||
|
void OnServiceDiscovered(uint16_t connectionHandle);
|
||||||
|
void DiscoverNextService(uint16_t connectionHandle);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
enum class Apps {None, Launcher, Clock, SysInfo, Meter, Gauge, Brightness, Music, FirmwareValidation, Paint};
|
enum class Apps {None, Launcher, Clock, SysInfo, Meter, Gauge, Brightness, Music, FirmwareValidation, Paint, Notifications};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,24 +1,26 @@
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "DisplayApp.h"
|
#include "DisplayApp.h"
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <task.h>
|
#include <task.h>
|
||||||
#include <libraries/log/nrf_log.h>
|
#include <libraries/log/nrf_log.h>
|
||||||
#include <nrf_font.h>
|
#include <nrf_font.h>
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
#include <Components/DateTime/DateTimeController.h>
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include <drivers/Cst816s.h>
|
#include <drivers/Cst816s.h>
|
||||||
#include <string>
|
#include "displayapp/screens/Notifications.h"
|
||||||
#include <DisplayApp/Screens/Tile.h>
|
#include "displayapp/screens/Tile.h"
|
||||||
#include <DisplayApp/Screens/Meter.h>
|
#include "displayapp/screens/Meter.h"
|
||||||
#include <DisplayApp/Screens/Gauge.h>
|
#include "displayapp/screens/Gauge.h"
|
||||||
#include <DisplayApp/Screens/Brightness.h>
|
#include "displayapp/screens/Brightness.h"
|
||||||
#include <DisplayApp/Screens/SystemInfo.h>
|
#include "displayapp/screens/SystemInfo.h"
|
||||||
#include <DisplayApp/Screens/Music.h>
|
#include "displayapp/screens/Music.h"
|
||||||
#include <Components/Ble/NotificationManager.h>
|
#include "components/ble/NotificationManager.h"
|
||||||
#include <DisplayApp/Screens/FirmwareUpdate.h>
|
#include "displayapp/screens/FirmwareUpdate.h"
|
||||||
#include <DisplayApp/Screens/ApplicationList.h>
|
#include "displayapp/screens/ApplicationList.h"
|
||||||
#include <DisplayApp/Screens/FirmwareValidation.h>
|
#include "displayapp/screens/FirmwareValidation.h"
|
||||||
#include <DisplayApp/Screens/InfiniPaint.h>
|
#include "displayapp/screens/InfiniPaint.h"
|
||||||
#include "../SystemTask/SystemTask.h"
|
#include "systemtask/SystemTask.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications;
|
using namespace Pinetime::Applications;
|
||||||
|
|
||||||
|
@ -34,7 +36,7 @@ DisplayApp::DisplayApp(Drivers::St7789 &lcd, Components::LittleVgl &lvgl, Driver
|
||||||
dateTimeController{dateTimeController},
|
dateTimeController{dateTimeController},
|
||||||
watchdog{watchdog},
|
watchdog{watchdog},
|
||||||
touchPanel{touchPanel},
|
touchPanel{touchPanel},
|
||||||
currentScreen{new Screens::Clock(this, dateTimeController, batteryController, bleController) },
|
currentScreen{new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager) },
|
||||||
systemTask{systemTask},
|
systemTask{systemTask},
|
||||||
notificationManager{notificationManager} {
|
notificationManager{notificationManager} {
|
||||||
msgQueue = xQueueCreate(queueSize, itemSize);
|
msgQueue = xQueueCreate(queueSize, itemSize);
|
||||||
|
@ -43,13 +45,13 @@ DisplayApp::DisplayApp(Drivers::St7789 &lcd, Components::LittleVgl &lvgl, Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayApp::Start() {
|
void DisplayApp::Start() {
|
||||||
if (pdPASS != xTaskCreate(DisplayApp::Process, "DisplayApp", 512, this, 0, &taskHandle))
|
if (pdPASS != xTaskCreate(DisplayApp::Process, "displayapp", 512, this, 0, &taskHandle))
|
||||||
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
|
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayApp::Process(void *instance) {
|
void DisplayApp::Process(void *instance) {
|
||||||
auto *app = static_cast<DisplayApp *>(instance);
|
auto *app = static_cast<DisplayApp *>(instance);
|
||||||
NRF_LOG_INFO("DisplayApp task started!");
|
NRF_LOG_INFO("displayapp task started!");
|
||||||
app->InitHw();
|
app->InitHw();
|
||||||
|
|
||||||
// Send a dummy notification to unlock the lvgl display driver for the first iteration
|
// Send a dummy notification to unlock the lvgl display driver for the first iteration
|
||||||
|
@ -113,8 +115,12 @@ void DisplayApp::Refresh() {
|
||||||
// clockScreen.SetBatteryPercentRemaining(batteryController.PercentRemaining());
|
// clockScreen.SetBatteryPercentRemaining(batteryController.PercentRemaining());
|
||||||
break;
|
break;
|
||||||
case Messages::NewNotification: {
|
case Messages::NewNotification: {
|
||||||
auto notification = notificationManager.Pop();
|
if(onClockApp) {
|
||||||
modal->Show(notification.message.data());
|
currentScreen.reset(nullptr);
|
||||||
|
lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Up);
|
||||||
|
onClockApp = false;
|
||||||
|
currentScreen.reset(new Screens::Notifications(this, notificationManager, Screens::Notifications::Modes::Preview));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Messages::TouchEvent: {
|
case Messages::TouchEvent: {
|
||||||
|
@ -148,7 +154,7 @@ void DisplayApp::Refresh() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Down);
|
// lvgl.SetFullRefresh(components::LittleVgl::FullRefreshDirections::Down);
|
||||||
// currentScreen.reset(nullptr);
|
// currentScreen.reset(nullptr);
|
||||||
// if(toggle) {
|
// if(toggle) {
|
||||||
// currentScreen.reset(new Screens::Tile(this));
|
// currentScreen.reset(new Screens::Tile(this));
|
||||||
|
@ -190,7 +196,7 @@ void DisplayApp::RunningState() {
|
||||||
case Apps::None:
|
case Apps::None:
|
||||||
case Apps::Launcher: currentScreen.reset(new Screens::ApplicationList(this)); break;
|
case Apps::Launcher: currentScreen.reset(new Screens::ApplicationList(this)); break;
|
||||||
case Apps::Clock:
|
case Apps::Clock:
|
||||||
currentScreen.reset(new Screens::Clock(this, dateTimeController, batteryController, bleController));
|
currentScreen.reset(new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager));
|
||||||
onClockApp = true;
|
onClockApp = true;
|
||||||
break;
|
break;
|
||||||
// case Apps::Test: currentScreen.reset(new Screens::Message(this)); break;
|
// case Apps::Test: currentScreen.reset(new Screens::Message(this)); break;
|
||||||
|
@ -201,6 +207,7 @@ void DisplayApp::RunningState() {
|
||||||
case Apps::Brightness : currentScreen.reset(new Screens::Brightness(this, brightnessController)); break;
|
case Apps::Brightness : currentScreen.reset(new Screens::Brightness(this, brightnessController)); break;
|
||||||
case Apps::Music : currentScreen.reset(new Screens::Music(this, systemTask.nimble().music())); break;
|
case Apps::Music : currentScreen.reset(new Screens::Music(this, systemTask.nimble().music())); break;
|
||||||
case Apps::FirmwareValidation: currentScreen.reset(new Screens::FirmwareValidation(this, validator)); break;
|
case Apps::FirmwareValidation: currentScreen.reset(new Screens::FirmwareValidation(this, validator)); break;
|
||||||
|
case Apps::Notifications: currentScreen.reset(new Screens::Notifications(this, notificationManager, Screens::Notifications::Modes::Normal)); break;
|
||||||
}
|
}
|
||||||
nextApp = Apps::None;
|
nextApp = Apps::None;
|
||||||
}
|
}
|
|
@ -3,21 +3,21 @@
|
||||||
#include <task.h>
|
#include <task.h>
|
||||||
#include <drivers/St7789.h>
|
#include <drivers/St7789.h>
|
||||||
#include <drivers/SpiMaster.h>
|
#include <drivers/SpiMaster.h>
|
||||||
#include <Components/Gfx/Gfx.h>
|
|
||||||
#include <bits/unique_ptr.h>
|
#include <bits/unique_ptr.h>
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
#include <Components/Battery/BatteryController.h>
|
#include "components/gfx/Gfx.h"
|
||||||
#include <Components/Brightness/BrightnessController.h>
|
#include "components/battery/BatteryController.h"
|
||||||
#include <Components/Ble/BleController.h>
|
#include "components/brightness/BrightnessController.h"
|
||||||
#include <Components/DateTime/DateTimeController.h>
|
#include "components/ble/BleController.h"
|
||||||
#include "../drivers/Cst816s.h"
|
#include "components/datetime/DateTimeController.h"
|
||||||
|
#include "components/ble/NotificationManager.h"
|
||||||
|
#include "components/firmwarevalidator/FirmwareValidator.h"
|
||||||
|
#include "drivers/Cst816s.h"
|
||||||
#include "LittleVgl.h"
|
#include "LittleVgl.h"
|
||||||
#include <date/date.h>
|
#include <date/date.h>
|
||||||
#include <DisplayApp/Screens/Clock.h>
|
#include "displayapp/screens/Clock.h"
|
||||||
|
#include "displayapp/screens/Modal.h"
|
||||||
#include <drivers/Watchdog.h>
|
#include <drivers/Watchdog.h>
|
||||||
#include <DisplayApp/Screens/Modal.h>
|
|
||||||
#include <Components/Ble/NotificationManager.h>
|
|
||||||
#include <Components/FirmwareValidator/FirmwareValidator.h>
|
|
||||||
#include "TouchEvents.h"
|
#include "TouchEvents.h"
|
||||||
#include "Apps.h"
|
#include "Apps.h"
|
||||||
|
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |