Merge remote-tracking branch 'upstream/develop' into pts-settings

main
Kieran Cawthray 2021-12-09 22:41:29 +07:00
commit 6cf4a933b6
46 changed files with 1963 additions and 243 deletions

3
.gitmodules vendored

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

@ -79,6 +79,10 @@ The following custom services are implemented in InfiniTime:
- Since InfiniTime 1.7: - Since InfiniTime 1.7:
* [Motion Service](MotionService.md): 00030000-78fc-48fe-8e23-433b3a1942d0 * [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
--- ---
## BLE services ## BLE services

@ -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_supp_cmd.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_hci_ev.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_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.c
libs/mynewt-nimble/porting/nimble/src/os_cputime_pwr2.c libs/mynewt-nimble/porting/nimble/src/os_cputime_pwr2.c
libs/mynewt-nimble/porting/nimble/src/os_mbuf.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 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 list(APPEND IMAGE_FILES
displayapp/icons/battery/os_battery_error.c displayapp/icons/battery/os_battery_error.c
displayapp/icons/battery/os_battery_100.c displayapp/icons/battery/os_battery_100.c
@ -407,6 +416,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/Label.cpp displayapp/screens/Label.cpp
displayapp/screens/FirmwareUpdate.cpp displayapp/screens/FirmwareUpdate.cpp
displayapp/screens/Music.cpp displayapp/screens/Music.cpp
displayapp/screens/Weather.cpp
displayapp/screens/Navigation.cpp displayapp/screens/Navigation.cpp
displayapp/screens/Metronome.cpp displayapp/screens/Metronome.cpp
displayapp/screens/Motion.cpp displayapp/screens/Motion.cpp
@ -421,8 +431,10 @@ list(APPEND SOURCE_FILES
displayapp/screens/BatteryInfo.cpp displayapp/screens/BatteryInfo.cpp
displayapp/screens/Steps.cpp displayapp/screens/Steps.cpp
displayapp/screens/Timer.cpp displayapp/screens/Timer.cpp
displayapp/screens/PassKey.cpp
displayapp/screens/Error.cpp displayapp/screens/Error.cpp
displayapp/screens/Alarm.cpp displayapp/screens/Alarm.cpp
displayapp/screens/Styles.cpp
displayapp/Colors.cpp displayapp/Colors.cpp
## Settings ## Settings
@ -470,6 +482,7 @@ list(APPEND SOURCE_FILES
components/ble/CurrentTimeService.cpp components/ble/CurrentTimeService.cpp
components/ble/AlertNotificationService.cpp components/ble/AlertNotificationService.cpp
components/ble/MusicService.cpp components/ble/MusicService.cpp
components/ble/weather/WeatherService.cpp
components/ble/NavigationService.cpp components/ble/NavigationService.cpp
displayapp/fonts/lv_font_navi_80.c displayapp/fonts/lv_font_navi_80.c
components/ble/BatteryInformationService.cpp components/ble/BatteryInformationService.cpp
@ -541,6 +554,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/ble/CurrentTimeService.cpp components/ble/CurrentTimeService.cpp
components/ble/AlertNotificationService.cpp components/ble/AlertNotificationService.cpp
components/ble/MusicService.cpp components/ble/MusicService.cpp
components/ble/weather/WeatherService.cpp
components/ble/BatteryInformationService.cpp components/ble/BatteryInformationService.cpp
components/ble/ImmediateAlertService.cpp components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp components/ble/ServiceDiscovery.cpp
@ -644,6 +658,9 @@ set(INCLUDE_FILES
components/datetime/DateTimeController.h components/datetime/DateTimeController.h
components/brightness/BrightnessController.h components/brightness/BrightnessController.h
components/motion/MotionController.h components/motion/MotionController.h
components/firmwarevalidator/FirmwareValidator.h
components/ble/BleController.h
components/ble/NotificationManager.h
components/ble/NimbleController.h components/ble/NimbleController.h
components/ble/DeviceInformationService.h components/ble/DeviceInformationService.h
components/ble/CurrentTimeClient.h components/ble/CurrentTimeClient.h
@ -656,6 +673,7 @@ set(INCLUDE_FILES
components/ble/BleClient.h components/ble/BleClient.h
components/ble/HeartRateService.h components/ble/HeartRateService.h
components/ble/MotionService.h components/ble/MotionService.h
components/ble/weather/WeatherService.h
components/settings/Settings.h components/settings/Settings.h
components/timer/TimerController.h components/timer/TimerController.h
components/alarm/AlarmController.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(-DCONFIG_GPIO_AS_PINRESET)
add_definitions(-DNIMBLE_CFG_CONTROLLER) add_definitions(-DNIMBLE_CFG_CONTROLLER)
add_definitions(-DOS_CPUTIME_FREQ) 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 . ../)
target_include_directories(nrf-sdk SYSTEM PUBLIC ${INCLUDES_FROM_LIBS}) target_include_directories(nrf-sdk SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(nrf-sdk PRIVATE target_compile_options(nrf-sdk PRIVATE
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wno-expansion-to-defined -Og -g3>
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Wno-expansion-to-defined -O3>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -fno-rtti> $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wno-expansion-to-defined -Og -fno-rtti>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os -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> $<$<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> $<$<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 # LITTLEFS_SRC
add_library(littlefs STATIC ${LITTLEFS_SRC}) add_library(littlefs STATIC ${LITTLEFS_SRC})
target_include_directories(littlefs SYSTEM PUBLIC . ../) 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") set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES}) add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME}) set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs) target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs QCBOR)
target_compile_options(${EXECUTABLE_NAME} PUBLIC target_compile_options(${EXECUTABLE_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Wextra -Wformat -Wno-missing-field-initializers -Wno-unused-parameter -Og -g3>
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -Os> $<$<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} -Og -g3 -fno-rtti> $<$<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} -Os -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> $<$<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(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld") set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES}) add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl 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}) set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>
@ -917,7 +954,7 @@ endif()
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery") set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES}) add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk 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}) set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY") target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
@ -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(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES}) add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk 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}) set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY") target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
@ -985,7 +1022,7 @@ endif()
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader") set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) 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}) 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}) set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3> $<$<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(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) 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}) 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}) set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3> $<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -Og -g3>

@ -8,7 +8,7 @@ void ButtonTimerCallback(TimerHandle_t xTimer) {
} }
void ButtonHandler::Init(Pinetime::System::SystemTask* systemTask) { 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) { ButtonActions ButtonHandler::HandleEvent(Events event) {

@ -18,7 +18,6 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include "app_timer.h"
#include "components/datetime/DateTimeController.h" #include "components/datetime/DateTimeController.h"
namespace Pinetime { namespace Pinetime {

@ -3,6 +3,7 @@
#include <hal/nrf_gpio.h> #include <hal/nrf_gpio.h>
#include <nrfx_saadc.h> #include <nrfx_saadc.h>
#include <algorithm> #include <algorithm>
#include <cmath>
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;

@ -53,8 +53,9 @@ int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle
// Ignore notifications with empty message // Ignore notifications with empty message
const auto packetLen = OS_MBUF_PKTLEN(ctxt->om); const auto packetLen = OS_MBUF_PKTLEN(ctxt->om);
if (packetLen <= headerSize) if (packetLen <= headerSize) {
return 0; return 0;
}
size_t bufferSize = std::min(packetLen + stringTerminatorSize, maxBufferSize); size_t bufferSize = std::min(packetLen + stringTerminatorSize, maxBufferSize);
auto messageSize = std::min(maxMessageSize, (bufferSize - headerSize)); auto messageSize = std::min(maxMessageSize, (bufferSize - headerSize));

@ -17,7 +17,7 @@ BatteryInformationService::BatteryInformationService(Controllers::Battery& batte
characteristicDefinition {{.uuid = &batteryLevelUuid.u, characteristicDefinition {{.uuid = &batteryLevelUuid.u,
.access_cb = BatteryInformationServiceCallback, .access_cb = BatteryInformationServiceCallback,
.arg = this, .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}, .val_handle = &batteryLevelHandle},
{0}}, {0}},
serviceDefinition { serviceDefinition {

@ -9,7 +9,7 @@ namespace Pinetime {
public: public:
using BleAddress = std::array<uint8_t, 6>; using BleAddress = std::array<uint8_t, 6>;
enum class FirmwareUpdateStates { Idle, Running, Validated, Error }; enum class FirmwareUpdateStates { Idle, Running, Validated, Error };
enum class AddressTypes { Public, Random }; enum class AddressTypes { Public, Random, RPA_Public, RPA_Random };
Ble() = default; Ble() = default;
bool IsConnected() const { bool IsConnected() const {
@ -48,6 +48,12 @@ namespace Pinetime {
void AddressType(AddressTypes t) { void AddressType(AddressTypes t) {
addressType = t; addressType = t;
} }
void SetPairingKey(uint32_t k) {
pairingKey = k;
}
uint32_t GetPairingKey() const {
return pairingKey;
}
private: private:
bool isConnected = false; bool isConnected = false;
@ -57,6 +63,7 @@ namespace Pinetime {
FirmwareUpdateStates firmwareUpdateState = FirmwareUpdateStates::Idle; FirmwareUpdateStates firmwareUpdateState = FirmwareUpdateStates::Idle;
BleAddress address; BleAddress address;
AddressTypes addressType; AddressTypes addressType;
uint32_t pairingKey = 0;
}; };
} }
} }

@ -1,4 +1,6 @@
#include "components/ble/NimbleController.h" #include "components/ble/NimbleController.h"
#include <cstring>
#include <hal/nrf_rtc.h> #include <hal/nrf_rtc.h>
#define min // workaround: nimble's min/max macros conflict with libstdc++ #define min // workaround: nimble's min/max macros conflict with libstdc++
#define max #define max
@ -6,13 +8,16 @@
#include <host/ble_hs.h> #include <host/ble_hs.h>
#include <host/ble_hs_id.h> #include <host/ble_hs_id.h>
#include <host/util/util.h> #include <host/util/util.h>
#undef max #include <controller/ble_ll.h>
#undef min #include <controller/ble_hw.h>
#include <services/gap/ble_svc_gap.h> #include <services/gap/ble_svc_gap.h>
#include <services/gatt/ble_svc_gatt.h> #include <services/gatt/ble_svc_gatt.h>
#undef max
#undef min
#include "components/ble/BleController.h" #include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h" #include "components/ble/NotificationManager.h"
#include "components/datetime/DateTimeController.h" #include "components/datetime/DateTimeController.h"
#include "components/fs/FS.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
@ -24,18 +29,22 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
Controllers::Battery& batteryController, Controllers::Battery& batteryController,
Pinetime::Drivers::SpiNorFlash& spiNorFlash, Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Controllers::HeartRateController& heartRateController, Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController) Controllers::MotionController& motionController,
Pinetime::Controllers::FS& fs)
: systemTask {systemTask}, : systemTask {systemTask},
bleController {bleController}, bleController {bleController},
dateTimeController {dateTimeController}, dateTimeController {dateTimeController},
notificationManager {notificationManager}, notificationManager {notificationManager},
spiNorFlash {spiNorFlash}, spiNorFlash {spiNorFlash},
fs {fs},
dfuService {systemTask, bleController, spiNorFlash}, dfuService {systemTask, bleController, spiNorFlash},
currentTimeClient {dateTimeController}, currentTimeClient {dateTimeController},
anService {systemTask, notificationManager}, anService {systemTask, notificationManager},
alertNotificationClient {systemTask, notificationManager}, alertNotificationClient {systemTask, notificationManager},
currentTimeService {dateTimeController}, currentTimeService {dateTimeController},
musicService {systemTask}, musicService {systemTask},
weatherService {systemTask, dateTimeController},
navService {systemTask}, navService {systemTask},
batteryInformationService {batteryController}, batteryInformationService {batteryController},
immediateAlertService {systemTask, notificationManager}, immediateAlertService {systemTask, notificationManager},
@ -45,12 +54,14 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
} }
void nimble_on_reset(int reason) { 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) { void nimble_on_sync(void) {
int rc; int rc;
NRF_LOG_INFO("Nimble is synced");
rc = ble_hs_util_ensure_addr(0); rc = ble_hs_util_ensure_addr(0);
ASSERT(rc == 0); ASSERT(rc == 0);
@ -69,6 +80,7 @@ void NimbleController::Init() {
nptr = this; nptr = this;
ble_hs_cfg.reset_cb = nimble_on_reset; ble_hs_cfg.reset_cb = nimble_on_reset;
ble_hs_cfg.sync_cb = nimble_on_sync; 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_gap_init();
ble_svc_gatt_init(); ble_svc_gatt_init();
@ -77,6 +89,7 @@ void NimbleController::Init() {
currentTimeClient.Init(); currentTimeClient.Init();
currentTimeService.Init(); currentTimeService.Init();
musicService.Init(); musicService.Init();
weatherService.Init();
navService.Init(); navService.Init();
anService.Init(); anService.Init();
dfuService.Init(); dfuService.Init();
@ -97,28 +110,38 @@ void NimbleController::Init() {
Pinetime::Controllers::Ble::BleAddress address; Pinetime::Controllers::Ble::BleAddress address;
rc = ble_hs_id_copy_addr(addrType, address.data(), nullptr); rc = ble_hs_id_copy_addr(addrType, address.data(), nullptr);
ASSERT(rc == 0); ASSERT(rc == 0);
bleController.AddressType((addrType == 0) ? Ble::AddressTypes::Public : Ble::AddressTypes::Random);
bleController.Address(std::move(address)); 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(); rc = ble_gatts_start();
ASSERT(rc == 0); ASSERT(rc == 0);
if (!ble_gap_adv_active() && !bleController.IsConnected()) RestoreBond();
if (!ble_gap_adv_active() && !bleController.IsConnected()) {
StartAdvertising(); StartAdvertising();
} }
}
void NimbleController::StartAdvertising() { void NimbleController::StartAdvertising() {
int rc;
/* set adv parameters */
struct ble_gap_adv_params adv_params; struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields; 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; struct ble_hs_adv_fields rsp_fields;
/* fill all fields and parameters with zeros */
memset(&adv_params, 0, sizeof(adv_params)); memset(&adv_params, 0, sizeof(adv_params));
memset(&fields, 0, sizeof(fields)); memset(&fields, 0, sizeof(fields));
memset(&rsp_fields, 0, sizeof(rsp_fields)); memset(&rsp_fields, 0, sizeof(rsp_fields));
@ -141,10 +164,11 @@ void NimbleController::StartAdvertising() {
fields.uuids128_is_complete = 1; fields.uuids128_is_complete = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; 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_len = strlen(deviceName);
rsp_fields.name_is_complete = 1; rsp_fields.name_is_complete = 1;
int rc;
rc = ble_gap_adv_set_fields(&fields); rc = ble_gap_adv_set_fields(&fields);
ASSERT(rc == 0); ASSERT(rc == 0);
@ -159,15 +183,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
switch (event->type) { switch (event->type) {
case BLE_GAP_EVENT_ADV_COMPLETE: case BLE_GAP_EVENT_ADV_COMPLETE:
NRF_LOG_INFO("Advertising event : 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(); StartAdvertising();
break; break;
case BLE_GAP_EVENT_CONNECT: case BLE_GAP_EVENT_CONNECT:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONNECT");
/* A new connection was established or a connection attempt failed. */ /* 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) { if (event->connect.status != 0) {
/* Connection failed; resume advertising. */ /* Connection failed; resume advertising. */
@ -186,10 +209,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
break; break;
case BLE_GAP_EVENT_DISCONNECT: 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); 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(); currentTimeClient.Reset();
alertNotificationClient.Reset(); alertNotificationClient.Reset();
connectionHandle = BLE_HS_CONN_HANDLE_NONE; connectionHandle = BLE_HS_CONN_HANDLE_NONE;
@ -199,18 +226,67 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
break; break;
case BLE_GAP_EVENT_CONN_UPDATE: case BLE_GAP_EVENT_CONN_UPDATE:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONN_UPDATE");
/* The central has updated the connection parameters. */ /* 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; break;
case BLE_GAP_EVENT_ENC_CHANGE: case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */ /* 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; break;
case BLE_GAP_EVENT_SUBSCRIBE: 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", "reason=%d prevn=%d curn=%d previ=%d curi=???\n",
event->subscribe.conn_handle, event->subscribe.conn_handle,
event->subscribe.attr_handle, event->subscribe.attr_handle,
@ -222,23 +298,21 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
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); heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.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); heartRateService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.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); heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle); motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
} }
break; break;
case BLE_GAP_EVENT_MTU: case BLE_GAP_EVENT_MTU:
NRF_LOG_INFO("mtu update event; conn_handle=%d cid=%d mtu=%d\n", NRF_LOG_INFO("MTU Update event; conn_handle=%d cid=%d mtu=%d", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
break; break;
case BLE_GAP_EVENT_REPEAT_PAIRING: { 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 /* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for * establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link. * 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: { case BLE_GAP_EVENT_NOTIFY_RX: {
/* Peer sent us a notification or indication. */ /* 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); size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
NRF_LOG_INFO("received %s; conn_handle=%d attr_handle=%d " 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); alertNotificationClient.OnNotification(event);
} break; } 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: default:
// NRF_LOG_INFO("Advertising event : %d", event->type); NRF_LOG_INFO("UNHANDLED GAP event : %d", event->type);
break; break;
} }
return 0; return 0;
@ -292,3 +375,82 @@ void NimbleController::NotifyBatteryLevel(uint8_t level) {
batteryInformationService.NotifyBatteryLevel(connectionHandle, 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");
}
}

@ -14,12 +14,14 @@
#include "components/ble/CurrentTimeService.h" #include "components/ble/CurrentTimeService.h"
#include "components/ble/DeviceInformationService.h" #include "components/ble/DeviceInformationService.h"
#include "components/ble/DfuService.h" #include "components/ble/DfuService.h"
#include "components/ble/HeartRateService.h"
#include "components/ble/ImmediateAlertService.h" #include "components/ble/ImmediateAlertService.h"
#include "components/ble/MusicService.h" #include "components/ble/MusicService.h"
#include "components/ble/NavigationService.h" #include "components/ble/NavigationService.h"
#include "components/ble/ServiceDiscovery.h" #include "components/ble/ServiceDiscovery.h"
#include "components/ble/HeartRateService.h"
#include "components/ble/MotionService.h" #include "components/ble/MotionService.h"
#include "components/ble/weather/WeatherService.h"
#include "components/fs/FS.h"
namespace Pinetime { namespace Pinetime {
namespace Drivers { namespace Drivers {
@ -45,7 +47,8 @@ namespace Pinetime {
Controllers::Battery& batteryController, Controllers::Battery& batteryController,
Pinetime::Drivers::SpiNorFlash& spiNorFlash, Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Controllers::HeartRateController& heartRateController, Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController); Controllers::MotionController& motionController,
Pinetime::Controllers::FS& fs);
void Init(); void Init();
void StartAdvertising(); void StartAdvertising();
int OnGAPEvent(ble_gap_event* event); int OnGAPEvent(ble_gap_event* event);
@ -70,6 +73,9 @@ namespace Pinetime {
Pinetime::Controllers::AlertNotificationService& alertService() { Pinetime::Controllers::AlertNotificationService& alertService() {
return anService; return anService;
}; };
Pinetime::Controllers::WeatherService& weather() {
return weatherService;
};
uint16_t connHandle(); uint16_t connHandle();
void NotifyBatteryLevel(uint8_t level); void NotifyBatteryLevel(uint8_t level);
@ -79,12 +85,16 @@ namespace Pinetime {
} }
private: private:
void PersistBond(struct ble_gap_conn_desc& desc);
void RestoreBond();
static constexpr const char* deviceName = "InfiniTime"; static constexpr const char* deviceName = "InfiniTime";
Pinetime::System::SystemTask& systemTask; Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::Ble& bleController; Pinetime::Controllers::Ble& bleController;
DateTime& dateTimeController; DateTime& dateTimeController;
Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Drivers::SpiNorFlash& spiNorFlash; Pinetime::Drivers::SpiNorFlash& spiNorFlash;
Pinetime::Controllers::FS& fs;
Pinetime::Controllers::DfuService dfuService; Pinetime::Controllers::DfuService dfuService;
DeviceInformationService deviceInformationService; DeviceInformationService deviceInformationService;
@ -93,21 +103,22 @@ namespace Pinetime {
AlertNotificationClient alertNotificationClient; AlertNotificationClient alertNotificationClient;
CurrentTimeService currentTimeService; CurrentTimeService currentTimeService;
MusicService musicService; MusicService musicService;
WeatherService weatherService;
NavigationService navService; NavigationService navService;
BatteryInformationService batteryInformationService; BatteryInformationService batteryInformationService;
ImmediateAlertService immediateAlertService; ImmediateAlertService immediateAlertService;
HeartRateService heartRateService; HeartRateService heartRateService;
MotionService motionService; MotionService motionService;
ServiceDiscovery serviceDiscovery;
uint8_t addrType; // 1 = Random, 0 = PUBLIC uint8_t addrType;
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE; uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
uint8_t fastAdvCount = 0; uint8_t fastAdvCount = 0;
uint8_t bondId[16] = {0};
ble_uuid128_t dfuServiceUuid { ble_uuid128_t dfuServiceUuid {
.u {.type = BLE_UUID_TYPE_128}, .u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}}; .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;

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

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

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

@ -25,6 +25,8 @@ namespace Pinetime {
Metronome, Metronome,
Motion, Motion,
Steps, Steps,
Weather,
PassKey,
QuickSettings, QuickSettings,
Settings, Settings,
SettingWatchFace, SettingWatchFace,

@ -29,6 +29,7 @@
#include "displayapp/screens/FlashLight.h" #include "displayapp/screens/FlashLight.h"
#include "displayapp/screens/BatteryInfo.h" #include "displayapp/screens/BatteryInfo.h"
#include "displayapp/screens/Steps.h" #include "displayapp/screens/Steps.h"
#include "displayapp/screens/PassKey.h"
#include "displayapp/screens/Error.h" #include "displayapp/screens/Error.h"
#include "drivers/Cst816s.h" #include "drivers/Cst816s.h"
@ -213,6 +214,9 @@ void DisplayApp::Refresh() {
} else { } else {
LoadApp(Apps::Alarm, DisplayApp::FullRefreshDirections::None); LoadApp(Apps::Alarm, DisplayApp::FullRefreshDirections::None);
} }
case Messages::ShowPairingKey:
LoadApp(Apps::PassKey, DisplayApp::FullRefreshDirections::Up);
break;
case Messages::TouchEvent: { case Messages::TouchEvent: {
if (state != States::Running) { if (state != States::Running) {
break; break;
@ -350,6 +354,11 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::None); ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::None);
break; break;
case Apps::PassKey:
currentScreen = std::make_unique<Screens::PassKey>(this, bleController.GetPairingKey());
ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::SwipeDown);
break;
case Apps::Notifications: case Apps::Notifications:
currentScreen = std::make_unique<Screens::Notifications>( currentScreen = std::make_unique<Screens::Notifications>(
this, notificationManager, systemTask->nimble().alertService(), motorController, Screens::Notifications::Modes::Normal); this, notificationManager, systemTask->nimble().alertService(), motorController, Screens::Notifications::Modes::Normal);
@ -492,9 +501,10 @@ void DisplayApp::SetFullRefresh(DisplayApp::FullRefreshDirections direction) {
} }
void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) { void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
if (systemTask != nullptr) if (systemTask != nullptr) {
systemTask->PushMessage(message); systemTask->PushMessage(message);
} }
}
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
this->systemTask = systemTask; this->systemTask = systemTask;

@ -19,6 +19,7 @@ namespace Pinetime {
UpdateTimeOut, UpdateTimeOut,
DimScreen, DimScreen,
RestoreBrightness, RestoreBrightness,
ShowPairingKey,
AlarmTriggered AlarmTriggered
}; };
} }

@ -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_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_color(&style_btn, LV_STATE_DEFAULT, theme.color_primary);
lv_style_set_border_width(&style_btn, LV_STATE_DEFAULT, 0); 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_DEFAULT, lv_color_hex(0xffffff));
lv_style_set_text_color(&style_btn, LV_STATE_CHECKED, lv_color_hex(0xffffff)); lv_style_set_text_color(&style_btn, LV_STATE_CHECKED, lv_color_hex(0xffffff));

@ -36,7 +36,7 @@ Alarm::Alarm(DisplayApp* app, Controllers::AlarmController& alarmController)
alarmHours = alarmController.Hours(); alarmHours = alarmController.Hours();
alarmMinutes = alarmController.Minutes(); 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); lv_obj_align(time, lv_scr_act(), LV_ALIGN_CENTER, 0, -25);
@ -223,7 +223,7 @@ void Alarm::ShowInfo() {
auto secToAlarm = timeToAlarm % 60; auto secToAlarm = timeToAlarm % 60;
lv_label_set_text_fmt( 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 { } else {
lv_label_set_text(txtMessage, "Alarm\nis not\nset."); lv_label_set_text(txtMessage, "Alarm\nis not\nset.");
} }

@ -18,7 +18,7 @@ FirmwareValidation::FirmwareValidation(Pinetime::Applications::DisplayApp* app,
: Screen {app}, validator {validator} { : Screen {app}, validator {validator} {
labelVersion = lv_label_create(lv_scr_act(), nullptr); labelVersion = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_fmt(labelVersion, lv_label_set_text_fmt(labelVersion,
"Version : %d.%d.%d\n" "Version : %lu.%lu.%lu\n"
"ShortRef : %s", "ShortRef : %s",
Version::Major(), Version::Major(),
Version::Minor(), Version::Minor(),

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

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

@ -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_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_style_local_radius(timebar, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0);
lv_obj_set_size(timebar, 200, 240); 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 // Display the time
timeDD1 = lv_label_create(lv_scr_act(), nullptr); 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_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_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); lv_obj_align(timeDD1, timebar, LV_ALIGN_IN_TOP_MID, 5, 5);
timeDD2 = lv_label_create(lv_scr_act(), nullptr); 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_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_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); lv_obj_align(timeDD2, timebar, LV_ALIGN_IN_BOTTOM_MID, 5, -5);
timeAMPM = lv_label_create(lv_scr_act(), nullptr); timeAMPM = lv_label_create(lv_scr_act(), nullptr);
@ -107,12 +107,12 @@ PineTimeStyle::PineTimeStyle(DisplayApp* app,
lv_obj_set_auto_realign(batteryIcon, true); lv_obj_set_auto_realign(batteryIcon, true);
bleIcon = lv_label_create(lv_scr_act(), nullptr); 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_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
lv_obj_align(bleIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 25); lv_label_set_text(bleIcon, "");
notificationIcon = lv_label_create(lv_scr_act(), nullptr); 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_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x000000));
lv_obj_align(notificationIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 50); lv_label_set_text(notificationIcon, "");
// Calendar icon // Calendar icon
calendarOuter = lv_obj_create(lv_scr_act(), nullptr); calendarOuter = lv_obj_create(lv_scr_act(), nullptr);
@ -342,6 +342,17 @@ void PineTimeStyle::SetBatteryIcon() {
lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent)); 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() { void PineTimeStyle::Refresh() {
isCharging = batteryController.IsCharging(); isCharging = batteryController.IsCharging();
if (isCharging.IsUpdated()) { if (isCharging.IsUpdated()) {
@ -361,13 +372,13 @@ void PineTimeStyle::Refresh() {
bleState = bleController.IsConnected(); bleState = bleController.IsConnected();
if (bleState.IsUpdated()) { if (bleState.IsUpdated()) {
lv_label_set_text(bleIcon, BleIcon::GetIcon(bleState.Get())); lv_label_set_text(bleIcon, BleIcon::GetIcon(bleState.Get()));
lv_obj_realign(bleIcon); AlignIcons();
} }
notificationState = notificatioManager.AreNewNotificationsAvailable(); notificationState = notificatioManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) { if (notificationState.IsUpdated()) {
lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(notificationState.Get())); lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
lv_obj_realign(notificationIcon); AlignIcons();
} }
currentDateTime = dateTimeController.CurrentDateTime(); currentDateTime = dateTimeController.CurrentDateTime();

@ -99,6 +99,7 @@ namespace Pinetime {
void SetBatteryIcon(); void SetBatteryIcon();
void CloseMenu(); void CloseMenu();
void AlignIcons();
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };

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

@ -0,0 +1,9 @@
#include <lvgl/lvgl.h>
namespace Pinetime {
namespace Applications {
namespace Screens {
void SetRadioButtonStyle(lv_obj_t* checkbox);
}
}
}

@ -1,3 +1,5 @@
#include <FreeRTOS.h>
#include <task.h>
#include "displayapp/screens/SystemInfo.h" #include "displayapp/screens/SystemInfo.h"
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h" #include "displayapp/DisplayApp.h"

@ -129,7 +129,7 @@ bool Twos::placeNewTile() {
return true; 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 ((grid[newRow][newCol].value == grid[oldRow][oldCol].value)) {
if ((newCol != oldCol) || (newRow != oldRow)) { if ((newCol != oldCol) || (newRow != oldRow)) {
if (!grid[newRow][newCol].merged) { 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; 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))) { if (((newCol >= 0) && (newCol != oldCol)) || ((newRow >= 0) && (newRow != oldRow))) {
grid[newRow][newCol].value = grid[oldRow][oldCol].value; grid[newRow][newCol].value = grid[oldRow][oldCol].value;
grid[oldRow][oldCol].value = 0; grid[oldRow][oldCol].value = 0;
@ -261,7 +261,7 @@ bool Twos::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
return false; return false;
} }
void Twos::updateGridDisplay(Tile grid[][4]) { void Twos::updateGridDisplay(TwosTile grid[][4]) {
for (int row = 0; row < 4; row++) { for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) { for (int col = 0; col < 4; col++) {
if (grid[row][col].value) { if (grid[row][col].value) {

@ -5,7 +5,7 @@
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
struct Tile { struct TwosTile {
bool merged = false; bool merged = false;
unsigned int value = 0; unsigned int value = 0;
}; };
@ -26,11 +26,11 @@ namespace Pinetime {
lv_obj_t* scoreText; lv_obj_t* scoreText;
lv_obj_t* gridDisplay; lv_obj_t* gridDisplay;
Tile grid[4][4]; TwosTile grid[4][4];
unsigned int score = 0; unsigned int score = 0;
void updateGridDisplay(Tile grid[][4]); void updateGridDisplay(TwosTile grid[][4]);
bool tryMerge(Tile grid[][4], int& newRow, int& newCol, int oldRow, int oldCol); bool tryMerge(TwosTile grid[][4], int& newRow, int& newCol, int oldRow, int oldCol);
bool tryMove(Tile 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(); bool placeNewTile();
}; };
} }

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

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

@ -2,6 +2,7 @@
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h" #include "displayapp/DisplayApp.h"
#include "displayapp/Messages.h" #include "displayapp/Messages.h"
#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.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) SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: Screen(app), settingsController {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_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
optionsTotal = 0; char buffer[12];
cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr); for (unsigned int i = 0; i < options.size(); i++) {
lv_checkbox_set_text_static(cbOption[optionsTotal], " 5 seconds"); cbOption[i] = lv_checkbox_create(container1, nullptr);
cbOption[optionsTotal]->user_data = this; sprintf(buffer, "%3d seconds", options[i] / 1000);
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler); lv_checkbox_set_text(cbOption[i], buffer);
if (settingsController.GetScreenTimeOut() == 5000) { cbOption[i]->user_data = this;
lv_checkbox_set_checked(cbOption[optionsTotal], true); 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++;
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);
}
optionsTotal++;
} }
SettingDisplay::~SettingDisplay() { SettingDisplay::~SettingDisplay() {
@ -82,25 +65,11 @@ SettingDisplay::~SettingDisplay() {
void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) { 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]) { if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true); lv_checkbox_set_checked(cbOption[i], true);
settingsController.SetScreenTimeOut(options[i]);
if (i == 0) {
settingsController.SetScreenTimeOut(5000);
};
if (i == 1) {
settingsController.SetScreenTimeOut(15000);
};
if (i == 2) {
settingsController.SetScreenTimeOut(20000);
};
if (i == 3) {
settingsController.SetScreenTimeOut(30000);
};
app->PushMessage(Applications::Display::Messages::UpdateTimeOut); app->PushMessage(Applications::Display::Messages::UpdateTimeOut);
} else { } else {
lv_checkbox_set_checked(cbOption[i], false); lv_checkbox_set_checked(cbOption[i], false);
} }

@ -1,7 +1,9 @@
#pragma once #pragma once
#include <array>
#include <cstdint> #include <cstdint>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "components/settings/Settings.h" #include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
@ -18,9 +20,10 @@ namespace Pinetime {
void UpdateSelected(lv_obj_t* object, lv_event_t event); void UpdateSelected(lv_obj_t* object, lv_event_t event);
private: private:
static constexpr std::array<uint16_t, 4> options = {5000, 15000, 20000, 30000};
Controllers::Settings& settingsController; Controllers::Settings& settingsController;
uint8_t optionsTotal; lv_obj_t* cbOption[options.size()];
lv_obj_t* cbOption[4];
}; };
} }
} }

@ -6,7 +6,7 @@
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
namespace { namespace {
static void event_handler(lv_obj_t * obj, lv_event_t event) { void event_handler(lv_obj_t* obj, lv_event_t event) {
SettingSteps* screen = static_cast<SettingSteps*>(obj->user_data); SettingSteps* screen = static_cast<SettingSteps*>(obj->user_data);
screen->UpdateSelected(obj, event); screen->UpdateSelected(obj, event);
} }
@ -30,33 +30,32 @@ SettingSteps::SettingSteps(
lv_obj_set_height(container1, LV_VER_RES - 60); lv_obj_set_height(container1, LV_VER_RES - 60);
lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT); 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_text_static(title,"Daily steps goal");
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); 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_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_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_text_static(icon, Symbols::shoe);
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
stepValue = lv_label_create(lv_scr_act(), nullptr);
stepValue = lv_label_create(lv_scr_act(), NULL);
lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); 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_text_fmt(stepValue, "%lu", settingsController.GetStepsGoal());
lv_label_set_align(stepValue, LV_LABEL_ALIGN_CENTER); lv_label_set_align(stepValue, LV_LABEL_ALIGN_CENTER);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, -10); 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; btnPlus->user_data = this;
lv_obj_set_size(btnPlus, 80, 50); lv_obj_set_size(btnPlus, 80, 50);
lv_obj_align(btnPlus, lv_scr_act(), LV_ALIGN_CENTER, 55, 80); 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_style_local_value_str(btnPlus, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, "+");
lv_obj_set_event_cb(btnPlus, event_handler); 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; btnMinus->user_data = this;
lv_obj_set_size(btnMinus, 80, 50); lv_obj_set_size(btnMinus, 80, 50);
lv_obj_set_event_cb(btnMinus, event_handler); lv_obj_set_event_cb(btnMinus, event_handler);

@ -1,6 +1,7 @@
#include "displayapp/screens/settings/SettingTimeFormat.h" #include "displayapp/screens/settings/SettingTimeFormat.h"
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h" #include "displayapp/DisplayApp.h"
#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.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) SettingTimeFormat::SettingTimeFormat(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: Screen(app), settingsController {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_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
optionsTotal = 0; for (unsigned int i = 0; i < options.size(); i++) {
cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr); cbOption[i] = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text_static(cbOption[optionsTotal], " 12-hour"); lv_checkbox_set_text(cbOption[i], options[i]);
cbOption[optionsTotal]->user_data = this; cbOption[i]->user_data = this;
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler); lv_obj_set_event_cb(cbOption[i], event_handler);
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { SetRadioButtonStyle(cbOption[i]);
lv_checkbox_set_checked(cbOption[optionsTotal], true);
} }
optionsTotal++; if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr); lv_checkbox_set_checked(cbOption[0], true);
lv_checkbox_set_text_static(cbOption[optionsTotal], " 24-hour"); } else if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
cbOption[optionsTotal]->user_data = this; lv_checkbox_set_checked(cbOption[1], true);
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler);
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
lv_checkbox_set_checked(cbOption[optionsTotal], true);
} }
optionsTotal++;
} }
SettingTimeFormat::~SettingTimeFormat() { SettingTimeFormat::~SettingTimeFormat() {
@ -66,7 +64,7 @@ SettingTimeFormat::~SettingTimeFormat() {
void SettingTimeFormat::UpdateSelected(lv_obj_t* object, lv_event_t event) { void SettingTimeFormat::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) { 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]) { if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true); lv_checkbox_set_checked(cbOption[i], true);

@ -1,7 +1,9 @@
#pragma once #pragma once
#include <array>
#include <cstdint> #include <cstdint>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "components/settings/Settings.h" #include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
@ -18,9 +20,9 @@ namespace Pinetime {
void UpdateSelected(lv_obj_t* object, lv_event_t event); void UpdateSelected(lv_obj_t* object, lv_event_t event);
private: private:
static constexpr std::array<const char*, 2> options = {" 12-hour", " 24-hour"};
Controllers::Settings& settingsController; Controllers::Settings& settingsController;
uint8_t optionsTotal; lv_obj_t* cbOption[options.size()];
lv_obj_t* cbOption[2];
}; };
} }
} }

@ -2,6 +2,7 @@
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "displayapp/DisplayApp.h" #include "displayapp/DisplayApp.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Symbols.h" #include "displayapp/screens/Symbols.h"
using namespace Pinetime::Applications::Screens; 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) SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: Screen(app), settingsController {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_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
optionsTotal = 0; for (unsigned int i = 0; i < options.size(); i++) {
cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr); cbOption[i] = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text_static(cbOption[optionsTotal], " Digital face"); lv_checkbox_set_text(cbOption[i], options[i]);
cbOption[optionsTotal]->user_data = this; cbOption[i]->user_data = this;
lv_obj_set_event_cb(cbOption[optionsTotal], event_handler); lv_obj_set_event_cb(cbOption[i], event_handler);
if (settingsController.GetClockFace() == 0) { SetRadioButtonStyle(cbOption[i]);
lv_checkbox_set_checked(cbOption[optionsTotal], true);
}
optionsTotal++; if (settingsController.GetClockFace() == i) {
cbOption[optionsTotal] = lv_checkbox_create(container1, nullptr); lv_checkbox_set_checked(cbOption[i], true);
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);
} }
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);
} }
optionsTotal++;
} }
SettingWatchFace::~SettingWatchFace() { SettingWatchFace::~SettingWatchFace() {
@ -77,7 +63,7 @@ SettingWatchFace::~SettingWatchFace() {
void SettingWatchFace::UpdateSelected(lv_obj_t* object, lv_event_t event) { void SettingWatchFace::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) { 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]) { if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true); lv_checkbox_set_checked(cbOption[i], true);
settingsController.SetClockFace(i); settingsController.SetClockFace(i);

@ -1,7 +1,9 @@
#pragma once #pragma once
#include <array>
#include <cstdint> #include <cstdint>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "components/settings/Settings.h" #include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
@ -18,9 +20,10 @@ namespace Pinetime {
void UpdateSelected(lv_obj_t* object, lv_event_t event); void UpdateSelected(lv_obj_t* object, lv_event_t event);
private: private:
static constexpr std::array<const char*, 3> options = {" Digital face", " Analog face", " PineTimeStyle"};
Controllers::Settings& settingsController; Controllers::Settings& settingsController;
uint8_t optionsTotal;
lv_obj_t* cbOption[2]; lv_obj_t* cbOption[options.size()];
}; };
} }
} }

@ -0,0 +1 @@
Subproject commit 9e2f70804393823cc6d16f9f1035ef7223faca04

@ -699,11 +699,11 @@
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_BONDING #ifndef MYNEWT_VAL_BLE_SM_BONDING
#define MYNEWT_VAL_BLE_SM_BONDING (0) #define MYNEWT_VAL_BLE_SM_BONDING (1)
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_IO_CAP #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 #endif
#ifndef MYNEWT_VAL_BLE_SM_KEYPRESS #ifndef MYNEWT_VAL_BLE_SM_KEYPRESS
@ -711,7 +711,7 @@
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_LEGACY #ifndef MYNEWT_VAL_BLE_SM_LEGACY
#define MYNEWT_VAL_BLE_SM_LEGACY (1) #define MYNEWT_VAL_BLE_SM_LEGACY (0)
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_MAX_PROCS #ifndef MYNEWT_VAL_BLE_SM_MAX_PROCS
@ -719,7 +719,7 @@
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_MITM #ifndef MYNEWT_VAL_BLE_SM_MITM
#define MYNEWT_VAL_BLE_SM_MITM (0) #define MYNEWT_VAL_BLE_SM_MITM (1)
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_OOB_DATA_FLAG #ifndef MYNEWT_VAL_BLE_SM_OOB_DATA_FLAG
@ -727,11 +727,11 @@
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_OUR_KEY_DIST #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 #endif
#ifndef MYNEWT_VAL_BLE_SM_SC #ifndef MYNEWT_VAL_BLE_SM_SC
#define MYNEWT_VAL_BLE_SM_SC (0) #define MYNEWT_VAL_BLE_SM_SC (1)
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_SC_DEBUG_KEYS #ifndef MYNEWT_VAL_BLE_SM_SC_DEBUG_KEYS
@ -739,7 +739,7 @@
#endif #endif
#ifndef MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST #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 #endif
#ifndef MYNEWT_VAL_BLE_STORE_MAX_BONDS #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) */ /* Overridden by @apache-mynewt-nimble/targets/riot (defined by @apache-mynewt-nimble/nimble/controller) */
#ifndef MYNEWT_VAL_BLE_LL_CFG_FEAT_LL_PRIVACY #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 #endif
#ifndef MYNEWT_VAL_BLE_LL_CFG_FEAT_SLAVE_INIT_FEAT_XCHG #ifndef MYNEWT_VAL_BLE_LL_CFG_FEAT_SLAVE_INIT_FEAT_XCHG

@ -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 * provided by NimBLE and in case of FreeRTOS it does not need to be wrapped
* since it has compatible prototype. * 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); NULL, configMAX_PRIORITIES - 1, &ll_task_h);
#endif #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 * 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. * 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); NULL, tskIDLE_PRIORITY + 1, &host_task_h);
} }

@ -19,14 +19,13 @@ void NrfLogger::Init() {
void NrfLogger::Process(void*) { void NrfLogger::Process(void*) {
NRF_LOG_INFO("Logger task started!"); NRF_LOG_INFO("Logger task started!");
// Suppress endless loop diagnostic
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop" #pragma ide diagnostic ignored "EndlessLoop"
while (true) { while (true) {
NRF_LOG_FLUSH(); NRF_LOG_FLUSH();
vTaskDelay(100); // Not good for power consumption, it will wake up every 100ms... vTaskDelay(100); // Not good for power consumption, it will wake up every 100ms...
} }
// Clear diagnostic suppression
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }

@ -22,6 +22,7 @@ namespace Pinetime {
DisableSleeping, DisableSleeping,
OnNewDay, OnNewDay,
OnChargingEvent, OnChargingEvent,
OnPairing,
SetOffAlarm, SetOffAlarm,
StopRinging, StopRinging,
MeasureBatteryTimerExpired, MeasureBatteryTimerExpired,

@ -109,14 +109,16 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
batteryController, batteryController,
spiNorFlash, spiNorFlash,
heartRateController, heartRateController,
motionController) { motionController,
fs) {
} }
void SystemTask::Start() { void SystemTask::Start() {
systemTasksMsgQueue = xQueueCreate(10, 1); 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); APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
} }
}
void SystemTask::Process(void* instance) { void SystemTask::Process(void* instance) {
auto* app = static_cast<SystemTask*>(instance); auto* app = static_cast<SystemTask*>(instance);
@ -186,20 +188,22 @@ void SystemTask::Work() {
pinConfig.skip_gpio_setup = false; pinConfig.skip_gpio_setup = false;
pinConfig.hi_accuracy = false; pinConfig.hi_accuracy = false;
pinConfig.is_watcher = false; pinConfig.is_watcher = false;
pinConfig.sense = (nrf_gpiote_polarity_t) NRF_GPIOTE_POLARITY_TOGGLE; pinConfig.sense = static_cast<nrf_gpiote_polarity_t>(NRF_GPIOTE_POLARITY_TOGGLE);
pinConfig.pull = (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pulldown; 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_init(PinMap::Button, &pinConfig, nrfx_gpiote_evt_handler);
nrfx_gpiote_in_event_enable(PinMap::Button, true); nrfx_gpiote_in_event_enable(PinMap::Button, true);
// Touchscreen // 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.skip_gpio_setup = true;
pinConfig.hi_accuracy = false; pinConfig.hi_accuracy = false;
pinConfig.is_watcher = false; pinConfig.is_watcher = false;
pinConfig.sense = (nrf_gpiote_polarity_t) NRF_GPIOTE_POLARITY_HITOLO; pinConfig.sense = static_cast<nrf_gpiote_polarity_t>(NRF_GPIOTE_POLARITY_HITOLO);
pinConfig.pull = (nrf_gpio_pin_pull_t) GPIO_PIN_CNF_PULL_Pullup; 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); nrfx_gpiote_in_init(PinMap::Cst816sIrq, &pinConfig, nrfx_gpiote_evt_handler);
@ -220,7 +224,6 @@ void SystemTask::Work() {
xTimerStart(dimTimer, 0); xTimerStart(dimTimer, 0);
xTimerStart(measureBatteryTimer, portMAX_DELAY); xTimerStart(measureBatteryTimer, portMAX_DELAY);
// Suppress endless loop diagnostic
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop" #pragma ide diagnostic ignored "EndlessLoop"
while (true) { while (true) {
@ -258,8 +261,9 @@ void SystemTask::Work() {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning); displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp); heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
if (!bleController.IsConnected()) if (!bleController.IsConnected()) {
nimbleController.RestartFastAdv(); nimbleController.RestartFastAdv();
}
isSleeping = false; isSleeping = false;
isWakingUp = false; isWakingUp = false;
@ -278,6 +282,9 @@ void SystemTask::Work() {
} }
} break; } break;
case Messages::GoToSleep: case Messages::GoToSleep:
if (doNotGoToSleep) {
break;
}
isGoingToSleep = true; isGoingToSleep = true;
NRF_LOG_INFO("[systemtask] Going to sleep"); NRF_LOG_INFO("[systemtask] Going to sleep");
xTimerStop(idleTimer, 0); xTimerStop(idleTimer, 0);
@ -323,8 +330,9 @@ void SystemTask::Work() {
break; break;
case Messages::BleFirmwareUpdateStarted: case Messages::BleFirmwareUpdateStarted:
doNotGoToSleep = true; doNotGoToSleep = true;
if (isSleeping && !isWakingUp) if (isSleeping && !isWakingUp) {
GoToRunning(); GoToRunning();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted); displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
break; break;
case Messages::BleFirmwareUpdateFinished: case Messages::BleFirmwareUpdateFinished:
@ -396,6 +404,13 @@ void SystemTask::Work() {
case Messages::BatteryPercentageUpdated: case Messages::BatteryPercentageUpdated:
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining()); nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
break; break;
case Messages::OnPairing:
if (isSleeping && !isWakingUp) {
GoToRunning();
}
motorController.RunForDuration(35);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey);
break;
default: default:
break; break;
@ -417,18 +432,21 @@ void SystemTask::Work() {
uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG); uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG);
dateTimeController.UpdateTime(systick_counter); dateTimeController.UpdateTime(systick_counter);
NoInit_BackUpTime = dateTimeController.CurrentDateTime(); NoInit_BackUpTime = dateTimeController.CurrentDateTime();
if (!nrf_gpio_pin_read(PinMap::Button)) if (!nrf_gpio_pin_read(PinMap::Button)) {
watchdog.Kick(); watchdog.Kick();
} }
// Clear diagnostic suppression }
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }
void SystemTask::UpdateMotion() {
if (isGoingToSleep or isWakingUp)
return;
if (isSleeping && !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist)) void SystemTask::UpdateMotion() {
if (isGoingToSleep or isWakingUp) {
return; return;
}
if (isSleeping && !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist)) {
return;
}
if (stepCounterMustBeReset) { if (stepCounterMustBeReset) {
motionSensor.ResetStepCounter(); motionSensor.ResetStepCounter();
@ -477,15 +495,17 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
} }
void SystemTask::GoToRunning() { void SystemTask::GoToRunning() {
if (isGoingToSleep or (not isSleeping) or isWakingUp) if (isGoingToSleep or (not isSleeping) or isWakingUp) {
return; return;
}
isWakingUp = true; isWakingUp = true;
PushMessage(Messages::GoToRunning); PushMessage(Messages::GoToRunning);
} }
void SystemTask::OnTouchEvent() { void SystemTask::OnTouchEvent() {
if (isGoingToSleep) if (isGoingToSleep) {
return; return;
}
if (!isSleeping) { if (!isSleeping) {
PushMessage(Messages::OnTouchEvent); PushMessage(Messages::OnTouchEvent);
} else if (!isWakingUp) { } else if (!isWakingUp) {
@ -497,7 +517,7 @@ void SystemTask::OnTouchEvent() {
} }
void SystemTask::PushMessage(System::Messages msg) { void SystemTask::PushMessage(System::Messages msg) {
if (msg == Messages::GoToSleep) { if (msg == Messages::GoToSleep && !doNotGoToSleep) {
isGoingToSleep = true; isGoingToSleep = true;
} }
@ -515,8 +535,9 @@ void SystemTask::PushMessage(System::Messages msg) {
} }
void SystemTask::OnDim() { void SystemTask::OnDim() {
if (doNotGoToSleep) if (doNotGoToSleep) {
return; return;
}
NRF_LOG_INFO("Dim timeout -> Dim screen") NRF_LOG_INFO("Dim timeout -> Dim screen")
displayApp.PushMessage(Pinetime::Applications::Display::Messages::DimScreen); displayApp.PushMessage(Pinetime::Applications::Display::Messages::DimScreen);
xTimerStart(idleTimer, 0); xTimerStart(idleTimer, 0);
@ -524,15 +545,17 @@ void SystemTask::OnDim() {
} }
void SystemTask::OnIdle() { void SystemTask::OnIdle() {
if (doNotGoToSleep) if (doNotGoToSleep) {
return; return;
}
NRF_LOG_INFO("Idle timeout -> Going to sleep") NRF_LOG_INFO("Idle timeout -> Going to sleep")
PushMessage(Messages::GoToSleep); PushMessage(Messages::GoToSleep);
} }
void SystemTask::ReloadIdleTimer() { void SystemTask::ReloadIdleTimer() {
if (isSleeping || isGoingToSleep) if (isSleeping || isGoingToSleep) {
return; return;
}
if (isDimmed) { if (isDimmed) {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
isDimmed = false; isDimmed = false;

@ -1,8 +1,6 @@
#pragma once #pragma once
#include "drivers/Cst816s.h" #include "drivers/Cst816s.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include <FreeRTOS.h>
#include <task.h>
namespace Pinetime { namespace Pinetime {
namespace Components { namespace Components {
@ -11,9 +9,6 @@ namespace Pinetime {
namespace Drivers { namespace Drivers {
class Cst816S; class Cst816S;
} }
namespace System {
class SystemTask;
}
namespace Controllers { namespace Controllers {
class TouchHandler { class TouchHandler {
public: public: