Merge branch 'develop' into master

This commit is contained in:
JF 2020-11-10 20:32:36 +01:00
commit 04abc91f15
193 changed files with 45843 additions and 1520 deletions

18
.gitignore vendored
View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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.

View File

@ -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`

View File

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

View File

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View File

@ -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>`

View File

@ -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,...

105
doc/openOCD.md Normal file
View File

@ -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).

BIN
images/pogopins.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
images/swd_pinout.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

42660
nrf52.svd Normal file

File diff suppressed because it is too large Load Diff

26
src/BootloaderVersion.cpp Normal file
View File

@ -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;
}

12
src/BootloaderVersion.h Normal file
View File

@ -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();
};
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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*)&currentTimeCharacteristicUuid), &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;
}

View File

@ -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);
}

View File

@ -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;
};
}
}

View File

@ -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;
}

View File

@ -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;
};
}
}

View File

@ -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;
};
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
};
}
}
}

View File

@ -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;
};
}
}
}

View File

@ -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);

View File

@ -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 &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) {
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);
}

View File

@ -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 &notificationManager); Pinetime::Controllers::NotificationManager &notificationManager);
@ -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 &notificationManager; Pinetime::Controllers::NotificationManager &notificationManager;
std::function<void(uint16_t)> onServiceDiscovered;
bool isCharacteristicDiscovered = false;
bool isDescriptorFound = false;
}; };
} }
} }

View File

@ -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;
} }

View File

@ -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 &notificationManager;
}; };
} }
} }

View File

@ -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;
}

View File

@ -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;
};
}
}

View File

@ -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;
};
}
}

View File

@ -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 *) &currentTimeCharacteristicUuid), &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);
}

View File

@ -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;
}; };
} }
} }

View File

@ -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 {

View File

@ -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;

View File

@ -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 &notificationManager) :
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;
}

View File

@ -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 &notificationManager);
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;
};
}
}

View File

@ -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;
}

View File

@ -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;
};
}
}

View File

@ -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({&currentTimeClient, &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);
} }

View File

@ -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;
}; };
} }
} }

View File

@ -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 &&notif) {
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;});
}

View File

@ -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};
};
}
}

View File

@ -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);
}

View File

@ -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);
};
}
}

View File

@ -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};
} }
} }

View File

@ -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;
} }

View File

@ -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"

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

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