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

This commit is contained in:
Kieran Cawthray 2021-12-09 22:41:29 +01:00
commit 6cf4a933b6
46 changed files with 1963 additions and 243 deletions

3
.gitmodules vendored
View File

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

View File

@ -2,7 +2,7 @@
## Introduction ## Introduction
This page describes the BLE implementation and API built in this firmware. This page describes the BLE implementation and API built in this firmware.
**Note** : I'm a beginner in BLE related technologies and the information in this document reflects my current knowledge and understanding of the BLE stack. This information might be erroneous or incomplete. Feel free to submit a PR if you think you can improve it. **Note**: I'm a beginner in BLE related technologies and the information in this document reflects my current knowledge and understanding of the BLE stack. This information might be erroneous or incomplete. Feel free to submit a PR if you think you can improve it.
--- ---
@ -72,12 +72,16 @@ The following custom services are implemented in InfiniTime:
* [Navigation Service](NavigationService.md) : 00010000-78fc-48fe-8e23-433b3a1942d0 * [Navigation Service](NavigationService.md) : 00010000-78fc-48fe-8e23-433b3a1942d0
- Since InfiniTime 0.13 - Since InfiniTime 0.13
* Call characteristic (extension to the Alert Notification Service): 00020001-78fc-48fe-8e23-433b3a1942d0 * Call characteristic (extension to the Alert Notification Service): 00020001-78fc-48fe-8e23-433b3a1942d0
- 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
--- ---

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,33 +29,39 @@ 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},
heartRateService {systemTask, heartRateController}, heartRateService {systemTask, heartRateController},
motionService{systemTask, motionController}, motionService {systemTask, motionController},
serviceDiscovery({&currentTimeClient, &alertNotificationClient}) { serviceDiscovery({&currentTimeClient, &alertNotificationClient}) {
} }
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,
@ -219,26 +295,24 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
event->subscribe.cur_notify, event->subscribe.cur_notify,
event->subscribe.prev_indicate); event->subscribe.prev_indicate);
if(event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) { if (event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle); 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");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,8 +501,9 @@ 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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
@ -41,8 +43,8 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
brightnessController {brightnessController}, brightnessController {brightnessController},
bleController {bleController}, bleController {bleController},
watchdog {watchdog}, watchdog {watchdog},
motionController{motionController}, motionController {motionController},
touchPanel{touchPanel}, touchPanel {touchPanel},
screens {app, screens {app,
0, 0,
{[this]() -> std::unique_ptr<Screen> { {[this]() -> std::unique_ptr<Screen> {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,8 +6,8 @@
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);

View File

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

View File

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

View File

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

View File

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

1
src/libs/QCBOR Submodule

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

View File

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

View File

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

View File

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

View File

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

View File

@ -109,13 +109,15 @@ 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) {
@ -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;

View File

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