diff --git a/.gitignore b/.gitignore index 81e49ae0..d65bc6b4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ cmake-build-* cmake-*/ CMakeFiles **/CMakeCache.txt +CMakeLists.txt.user* cmake_install.cmake Makefile build diff --git a/doc/ble.md b/doc/ble.md index 2a4ecf4c..8918aadd 100644 --- a/doc/ble.md +++ b/doc/ble.md @@ -14,6 +14,8 @@ This page describes the BLE implementation and API built in this firmware. - [BLE Services](#ble-services) - [CTS](#cts) - [ANS](#ans) +- [BLE Clients](#ble-clients) + - [IAC](#iac) - [Getting Information](#getting-information) - [Firmware Version](#firmware-version) - [Battery Level](#battery-level) @@ -113,6 +115,16 @@ The following custom services are implemented in InfiniTime: ![ANS sequence diagram](./ble/ans_sequence.png "ANS sequence diagram") +## BLE clients + +### IAC + +InfiniTime implements Immediade Alert Service client, that can be used to send notifications to companion app. +This is useful for "Find my Phone" functionality. + +More documentation about this service can be found here. + +[Immediade Alert Service](https://www.bluetooth.com/specifications/specs/immediate-alert-service-1-0/) --- ### Getting Information diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8ece62..d61f458c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -385,6 +385,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Notifications.cpp displayapp/screens/Twos.cpp displayapp/screens/HeartRate.cpp + displayapp/screens/FindMyPhone.cpp displayapp/screens/FlashLight.cpp displayapp/screens/List.cpp displayapp/screens/CheckboxList.cpp @@ -459,6 +460,7 @@ list(APPEND SOURCE_FILES components/ble/BatteryInformationService.cpp components/ble/FSService.cpp components/ble/ImmediateAlertService.cpp + components/ble/ImmediateAlertClient.cpp components/ble/ServiceDiscovery.cpp components/ble/HeartRateService.cpp components/ble/MotionService.cpp @@ -527,6 +529,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/ble/BatteryInformationService.cpp components/ble/FSService.cpp components/ble/ImmediateAlertService.cpp + components/ble/ImmediateAlertClient.cpp components/ble/ServiceDiscovery.cpp components/ble/NavigationService.cpp components/ble/HeartRateService.cpp @@ -606,6 +609,7 @@ set(INCLUDE_FILES displayapp/Apps.h displayapp/screens/Notifications.h displayapp/screens/HeartRate.h + displayapp/screens/FindMyPhone.h displayapp/screens/Metronome.h displayapp/screens/Motion.h displayapp/screens/Timer.h @@ -645,6 +649,7 @@ set(INCLUDE_FILES components/ble/BatteryInformationService.h components/ble/FSService.h components/ble/ImmediateAlertService.h + components/ble/ImmediateAlertClient.h components/ble/ServiceDiscovery.h components/ble/BleClient.h components/ble/HeartRateService.h @@ -832,7 +837,7 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") # add_definitions(-DCLOCK_CONFIG_LOG_LEVEL=4) # add_definitions(-DRTC_CONFIG_LOG_ENABLED=1) # add_definitions(-DRTC_CONFIG_LOG_LEVEL=4) - + # Nimble Logging add_definitions(-DMYNEWT_VAL_NEWT_FEATURE_LOGCFG=1) # add_definitions(-DMYNEWT_VAL_LOG_LEVEL=0) diff --git a/src/FreeRTOSConfig.h b/src/FreeRTOSConfig.h index 67c33a34..4696e386 100644 --- a/src/FreeRTOSConfig.h +++ b/src/FreeRTOSConfig.h @@ -62,7 +62,7 @@ #define configTICK_RATE_HZ 1024 #define configMAX_PRIORITIES (3) #define configMINIMAL_STACK_SIZE (120) -#define configTOTAL_HEAP_SIZE (1024 * 40) +#define configTOTAL_HEAP_SIZE (1024 * 39) #define configMAX_TASK_NAME_LEN (4) #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 diff --git a/src/components/ble/ImmediateAlertClient.cpp b/src/components/ble/ImmediateAlertClient.cpp new file mode 100644 index 00000000..9190f460 --- /dev/null +++ b/src/components/ble/ImmediateAlertClient.cpp @@ -0,0 +1,113 @@ +#include "components/ble/ImmediateAlertClient.h" +#include +#include +#include "systemtask/SystemTask.h" + +using namespace Pinetime::Controllers; + +constexpr ble_uuid16_t ImmediateAlertClient::immediateAlertClientUuid; +constexpr ble_uuid16_t ImmediateAlertClient::alertLevelCharacteristicUuid; + +namespace { + int OnDiscoveryEventCallback(uint16_t conn_handle, const struct ble_gatt_error* error, const struct ble_gatt_svc* service, void* arg) { + auto client = static_cast(arg); + return client->OnDiscoveryEvent(conn_handle, error, service); + } + + int OnImmediateAlertCharacteristicDiscoveredCallback(uint16_t conn_handle, + const struct ble_gatt_error* error, + const struct ble_gatt_chr* chr, + void* arg) { + auto client = static_cast(arg); + return client->OnCharacteristicDiscoveryEvent(conn_handle, error, chr); + } +} + +ImmediateAlertClient::ImmediateAlertClient(Pinetime::System::SystemTask& systemTask) + : systemTask {systemTask}, + characteristicDefinition {{ + .uuid = &alertLevelCharacteristicUuid.u, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE_NO_RSP, + }, + {0}}, + serviceDefinition { + {/* Device Information Service */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &immediateAlertClientUuid.u, + .characteristics = characteristicDefinition}, + {0}, + } { +} + +void ImmediateAlertClient::Init() { +} + +bool ImmediateAlertClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error* error, const ble_gatt_svc* service) { + if (service == nullptr && error->status == BLE_HS_EDONE) { + if (isDiscovered) { + NRF_LOG_INFO("IAS found, starting characteristics discovery"); + + ble_gattc_disc_all_chrs(connectionHandle, iasStartHandle, iasEndHandle, OnImmediateAlertCharacteristicDiscoveredCallback, this); + } else { + NRF_LOG_INFO("IAS not found"); + onServiceDiscovered(connectionHandle); + } + return true; + } + + if (service != nullptr && ble_uuid_cmp(&immediateAlertClientUuid.u, &service->uuid.u) == 0) { + NRF_LOG_INFO("IAS discovered : 0x%x - 0x%x", service->start_handle, service->end_handle); + isDiscovered = true; + iasStartHandle = service->start_handle; + iasEndHandle = service->end_handle; + } + return false; +} + +int ImmediateAlertClient::OnCharacteristicDiscoveryEvent(uint16_t conn_handle, + const ble_gatt_error* error, + const ble_gatt_chr* characteristic) { + + if (error->status != 0 && error->status != BLE_HS_EDONE) { + NRF_LOG_INFO("IAS Characteristic discovery ERROR"); + onServiceDiscovered(conn_handle); + return 0; + } + + if (characteristic == nullptr && error->status == BLE_HS_EDONE) { + if (!isCharacteristicDiscovered) { + NRF_LOG_INFO("IAS Characteristic discovery unsuccessful"); + onServiceDiscovered(conn_handle); + } + + return 0; + } + + if (characteristic != nullptr && ble_uuid_cmp(&alertLevelCharacteristicUuid.u, &characteristic->uuid.u) == 0) { + NRF_LOG_INFO("AIS Characteristic discovered : 0x%x", characteristic->val_handle); + isCharacteristicDiscovered = true; + alertLevelHandle = characteristic->val_handle; + } + return 0; +} + +void ImmediateAlertClient::Discover(uint16_t connectionHandle, std::function onServiceDiscovered) { + NRF_LOG_INFO("[IAS] Starting discovery"); + this->onServiceDiscovered = onServiceDiscovered; + ble_gattc_disc_svc_by_uuid(connectionHandle, &immediateAlertClientUuid.u, OnDiscoveryEventCallback, this); +} + +bool ImmediateAlertClient::sendImmediateAlert(ImmediateAlertClient::Levels level) { + + auto* om = ble_hs_mbuf_from_flat(&level, 1); + + uint16_t connectionHandle = systemTask.nimble().connHandle(); + + if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) { + return false; + } + + ble_gattc_write_no_rsp(connectionHandle, alertLevelHandle, om); + return true; +} diff --git a/src/components/ble/ImmediateAlertClient.h b/src/components/ble/ImmediateAlertClient.h new file mode 100644 index 00000000..b9e8d56b --- /dev/null +++ b/src/components/ble/ImmediateAlertClient.h @@ -0,0 +1,61 @@ +#pragma once +#define min // workaround: nimble's min/max macros conflict with libstdc++ +#define max +#include +#undef max +#undef min +#include +#include "components/ble/BleClient.h" + +namespace Pinetime { + namespace System { + class SystemTask; + } + + namespace Controllers { + class NotificationManager; + + class ImmediateAlertClient : public BleClient { + public: + enum class Levels : uint8_t { NoAlert = 0, MildAlert = 1, HighAlert = 2 }; + + ImmediateAlertClient(Pinetime::System::SystemTask& systemTask); + void Init(); + + bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error* error, const ble_gatt_svc* service); + int OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error* error, const ble_gatt_chr* characteristic); + + bool sendImmediateAlert(Levels level); + + static constexpr const ble_uuid16_t* Uuid() { + return &ImmediateAlertClient::immediateAlertClientUuid; + } + + static constexpr const ble_uuid16_t* AlertLevelCharacteristicUuid() { + return &ImmediateAlertClient::alertLevelCharacteristicUuid; + } + + void Discover(uint16_t connectionHandle, std::function lambda) override; + + private: + Pinetime::System::SystemTask& systemTask; + + static constexpr uint16_t immediateAlertClientId {0x1802}; + static constexpr uint16_t alertLevelId {0x2A06}; + + static constexpr ble_uuid16_t immediateAlertClientUuid {.u {.type = BLE_UUID_TYPE_16}, .value = immediateAlertClientId}; + static constexpr ble_uuid16_t alertLevelCharacteristicUuid {.u {.type = BLE_UUID_TYPE_16}, .value = alertLevelId}; + + bool isDiscovered = false; + uint16_t iasStartHandle; + uint16_t iasEndHandle; + bool isCharacteristicDiscovered = false; + + struct ble_gatt_chr_def characteristicDefinition[3]; + struct ble_gatt_svc_def serviceDefinition[2]; + + uint16_t alertLevelHandle; + std::function onServiceDiscovered; + }; + } +} diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp index 2e7f8003..1960aa6a 100644 --- a/src/components/ble/NimbleController.cpp +++ b/src/components/ble/NimbleController.cpp @@ -46,10 +46,11 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask, weatherService {dateTimeController}, batteryInformationService {batteryController}, immediateAlertService {systemTask, notificationManager}, + iaClient {systemTask}, heartRateService {*this, heartRateController}, motionService {*this, motionController}, fsService {systemTask, fs}, - serviceDiscovery({¤tTimeClient, &alertNotificationClient}) { + serviceDiscovery({¤tTimeClient, &alertNotificationClient, &iaClient}) { } void nimble_on_reset(int reason) { @@ -95,6 +96,7 @@ void NimbleController::Init() { dfuService.Init(); batteryInformationService.Init(); immediateAlertService.Init(); + iaClient.Init(); heartRateService.Init(); motionService.Init(); fsService.Init(); diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h index 29a395ea..c3e95b84 100644 --- a/src/components/ble/NimbleController.h +++ b/src/components/ble/NimbleController.h @@ -17,6 +17,7 @@ #include "components/ble/FSService.h" #include "components/ble/HeartRateService.h" #include "components/ble/ImmediateAlertService.h" +#include "components/ble/ImmediateAlertClient.h" #include "components/ble/MusicService.h" #include "components/ble/NavigationService.h" #include "components/ble/ServiceDiscovery.h" @@ -71,6 +72,10 @@ namespace Pinetime { return weatherService; }; + Pinetime::Controllers::ImmediateAlertClient& immediateAlertClient() { + return iaClient; + } + uint16_t connHandle(); void NotifyBatteryLevel(uint8_t level); @@ -103,6 +108,7 @@ namespace Pinetime { NavigationService navService; BatteryInformationService batteryInformationService; ImmediateAlertService immediateAlertService; + ImmediateAlertClient iaClient; HeartRateService heartRateService; MotionService motionService; FSService fsService; diff --git a/src/components/ble/ServiceDiscovery.cpp b/src/components/ble/ServiceDiscovery.cpp index 03bcfeb4..554cf3e7 100644 --- a/src/components/ble/ServiceDiscovery.cpp +++ b/src/components/ble/ServiceDiscovery.cpp @@ -4,7 +4,7 @@ using namespace Pinetime::Controllers; -ServiceDiscovery::ServiceDiscovery(std::array&& clients) : clients {clients} { +ServiceDiscovery::ServiceDiscovery(std::array&& clients) : clients {clients} { } void ServiceDiscovery::StartDiscovery(uint16_t connectionHandle) { @@ -29,4 +29,4 @@ void ServiceDiscovery::DiscoverNextService(uint16_t connectionHandle) { this->OnServiceDiscovered(connectionHandle); }; (*clientIterator)->Discover(connectionHandle, discoverNextService); -} \ No newline at end of file +} diff --git a/src/components/ble/ServiceDiscovery.h b/src/components/ble/ServiceDiscovery.h index fc3b38c0..039862a4 100644 --- a/src/components/ble/ServiceDiscovery.h +++ b/src/components/ble/ServiceDiscovery.h @@ -9,13 +9,13 @@ namespace Pinetime { class ServiceDiscovery { public: - ServiceDiscovery(std::array&& bleClients); + ServiceDiscovery(std::array&& bleClients); void StartDiscovery(uint16_t connectionHandle); private: BleClient** clientIterator; - std::array clients; + std::array clients; void OnServiceDiscovered(uint16_t connectionHandle); void DiscoverNextService(uint16_t connectionHandle); }; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 394a3239..cfdbebff 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -1,6 +1,7 @@ #include "displayapp/DisplayApp.h" #include #include "displayapp/screens/HeartRate.h" +#include "displayapp/screens/FindMyPhone.h" #include "displayapp/screens/Motion.h" #include "displayapp/screens/Timer.h" #include "displayapp/screens/Alarm.h" diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267..27298a82 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -42,6 +42,7 @@ namespace Pinetime { SettingChimes, SettingShakeThreshold, SettingBluetooth, + FindMyPhone, Error }; diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index d7858760..334e1f98 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -13,6 +13,7 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::FindMyPhone") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion") set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware") diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 41c383c0..fd68531c 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf002" } ], "bpp": 1, diff --git a/src/displayapp/screens/FindMyPhone.cpp b/src/displayapp/screens/FindMyPhone.cpp new file mode 100644 index 00000000..943522a1 --- /dev/null +++ b/src/displayapp/screens/FindMyPhone.cpp @@ -0,0 +1,126 @@ +#include "displayapp/screens/FindMyPhone.h" +#include + +#include "displayapp/DisplayApp.h" +#include "displayapp/InfiniTimeTheme.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + static constexpr char defaultLabelText[] = "Find my phone"; + static constexpr char alertSentLabelText[] = "Alert sent"; + static constexpr char noConnectionLabelText[] = "No connection"; + static constexpr auto restoreLabelTimeoutTicks = pdMS_TO_TICKS(2 * 1000); + + void btnImmediateAlertEventHandler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->OnImmediateAlertEvent(obj, event); + } + + void RestoreLabelTaskCallback(lv_task_t* task) { + auto* screen = static_cast(task->user_data); + screen->RestoreLabelText(); + screen->StopRestoreLabelTask(); + } +} + +FindMyPhone::FindMyPhone(Pinetime::Controllers::ImmediateAlertClient& immediateAlertClient) : immediateAlertClient {immediateAlertClient} { + last_level = Pinetime::Controllers::ImmediateAlertClient::Levels::NoAlert; + + container = lv_cont_create(lv_scr_act(), nullptr); + + lv_obj_set_size(container, LV_HOR_RES, LV_VER_RES); + lv_obj_set_style_local_bg_color(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_pad_all(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); + lv_obj_set_style_local_pad_inner(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); + lv_obj_set_style_local_border_width(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); + + label_title = lv_label_create(lv_scr_act(), nullptr); + + lv_label_set_text_static(label_title, defaultLabelText); + lv_obj_set_style_local_text_color(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_obj_align(label_title, nullptr, LV_ALIGN_CENTER, 0, -40); + + bt_none = lv_btn_create(container, nullptr); + bt_none->user_data = this; + lv_obj_set_event_cb(bt_none, btnImmediateAlertEventHandler); + lv_obj_set_size(bt_none, 76, 76); + lv_obj_align(bt_none, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + label_none = lv_label_create(bt_none, nullptr); + lv_label_set_text_static(label_none, "None"); + lv_obj_set_style_local_bg_color(bt_none, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + + bt_mild = lv_btn_create(container, nullptr); + bt_mild->user_data = this; + lv_obj_set_event_cb(bt_mild, btnImmediateAlertEventHandler); + lv_obj_set_size(bt_mild, 76, 76); + lv_obj_align(bt_mild, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + label_mild = lv_label_create(bt_mild, nullptr); + lv_label_set_text_static(label_mild, "Mild"); + lv_obj_set_style_local_bg_color(bt_mild, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight); + + bt_high = lv_btn_create(container, nullptr); + bt_high->user_data = this; + lv_obj_set_event_cb(bt_high, btnImmediateAlertEventHandler); + lv_obj_set_size(bt_high, 76, 76); + lv_obj_align(bt_high, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); + label_high = lv_label_create(bt_high, nullptr); + lv_label_set_text_static(label_high, "High"); + lv_obj_set_style_local_bg_color(bt_high, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); +} + +FindMyPhone::~FindMyPhone() { + lv_obj_clean(lv_scr_act()); +} + +void FindMyPhone::OnImmediateAlertEvent(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + if (obj == bt_none) { + last_level = Pinetime::Controllers::ImmediateAlertClient::Levels::NoAlert; + } else if (obj == bt_mild) { + last_level = Pinetime::Controllers::ImmediateAlertClient::Levels::MildAlert; + } else if (obj == bt_high) { + last_level = Pinetime::Controllers::ImmediateAlertClient::Levels::HighAlert; + } + UpdateImmediateAlerts(); + } +} + +void FindMyPhone::UpdateImmediateAlerts() { + switch (last_level) { + case Pinetime::Controllers::ImmediateAlertClient::Levels::NoAlert: + lv_obj_set_style_local_text_color(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + break; + case Pinetime::Controllers::ImmediateAlertClient::Levels::MildAlert: + lv_obj_set_style_local_text_color(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight); + break; + case Pinetime::Controllers::ImmediateAlertClient::Levels::HighAlert: + lv_obj_set_style_local_text_color(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); + break; + } + if (immediateAlertClient.sendImmediateAlert(last_level)) { + lv_label_set_text_static(label_title, alertSentLabelText); + } else { + lv_label_set_text_static(label_title, noConnectionLabelText); + } + ScheduleRestoreLabelTask(); +} + +void FindMyPhone::ScheduleRestoreLabelTask() { + if (taskRestoreLabelText) { + return; + } + taskRestoreLabelText = lv_task_create(RestoreLabelTaskCallback, restoreLabelTimeoutTicks, LV_TASK_PRIO_MID, this); +} + +void FindMyPhone::StopRestoreLabelTask() { + if (taskRestoreLabelText) { + lv_task_del(taskRestoreLabelText); + taskRestoreLabelText = nullptr; + } +} + +void FindMyPhone::RestoreLabelText() { + lv_label_set_text_static(label_title, defaultLabelText); + lv_obj_set_style_local_text_color(label_title, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); +} diff --git a/src/displayapp/screens/FindMyPhone.h b/src/displayapp/screens/FindMyPhone.h new file mode 100644 index 00000000..a96bfc65 --- /dev/null +++ b/src/displayapp/screens/FindMyPhone.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include "displayapp/screens/Screen.h" +#include "Symbols.h" +#include "systemtask/SystemTask.h" +#include "components/ble/ImmediateAlertClient.h" + +#include +#include + +namespace Pinetime { + + namespace Controllers { + class ImmediateAlertClient; + } + + namespace Applications { + namespace Screens { + + class FindMyPhone : public Screen { + public: + explicit FindMyPhone(Pinetime::Controllers::ImmediateAlertClient& immediateAlertClient); + ~FindMyPhone() override; + + void OnImmediateAlertEvent(lv_obj_t* obj, lv_event_t event); + + void ScheduleRestoreLabelTask(); + void StopRestoreLabelTask(); + void RestoreLabelText(); + + private: + Pinetime::Controllers::ImmediateAlertClient& immediateAlertClient; + + void UpdateImmediateAlerts(); + + lv_obj_t* container; + lv_obj_t* label_title; + lv_obj_t* bt_none; + lv_obj_t* bt_high; + lv_obj_t* bt_mild; + lv_obj_t* label_none; + lv_obj_t* label_high; + lv_obj_t* label_mild; + lv_task_t* taskRestoreLabelText = nullptr; + + Pinetime::Controllers::ImmediateAlertClient::Levels last_level; + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::FindMyPhone; + static constexpr const char* icon = Screens::Symbols::magnifyingGlass; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::FindMyPhone(controllers.systemTask->nimble().immediateAlertClient()); + }; + }; + } +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index bd958b28..1d90df26 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -39,6 +39,7 @@ namespace Pinetime { static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; static constexpr const char* sleep = "\xEE\xBD\x84"; + static constexpr const char* magnifyingGlass = "\xEF\x80\x82"; // f002 // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85";