summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKieran Cawthray <kieranc@gmail.com>2021-12-09 22:41:29 +0100
committerKieran Cawthray <kieranc@gmail.com>2021-12-09 22:41:29 +0100
commit6cf4a933b6323fc24a3b27ae55a3c12d31d6a841 (patch)
treeffee2e59a62efe62aa8255f68e7cc89be1cad152
parentae4b9e0f2e877d200bd780f99e2a8952f9f8bf5b (diff)
parent42a5cdb5b776c2cdeb08a8c6f26606282a809178 (diff)
Merge remote-tracking branch 'upstream/develop' into pts-settings
-rw-r--r--.gitmodules3
-rw-r--r--doc/ble.md18
-rw-r--r--src/CMakeLists.txt67
-rw-r--r--src/buttonhandler/ButtonHandler.cpp2
-rw-r--r--src/components/alarm/AlarmController.h1
-rw-r--r--src/components/battery/BatteryController.cpp1
-rw-r--r--src/components/ble/AlertNotificationService.cpp3
-rw-r--r--src/components/ble/BatteryInformationService.cpp2
-rw-r--r--src/components/ble/BleController.h11
-rw-r--r--src/components/ble/NimbleController.cpp238
-rw-r--r--src/components/ble/NimbleController.h23
-rw-r--r--src/components/ble/weather/WeatherData.h385
-rw-r--r--src/components/ble/weather/WeatherService.cpp604
-rw-r--r--src/components/ble/weather/WeatherService.h172
-rw-r--r--src/displayapp/Apps.h2
-rw-r--r--src/displayapp/DisplayApp.cpp12
-rw-r--r--src/displayapp/Messages.h1
-rw-r--r--src/displayapp/lv_pinetime_theme.c1
-rw-r--r--src/displayapp/screens/Alarm.cpp4
-rw-r--r--src/displayapp/screens/FirmwareValidation.cpp2
-rw-r--r--src/displayapp/screens/PassKey.cpp24
-rw-r--r--src/displayapp/screens/PassKey.h21
-rw-r--r--src/displayapp/screens/PineTimeStyle.cpp29
-rw-r--r--src/displayapp/screens/PineTimeStyle.h1
-rw-r--r--src/displayapp/screens/Styles.cpp8
-rw-r--r--src/displayapp/screens/Styles.h9
-rw-r--r--src/displayapp/screens/SystemInfo.cpp10
-rw-r--r--src/displayapp/screens/Twos.cpp6
-rw-r--r--src/displayapp/screens/Twos.h10
-rw-r--r--src/displayapp/screens/Weather.cpp222
-rw-r--r--src/displayapp/screens/Weather.h45
-rw-r--r--src/displayapp/screens/settings/SettingDisplay.cpp65
-rw-r--r--src/displayapp/screens/settings/SettingDisplay.h7
-rw-r--r--src/displayapp/screens/settings/SettingSteps.cpp15
-rw-r--r--src/displayapp/screens/settings/SettingTimeFormat.cpp30
-rw-r--r--src/displayapp/screens/settings/SettingTimeFormat.h6
-rw-r--r--src/displayapp/screens/settings/SettingWatchFace.cpp40
-rw-r--r--src/displayapp/screens/settings/SettingWatchFace.h7
m---------src/libs/QCBOR0
-rw-r--r--src/libs/mynewt-nimble/porting/nimble/include/syscfg/syscfg.h16
-rw-r--r--src/libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c4
-rw-r--r--src/logging/NrfLogger.cpp3
-rw-r--r--src/sdk_config.h2
-rw-r--r--src/systemtask/Messages.h1
-rw-r--r--src/systemtask/SystemTask.cpp63
-rw-r--r--src/touchhandler/TouchHandler.h5
46 files changed, 1960 insertions, 241 deletions
diff --git a/.gitmodules b/.gitmodules
index 815fc022..8d302ae7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "src/libs/littlefs"]
path = src/libs/littlefs
url = https://github.com/littlefs-project/littlefs.git
+[submodule "src/libs/QCBOR"]
+ path = src/libs/QCBOR
+ url = https://github.com/laurencelundblade/QCBOR.git
diff --git a/doc/ble.md b/doc/ble.md
index 8573166f..2b86243e 100644
--- a/doc/ble.md
+++ b/doc/ble.md
@@ -2,7 +2,7 @@
## Introduction
This page describes the BLE implementation and API built in this firmware.
-**Note** : I'm a beginner in BLE related technologies and the information in this document reflects my current knowledge and understanding of the BLE stack. This information might be erroneous or incomplete. Feel free to submit a PR if you think you can improve it.
+**Note**: I'm a beginner in BLE related technologies and the information in this document reflects my current knowledge and understanding of the BLE stack. This information might be erroneous or incomplete. Feel free to submit a PR if you think you can improve it.
---
@@ -72,12 +72,16 @@ The following custom services are implemented in InfiniTime:
* [Navigation Service](NavigationService.md) : 00010000-78fc-48fe-8e23-433b3a1942d0
- - Since InfiniTime 0.13
- * Call characteristic (extension to the Alert Notification Service): 00020001-78fc-48fe-8e23-433b3a1942d0
-
-
- - Since InfiniTime 1.7:
- * [Motion Service](MotionService.md) : 00030000-78fc-48fe-8e23-433b3a1942d0
+- Since InfiniTime 0.13
+ * Call characteristic (extension to the Alert Notification Service): 00020001-78fc-48fe-8e23-433b3a1942d0
+
+
+- Since InfiniTime 1.7:
+ * [Motion Service](MotionService.md): 00030000-78fc-48fe-8e23-433b3a1942d0
+
+
+- Since InfiniTime 1.8:
+ * [Weather Service](/src/components/ble/weather/WeatherService.h): 00040000-78fc-48fe-8e23-433b3a1942d0
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 488ba65d..a9e9d859 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -154,6 +154,7 @@ set(NIMBLE_SRC
libs/mynewt-nimble/nimble/controller/src/ble_ll_supp_cmd.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_hci_ev.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_rfmgmt.c
+ libs/mynewt-nimble/nimble/controller/src/ble_ll_resolv.c
libs/mynewt-nimble/porting/nimble/src/os_cputime.c
libs/mynewt-nimble/porting/nimble/src/os_cputime_pwr2.c
libs/mynewt-nimble/porting/nimble/src/os_mbuf.c
@@ -357,6 +358,14 @@ set(LVGL_SRC
libs/lvgl/src/lv_widgets/lv_win.c
)
+set(QCBOR_SRC
+ libs/QCBOR/src/ieee754.c
+ libs/QCBOR/src/qcbor_decode.c
+ libs/QCBOR/src/qcbor_encode.c
+ libs/QCBOR/src/qcbor_err_to_str.c
+ libs/QCBOR/src/UsefulBuf.c
+ )
+
list(APPEND IMAGE_FILES
displayapp/icons/battery/os_battery_error.c
displayapp/icons/battery/os_battery_100.c
@@ -407,6 +416,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/Label.cpp
displayapp/screens/FirmwareUpdate.cpp
displayapp/screens/Music.cpp
+ displayapp/screens/Weather.cpp
displayapp/screens/Navigation.cpp
displayapp/screens/Metronome.cpp
displayapp/screens/Motion.cpp
@@ -421,8 +431,10 @@ list(APPEND SOURCE_FILES
displayapp/screens/BatteryInfo.cpp
displayapp/screens/Steps.cpp
displayapp/screens/Timer.cpp
+ displayapp/screens/PassKey.cpp
displayapp/screens/Error.cpp
displayapp/screens/Alarm.cpp
+ displayapp/screens/Styles.cpp
displayapp/Colors.cpp
## Settings
@@ -470,6 +482,7 @@ list(APPEND SOURCE_FILES
components/ble/CurrentTimeService.cpp
components/ble/AlertNotificationService.cpp
components/ble/MusicService.cpp
+ components/ble/weather/WeatherService.cpp
components/ble/NavigationService.cpp
displayapp/fonts/lv_font_navi_80.c
components/ble/BatteryInformationService.cpp
@@ -541,6 +554,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/ble/CurrentTimeService.cpp
components/ble/AlertNotificationService.cpp
components/ble/MusicService.cpp
+ components/ble/weather/WeatherService.cpp
components/ble/BatteryInformationService.cpp
components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp
@@ -644,6 +658,9 @@ set(INCLUDE_FILES
components/datetime/DateTimeController.h
components/brightness/BrightnessController.h
components/motion/MotionController.h
+ components/firmwarevalidator/FirmwareValidator.h
+ components/ble/BleController.h
+ components/ble/NotificationManager.h
components/ble/NimbleController.h
components/ble/DeviceInformationService.h
components/ble/CurrentTimeClient.h
@@ -656,6 +673,7 @@ set(INCLUDE_FILES
components/ble/BleClient.h
components/ble/HeartRateService.h
components/ble/MotionService.h
+ components/ble/weather/WeatherService.h
components/settings/Settings.h
components/timer/TimerController.h
components/alarm/AlarmController.h
@@ -781,7 +799,7 @@ link_directories(
)
-set(COMMON_FLAGS -MP -MD -mthumb -mabi=aapcs -Wall -Wno-unknown-pragmas -g3 -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin --short-enums -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wreturn-type -Werror=return-type -fstack-usage -fno-exceptions -fno-non-call-exceptions)
+set(COMMON_FLAGS -MP -MD -mthumb -mabi=aapcs -Wall -Wextra -Warray-bounds=2 -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=2 -Wformat-nonliteral -ftree-vrp -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unknown-pragmas -Wno-expansion-to-defined -g3 -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin --short-enums -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wreturn-type -Werror=return-type -fstack-usage -fno-exceptions -fno-non-call-exceptions)
add_definitions(-DCONFIG_GPIO_AS_PINRESET)
add_definitions(-DNIMBLE_CFG_CONTROLLER)
add_definitions(-DOS_CPUTIME_FREQ)
@@ -803,10 +821,10 @@ add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES})
target_include_directories(nrf-sdk SYSTEM PUBLIC . ../)
target_include_directories(nrf-sdk SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(nrf-sdk PRIVATE
- $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
- $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os>
- $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -fno-rtti>
- $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os -fno-rtti>
+ $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wno-expansion-to-defined -Og -g3>
+ $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Wno-expansion-to-defined -O3>
+ $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wno-expansion-to-defined -Og -fno-rtti>
+ $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Wno-expansion-to-defined -O3 -fno-rtti>
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -x assembler-with-cpp>
)
@@ -834,6 +852,25 @@ target_compile_options(lvgl PRIVATE
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -x assembler-with-cpp>
)
+# QCBOR
+add_library(QCBOR STATIC ${QCBOR_SRC})
+target_include_directories(QCBOR SYSTEM PUBLIC libs/QCBOR/inc)
+# This is required with the current configuration
+target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_FLOAT_HW_USE)
+# These are for space-saving
+target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_PREFERRED_FLOAT)
+target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_EXP_AND_MANTISSA)
+target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_STRINGS)
+#target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_ARRAYS)
+target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_UNCOMMON_TAGS)
+target_compile_definitions(QCBOR PUBLIC USEFULBUF_CONFIG_LITTLE_ENDIAN)
+set_target_properties(QCBOR PROPERTIES LINKER_LANGUAGE C)
+target_compile_options(QCBOR PRIVATE
+ $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -O0 -g3>
+ $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -O3>
+ $<$<COMPILE_LANGUAGE:ASM>: -MP -MD -x assembler-with-cpp>
+ )
+
# LITTLEFS_SRC
add_library(littlefs STATIC ${LITTLEFS_SRC})
target_include_directories(littlefs SYSTEM PUBLIC . ../)
@@ -852,12 +889,12 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
-target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs)
+target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs QCBOR)
target_compile_options(${EXECUTABLE_NAME} PUBLIC
- $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
- $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os>
- $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3 -fno-rtti>
- $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os -fno-rtti>
+ $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wextra -Wformat -Wno-missing-field-initializers -Wno-unused-parameter -Og -g3>
+ $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Wextra -Wformat -Wno-missing-field-initializers -Wno-unused-parameter -Os>
+ $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wextra -Wformat -Wno-missing-field-initializers -Wno-unused-parameter -Og -g3 -fno-rtti>
+ $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Wextra -Wformat -Wno-missing-field-initializers -Wno-unused-parameter -Os -fno-rtti>
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -x assembler-with-cpp>
)
@@ -881,7 +918,7 @@ set(IMAGE_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERSION_
set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs)
+target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs QCBOR)
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
@@ -917,7 +954,7 @@ endif()
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs)
+target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs QCBOR)
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
@@ -947,7 +984,7 @@ set(EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-${
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs)
+target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs QCBOR)
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
@@ -985,7 +1022,7 @@ endif()
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk)
+target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk QCBOR)
set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
@@ -1018,7 +1055,7 @@ set(EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOA
set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
set(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk)
+target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk QCBOR)
set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
diff --git a/src/buttonhandler/ButtonHandler.cpp b/src/buttonhandler/ButtonHandler.cpp
index 91e8bbd0..02ee22cf 100644
--- a/src/buttonhandler/ButtonHandler.cpp
+++ b/src/buttonhandler/ButtonHandler.cpp
@@ -8,7 +8,7 @@ void ButtonTimerCallback(TimerHandle_t xTimer) {
}
void ButtonHandler::Init(Pinetime::System::SystemTask* systemTask) {
- buttonTimer = xTimerCreate("buttonTimer", 0, pdFALSE, systemTask, ButtonTimerCallback);
+ buttonTimer = xTimerCreate("buttonTimer", pdMS_TO_TICKS(200), pdFALSE, systemTask, ButtonTimerCallback);
}
ButtonActions ButtonHandler::HandleEvent(Events event) {
diff --git a/src/components/alarm/AlarmController.h b/src/components/alarm/AlarmController.h
index bf85d431..f39fbded 100644
--- a/src/components/alarm/AlarmController.h
+++ b/src/components/alarm/AlarmController.h
@@ -18,7 +18,6 @@
#pragma once
#include <cstdint>
-#include "app_timer.h"
#include "components/datetime/DateTimeController.h"
namespace Pinetime {
diff --git a/src/components/battery/BatteryController.cpp b/src/components/battery/BatteryController.cpp
index c875cb8d..300d0978 100644
--- a/src/components/battery/BatteryController.cpp
+++ b/src/components/battery/BatteryController.cpp
@@ -3,6 +3,7 @@
#include <hal/nrf_gpio.h>
#include <nrfx_saadc.h>
#include <algorithm>
+#include <cmath>
using namespace Pinetime::Controllers;
diff --git a/src/components/ble/AlertNotificationService.cpp b/src/components/ble/AlertNotificationService.cpp
index f616cce8..04819122 100644
--- a/src/components/ble/AlertNotificationService.cpp
+++ b/src/components/ble/AlertNotificationService.cpp
@@ -53,8 +53,9 @@ int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle
// Ignore notifications with empty message
const auto packetLen = OS_MBUF_PKTLEN(ctxt->om);
- if (packetLen <= headerSize)
+ if (packetLen <= headerSize) {
return 0;
+ }
size_t bufferSize = std::min(packetLen + stringTerminatorSize, maxBufferSize);
auto messageSize = std::min(maxMessageSize, (bufferSize - headerSize));
diff --git a/src/components/ble/BatteryInformationService.cpp b/src/components/ble/BatteryInformationService.cpp
index 9a3f86f5..82df7b15 100644
--- a/src/components/ble/BatteryInformationService.cpp
+++ b/src/components/ble/BatteryInformationService.cpp
@@ -17,7 +17,7 @@ BatteryInformationService::BatteryInformationService(Controllers::Battery& batte
characteristicDefinition {{.uuid = &batteryLevelUuid.u,
.access_cb = BatteryInformationServiceCallback,
.arg = this,
- .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
+ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY,
.val_handle = &batteryLevelHandle},
{0}},
serviceDefinition {
diff --git a/src/components/ble/BleController.h b/src/components/ble/BleController.h
index 2cba26a9..72b87663 100644
--- a/src/components/ble/BleController.h
+++ b/src/components/ble/BleController.h
@@ -9,7 +9,7 @@ namespace Pinetime {
public:
using BleAddress = std::array<uint8_t, 6>;
enum class FirmwareUpdateStates { Idle, Running, Validated, Error };
- enum class AddressTypes { Public, Random };
+ enum class AddressTypes { Public, Random, RPA_Public, RPA_Random };
Ble() = default;
bool IsConnected() const {
@@ -48,6 +48,12 @@ namespace Pinetime {
void AddressType(AddressTypes t) {
addressType = t;
}
+ void SetPairingKey(uint32_t k) {
+ pairingKey = k;
+ }
+ uint32_t GetPairingKey() const {
+ return pairingKey;
+ }
private:
bool isConnected = false;
@@ -57,6 +63,7 @@ namespace Pinetime {
FirmwareUpdateStates firmwareUpdateState = FirmwareUpdateStates::Idle;
BleAddress address;
AddressTypes addressType;
+ uint32_t pairingKey = 0;
};
}
-} \ No newline at end of file
+}
diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp
index 43a8b0d6..acf4f94b 100644
--- a/src/components/ble/NimbleController.cpp
+++ b/src/components/ble/NimbleController.cpp
@@ -1,4 +1,6 @@
#include "components/ble/NimbleController.h"
+#include <cstring>
+
#include <hal/nrf_rtc.h>
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
@@ -6,13 +8,16 @@
#include <host/ble_hs.h>
#include <host/ble_hs_id.h>
#include <host/util/util.h>
-#undef max
-#undef min
+#include <controller/ble_ll.h>
+#include <controller/ble_hw.h>
#include <services/gap/ble_svc_gap.h>
#include <services/gatt/ble_svc_gatt.h>
+#undef max
+#undef min
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/datetime/DateTimeController.h"
+#include "components/fs/FS.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers;
@@ -24,37 +29,43 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
Controllers::Battery& batteryController,
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Controllers::HeartRateController& heartRateController,
- Controllers::MotionController& motionController)
+ Controllers::MotionController& motionController,
+ Pinetime::Controllers::FS& fs)
: systemTask {systemTask},
bleController {bleController},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
spiNorFlash {spiNorFlash},
+ fs {fs},
dfuService {systemTask, bleController, spiNorFlash},
+
currentTimeClient {dateTimeController},
anService {systemTask, notificationManager},
alertNotificationClient {systemTask, notificationManager},
currentTimeService {dateTimeController},
musicService {systemTask},
+ weatherService {systemTask, dateTimeController},
navService {systemTask},
batteryInformationService {batteryController},
immediateAlertService {systemTask, notificationManager},
heartRateService {systemTask, heartRateController},
- motionService{systemTask, motionController},
+ motionService {systemTask, motionController},
serviceDiscovery({&currentTimeClient, &alertNotificationClient}) {
}
void nimble_on_reset(int reason) {
- NRF_LOG_INFO("Resetting state; reason=%d\n", reason);
+ NRF_LOG_INFO("Nimble lost sync, resetting state; reason=%d", reason);
}
void nimble_on_sync(void) {
- int rc;
+ int rc;
+
+ NRF_LOG_INFO("Nimble is synced");
- rc = ble_hs_util_ensure_addr(0);
- ASSERT(rc == 0);
+ rc = ble_hs_util_ensure_addr(0);
+ ASSERT(rc == 0);
- nptr->StartAdvertising();
+ nptr->StartAdvertising();
}
int GAPEventCallback(struct ble_gap_event* event, void* arg) {
@@ -69,6 +80,7 @@ void NimbleController::Init() {
nptr = this;
ble_hs_cfg.reset_cb = nimble_on_reset;
ble_hs_cfg.sync_cb = nimble_on_sync;
+ ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_svc_gap_init();
ble_svc_gatt_init();
@@ -77,6 +89,7 @@ void NimbleController::Init() {
currentTimeClient.Init();
currentTimeService.Init();
musicService.Init();
+ weatherService.Init();
navService.Init();
anService.Init();
dfuService.Init();
@@ -97,28 +110,38 @@ void NimbleController::Init() {
Pinetime::Controllers::Ble::BleAddress address;
rc = ble_hs_id_copy_addr(addrType, address.data(), nullptr);
ASSERT(rc == 0);
- bleController.AddressType((addrType == 0) ? Ble::AddressTypes::Public : Ble::AddressTypes::Random);
+
bleController.Address(std::move(address));
+ switch (addrType) {
+ case BLE_OWN_ADDR_PUBLIC:
+ bleController.AddressType(Ble::AddressTypes::Public);
+ break;
+ case BLE_OWN_ADDR_RANDOM:
+ bleController.AddressType(Ble::AddressTypes::Random);
+ break;
+ case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT:
+ bleController.AddressType(Ble::AddressTypes::RPA_Public);
+ break;
+ case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT:
+ bleController.AddressType(Ble::AddressTypes::RPA_Random);
+ break;
+ }
rc = ble_gatts_start();
ASSERT(rc == 0);
- if (!ble_gap_adv_active() && !bleController.IsConnected())
+ RestoreBond();
+
+ if (!ble_gap_adv_active() && !bleController.IsConnected()) {
StartAdvertising();
+ }
}
void NimbleController::StartAdvertising() {
- int rc;
-
- /* set adv parameters */
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
- /* advertising payload is split into advertising data and advertising
- response, because all data cannot fit into single packet; name of device
- is sent as response to scan request */
struct ble_hs_adv_fields rsp_fields;
- /* fill all fields and parameters with zeros */
memset(&adv_params, 0, sizeof(adv_params));
memset(&fields, 0, sizeof(fields));
memset(&rsp_fields, 0, sizeof(rsp_fields));
@@ -141,10 +164,11 @@ void NimbleController::StartAdvertising() {
fields.uuids128_is_complete = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
- rsp_fields.name = (uint8_t*) deviceName;
+ rsp_fields.name = reinterpret_cast<const uint8_t*>(deviceName);
rsp_fields.name_len = strlen(deviceName);
rsp_fields.name_is_complete = 1;
+ int rc;
rc = ble_gap_adv_set_fields(&fields);
ASSERT(rc == 0);
@@ -159,15 +183,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
switch (event->type) {
case BLE_GAP_EVENT_ADV_COMPLETE:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_ADV_COMPLETE");
- NRF_LOG_INFO("reason=%d; status=%d", event->adv_complete.reason, event->connect.status);
+ NRF_LOG_INFO("reason=%d; status=%0X", event->adv_complete.reason, event->connect.status);
StartAdvertising();
break;
case BLE_GAP_EVENT_CONNECT:
- NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONNECT");
-
/* A new connection was established or a connection attempt failed. */
- NRF_LOG_INFO("connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed", event->connect.status);
+ NRF_LOG_INFO("Connect event : BLE_GAP_EVENT_CONNECT");
+ NRF_LOG_INFO("connection %s; status=%0X ", event->connect.status == 0 ? "established" : "failed", event->connect.status);
if (event->connect.status != 0) {
/* Connection failed; resume advertising. */
@@ -186,10 +209,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
break;
case BLE_GAP_EVENT_DISCONNECT:
- NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_DISCONNECT");
+ /* Connection terminated; resume advertising. */
+ NRF_LOG_INFO("Disconnect event : BLE_GAP_EVENT_DISCONNECT");
NRF_LOG_INFO("disconnect reason=%d", event->disconnect.reason);
- /* Connection terminated; resume advertising. */
+ if (event->disconnect.conn.sec_state.bonded) {
+ PersistBond(event->disconnect.conn);
+ }
+
currentTimeClient.Reset();
alertNotificationClient.Reset();
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
@@ -199,18 +226,67 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
break;
case BLE_GAP_EVENT_CONN_UPDATE:
- NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONN_UPDATE");
/* The central has updated the connection parameters. */
- NRF_LOG_INFO("update status=%d ", event->conn_update.status);
+ NRF_LOG_INFO("Update event : BLE_GAP_EVENT_CONN_UPDATE");
+ NRF_LOG_INFO("update status=%0X ", event->conn_update.status);
+ break;
+
+ case BLE_GAP_EVENT_CONN_UPDATE_REQ:
+ /* The central has requested updated connection parameters */
+ NRF_LOG_INFO("Update event : BLE_GAP_EVENT_CONN_UPDATE_REQ");
+ NRF_LOG_INFO("update request : itvl_min=%d itvl_max=%d latency=%d supervision=%d",
+ event->conn_update_req.peer_params->itvl_min,
+ event->conn_update_req.peer_params->itvl_max,
+ event->conn_update_req.peer_params->latency,
+ event->conn_update_req.peer_params->supervision_timeout);
break;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
- NRF_LOG_INFO("encryption change event; status=%d ", event->enc_change.status);
+ NRF_LOG_INFO("Security event : BLE_GAP_EVENT_ENC_CHANGE");
+ NRF_LOG_INFO("encryption change event; status=%0X ", event->enc_change.status);
+
+ if (event->enc_change.status == 0) {
+ struct ble_gap_conn_desc desc;
+ ble_gap_conn_find(event->enc_change.conn_handle, &desc);
+ if (desc.sec_state.bonded) {
+ PersistBond(desc);
+ }
+
+ NRF_LOG_INFO("new state: encrypted=%d authenticated=%d bonded=%d key_size=%d",
+ desc.sec_state.encrypted,
+ desc.sec_state.authenticated,
+ desc.sec_state.bonded,
+ desc.sec_state.key_size);
+ }
+ break;
+
+ case BLE_GAP_EVENT_PASSKEY_ACTION:
+ /* Authentication has been requested for this connection.
+ *
+ * BLE authentication is determined by the combination of I/O capabilities
+ * on the central and peripheral. When the peripheral is display only and
+ * the central has a keyboard and display then passkey auth is selected.
+ * When both the central and peripheral have displays and support yes/no
+ * buttons then numeric comparison is selected. We currently advertise
+ * display capability only so we only handle the "display" action here.
+ *
+ * Standards insist that the rand() PRNG be deterministic.
+ * Use the nimble TRNG here since rand() is predictable.
+ */
+ NRF_LOG_INFO("Security event : BLE_GAP_EVENT_PASSKEY_ACTION");
+ if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
+ struct ble_sm_io pkey = {0};
+ pkey.action = event->passkey.params.action;
+ pkey.passkey = ble_ll_rand() % 1000000;
+ bleController.SetPairingKey(pkey.passkey);
+ systemTask.PushMessage(Pinetime::System::Messages::OnPairing);
+ ble_sm_inject_io(event->passkey.conn_handle, &pkey);
+ }
break;
case BLE_GAP_EVENT_SUBSCRIBE:
- NRF_LOG_INFO("subscribe event; conn_handle=%d attr_handle=%d "
+ NRF_LOG_INFO("Subscribe event; conn_handle=%d attr_handle=%d "
"reason=%d prevn=%d curn=%d previ=%d curi=???\n",
event->subscribe.conn_handle,
event->subscribe.attr_handle,
@@ -219,26 +295,24 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
event->subscribe.cur_notify,
event->subscribe.prev_indicate);
- if(event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
+ if (event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
- }
- else if(event->subscribe.prev_notify == 0 && event->subscribe.cur_notify == 1) {
+ } else if (event->subscribe.prev_notify == 0 && event->subscribe.cur_notify == 1) {
heartRateService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
- }
- else if(event->subscribe.prev_notify == 1 && event->subscribe.cur_notify == 0) {
+ } else if (event->subscribe.prev_notify == 1 && event->subscribe.cur_notify == 0) {
heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
}
break;
case BLE_GAP_EVENT_MTU:
- NRF_LOG_INFO("mtu update event; conn_handle=%d cid=%d mtu=%d\n",
- event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
+ NRF_LOG_INFO("MTU Update event; conn_handle=%d cid=%d mtu=%d", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
break;
case BLE_GAP_EVENT_REPEAT_PAIRING: {
+ NRF_LOG_INFO("Pairing event : BLE_GAP_EVENT_REPEAT_PAIRING");
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
@@ -257,6 +331,8 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
case BLE_GAP_EVENT_NOTIFY_RX: {
/* Peer sent us a notification or indication. */
+ /* Attribute data is contained in event->notify_rx.attr_data. */
+ NRF_LOG_INFO("Notify event : BLE_GAP_EVENT_NOTIFY_RX");
size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
NRF_LOG_INFO("received %s; conn_handle=%d attr_handle=%d "
@@ -268,10 +344,17 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
alertNotificationClient.OnNotification(event);
} break;
- /* Attribute data is contained in event->notify_rx.attr_data. */
+
+ case BLE_GAP_EVENT_NOTIFY_TX:
+ NRF_LOG_INFO("Notify event : BLE_GAP_EVENT_NOTIFY_TX");
+ break;
+
+ case BLE_GAP_EVENT_IDENTITY_RESOLVED:
+ NRF_LOG_INFO("Identity event : BLE_GAP_EVENT_IDENTITY_RESOLVED");
+ break;
default:
- // NRF_LOG_INFO("Advertising event : %d", event->type);
+ NRF_LOG_INFO("UNHANDLED GAP event : %d", event->type);
break;
}
return 0;
@@ -292,3 +375,82 @@ void NimbleController::NotifyBatteryLevel(uint8_t level) {
batteryInformationService.NotifyBatteryLevel(connectionHandle, level);
}
}
+
+void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
+ union ble_store_key key;
+ union ble_store_value our_sec, peer_sec, peer_cccd_set[MYNEWT_VAL(BLE_STORE_MAX_CCCDS)] = {0};
+ int rc;
+
+ memset(&key, 0, sizeof key);
+ memset(&our_sec, 0, sizeof our_sec);
+ key.sec.peer_addr = desc.peer_id_addr;
+ rc = ble_store_read_our_sec(&key.sec, &our_sec.sec);
+
+ if (memcmp(&our_sec.sec, &bondId, sizeof bondId) == 0) {
+ return;
+ }
+
+ memcpy(&bondId, &our_sec.sec, sizeof bondId);
+
+ memset(&key, 0, sizeof key);
+ memset(&peer_sec, 0, sizeof peer_sec);
+ key.sec.peer_addr = desc.peer_id_addr;
+ rc += ble_store_read_peer_sec(&key.sec, &peer_sec.sec);
+
+ if (rc == 0) {
+ memset(&key, 0, sizeof key);
+ key.cccd.peer_addr = desc.peer_id_addr;
+ int peer_count = 0;
+ ble_store_util_count(BLE_STORE_OBJ_TYPE_CCCD, &peer_count);
+ for (int i = 0; i < peer_count; i++) {
+ key.cccd.idx = peer_count;
+ ble_store_read_cccd(&key.cccd, &peer_cccd_set[i].cccd);
+ }
+
+ /* Wakeup Spi and SpiNorFlash before accessing the file system
+ * This should be fixed in the FS driver
+ */
+ systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
+ systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
+ vTaskDelay(10);
+
+ lfs_file_t file_p;
+
+ rc = fs.FileOpen(&file_p, "/bond.dat", LFS_O_WRONLY | LFS_O_CREAT);
+ if (rc == 0) {
+ fs.FileWrite(&file_p, reinterpret_cast<uint8_t*>(&our_sec.sec), sizeof our_sec);
+ fs.FileWrite(&file_p, reinterpret_cast<uint8_t*>(&peer_sec.sec), sizeof peer_sec);
+ fs.FileWrite(&file_p, reinterpret_cast<const uint8_t*>(&peer_count), 1);
+ for (int i = 0; i < peer_count; i++) {
+ fs.FileWrite(&file_p, reinterpret_cast<uint8_t*>(&peer_cccd_set[i].cccd), sizeof(struct ble_store_value_cccd));
+ }
+ fs.FileClose(&file_p);
+ }
+ systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
+ }
+}
+
+void NimbleController::RestoreBond() {
+ lfs_file_t file_p;
+ union ble_store_value sec, cccd;
+ uint8_t peer_count = 0;
+
+ if (fs.FileOpen(&file_p, "/bond.dat", LFS_O_RDONLY) == 0) {
+ memset(&sec, 0, sizeof sec);
+ fs.FileRead(&file_p, reinterpret_cast<uint8_t*>(&sec.sec), sizeof sec);
+ ble_store_write_our_sec(&sec.sec);
+
+ memset(&sec, 0, sizeof sec);
+ fs.FileRead(&file_p, reinterpret_cast<uint8_t*>(&sec.sec), sizeof sec);
+ ble_store_write_peer_sec(&sec.sec);
+
+ fs.FileRead(&file_p, &peer_count, 1);
+ for (int i = 0; i < peer_count; i++) {
+ fs.FileRead(&file_p, reinterpret_cast<uint8_t*>(&cccd.cccd), sizeof(struct ble_store_value_cccd));
+ ble_store_write_cccd(&cccd.cccd);
+ }
+
+ fs.FileClose(&file_p);
+ fs.FileDelete("/bond.dat");
+ }
+}
diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h
index 895b87f2..12bd6924 100644
--- a/src/components/ble/NimbleController.h
+++ b/src/components/ble/NimbleController.h
@@ -14,12 +14,14 @@
#include "components/ble/CurrentTimeService.h"
#include "components/ble/DeviceInformationService.h"
#include "components/ble/DfuService.h"
+#include "components/ble/HeartRateService.h"
#include "components/ble/ImmediateAlertService.h"
#include "components/ble/MusicService.h"
#include "components/ble/NavigationService.h"
#include "components/ble/ServiceDiscovery.h"
-#include "components/ble/HeartRateService.h"
#include "components/ble/MotionService.h"
+#include "components/ble/weather/WeatherService.h"
+#include "components/fs/FS.h"
namespace Pinetime {
namespace Drivers {
@@ -45,7 +47,8 @@ namespace Pinetime {
Controllers::Battery& batteryController,
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Controllers::HeartRateController& heartRateController,
- Controllers::MotionController& motionController);
+ Controllers::MotionController& motionController,
+ Pinetime::Controllers::FS& fs);
void Init();
void StartAdvertising();
int OnGAPEvent(ble_gap_event* event);
@@ -70,6 +73,9 @@ namespace Pinetime {
Pinetime::Controllers::AlertNotificationService& alertService() {
return anService;
};
+ Pinetime::Controllers::WeatherService& weather() {
+ return weatherService;
+ };
uint16_t connHandle();
void NotifyBatteryLevel(uint8_t level);
@@ -79,12 +85,16 @@ namespace Pinetime {
}
private:
+ void PersistBond(struct ble_gap_conn_desc& desc);
+ void RestoreBond();
+
static constexpr const char* deviceName = "InfiniTime";
Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::Ble& bleController;
DateTime& dateTimeController;
Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
+ Pinetime::Controllers::FS& fs;
Pinetime::Controllers::DfuService dfuService;
DeviceInformationService deviceInformationService;
@@ -93,23 +103,24 @@ namespace Pinetime {
AlertNotificationClient alertNotificationClient;
CurrentTimeService currentTimeService;
MusicService musicService;
+ WeatherService weatherService;
NavigationService navService;
BatteryInformationService batteryInformationService;
ImmediateAlertService immediateAlertService;
HeartRateService heartRateService;
MotionService motionService;
+ ServiceDiscovery serviceDiscovery;
- uint8_t addrType; // 1 = Random, 0 = PUBLIC
+ uint8_t addrType;
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
uint8_t fastAdvCount = 0;
+ uint8_t bondId[16] = {0};
ble_uuid128_t dfuServiceUuid {
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
-
- ServiceDiscovery serviceDiscovery;
};
- static NimbleController* nptr;
+ static NimbleController* nptr;
}
}
diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h
new file mode 100644
index 00000000..613d5acb
--- /dev/null
+++ b/src/components/ble/weather/WeatherData.h
@@ -0,0 +1,385 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+/**
+ * Different weather events, weather data structures used by {@link WeatherService.h}
+ *
+ * How to upload events to the timeline?
+ *
+ * All timeline write payloads are simply CBOR-encoded payloads of the structs described below.
+ *
+ * All payloads have a mandatory header part and the dynamic part that
+ * depends on the event type specified in the header. If you don't,
+ * you'll get an error returned. Data is relatively well-validated,
+ * so keep in the bounds of the data types given.
+ *
+ * Write all struct members (CamelCase keys) into a single finite-sized map, and write it to the characteristic.
+ * Mind the MTU.
+ *
+ * How to debug?
+ *
+ * There's a Screen that you can compile into your firmware that shows currently valid events.
+ * You can adapt that to display something else. That part right now is very much work in progress
+ * because the exact requirements are not yet known.
+ *
+ *
+ * Implemented based on and other material:
+ * https://en.wikipedia.org/wiki/METAR
+ * https://www.weather.gov/jetstream/obscurationtypes
+ * http://www.faraim.org/aim/aim-4-03-14-493.html
+ */
+
+namespace Pinetime {
+ namespace Controllers {
+ class WeatherData {
+ public:
+ /**
+ * Visibility obscuration types
+ */
+ enum class obscurationtype {
+ /** No obscuration */
+ None = 0,
+ /** Water particles suspended in the air; low visibility; does not fall */
+ Fog = 1,
+ /** Tiny, dry particles in the air; invisible to the eye; opalescent */
+ Haze = 2,
+ /** Small fire-created particles suspended in the air */
+ Smoke = 3,
+ /** Fine rock powder, from for example volcanoes */
+ Ash = 4,
+ /** Fine particles of earth suspended in the air by the wind */
+ Dust = 5,
+ /** Fine particles of sand suspended in the air by the wind */
+ Sand = 6,
+ /** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */
+ Mist = 7,
+ /** This is SPECIAL in the sense that the thing raining down is doing the obscuration */
+ Precipitation = 8,
+ Length
+ };
+
+ /**
+ * Types of precipitation
+ */
+ enum class precipitationtype {
+ /**
+ * No precipitation
+ *
+ * Theoretically we could just _not_ send the event, but then
+ * how do we differentiate between no precipitation and
+ * no information about precipitation
+ */
+ None = 0,
+ /** Drops larger than a drizzle; also widely separated drizzle */
+ Rain = 1,
+ /** Fairly uniform rain consisting of fine drops */
+ Drizzle = 2,
+ /** Rain that freezes upon contact with objects and ground */
+ FreezingRain = 3,
+ /** Rain + hail; ice pellets; small translucent frozen raindrops */
+ Sleet = 4,
+ /** Larger ice pellets; falling separately or in irregular clumps */
+ Hail = 5,
+ /** Hail with smaller grains of ice; mini-snowballs */
+ SmallHail = 6,
+ /** Snow... */
+ Snow = 7,
+ /** Frozen drizzle; very small snow crystals */
+ SnowGrains = 8,
+ /** Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */
+ IceCrystals = 9,
+ /** It's raining down ash, e.g. from a volcano */
+ Ash = 10,
+ Length
+ };
+
+ /**
+ * These are special events that can "enhance" the "experience" of existing weather events
+ */
+ enum class specialtype {
+ /** Strong wind with a sudden onset that lasts at least a minute */
+ Squall = 0,
+ /** Series of waves in a water body caused by the displacement of a large volume of water */
+ Tsunami = 1,
+ /** Violent; rotating column of air */
+ Tornado = 2,
+ /** Unplanned; unwanted; uncontrolled fire in an area */
+ Fire = 3,
+ /** Thunder and/or lightning */
+ Thunder = 4,
+ Length
+ };
+
+ /**
+ * These are used for weather timeline manipulation
+ * that isn't just adding to the stack of weather events
+ */
+ enum class controlcodes {
+ /** How much is stored already */
+ GetLength = 0,
+ /** This wipes the entire timeline */
+ DelTimeline = 1,
+ /** There's a currently valid timeline event with the given type */
+ HasValidEvent = 3,
+ Length
+ };
+
+ /**
+ * Events have types
+ * then they're easier to parse after sending them over the air
+ */
+ enum class eventtype : uint8_t {
+ /** @see obscuration */
+ Obscuration = 0,
+ /** @see precipitation */
+ Precipitation = 1,
+ /** @see wind */
+ Wind = 2,
+ /** @see temperature */
+ Temperature = 3,
+ /** @see airquality */
+ AirQuality = 4,
+ /** @see special */
+ Special = 5,
+ /** @see pressure */
+ Pressure = 6,
+ /** @see location */
+ Location = 7,
+ /** @see cloud */
+ Clouds = 8,
+ /** @see humidity */
+ Humidity = 9,
+ Length
+ };
+
+ /**
+ * Valid event query
+ *
+ * NOTE: Not currently available, until needs are better known
+ */
+ class ValidEventQuery {
+ public:
+ static constexpr controlcodes code = controlcodes::HasValidEvent;
+ eventtype eventType;
+ };
+
+ /** The header used for further parsing */
+ class TimelineHeader {
+ public:
+ /**
+ * UNIX timestamp
+ * TODO: This is currently WITH A TIMEZONE OFFSET!
+ * Please send events with the timestamp offset by the timezone.
+ **/
+ uint64_t timestamp;
+ /**
+ * Time in seconds until the event expires
+ *
+ * 32 bits ought to be enough for everyone
+ *
+ * If there's a newer event of the same type then it overrides this one, even if it hasn't expired
+ */
+ uint32_t expires;
+ /**
+ * What type of weather-related event
+ */
+ eventtype eventType;
+ };
+
+ /** Specifies how cloudiness is stored */
+ class Clouds : public TimelineHeader {
+ public:
+ /** Cloud coverage in percentage, 0-100% */
+ uint8_t amount;
+ };
+
+ /** Specifies how obscuration is stored */
+ class Obscuration : public TimelineHeader {
+ public:
+ /** Type of precipitation */
+ obscurationtype type;
+ /**
+ * Visibility distance in meters
+ * 65535 is reserved for unspecified
+ */
+ uint16_t amount;
+ };
+
+ /** Specifies how precipitation is stored */
+ class Precipitation : public TimelineHeader {
+ public:
+ /** Type of precipitation */
+ precipitationtype type;
+ /**
+ * How much is it going to rain? In millimeters
+ * 255 is reserved for unspecified
+ **/
+ uint8_t amount;
+ };
+
+ /**
+ * How wind speed is stored
+ *
+ * In order to represent bursts of wind instead of constant wind,
+ * you have minimum and maximum speeds.
+ *
+ * As direction can fluctuate wildly and some watchfaces might wish to display it nicely,
+ * we're following the aerospace industry weather report option of specifying a range.
+ */
+ class Wind : public TimelineHeader {
+ public:
+ /** Meters per second */
+ uint8_t speedMin;
+ /** Meters per second */
+ uint8_t speedMax;
+ /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
+ uint8_t directionMin;
+ /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
+ uint8_t directionMax;
+ };
+
+ /**
+ * How temperature is stored
+ *
+ * As it's annoying to figure out the dewpoint on the watch,
+ * please send it from the companion
+ *
+ * We don't do floats, picodegrees are not useful. Make sure to multiply.
+ */
+ class Temperature : public TimelineHeader {
+ public:
+ /**
+ * Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
+ * -32768 is reserved for "no data"
+ */
+ int16_t temperature;
+ /**
+ * Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
+ * -32768 is reserved for "no data"
+ */
+ int16_t dewPoint;
+ };
+
+ /**
+ * How location info is stored
+ *
+ * This can be mostly static with long expiration,
+ * as it usually is, but it could change during a trip for ex.
+ * so we allow changing it dynamically.
+ *
+ * Location info can be for some kind of map watchface
+ * or daylight calculations, should those be required.
+ *
+ */
+ class Location : public TimelineHeader {
+ public:
+ /** Location name */
+ std::string location;
+ /** Altitude relative to sea level in meters */
+ int16_t altitude;
+ /** Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
+ int32_t latitude;
+ /** Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
+ int32_t longitude;
+ };
+
+ /**
+ * How humidity is stored
+ */
+ class Humidity : public TimelineHeader {
+ public:
+ /** Relative humidity, 0-100% */
+ uint8_t humidity;
+ };
+
+ /**
+ * How air pressure is stored
+ */
+ class Pressure : public TimelineHeader {
+ public:
+ /** Air pressure in hectopascals (hPa) */
+ int16_t pressure;
+ };
+
+ /**
+ * How special events are stored
+ */
+ class Special : public TimelineHeader {
+ public:
+ /** Special event's type */
+ specialtype type;
+ };
+
+ /**
+ * How air quality is stored
+ *
+ * These events are a bit more complex because the topic is not simple,
+ * the intention is to heavy-lift the annoying preprocessing from the watch
+ * this allows watchface or watchapp makers to generate accurate alerts and graphics
+ *
+ * If this needs further enforced standardization, pull requests are welcome
+ */
+ class AirQuality : public TimelineHeader {
+ public:
+ /**
+ * The name of the pollution
+ *
+ * for the sake of better compatibility with watchapps
+ * that might want to use this data for say visuals
+ * don't localize the name.
+ *
+ * Ideally watchapp itself localizes the name, if it's at all needed.
+ *
+ * E.g.
+ * For generic ones use "PM0.1", "PM5", "PM10"
+ * For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3"
+ * For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores
+ */
+ std::string polluter;
+ /**
+ * Amount of the pollution in SI units,
+ * otherwise it's going to be difficult to create UI, alerts
+ * and so on and for.
+ *
+ * See more:
+ * https://ec.europa.eu/environment/air/quality/standards.htm
+ * http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
+ *
+ * Example units:
+ * count/m³ for pollen
+ * µgC/m³ for micrograms of organic carbon
+ * µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust
+ * mg/m³ CO2, CO
+ * ng/m³ for heavy metals
+ *
+ * List is not comprehensive, should be improved.
+ * The current ones are what watchapps assume!
+ *
+ * Note: ppb and ppm to concentration should be calculated on the companion, using
+ * the correct formula (taking into account temperature and air pressure)
+ *
+ * Note2: The amount is off by times 100, for two decimal places of precision.
+ * E.g. 54.32µg/m³ is 5432
+ *
+ */
+ uint32_t amount;
+ };
+ };
+ }
+}
diff --git a/src/components/ble/weather/WeatherService.cpp b/src/components/ble/weather/WeatherService.cpp
new file mode 100644
index 00000000..23f53b74
--- /dev/null
+++ b/src/components/ble/weather/WeatherService.cpp
@@ -0,0 +1,604 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <qcbor/qcbor_spiffy_decode.h>
+#include "WeatherService.h"
+#include "libs/QCBOR/inc/qcbor/qcbor.h"
+#include "systemtask/SystemTask.h"
+
+int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(connHandle, attrHandle, ctxt);
+}
+
+namespace Pinetime {
+ namespace Controllers {
+ WeatherService::WeatherService(System::SystemTask& system, DateTime& dateTimeController)
+ : system(system), dateTimeController(dateTimeController) {
+ nullHeader = &nullTimelineheader;
+ nullTimelineheader->timestamp = 0;
+ }
+
+ void WeatherService::Init() {
+ uint8_t res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+ }
+
+ int WeatherService::OnCommand(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt) {
+ if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ const uint8_t packetLen = OS_MBUF_PKTLEN(ctxt->om); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ if (packetLen <= 0) {
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ // Decode
+ QCBORDecodeContext decodeContext;
+ UsefulBufC encodedCbor = {ctxt->om->om_data, OS_MBUF_PKTLEN(ctxt->om)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+
+ QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL);
+ // KINDLY provide us a fixed-length map
+ QCBORDecode_EnterMap(&decodeContext, nullptr);
+ // Always encodes to the smallest number of bytes based on the value
+ int64_t tmpTimestamp = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp);
+ if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ int64_t tmpExpires = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires);
+ if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpExpires < 0 || tmpExpires > 4294967295) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ int64_t tmpEventType = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType);
+ if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpEventType < 0 ||
+ tmpEventType >= static_cast<int64_t>(WeatherData::eventtype::Length)) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+
+ switch (static_cast<WeatherData::eventtype>(tmpEventType)) {
+ case WeatherData::eventtype::AirQuality: {
+ std::unique_ptr<WeatherData::AirQuality> airquality = std::make_unique<WeatherData::AirQuality>();
+ airquality->timestamp = tmpTimestamp;
+ airquality->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ airquality->expires = tmpExpires;
+
+ UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
+ QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Polluter", &stringBuf);
+ if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ airquality->polluter = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 4294967295) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(airquality))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Obscuration: {
+ std::unique_ptr<WeatherData::Obscuration> obscuration = std::make_unique<WeatherData::Obscuration>();
+ obscuration->timestamp = tmpTimestamp;
+ obscuration->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ obscuration->expires = tmpExpires;
+
+ int64_t tmpType = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
+ if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::obscurationtype::Length)) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ obscuration->type = static_cast<WeatherData::obscurationtype>(tmpType);
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 65535) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(obscuration))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Precipitation: {
+ std::unique_ptr<WeatherData::Precipitation> precipitation = std::make_unique<WeatherData::Precipitation>();
+ precipitation->timestamp = tmpTimestamp;
+ precipitation->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ precipitation->expires = tmpExpires;
+
+ int64_t tmpType = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
+ if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::precipitationtype::Length)) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ precipitation->type = static_cast<WeatherData::precipitationtype>(tmpType);
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(precipitation))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Wind: {
+ std::unique_ptr<WeatherData::Wind> wind = std::make_unique<WeatherData::Wind>();
+ wind->timestamp = tmpTimestamp;
+ wind->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ wind->expires = tmpExpires;
+
+ int64_t tmpMin = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMin);
+ if (tmpMin < 0 || tmpMin > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ int64_t tmpMax = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax);
+ if (tmpMax < 0 || tmpMax > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ int64_t tmpDMin = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin);
+ if (tmpDMin < 0 || tmpDMin > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ int64_t tmpDMax = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax);
+ if (tmpDMax < 0 || tmpDMax > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(wind))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Temperature: {
+ std::unique_ptr<WeatherData::Temperature> temperature = std::make_unique<WeatherData::Temperature>();
+ temperature->timestamp = tmpTimestamp;
+ temperature->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ temperature->expires = tmpExpires;
+
+ int64_t tmpTemperature = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Temperature", &tmpTemperature);
+ if (tmpTemperature < -32768 || tmpTemperature > 32767) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ temperature->temperature =
+ static_cast<int16_t>(tmpTemperature); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ int64_t tmpDewPoint = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint);
+ if (tmpDewPoint < -32768 || tmpDewPoint > 32767) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ temperature->dewPoint =
+ static_cast<int16_t>(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(temperature))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Special: {
+ std::unique_ptr<WeatherData::Special> special = std::make_unique<WeatherData::Special>();
+ special->timestamp = tmpTimestamp;
+ special->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ special->expires = tmpExpires;
+
+ int64_t tmpType = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
+ if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::specialtype::Length)) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ special->type = static_cast<WeatherData::specialtype>(tmpType);
+
+ if (!AddEventToTimeline(std::move(special))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Pressure: {
+ std::unique_ptr<WeatherData::Pressure> pressure = std::make_unique<WeatherData::Pressure>();
+ pressure->timestamp = tmpTimestamp;
+ pressure->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ pressure->expires = tmpExpires;
+
+ int64_t tmpPressure = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure);
+ if (tmpPressure < 0 || tmpPressure >= 65535) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(pressure))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Location: {
+ std::unique_ptr<WeatherData::Location> location = std::make_unique<WeatherData::Location>();
+ location->timestamp = tmpTimestamp;
+ location->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ location->expires = tmpExpires;
+
+ UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
+ QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Location", &stringBuf);
+ if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->location = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
+
+ int64_t tmpAltitude = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude);
+ if (tmpAltitude < -32768 || tmpAltitude >= 32767) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->altitude = static_cast<int16_t>(tmpAltitude);
+
+ int64_t tmpLatitude = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude);
+ if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->latitude = static_cast<int32_t>(tmpLatitude);
+
+ int64_t tmpLongitude = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude);
+ if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->latitude = static_cast<int32_t>(tmpLongitude);
+
+ if (!AddEventToTimeline(std::move(location))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Clouds: {
+ std::unique_ptr<WeatherData::Clouds> clouds = std::make_unique<WeatherData::Clouds>();
+ clouds->timestamp = tmpTimestamp;
+ clouds->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ clouds->expires = tmpExpires;
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ clouds->amount = static_cast<uint8_t>(tmpAmount);
+
+ if (!AddEventToTimeline(std::move(clouds))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Humidity: {
+ std::unique_ptr<WeatherData::Humidity> humidity = std::make_unique<WeatherData::Humidity>();
+ humidity->timestamp = tmpTimestamp;
+ humidity->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ humidity->expires = tmpExpires;
+
+ int64_t tmpType = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType);
+ if (tmpType < 0 || tmpType >= 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ humidity->humidity = static_cast<uint8_t>(tmpType);
+
+ if (!AddEventToTimeline(std::move(humidity))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ default: {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ }
+
+ QCBORDecode_ExitMap(&decodeContext);
+ GetTimelineLength();
+ TidyTimeline();
+
+ if (QCBORDecode_Finish(&decodeContext) != QCBOR_SUCCESS) {
+ return BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
+ // Encode
+ uint8_t buffer[64];
+ QCBOREncodeContext encodeContext;
+ /* TODO: This is very much still a test endpoint
+ * it needs a characteristic UUID check
+ * and actual implementations that show
+ * what actually has to be read.
+ * WARN: Consider commands not part of the API for now!
+ */
+ QCBOREncode_Init(&encodeContext, UsefulBuf_FROM_BYTE_ARRAY(buffer));
+ QCBOREncode_OpenMap(&encodeContext);
+ QCBOREncode_AddTextToMap(&encodeContext, "test", UsefulBuf_FROM_SZ_LITERAL("test"));
+ QCBOREncode_AddInt64ToMap(&encodeContext, "test", 1ul);
+ QCBOREncode_CloseMap(&encodeContext);
+
+ UsefulBufC encodedEvent;
+ auto uErr = QCBOREncode_Finish(&encodeContext, &encodedEvent);
+ if (uErr != 0) {
+ return BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ auto res = os_mbuf_append(ctxt->om, &buffer, sizeof(buffer));
+ if (res == 0) {
+ return BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+
+ return 0;
+ }
+ return 0;
+ }
+
+ std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(*this->nullHeader);
+ }
+
+ size_t WeatherService::GetTimelineLength() const {
+ return timeline.size();
+ }
+
+ bool WeatherService::AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event) {
+ if (timeline.size() == timeline.max_size()) {
+ return false;
+ }
+
+ timeline.push_back(std::move(event));
+ return true;
+ }
+
+ bool WeatherService::HasTimelineEventOfType(const WeatherData::eventtype type) const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : timeline) {
+ if (header->eventType == type && IsEventStillValid(header, currentTimestamp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void WeatherService::TidyTimeline() {
+ uint64_t timeCurrent = GetCurrentUnixTimestamp();
+ timeline.erase(std::remove_if(std::begin(timeline),
+ std::end(timeline),
+ [&](std::unique_ptr<WeatherData::TimelineHeader> const& header) {
+ return !IsEventStillValid(header, timeCurrent);
+ }),
+ std::end(timeline));
+
+ std::sort(std::begin(timeline), std::end(timeline), CompareTimelineEvents);
+ }
+
+ bool WeatherService::CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
+ const std::unique_ptr<WeatherData::TimelineHeader>& second) {
+ return first->timestamp > second->timestamp;
+ }
+
+ bool WeatherService::IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp) {
+ // Not getting timestamp in isEventStillValid for more speed
+ return uniquePtr->timestamp + uniquePtr->expires >= timestamp;
+ }
+
+ uint64_t WeatherService::GetCurrentUnixTimestamp() const {
+ return std::chrono::duration_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime().time_since_epoch()).count();
+ }
+
+ int16_t WeatherService::GetTodayMinTemp() const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ uint64_t currentDayEnd = currentTimestamp - ((24 - dateTimeController.Hours()) * 60 * 60) -
+ ((60 - dateTimeController.Minutes()) * 60) - (60 - dateTimeController.Seconds());
+ int16_t result = -32768;
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp) &&
+ header->timestamp < currentDayEnd &&
+ reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
+ int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
+ if (result == -32768) {
+ result = temperature;
+ } else if (result > temperature) {
+ result = temperature;
+ } else {
+ // The temperature in this item is higher than the lowest we've found
+ }
+ }
+ }
+
+ return result;
+ }
+
+ int16_t WeatherService::GetTodayMaxTemp() const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ uint64_t currentDayEnd = currentTimestamp - ((24 - dateTimeController.Hours()) * 60 * 60) -
+ ((60 - dateTimeController.Minutes()) * 60) - (60 - dateTimeController.Seconds());
+ int16_t result = -32768;
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp) &&
+ header->timestamp < currentDayEnd &&
+ reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
+ int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
+ if (result == -32768) {
+ result = temperature;
+ } else if (result < temperature) {
+ result = temperature;
+ } else {
+ // The temperature in this item is lower than the highest we've found
+ }
+ }
+ }
+
+ return result;
+ }
+
+ void WeatherService::CleanUpQcbor(QCBORDecodeContext* decodeContext) {
+ QCBORDecode_ExitMap(decodeContext);
+ QCBORDecode_Finish(decodeContext);
+ }
+ }
+}
diff --git a/src/components/ble/weather/WeatherService.h b/src/components/ble/weather/WeatherService.h
new file mode 100644
index 00000000..eca70cbd
--- /dev/null
+++ b/src/components/ble/weather/WeatherService.h
@@ -0,0 +1,172 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <vector>
+#include <memory>
+
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#include <host/ble_uuid.h>
+#undef max
+#undef min
+
+#include "WeatherData.h"
+#include "libs/QCBOR/inc/qcbor/qcbor.h"
+#include "components/datetime/DateTimeController.h"
+
+int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+
+ class WeatherService {
+ public:
+ explicit WeatherService(System::SystemTask& system, DateTime& dateTimeController);
+
+ void Init();
+
+ int OnCommand(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt);
+
+ /*
+ * Helper functions for quick access to currently valid data
+ */
+ std::unique_ptr<WeatherData::Location>& GetCurrentLocation();
+ std::unique_ptr<WeatherData::Clouds>& GetCurrentClouds();
+ std::unique_ptr<WeatherData::Obscuration>& GetCurrentObscuration();
+ std::unique_ptr<WeatherData::Precipitation>& GetCurrentPrecipitation();
+ std::unique_ptr<WeatherData::Wind>& GetCurrentWind();
+ std::unique_ptr<WeatherData::Temperature>& GetCurrentTemperature();
+ std::unique_ptr<WeatherData::Humidity>& GetCurrentHumidity();
+ std::unique_ptr<WeatherData::Pressure>& GetCurrentPressure();
+ std::unique_ptr<WeatherData::AirQuality>& GetCurrentQuality();
+
+ /**
+ * Searches for the current day's maximum temperature
+ * @return -32768 if there's no data, degrees Celsius times 100 otherwise
+ */
+ int16_t GetTodayMaxTemp() const;
+ /**
+ * Searches for the current day's minimum temperature
+ * @return -32768 if there's no data, degrees Celsius times 100 otherwise
+ */
+ int16_t GetTodayMinTemp() const;
+
+ /*
+ * Management functions
+ */
+ /**
+ * Adds an event to the timeline
+ * @return
+ */
+ bool AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event);
+ /**
+ * Gets the current timeline length
+ */
+ size_t GetTimelineLength() const;
+ /**
+ * Checks if an event of a certain type exists in the timeline
+ */
+ bool HasTimelineEventOfType(WeatherData::eventtype type) const;
+
+ private:
+ // 00040000-78fc-48fe-8e23-433b3a1942d0
+ static constexpr ble_uuid128_t BaseUuid() {
+ return CharUuid(0x00, 0x00);
+ }
+
+ // 0004yyxx-78fc-48fe-8e23-433b3a1942d0
+ static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
+ return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
+ .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}};
+ }
+
+ ble_uuid128_t weatherUuid {BaseUuid()};
+
+ /**
+ * Just write timeline data here.
+ *
+ * See {@link WeatherData.h} for more information.
+ */
+ ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
+ /**
+ * This doesn't take timeline data, provides some control over it.
+ *
+ * NOTE: Currently not supported. Companion app implementer feedback required.
+ * There's very little point in solidifying an API before we know the needs.
+ */
+ ble_uuid128_t weatherControlCharUuid {CharUuid(0x00, 0x02)};
+
+ const struct ble_gatt_chr_def characteristicDefinition[3] = {
+ {.uuid = &weatherDataCharUuid.u,
+ .access_cb = WeatherCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE,
+ .val_handle = &eventHandle},
+ {.uuid = &weatherControlCharUuid.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ},
+ {nullptr}};
+ const struct ble_gatt_svc_def serviceDefinition[2] = {
+ {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition}, {0}};
+
+ uint16_t eventHandle {};
+
+ Pinetime::System::SystemTask& system;
+ Pinetime::Controllers::DateTime& dateTimeController;
+
+ std::vector<std::unique_ptr<WeatherData::TimelineHeader>> timeline;
+ std::unique_ptr<WeatherData::TimelineHeader> nullTimelineheader = std::make_unique<WeatherData::TimelineHeader>();
+ std::unique_ptr<WeatherData::TimelineHeader>* nullHeader;
+
+ /**
+ * Cleans up the timeline of expired events
+ */
+ void TidyTimeline();
+
+ /**
+ * Compares two timeline events
+ */
+ static bool CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
+ const std::unique_ptr<WeatherData::TimelineHeader>& second);
+
+ /**
+ * Returns current UNIX timestamp
+ */
+ uint64_t GetCurrentUnixTimestamp() const;
+
+ /**
+ * Checks if the event hasn't gone past and expired
+ *
+ * @param header timeline event to check
+ * @param currentTimestamp what's the time right now
+ * @return if the event is valid
+ */
+ static bool IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp);
+
+ /**
+ * This is a helper function that closes a QCBOR map and decoding context cleanly
+ */
+ void CleanUpQcbor(QCBORDecodeContext* decodeContext);
+ };
+ }
+}
diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h
index 59b45ce2..c77bd29f 100644
--- a/src/displayapp/Apps.h
+++ b/src/displayapp/Apps.h
@@ -25,6 +25,8 @@ namespace Pinetime {
Metronome,
Motion,
Steps,
+ Weather,
+ PassKey,
QuickSettings,
Settings,
SettingWatchFace,
diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index 1819eed9..ff70363a 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -29,6 +29,7 @@
#include "displayapp/screens/FlashLight.h"
#include "displayapp/screens/BatteryInfo.h"
#include "displayapp/screens/Steps.h"
+#include "displayapp/screens/PassKey.h"
#include "displayapp/screens/Error.h"
#include "drivers/Cst816s.h"
@@ -213,6 +214,9 @@ void DisplayApp::Refresh() {
} else {
LoadApp(Apps::Alarm, DisplayApp::FullRefreshDirections::None);
}
+ case Messages::ShowPairingKey:
+ LoadApp(Apps::PassKey, DisplayApp::FullRefreshDirections::Up);
+ break;
case Messages::TouchEvent: {
if (state != States::Running) {
break;
@@ -350,6 +354,11 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::None);
break;
+ case Apps::PassKey:
+ currentScreen = std::make_unique<Screens::PassKey>(this, bleController.GetPairingKey());
+ ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::SwipeDown);
+ break;
+
case Apps::Notifications:
currentScreen = std::make_unique<Screens::Notifications>(
this, notificationManager, systemTask->nimble().alertService(), motorController, Screens::Notifications::Modes::Normal);
@@ -492,8 +501,9 @@ void DisplayApp::SetFullRefresh(DisplayApp::FullRefreshDirections direction) {
}
void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
- if (systemTask != nullptr)
+ if (systemTask != nullptr) {
systemTask->PushMessage(message);
+ }
}
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h
index 29e09eb3..b22d6c3c 100644
--- a/src/displayapp/Messages.h
+++ b/src/displayapp/Messages.h
@@ -19,6 +19,7 @@ namespace Pinetime {
UpdateTimeOut,
DimScreen,
RestoreBrightness,
+ ShowPairingKey,
AlarmTriggered
};
}
diff --git a/src/displayapp/lv_pinetime_theme.c b/src/displayapp/lv_pinetime_theme.c
index 1780e64b..b74b2fd7 100644
--- a/src/displayapp/lv_pinetime_theme.c
+++ b/src/displayapp/lv_pinetime_theme.c
@@ -119,7 +119,6 @@ static void basic_init(void) {
lv_style_set_bg_color(&style_btn, LV_STATE_DISABLED | LV_STATE_CHECKED, lv_color_hex3(0x888));
lv_style_set_border_color(&style_btn, LV_STATE_DEFAULT, theme.color_primary);
lv_style_set_border_width(&style_btn, LV_STATE_DEFAULT, 0);
- lv_style_set_border_opa(&style_btn, LV_STATE_CHECKED, LV_OPA_TRANSP);
lv_style_set_text_color(&style_btn, LV_STATE_DEFAULT, lv_color_hex(0xffffff));
lv_style_set_text_color(&style_btn, LV_STATE_CHECKED, lv_color_hex(0xffffff));
diff --git a/src/displayapp/screens/Alarm.cpp b/src/displayapp/screens/Alarm.cpp
index 772e5d45..537ac0e0 100644
--- a/src/displayapp/screens/Alarm.cpp
+++ b/src/displayapp/screens/Alarm.cpp
@@ -36,7 +36,7 @@ Alarm::Alarm(DisplayApp* app, Controllers::AlarmController& alarmController)
alarmHours = alarmController.Hours();
alarmMinutes = alarmController.Minutes();
- lv_label_set_text_fmt(time, "%02lu:%02lu", alarmHours, alarmMinutes);
+ lv_label_set_text_fmt(time, "%02hhu:%02hhu", alarmHours, alarmMinutes);
lv_obj_align(time, lv_scr_act(), LV_ALIGN_CENTER, 0, -25);
@@ -223,7 +223,7 @@ void Alarm::ShowInfo() {
auto secToAlarm = timeToAlarm % 60;
lv_label_set_text_fmt(
- txtMessage, "Time to\nalarm:\n%2d Days\n%2d Hours\n%2d Minutes\n%2d Seconds", daysToAlarm, hrsToAlarm, minToAlarm, secToAlarm);
+ txtMessage, "Time to\nalarm:\n%2lu Days\n%2lu Hours\n%2lu Minutes\n%2lu Seconds", daysToAlarm, hrsToAlarm, minToAlarm, secToAlarm);
} else {
lv_label_set_text(txtMessage, "Alarm\nis not\nset.");
}
diff --git a/src/displayapp/screens/FirmwareValidation.cpp b/src/displayapp/screens/FirmwareValidation.cpp
index ea417135..c7a5b27e 100644
--- a/src/displayapp/screens/FirmwareValidation.cpp
+++ b/src/displayapp/screens/FirmwareValidation.cpp
@@ -18,7 +18,7 @@ FirmwareValidation::FirmwareValidation(Pinetime::Applications::DisplayApp* app,
: Screen {app}, validator {validator} {
labelVersion = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_fmt(labelVersion,
- "Version : %d.%d.%d\n"
+ "Version : %lu.%lu.%lu\n"
"ShortRef : %s",
Version::Major(),
Version::Minor(),
diff --git a/src/displayapp/screens/PassKey.cpp b/src/displayapp/screens/PassKey.cpp
new file mode 100644
index 00000000..9e43a541
--- /dev/null
+++ b/src/displayapp/screens/PassKey.cpp
@@ -0,0 +1,24 @@
+#include "PassKey.h"
+#include "displayapp/DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+
+PassKey::PassKey(Pinetime::Applications::DisplayApp* app, uint32_t key) : Screen(app) {
+ passkeyLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(passkeyLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFFFF00));
+ lv_obj_set_style_local_text_font(passkeyLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
+ lv_label_set_text_fmt(passkeyLabel, "%06u", key);
+ lv_obj_align(passkeyLabel, nullptr, LV_ALIGN_CENTER, 0, -20);
+
+ backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_click(backgroundLabel, true);
+ lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
+ lv_obj_set_size(backgroundLabel, 240, 240);
+ lv_obj_set_pos(backgroundLabel, 0, 0);
+ lv_label_set_text(backgroundLabel, "");
+}
+
+PassKey::~PassKey() {
+ lv_obj_clean(lv_scr_act());
+}
+
diff --git a/src/displayapp/screens/PassKey.h b/src/displayapp/screens/PassKey.h
new file mode 100644
index 00000000..16e72a3c
--- /dev/null
+++ b/src/displayapp/screens/PassKey.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "Screen.h"
+#include <lvgl/lvgl.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class PassKey : public Screen {
+ public:
+ PassKey(DisplayApp* app, uint32_t key);
+ ~PassKey() override;
+
+ private:
+ lv_obj_t* passkeyLabel;
+ lv_obj_t* backgroundLabel;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/PineTimeStyle.cpp b/src/displayapp/screens/PineTimeStyle.cpp
index fae3c4ab..b2b972dc 100644
--- a/src/displayapp/screens/PineTimeStyle.cpp
+++ b/src/displayapp/screens/PineTimeStyle.cpp
@@ -71,19 +71,19 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_set_style_local_bg_color(timebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Convert(settingsController.GetPTSColorBG()));
lv_obj_set_style_local_radius(timebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0);
lv_obj_set_size(timebar, 200, 240);
- lv_obj_align(timebar, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 5, 0);
+ lv_obj_align(timebar, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 0);
// Display the time
timeDD1 = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(timeDD1, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &open_sans_light);
lv_obj_set_style_local_text_color(timeDD1, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Convert(settingsController.GetPTSColorTime()));
- lv_label_set_text(timeDD1, "12");
+ lv_label_set_text(timeDD1, "00");
lv_obj_align(timeDD1, timebar, LV_ALIGN_IN_TOP_MID, 5, 5);
timeDD2 = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(timeDD2, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &open_sans_light);
lv_obj_set_style_local_text_color(timeDD2, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Convert(settingsController.GetPTSColorTime()));
- lv_label_set_text(timeDD2, "34");
+ lv_label_set_text(timeDD2, "00");
lv_obj_align(timeDD2, timebar, LV_ALIGN_IN_BOTTOM_MID, 5, -5);
timeAMPM = lv_label_create(lv_scr_act(), nullptr);
@@ -107,12 +107,12 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_set_auto_realign(batteryIcon, true);
bleIcon = lv_label_create(lv_scr_act(), nullptr);
- lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
- lv_obj_align(bleIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 25);
+ lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
+ lv_label_set_text(bleIcon, "");
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
- lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
- lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 50);
+ lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
+ lv_label_set_text(notificationIcon, "");
// Calendar icon
calendarOuter = lv_obj_create(lv_scr_act(), nullptr);
@@ -342,6 +342,17 @@ void PineTimeStyle::SetBatteryIcon() {
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
}
+void PineTimeStyle::AlignIcons() {
+ if (notificationState.Get() && bleState.Get()) {
+ lv_obj_align(bleIcon, sidebar, LV_ALIGN_IN_TOP_MID, 8, 25);
+ lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, -8, 25);
+ } else if (notificationState.Get() && !bleState.Get()) {
+ lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 25);
+ } else {
+ lv_obj_align(bleIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 25);
+ }
+}
+
void PineTimeStyle::Refresh() {
isCharging = batteryController.IsCharging();
if (isCharging.IsUpdated()) {
@@ -361,13 +372,13 @@ void PineTimeStyle::Refresh() {
bleState = bleController.IsConnected();
if (bleState.IsUpdated()) {
lv_label_set_text(bleIcon, BleIcon::GetIcon(bleState.Get()));
- lv_obj_realign(bleIcon);
+ AlignIcons();
}
notificationState = notificatioManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
- lv_obj_realign(notificationIcon);
+ AlignIcons();
}
currentDateTime = dateTimeController.CurrentDateTime();
diff --git a/src/displayapp/screens/PineTimeStyle.h b/src/displayapp/screens/PineTimeStyle.h
index c6436887..df8b7d5a 100644
--- a/src/displayapp/screens/PineTimeStyle.h
+++ b/src/displayapp/screens/PineTimeStyle.h
@@ -99,6 +99,7 @@ namespace Pinetime {
void SetBatteryIcon();
void CloseMenu();
+ void AlignIcons();
lv_task_t* taskRefresh;
};
diff --git a/src/displayapp/screens/Styles.cpp b/src/displayapp/screens/Styles.cpp
new file mode 100644
index 00000000..7f43fb99
--- /dev/null
+++ b/src/displayapp/screens/Styles.cpp
@@ -0,0 +1,8 @@
+#include "Styles.h"
+
+void Pinetime::Applications::Screens::SetRadioButtonStyle(lv_obj_t* checkbox) {
+ lv_obj_set_style_local_radius(checkbox, LV_CHECKBOX_PART_BULLET, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
+ lv_obj_set_style_local_border_width(checkbox, LV_CHECKBOX_PART_BULLET, LV_STATE_CHECKED, 9);
+ lv_obj_set_style_local_border_color(checkbox, LV_CHECKBOX_PART_BULLET, LV_STATE_CHECKED, LV_COLOR_GREEN);
+ lv_obj_set_style_local_bg_color(checkbox, LV_CHECKBOX_PART_BULLET, LV_STATE_CHECKED, LV_COLOR_WHITE);
+}
diff --git a/src/displayapp/screens/Styles.h b/src/displayapp/screens/Styles.h
new file mode 100644
index 00000000..a5fbb9f6
--- /dev/null
+++ b/src/displayapp/screens/Styles.h
@@ -0,0 +1,9 @@
+#include <lvgl/lvgl.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ void SetRadioButtonStyle(lv_obj_t* checkbox);
+ }
+ }
+}
diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp
index c363e2dd..e0138f86 100644
--- a/src/displayapp/screens/SystemInfo.cpp
+++ b/src/displayapp/screens/SystemInfo.cpp
@@ -1,3 +1,5 @@
+#include <FreeRTOS.h>
+#include <task.h>
#include "displayapp/screens/SystemInfo.h"
#include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h"
@@ -41,8 +43,8 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
brightnessController {brightnessController},
bleController {bleController},
watchdog {watchdog},
- motionController{motionController},
- touchPanel{touchPanel},
+ motionController {motionController},
+ touchPanel {touchPanel},
screens {app,
0,
{[this]() -> std::unique_ptr<Screen> {
@@ -182,7 +184,7 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
" #444444 used# %d (%d%%)\n"
" #444444 max used# %lu\n"
" #444444 frag# %d%%\n"
- " #444444 free# %d",
+ " #444444 free# %d",
bleAddr[5],
bleAddr[4],
bleAddr[3],
@@ -274,4 +276,4 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen5() {
lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
return std::make_unique<Screens::Label>(4, 5, app, label);
-}
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Twos.cpp b/src/displayapp/screens/Twos.cpp
index a1f0ba25..b15332f1 100644
--- a/src/displayapp/screens/Twos.cpp
+++ b/src/displayapp/screens/Twos.cpp
@@ -129,7 +129,7 @@ bool Twos::placeNewTile() {
return true;
}
-bool Twos::tryMerge(Tile grid[][4], int& newRow, int& newCol, int oldRow, int oldCol) {
+bool Twos::tryMerge(TwosTile grid[][4], int& newRow, int& newCol, int oldRow, int oldCol) {
if ((grid[newRow][newCol].value == grid[oldRow][oldCol].value)) {
if ((newCol != oldCol) || (newRow != oldRow)) {
if (!grid[newRow][newCol].merged) {
@@ -146,7 +146,7 @@ bool Twos::tryMerge(Tile grid[][4], int& newRow, int& newCol, int oldRow, int ol
return false;
}
-bool Twos::tryMove(Tile grid[][4], int newRow, int newCol, int oldRow, int oldCol) {
+bool Twos::tryMove(TwosTile grid[][4], int newRow, int newCol, int oldRow, int oldCol) {
if (((newCol >= 0) && (newCol != oldCol)) || ((newRow >= 0) && (newRow != oldRow))) {
grid[newRow][newCol].value = grid[oldRow][oldCol].value;
grid[oldRow][oldCol].value = 0;
@@ -261,7 +261,7 @@ bool Twos::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
return false;
}
-void Twos::updateGridDisplay(Tile grid[][4]) {
+void Twos::updateGridDisplay(TwosTile grid[][4]) {
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
if (grid[row][col].value) {
diff --git a/src/displayapp/screens/Twos.h b/src/displayapp/screens/Twos.h
index 48ea0794..5a0c4350 100644
--- a/src/displayapp/screens/Twos.h
+++ b/src/displayapp/screens/Twos.h
@@ -5,7 +5,7 @@
namespace Pinetime {
namespace Applications {
- struct Tile {
+ struct TwosTile {
bool merged = false;
unsigned int value = 0;
};
@@ -26,11 +26,11 @@ namespace Pinetime {
lv_obj_t* scoreText;
lv_obj_t* gridDisplay;
- Tile grid[4][4];
+ TwosTile grid[4][4];
unsigned int score = 0;
- void updateGridDisplay(Tile grid[][4]);
- bool tryMerge(Tile grid[][4], int& newRow, int& newCol, int oldRow, int oldCol);
- bool tryMove(Tile grid[][4], int newRow, int newCol, int oldRow, int oldCol);
+ void updateGridDisplay(TwosTile grid[][4]);
+ bool tryMerge(TwosTile grid[][4], int& newRow, int& newCol, int oldRow, int oldCol);
+ bool tryMove(TwosTile grid[][4], int newRow, int newCol, int oldRow, int oldCol);
bool placeNewTile();
};
}
diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp
new file mode 100644
index 00000000..1d0a83bd
--- /dev/null
+++ b/src/displayapp/screens/Weather.cpp
@@ -0,0 +1,222 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "Weather.h"
+#include <lvgl/lvgl.h>
+#include <components/ble/weather/WeatherService.h>
+#include "Label.h"
+#include "components/battery/BatteryController.h"
+#include "components/ble/BleController.h"
+#include "components/ble/weather/WeatherData.h"
+
+using namespace Pinetime::Applications::Screens;
+
+Weather::Weather(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::WeatherService& weather)
+ : Screen(app),
+ dateTimeController {dateTimeController},
+ weatherService(weather),
+ screens {app,
+ 0,
+ {[this]() -> std::unique_ptr<Screen> {
+ return CreateScreenTemperature();
+ },
+ [this]() -> std::unique_ptr<Screen> {
+ return CreateScreenAir();
+ },
+ [this]() -> std::unique_ptr<Screen> {
+ return CreateScreenClouds();
+ },
+ [this]() -> std::unique_ptr<Screen> {
+ return CreateScreenPrecipitation();
+ },
+ [this]() -> std::unique_ptr<Screen> {
+ return CreateScreenHumidity();
+ }},
+ Screens::ScreenListModes::UpDown} {
+}
+
+Weather::~Weather() {
+ lv_obj_clean(lv_scr_act());
+}
+
+void Weather::Refresh() {
+ if (running) {
+ // screens.Refresh();
+ }
+}
+
+bool Weather::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+bool Weather::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ return screens.OnTouchEvent(event);
+}
+
+std::unique_ptr<Screen> Weather::CreateScreenTemperature() {
+ lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_recolor(label, true);
+ std::unique_ptr<Controllers::WeatherData::Temperature>& current = weatherService.GetCurrentTemperature();
+ if (current->timestamp == 0) {
+ // Do not use the data, it's invalid
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Temperature#\n\n"
+ "#444444 %d#°C \n\n"
+ "#444444 %d#\n\n"
+ "%d\n"
+ "%d\n",
+ 0,
+ 0,
+ 0,
+ 0);
+ } else {
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Temperature#\n\n"
+ "#444444 %d#°C \n\n"
+ "#444444 %hd#\n\n"
+ "%llu\n"
+ "%lu\n",
+ current->temperature / 100,
+ current->dewPoint,
+ current->timestamp,
+ current->expires);
+ }
+ lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
+ lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
+ return std::unique_ptr<Screen>(new Screens::Label(0, 5, app, label));
+}
+
+std::unique_ptr<Screen> Weather::CreateScreenAir() {
+ lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_recolor(label, true);
+ std::unique_ptr<Controllers::WeatherData::AirQuality>& current = weatherService.GetCurrentQuality();
+ if (current->timestamp == 0) {
+ // Do not use the data, it's invalid
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Air quality#\n\n"
+ "#444444 %s#\n"
+ "#444444 %d#\n\n"
+ "%d\n"
+ "%d\n",
+ "",
+ 0,
+ 0,
+ 0);
+ } else {
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Air quality#\n\n"
+ "#444444 %s#\n"
+ "#444444 %lu#\n\n"
+ "%llu\n"
+ "%lu\n",
+ current->polluter.c_str(),
+ (current->amount / 100),
+ current->timestamp,
+ current->expires);
+ }
+ lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
+ lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
+ return std::unique_ptr<Screen>(new Screens::Label(0, 5, app, label));
+}
+
+std::unique_ptr<Screen> Weather::CreateScreenClouds() {
+ lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_recolor(label, true);
+ std::unique_ptr<Controllers::WeatherData::Clouds>& current = weatherService.GetCurrentClouds();
+ if (current->timestamp == 0) {
+ // Do not use the data, it's invalid
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Clouds#\n\n"
+ "#444444 %d%%#\n\n"
+ "%d\n"
+ "%d\n",
+ 0,
+ 0,
+ 0);
+ } else {
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Clouds#\n\n"
+ "#444444 %hhu%%#\n\n"
+ "%llu\n"
+ "%lu\n",
+ current->amount,
+ current->timestamp,
+ current->expires);
+ }
+ lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
+ lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
+ return std::unique_ptr<Screen>(new Screens::Label(0, 5, app, label));
+}
+
+std::unique_ptr<Screen> Weather::CreateScreenPrecipitation() {
+ lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_recolor(label, true);
+ std::unique_ptr<Controllers::WeatherData::Precipitation>& current = weatherService.GetCurrentPrecipitation();
+ if (current->timestamp == 0) {
+ // Do not use the data, it's invalid
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Precipitation#\n\n"
+ "#444444 %d%%#\n\n"
+ "%d\n"
+ "%d\n",
+ 0,
+ 0,
+ 0);
+ } else {
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Precipitation#\n\n"
+ "#444444 %hhu%%#\n\n"
+ "%llu\n"
+ "%lu\n",
+ current->amount,
+ current->timestamp,
+ current->expires);
+ }
+ lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
+ lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
+ return std::unique_ptr<Screen>(new Screens::Label(0, 5, app, label));
+}
+
+std::unique_ptr<Screen> Weather::CreateScreenHumidity() {
+ lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_recolor(label, true);
+ std::unique_ptr<Controllers::WeatherData::Humidity>& current = weatherService.GetCurrentHumidity();
+ if (current->timestamp == 0) {
+ // Do not use the data, it's invalid
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Humidity#\n\n"
+ "#444444 %d%%#\n\n"
+ "%d\n"
+ "%d\n",
+ 0,
+ 0,
+ 0);
+ } else {
+ lv_label_set_text_fmt(label,
+ "#FFFF00 Humidity#\n\n"
+ "#444444 %hhu%%#\n\n"
+ "%llu\n"
+ "%lu\n",
+ current->humidity,
+ current->timestamp,
+ current->expires);
+ }
+ lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
+ lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
+ return std::unique_ptr<Screen>(new Screens::Label(0, 5, app, label));
+}
diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h
new file mode 100644
index 00000000..34f95fce
--- /dev/null
+++ b/src/displayapp/screens/Weather.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <memory>
+#include <components/ble/weather/WeatherService.h>
+#include "Screen.h"
+#include "ScreenList.h"
+
+namespace Pinetime {
+ namespace Applications {
+ class DisplayApp;
+
+ namespace Screens {
+ class Weather : public Screen {
+ public:
+ explicit Weather(DisplayApp* app, Pinetime::Controllers::WeatherService& weather);
+
+ ~Weather() override;
+
+ void Refresh() override;
+
+ bool OnButtonPushed() override;
+
+ bool OnTouchEvent(TouchEvents event) override;
+
+ private:
+ bool running = true;
+
+ Pinetime::Controllers::DateTime& dateTimeController;
+ Controllers::WeatherService& weatherService;
+
+ ScreenList<5> screens;
+
+ std::unique_ptr<Screen> CreateScreenTemperature();
+
+ std::unique_ptr<Screen> CreateScreenAir();
+
+ std::unique_ptr<Screen> CreateScreenClouds();
+
+ std::unique_ptr<Screen> CreateScreenPrecipitation();
+
+ std::unique_ptr<Screen> CreateScreenHumidity();
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp
index 666dfb80..9e972afc 100644
--- a/src/displayapp/screens/settings/SettingDisplay.cpp
+++ b/src/displayapp/screens/settings/SettingDisplay.cpp
@@ -2,6 +2,7 @@
#include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h"
#include "displayapp/Messages.h"
+#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
@@ -14,6 +15,8 @@ namespace {
}
}
+constexpr std::array<uint16_t, 4> SettingDisplay::options;
+
SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: Screen(app), settingsController {settingsController} {
@@ -40,39 +43,19 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
- optionsTotal = 0;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " 5 seconds");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetScreenTimeOut() == 5000) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
- }
- optionsTotal++;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " 15 seconds");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetScreenTimeOut() == 15000) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
- }
- optionsTotal++;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " 20 seconds");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetScreenTimeOut() == 20000) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
- }
- optionsTotal++;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " 30 seconds");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetScreenTimeOut() == 30000) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
+ char buffer[12];
+ for (unsigned int i = 0; i < options.size(); i++) {
+ cbOption[i] = lv_checkbox_create(container1, nullptr);
+ sprintf(buffer, "%3d seconds", options[i] / 1000);
+ lv_checkbox_set_text(cbOption[i], buffer);
+ cbOption[i]->user_data = this;
+ lv_obj_set_event_cb(cbOption[i], event_handler);
+ SetRadioButtonStyle(cbOption[i]);
+
+ if (settingsController.GetScreenTimeOut() == options[i]) {
+ lv_checkbox_set_checked(cbOption[i], true);
+ }
}
- optionsTotal++;
}
SettingDisplay::~SettingDisplay() {
@@ -82,25 +65,11 @@ SettingDisplay::~SettingDisplay() {
void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) {
- for (int i = 0; i < optionsTotal; i++) {
+ for (unsigned int i = 0; i < options.size(); i++) {
if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true);
-
- if (i == 0) {
- settingsController.SetScreenTimeOut(5000);
- };
- if (i == 1) {
- settingsController.SetScreenTimeOut(15000);
- };
- if (i == 2) {
- settingsController.SetScreenTimeOut(20000);
- };
- if (i == 3) {
- settingsController.SetScreenTimeOut(30000);
- };
-
+ settingsController.SetScreenTimeOut(options[i]);
app->PushMessage(Applications::Display::Messages::UpdateTimeOut);
-
} else {
lv_checkbox_set_checked(cbOption[i], false);
}
diff --git a/src/displayapp/screens/settings/SettingDisplay.h b/src/displayapp/screens/settings/SettingDisplay.h
index 51b23aca..dc56419d 100644
--- a/src/displayapp/screens/settings/SettingDisplay.h
+++ b/src/displayapp/screens/settings/SettingDisplay.h
@@ -1,7 +1,9 @@
#pragma once
+#include <array>
#include <cstdint>
#include <lvgl/lvgl.h>
+
#include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h"
@@ -18,9 +20,10 @@ namespace Pinetime {
void UpdateSelected(lv_obj_t* object, lv_event_t event);
private:
+ static constexpr std::array<uint16_t, 4> options = {5000, 15000, 20000, 30000};
+
Controllers::Settings& settingsController;
- uint8_t optionsTotal;
- lv_obj_t* cbOption[4];
+ lv_obj_t* cbOption[options.size()];
};
}
}
diff --git a/src/displayapp/screens/settings/SettingSteps.cpp b/src/displayapp/screens/settings/SettingSteps.cpp
index 149840df..5ca3eecd 100644
--- a/src/displayapp/screens/settings/SettingSteps.cpp
+++ b/src/displayapp/screens/settings/SettingSteps.cpp
@@ -6,8 +6,8 @@
using namespace Pinetime::Applications::Screens;
namespace {
- static void event_handler(lv_obj_t * obj, lv_event_t event) {
- SettingSteps* screen = static_cast<SettingSteps *>(obj->user_data);
+ void event_handler(lv_obj_t* obj, lv_event_t event) {
+ SettingSteps* screen = static_cast<SettingSteps*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
}
@@ -30,33 +30,32 @@ SettingSteps::SettingSteps(
lv_obj_set_height(container1, LV_VER_RES - 60);
lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT);
- lv_obj_t * title = lv_label_create(lv_scr_act(), NULL);
+ lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(title,"Daily steps goal");
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 15, 15);
- lv_obj_t * icon = lv_label_create(lv_scr_act(), NULL);
+ lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_label_set_text_static(icon, Symbols::shoe);
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
-
- stepValue = lv_label_create(lv_scr_act(), NULL);
+ stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
lv_label_set_text_fmt(stepValue, "%lu", settingsController.GetStepsGoal());
lv_label_set_align(stepValue, LV_LABEL_ALIGN_CENTER);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, -10);
- btnPlus = lv_btn_create(lv_scr_act(), NULL);
+ btnPlus = lv_btn_create(lv_scr_act(), nullptr);
btnPlus->user_data = this;
lv_obj_set_size(btnPlus, 80, 50);
lv_obj_align(btnPlus, lv_scr_act(), LV_ALIGN_CENTER, 55, 80);
lv_obj_set_style_local_value_str(btnPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+");
lv_obj_set_event_cb(btnPlus, event_handler);
- btnMinus = lv_btn_create(lv_scr_act(), NULL);
+ btnMinus = lv_btn_create(lv_scr_act(), nullptr);
btnMinus->user_data = this;
lv_obj_set_size(btnMinus, 80, 50);
lv_obj_set_event_cb(btnMinus, event_handler);
diff --git a/src/displayapp/screens/settings/SettingTimeFormat.cpp b/src/displayapp/screens/settings/SettingTimeFormat.cpp
index c6bdf401..bd9af156 100644
--- a/src/displayapp/screens/settings/SettingTimeFormat.cpp
+++ b/src/displayapp/screens/settings/SettingTimeFormat.cpp
@@ -1,6 +1,7 @@
#include "displayapp/screens/settings/SettingTimeFormat.h"
#include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h"
+#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
@@ -13,6 +14,8 @@ namespace {
}
}
+constexpr std::array<const char*, 2> SettingTimeFormat::options;
+
SettingTimeFormat::SettingTimeFormat(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: Screen(app), settingsController {settingsController} {
@@ -39,24 +42,19 @@ SettingTimeFormat::SettingTimeFormat(Pinetime::Applications::DisplayApp* app, Pi
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
- optionsTotal = 0;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " 12-hour");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
+ for (unsigned int i = 0; i < options.size(); i++) {
+ cbOption[i] = lv_checkbox_create(container1, nullptr);
+ lv_checkbox_set_text(cbOption[i], options[i]);
+ cbOption[i]->user_data = this;
+ lv_obj_set_event_cb(cbOption[i], event_handler);
+ SetRadioButtonStyle(cbOption[i]);
}
- optionsTotal++;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " 24-hour");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
+ if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
+ lv_checkbox_set_checked(cbOption[0], true);
+ } else if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
+ lv_checkbox_set_checked(cbOption[1], true);
}
- optionsTotal++;
}
SettingTimeFormat::~SettingTimeFormat() {
@@ -66,7 +64,7 @@ SettingTimeFormat::~SettingTimeFormat() {
void SettingTimeFormat::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) {
- for (int i = 0; i < optionsTotal; i++) {
+ for (unsigned int i = 0; i < options.size(); i++) {
if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true);
diff --git a/src/displayapp/screens/settings/SettingTimeFormat.h b/src/displayapp/screens/settings/SettingTimeFormat.h
index eac4bdc9..818edf0c 100644
--- a/src/displayapp/screens/settings/SettingTimeFormat.h
+++ b/src/displayapp/screens/settings/SettingTimeFormat.h
@@ -1,7 +1,9 @@
#pragma once
+#include <array>
#include <cstdint>
#include <lvgl/lvgl.h>
+
#include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h"
@@ -18,9 +20,9 @@ namespace Pinetime {
void UpdateSelected(lv_obj_t* object, lv_event_t event);
private:
+ static constexpr std::array<const char*, 2> options = {" 12-hour", " 24-hour"};
Controllers::Settings& settingsController;
- uint8_t optionsTotal;
- lv_obj_t* cbOption[2];
+ lv_obj_t* cbOption[options.size()];
};
}
}
diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp
index 8e6e7cf4..a24eaa15 100644
--- a/src/displayapp/screens/settings/SettingWatchFace.cpp
+++ b/src/displayapp/screens/settings/SettingWatchFace.cpp
@@ -2,6 +2,7 @@
#include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h"
#include "displayapp/screens/Screen.h"
+#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Symbols.h"
using namespace Pinetime::Applications::Screens;
@@ -13,6 +14,8 @@ namespace {
}
}
+constexpr std::array<const char*, 3> SettingWatchFace::options;
+
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: Screen(app), settingsController {settingsController} {
@@ -40,34 +43,17 @@ SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, Pine
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
- optionsTotal = 0;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " Digital face");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetClockFace() == 0) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
- }
-
- optionsTotal++;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " Analog face");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetClockFace() == 1) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
- }
+ for (unsigned int i = 0; i < options.size(); i++) {
+ cbOption[i] = lv_checkbox_create(container1, nullptr);
+ lv_checkbox_set_text(cbOption[i], options[i]);
+ cbOption[i]->user_data = this;
+ lv_obj_set_event_cb(cbOption[i], event_handler);
+ SetRadioButtonStyle(cbOption[i]);
- optionsTotal++;
- cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr);
- lv_checkbox_set_text_static(cbOption[optionsTotal], " PineTimeStyle");
- cbOption[optionsTotal]->user_data = this;
- lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
- if (settingsController.GetClockFace() == 2) {
- lv_checkbox_set_checked(cbOption[optionsTotal], true);
+ if (settingsController.GetClockFace() == i) {
+ lv_checkbox_set_checked(cbOption[i], true);
+ }
}
-
- optionsTotal++;
}
SettingWatchFace::~SettingWatchFace() {
@@ -77,7 +63,7 @@ SettingWatchFace::~SettingWatchFace() {
void SettingWatchFace::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) {
- for (uint8_t i = 0; i < optionsTotal; i++) {
+ for (unsigned int i = 0; i < options.size(); i++) {
if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true);
settingsController.SetClockFace(i);
diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h
index d4a96c6d..ccba7d13 100644
--- a/src/displayapp/screens/settings/SettingWatchFace.h
+++ b/src/displayapp/screens/settings/SettingWatchFace.h
@@ -1,7 +1,9 @@
#pragma once
+#include <array>
#include <cstdint>
#include <lvgl/lvgl.h>
+
#include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h"
@@ -18,9 +20,10 @@ namespace Pinetime {
void UpdateSelected(lv_obj_t* object, lv_event_t event);
private:
+ static constexpr std::array<const char*, 3> options = {" Digital face", " Analog face", " PineTimeStyle"};
Controllers::Settings& settingsController;
- uint8_t optionsTotal;
- lv_obj_t* cbOption[2];
+
+ lv_obj_t* cbOption[options.size()];
};
}
}
diff --git a/src/libs/QCBOR b/src/libs/QCBOR
new file mode 160000
+Subproject 9e2f70804393823cc6d16f9f1035ef7223faca0
diff --git a/src/libs/mynewt-nimble/porting/nimble/include/syscfg/syscfg.h b/src/libs/mynewt-nimble/porting/nimble/include/syscfg/syscfg.h
index 94b72cb6..b3f23411 100644
--- a/src/libs/mynewt-nimble/porting/nimble/include/syscfg/syscfg.h
+++ b/src/libs/mynewt-nimble/porting/nimble/include/syscfg/syscfg.h
@@ -699,11 +699,11 @@
#endif
#ifndef MYNEWT_VAL_BLE_SM_BONDING
-#define MYNEWT_VAL_BLE_SM_BONDING (0)
+#define MYNEWT_VAL_BLE_SM_BONDING (1)
#endif
#ifndef MYNEWT_VAL_BLE_SM_IO_CAP
-#define MYNEWT_VAL_BLE_SM_IO_CAP (BLE_HS_IO_NO_INPUT_OUTPUT)
+#define MYNEWT_VAL_BLE_SM_IO_CAP (BLE_HS_IO_DISPLAY_ONLY)
#endif
#ifndef MYNEWT_VAL_BLE_SM_KEYPRESS
@@ -711,7 +711,7 @@
#endif
#ifndef MYNEWT_VAL_BLE_SM_LEGACY
-#define MYNEWT_VAL_BLE_SM_LEGACY (1)
+#define MYNEWT_VAL_BLE_SM_LEGACY (0)
#endif
#ifndef MYNEWT_VAL_BLE_SM_MAX_PROCS
@@ -719,7 +719,7 @@
#endif
#ifndef MYNEWT_VAL_BLE_SM_MITM
-#define MYNEWT_VAL_BLE_SM_MITM (0)
+#define MYNEWT_VAL_BLE_SM_MITM (1)
#endif
#ifndef MYNEWT_VAL_BLE_SM_OOB_DATA_FLAG
@@ -727,11 +727,11 @@
#endif
#ifndef MYNEWT_VAL_BLE_SM_OUR_KEY_DIST
-#define MYNEWT_VAL_BLE_SM_OUR_KEY_DIST (0)
+#define MYNEWT_VAL_BLE_SM_OUR_KEY_DIST (7)
#endif
#ifndef MYNEWT_VAL_BLE_SM_SC
-#define MYNEWT_VAL_BLE_SM_SC (0)
+#define MYNEWT_VAL_BLE_SM_SC (1)
#endif
#ifndef MYNEWT_VAL_BLE_SM_SC_DEBUG_KEYS
@@ -739,7 +739,7 @@
#endif
#ifndef MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST
-#define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (0)
+#define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (3)
#endif
#ifndef MYNEWT_VAL_BLE_STORE_MAX_BONDS
@@ -1089,7 +1089,7 @@
/* Overridden by @apache-mynewt-nimble/targets/riot (defined by @apache-mynewt-nimble/nimble/controller) */
#ifndef MYNEWT_VAL_BLE_LL_CFG_FEAT_LL_PRIVACY
-#define MYNEWT_VAL_BLE_LL_CFG_FEAT_LL_PRIVACY (0)
+#define MYNEWT_VAL_BLE_LL_CFG_FEAT_LL_PRIVACY (1)
#endif
#ifndef MYNEWT_VAL_BLE_LL_CFG_FEAT_SLAVE_INIT_FEAT_XCHG
diff --git a/src/libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c b/src/libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c
index 8ee3475a..b9902781 100644
--- a/src/libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c
+++ b/src/libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c
@@ -37,7 +37,7 @@ nimble_port_freertos_init(TaskFunction_t host_task_fn)
* provided by NimBLE and in case of FreeRTOS it does not need to be wrapped
* since it has compatible prototype.
*/
- xTaskCreate(nimble_port_ll_task_func, "ll", configMINIMAL_STACK_SIZE + 400,
+ xTaskCreate(nimble_port_ll_task_func, "ll", configMINIMAL_STACK_SIZE + 200,
NULL, configMAX_PRIORITIES - 1, &ll_task_h);
#endif
@@ -46,6 +46,6 @@ nimble_port_freertos_init(TaskFunction_t host_task_fn)
* have separate task for NimBLE host, but since something needs to handle
* default queue it is just easier to make separate task which does this.
*/
- xTaskCreate(host_task_fn, "ble", configMINIMAL_STACK_SIZE + 400,
+ xTaskCreate(host_task_fn, "ble", configMINIMAL_STACK_SIZE + 600,
NULL, tskIDLE_PRIORITY + 1, &host_task_h);
}
diff --git a/src/logging/NrfLogger.cpp b/src/logging/NrfLogger.cpp
index ab54afe9..f8d95a63 100644
--- a/src/logging/NrfLogger.cpp
+++ b/src/logging/NrfLogger.cpp
@@ -19,14 +19,13 @@ void NrfLogger::Init() {
void NrfLogger::Process(void*) {
NRF_LOG_INFO("Logger task started!");
-// Suppress endless loop diagnostic
+
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
while (true) {
NRF_LOG_FLUSH();
vTaskDelay(100); // Not good for power consumption, it will wake up every 100ms...
}
-// Clear diagnostic suppression
#pragma clang diagnostic pop
}
diff --git a/src/sdk_config.h b/src/sdk_config.h
index 38d47a7f..7634dca1 100644
--- a/src/sdk_config.h
+++ b/src/sdk_config.h
@@ -12580,4 +12580,4 @@
#endif
// <<< end of configuration section >>>
-#endif // SDK_CONFIG_H \ No newline at end of file
+#endif // SDK_CONFIG_H
diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h
index b7142704..516f6462 100644
--- a/src/systemtask/Messages.h
+++ b/src/systemtask/Messages.h
@@ -22,6 +22,7 @@ namespace Pinetime {
DisableSleeping,
OnNewDay,
OnChargingEvent,
+ OnPairing,
SetOffAlarm,
StopRinging,
MeasureBatteryTimerExpired,
diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp
index 1120b80d..28f81243 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -109,13 +109,15 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
batteryController,
spiNorFlash,
heartRateController,
- motionController) {
+ motionController,
+ fs) {
}
void SystemTask::Start() {
systemTasksMsgQueue = xQueueCreate(10, 1);
- if (pdPASS != xTaskCreate(SystemTask::Process, "MAIN", 350, this, 0, &taskHandle))
+ if (pdPASS != xTaskCreate(SystemTask::Process, "MAIN", 350, this, 0, &taskHandle)) {
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
+ }
}
void SystemTask::Process(void* instance) {
@@ -186,20 +188,22 @@ void SystemTask::Work() {
pinConfig.skip_gpio_setup = false;
pinConfig.hi_accuracy = false;
pinConfig.is_watcher = false;
- pinConfig.sense = (nrf_gpiote_polarity_t) NRF_GPIOTE_POLARITY_TOGGLE;
- pinConfig.pull = (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pulldown;
+ pinConfig.sense = static_cast<nrf_gpiote_polarity_t>(NRF_GPIOTE_POLARITY_TOGGLE);
+ pinConfig.pull = static_cast<nrf_gpio_pin_pull_t>(GPIO_PIN_CNF_PULL_Pulldown);
nrfx_gpiote_in_init(PinMap::Button, &pinConfig, nrfx_gpiote_evt_handler);
nrfx_gpiote_in_event_enable(PinMap::Button, true);
// Touchscreen
- nrf_gpio_cfg_sense_input(PinMap::Cst816sIrq, (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pullup, (nrf_gpio_pin_sense_t) GPIO_PIN_CNF_SENSE_Low);
+ nrf_gpio_cfg_sense_input(PinMap::Cst816sIrq,
+ static_cast<nrf_gpio_pin_pull_t>(GPIO_PIN_CNF_PULL_Pullup),
+ static_cast<nrf_gpio_pin_sense_t> GPIO_PIN_CNF_SENSE_Low);
pinConfig.skip_gpio_setup = true;
pinConfig.hi_accuracy = false;
pinConfig.is_watcher = false;
- pinConfig.sense = (nrf_gpiote_polarity_t) NRF_GPIOTE_POLARITY_HITOLO;
- pinConfig.pull = (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pullup;
+ pinConfig.sense = static_cast<nrf_gpiote_polarity_t>(NRF_GPIOTE_POLARITY_HITOLO);
+ pinConfig.pull = static_cast<nrf_gpio_pin_pull_t> GPIO_PIN_CNF_PULL_Pullup;
nrfx_gpiote_in_init(PinMap::Cst816sIrq, &pinConfig, nrfx_gpiote_evt_handler);
@@ -220,7 +224,6 @@ void SystemTask::Work() {
xTimerStart(dimTimer, 0);
xTimerStart(measureBatteryTimer, portMAX_DELAY);
-// Suppress endless loop diagnostic
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
while (true) {
@@ -258,8 +261,9 @@ void SystemTask::Work() {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
- if (!bleController.IsConnected())
+ if (!bleController.IsConnected()) {
nimbleController.RestartFastAdv();
+ }
isSleeping = false;
isWakingUp = false;
@@ -278,6 +282,9 @@ void SystemTask::Work() {
}
} break;
case Messages::GoToSleep:
+ if (doNotGoToSleep) {
+ break;
+ }
isGoingToSleep = true;
NRF_LOG_INFO("[systemtask] Going to sleep");
xTimerStop(idleTimer, 0);
@@ -323,8 +330,9 @@ void SystemTask::Work() {
break;
case Messages::BleFirmwareUpdateStarted:
doNotGoToSleep = true;
- if (isSleeping && !isWakingUp)
+ if (isSleeping && !isWakingUp) {
GoToRunning();
+ }
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
break;
case Messages::BleFirmwareUpdateFinished:
@@ -396,6 +404,13 @@ void SystemTask::Work() {
case Messages::BatteryPercentageUpdated:
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
break;
+ case Messages::OnPairing:
+ if (isSleeping && !isWakingUp) {
+ GoToRunning();
+ }
+ motorController.RunForDuration(35);
+ displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey);
+ break;
default:
break;
@@ -417,18 +432,21 @@ void SystemTask::Work() {
uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG);
dateTimeController.UpdateTime(systick_counter);
NoInit_BackUpTime = dateTimeController.CurrentDateTime();
- if (!nrf_gpio_pin_read(PinMap::Button))
+ if (!nrf_gpio_pin_read(PinMap::Button)) {
watchdog.Kick();
+ }
}
-// Clear diagnostic suppression
#pragma clang diagnostic pop
}
+
void SystemTask::UpdateMotion() {
- if (isGoingToSleep or isWakingUp)
+ if (isGoingToSleep or isWakingUp) {
return;
+ }
- if (isSleeping && !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist))
+ if (isSleeping && !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist)) {
return;
+ }
if (stepCounterMustBeReset) {
motionSensor.ResetStepCounter();
@@ -477,15 +495,17 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
}
void SystemTask::GoToRunning() {
- if (isGoingToSleep or (not isSleeping) or isWakingUp)
+ if (isGoingToSleep or (not isSleeping) or isWakingUp) {
return;
+ }
isWakingUp = true;
PushMessage(Messages::GoToRunning);
}
void SystemTask::OnTouchEvent() {
- if (isGoingToSleep)
+ if (isGoingToSleep) {
return;
+ }
if (!isSleeping) {
PushMessage(Messages::OnTouchEvent);
} else if (!isWakingUp) {
@@ -497,7 +517,7 @@ void SystemTask::OnTouchEvent() {
}
void SystemTask::PushMessage(System::Messages msg) {
- if (msg == Messages::GoToSleep) {
+ if (msg == Messages::GoToSleep && !doNotGoToSleep) {
isGoingToSleep = true;
}
@@ -515,8 +535,9 @@ void SystemTask::PushMessage(System::Messages msg) {
}
void SystemTask::OnDim() {
- if (doNotGoToSleep)
+ if (doNotGoToSleep) {
return;
+ }
NRF_LOG_INFO("Dim timeout -> Dim screen")
displayApp.PushMessage(Pinetime::Applications::Display::Messages::DimScreen);
xTimerStart(idleTimer, 0);
@@ -524,15 +545,17 @@ void SystemTask::OnDim() {
}
void SystemTask::OnIdle() {
- if (doNotGoToSleep)
+ if (doNotGoToSleep) {
return;
+ }
NRF_LOG_INFO("Idle timeout -> Going to sleep")
PushMessage(Messages::GoToSleep);
}
void SystemTask::ReloadIdleTimer() {
- if (isSleeping || isGoingToSleep)
+ if (isSleeping || isGoingToSleep) {
return;
+ }
if (isDimmed) {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
isDimmed = false;
diff --git a/src/touchhandler/TouchHandler.h b/src/touchhandler/TouchHandler.h
index f5442939..ed452b3a 100644
--- a/src/touchhandler/TouchHandler.h
+++ b/src/touchhandler/TouchHandler.h
@@ -1,8 +1,6 @@
#pragma once
#include "drivers/Cst816s.h"
#include "systemtask/SystemTask.h"
-#include <FreeRTOS.h>
-#include <task.h>
namespace Pinetime {
namespace Components {
@@ -11,9 +9,6 @@ namespace Pinetime {
namespace Drivers {
class Cst816S;
}
- namespace System {
- class SystemTask;
- }
namespace Controllers {
class TouchHandler {
public: