Compare commits

...

12 commits

Author SHA1 Message Date
Vyacheslav Chigrin f9dd8f1f07
Merge 299edca193 into a2356f2f4a 2024-09-30 18:21:47 +03:00
NeroBurner a2356f2f4a
MusicService: add missing includes for TickType_t and xTaskGetTickCount (#2130)
Some checks failed
CI / build-firmware (push) Successful in 10m55s
CI / build-simulator (push) Failing after 3s
CI / get-base-ref-size (push) Has been skipped
CI / Compare build size (push) Has been skipped
Add `FreeRTOS.h` include for the directly used data type `TickType_t` in the header
and the function `xTaskGetTickCount` from FreeRTOS's `task.h`
2024-09-29 21:10:32 +02:00
NeroBurner 3db4e012ce
Remove unused pointer to DisplayApp member variables (#2125)
Some checks failed
CI / build-firmware (push) Successful in 10m10s
CI / build-simulator (push) Failing after 3s
CI / get-base-ref-size (push) Has been skipped
CI / Compare build size (push) Has been skipped
In the screens that use `DisplayApp *app` and pass it to a child item,
or use the reference just in the constructor. Afterwards the `app`
member is not used. So remove it from the private member variables.

Completely remove `app` parameter from `SettingDisplay` constructor as
it is unused.
2024-09-29 19:39:14 +02:00
NeroBurner a0cd439efc
Alarm persist to flash (#1367)
Some checks failed
CI / build-firmware (push) Successful in 13m37s
CI / build-simulator (push) Failing after 4s
CI / get-base-ref-size (push) Has been skipped
CI / Compare build size (push) Has been skipped
* AlarmController: Add saving alarm time to file

Save the set alarm time to the SPI NOR flash, so it does not reset to
the default value when the watch resets, e.g. due to watchdog timeout
or reflashing of a new version of InfiniTime.

Just like the `Settings.h` `LoadSettingsFromFile()` the previous alarm
at boot (if available) and `SaveSettingsToFile()` the current alarm when
the `Alarm.h` screen is closed (only if the settings have changed).

The alarm-settings file is stored in `.system/alarm.dat`. The `.system`
folder is created if it doesn't yet exist.

Fixes: https://github.com/InfiniTimeOrg/InfiniTime/issues/1330

* alarmController: close .system dir after usage

Close the `lfs_dir` object for the `.system` dir after usage. Otherwise
on the second changed alarm the system will lockup because the `.system`
dir is already open and was never closed.

---------

Co-authored-by: Galdor Takacs <g@ldor.de>
2024-09-28 08:14:08 +02:00
Vyacheslav Chigrin 299edca193 Update documentation. 2024-07-02 23:09:03 +03:00
Vyacheslav Chigrin 8e29d82985 Decrease heap size to fix build. 2024-07-02 23:09:03 +03:00
Vyacheslav Chigrin 0e76bf1766 Restore FindMyPhone UI after task sent. 2024-07-02 23:09:03 +03:00
Vyacheslav Chigrin 15daf4cb84 Fix delivering notification level change in ImmediateAlertClient. 2024-07-02 23:09:03 +03:00
Jozef Mlich 4aef5a9f4d enable discovery 2024-07-02 23:09:03 +03:00
Jozef Mlich 145b45c501 Make it separate as Immediate Alert Client 2024-07-02 23:09:03 +03:00
Jozef Mlich 96b171524f update screen layout 2024-07-02 23:09:03 +03:00
Jozef Mlich 0e1a074720 Try to implement find my phone feature #343
Co-authored-by: Vyacheslav Chigrin <vyacheslav.chigrin@izba.dev>
2024-07-02 23:09:03 +03:00
32 changed files with 544 additions and 70 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ cmake-build-*
cmake-*/
CMakeFiles
**/CMakeCache.txt
CMakeLists.txt.user*
cmake_install.cmake
Makefile
build

View file

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

View file

@ -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
@ -528,6 +530,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
@ -608,6 +611,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
@ -647,6 +651,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
@ -835,7 +840,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)

View file

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

View file

@ -19,11 +19,13 @@
#include "systemtask/SystemTask.h"
#include "task.h"
#include <chrono>
#include <libraries/log/nrf_log.h>
using namespace Pinetime::Controllers;
using namespace std::chrono_literals;
AlarmController::AlarmController(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} {
AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs)
: dateTimeController {dateTimeController}, fs {fs} {
}
namespace {
@ -36,11 +38,28 @@ namespace {
void AlarmController::Init(System::SystemTask* systemTask) {
this->systemTask = systemTask;
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm);
LoadSettingsFromFile();
if (alarm.isEnabled) {
NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling");
ScheduleAlarm();
}
}
void AlarmController::SaveAlarm() {
// verify if it is necessary to save
if (alarmChanged) {
SaveSettingsToFile();
}
alarmChanged = false;
}
void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
hours = alarmHr;
minutes = alarmMin;
if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
return;
}
alarm.hours = alarmHr;
alarm.minutes = alarmMin;
alarmChanged = true;
}
void AlarmController::ScheduleAlarm() {
@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() {
tm* tmAlarmTime = std::localtime(&ttAlarmTime);
// If the time being set has already passed today,the alarm should be set for tomorrow
if (hours < dateTimeController.Hours() || (hours == dateTimeController.Hours() && minutes <= dateTimeController.Minutes())) {
if (alarm.hours < dateTimeController.Hours() ||
(alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) {
tmAlarmTime->tm_mday += 1;
// tm_wday doesn't update automatically
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
}
tmAlarmTime->tm_hour = hours;
tmAlarmTime->tm_min = minutes;
tmAlarmTime->tm_hour = alarm.hours;
tmAlarmTime->tm_min = alarm.minutes;
tmAlarmTime->tm_sec = 0;
// if alarm is in weekday-only mode, make sure it shifts to the next weekday
if (recurrence == RecurType::Weekdays) {
if (alarm.recurrence == RecurType::Weekdays) {
if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
tmAlarmTime->tm_mday += 1;
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() {
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
xTimerStart(alarmTimer, 0);
state = AlarmState::Set;
if (!alarm.isEnabled) {
alarm.isEnabled = true;
alarmChanged = true;
}
}
uint32_t AlarmController::SecondsToAlarm() const {
@ -88,20 +111,72 @@ uint32_t AlarmController::SecondsToAlarm() const {
void AlarmController::DisableAlarm() {
xTimerStop(alarmTimer, 0);
state = AlarmState::Not_Set;
isAlerting = false;
if (alarm.isEnabled) {
alarm.isEnabled = false;
alarmChanged = true;
}
}
void AlarmController::SetOffAlarmNow() {
state = AlarmState::Alerting;
isAlerting = true;
systemTask->PushMessage(System::Messages::SetOffAlarm);
}
void AlarmController::StopAlerting() {
// Alarm state is off unless this is a recurring alarm
if (recurrence == RecurType::None) {
state = AlarmState::Not_Set;
isAlerting = false;
// Disable alarm unless it is recurring
if (alarm.recurrence == RecurType::None) {
alarm.isEnabled = false;
alarmChanged = true;
} else {
// set next instance
ScheduleAlarm();
}
}
void AlarmController::SetRecurrence(RecurType recurrence) {
if (alarm.recurrence != recurrence) {
alarm.recurrence = recurrence;
alarmChanged = true;
}
}
void AlarmController::LoadSettingsFromFile() {
lfs_file_t alarmFile;
AlarmSettings alarmBuffer;
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) {
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file");
return;
}
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(&alarmBuffer), sizeof(alarmBuffer));
fs.FileClose(&alarmFile);
if (alarmBuffer.version != alarmFormatVersion) {
NRF_LOG_WARNING("[AlarmController] Loaded alarm settings has version %u instead of %u, discarding",
alarmBuffer.version,
alarmFormatVersion);
return;
}
alarm = alarmBuffer;
NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file");
}
void AlarmController::SaveSettingsToFile() const {
lfs_dir systemDir;
if (fs.DirOpen("/.system", &systemDir) != LFS_ERR_OK) {
fs.DirCreate("/.system");
}
fs.DirClose(&systemDir);
lfs_file_t alarmFile;
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) {
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file for saving");
return;
}
fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(&alarm), sizeof(alarm));
fs.FileClose(&alarmFile);
NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version);
}

View file

@ -30,47 +30,65 @@ namespace Pinetime {
namespace Controllers {
class AlarmController {
public:
AlarmController(Controllers::DateTime& dateTimeController);
AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);
void Init(System::SystemTask* systemTask);
void SaveAlarm();
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
void ScheduleAlarm();
void DisableAlarm();
void SetOffAlarmNow();
uint32_t SecondsToAlarm() const;
void StopAlerting();
enum class AlarmState { Not_Set, Set, Alerting };
enum class RecurType { None, Daily, Weekdays };
uint8_t Hours() const {
return hours;
return alarm.hours;
}
uint8_t Minutes() const {
return minutes;
return alarm.minutes;
}
AlarmState State() const {
return state;
bool IsAlerting() const {
return isAlerting;
}
bool IsEnabled() const {
return alarm.isEnabled;
}
RecurType Recurrence() const {
return recurrence;
return alarm.recurrence;
}
void SetRecurrence(RecurType recurType) {
recurrence = recurType;
}
void SetRecurrence(RecurType recurrence);
private:
// Versions 255 is reserved for now, so the version field can be made
// bigger, should it ever be needed.
static constexpr uint8_t alarmFormatVersion = 1;
struct AlarmSettings {
uint8_t version = alarmFormatVersion;
uint8_t hours = 7;
uint8_t minutes = 0;
RecurType recurrence = RecurType::None;
bool isEnabled = false;
};
bool isAlerting = false;
bool alarmChanged = false;
Controllers::DateTime& dateTimeController;
Controllers::FS& fs;
System::SystemTask* systemTask = nullptr;
TimerHandle_t alarmTimer;
uint8_t hours = 7;
uint8_t minutes = 0;
AlarmSettings alarm;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
AlarmState state = AlarmState::Not_Set;
RecurType recurrence = RecurType::None;
void LoadSettingsFromFile();
void SaveSettingsToFile() const;
};
}
}

View file

@ -0,0 +1,113 @@
#include "components/ble/ImmediateAlertClient.h"
#include <cstring>
#include <nrf_log.h>
#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<ImmediateAlertClient*>(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<ImmediateAlertClient*>(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<void(uint16_t)> 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;
}

View file

@ -0,0 +1,61 @@
#pragma once
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
#include <host/ble_gap.h>
#undef max
#undef min
#include <cstdint>
#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<void(uint16_t)> 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<void(uint16_t)> onServiceDiscovered;
};
}
}

View file

@ -18,6 +18,8 @@
#include "components/ble/MusicService.h"
#include "components/ble/NimbleController.h"
#include <cstring>
#include <FreeRTOS.h>
#include <task.h>
namespace {
// 0000yyxx-78fc-48fe-8e23-433b3a1942d0

View file

@ -25,6 +25,7 @@
#include <host/ble_uuid.h>
#undef max
#undef min
#include <FreeRTOS.h>
namespace Pinetime {
namespace Controllers {

View file

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

View file

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

View file

@ -4,7 +4,7 @@
using namespace Pinetime::Controllers;
ServiceDiscovery::ServiceDiscovery(std::array<BleClient*, 2>&& clients) : clients {clients} {
ServiceDiscovery::ServiceDiscovery(std::array<BleClient*, 3>&& 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);
}
}

View file

@ -9,13 +9,13 @@ namespace Pinetime {
class ServiceDiscovery {
public:
ServiceDiscovery(std::array<BleClient*, 2>&& bleClients);
ServiceDiscovery(std::array<BleClient*, 3>&& bleClients);
void StartDiscovery(uint16_t connectionHandle);
private:
BleClient** clientIterator;
std::array<BleClient*, 2> clients;
std::array<BleClient*, 3> clients;
void OnServiceDiscovered(uint16_t connectionHandle);
void DiscoverNextService(uint16_t connectionHandle);
};

View file

@ -1,6 +1,7 @@
#include "displayapp/DisplayApp.h"
#include <libraries/log/nrf_log.h>
#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"
@ -582,7 +583,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
break;
case Apps::SettingDisplay:
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController);
currentScreen = std::make_unique<Screens::SettingDisplay>(settingsController);
break;
case Apps::SettingSteps:
currentScreen = std::make_unique<Screens::SettingSteps>(settingsController);

View file

@ -42,6 +42,7 @@ namespace Pinetime {
SettingChimes,
SettingShakeThreshold,
SettingBluetooth,
FindMyPhone,
Error
};

View file

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

View file

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

View file

@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
UpdateAlarmTime();
if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) {
if (alarmController.IsAlerting()) {
SetAlerting();
} else {
SetSwitchState(LV_ANIM_OFF);
@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
}
Alarm::~Alarm() {
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
if (alarmController.IsAlerting()) {
StopAlerting();
}
lv_obj_clean(lv_scr_act());
alarmController.SaveAlarm();
}
void Alarm::DisableAlarm() {
if (alarmController.State() == AlarmController::AlarmState::Set) {
if (alarmController.IsEnabled()) {
alarmController.DisableAlarm();
lv_switch_off(enableSwitch, LV_ANIM_ON);
}
@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() {
HideInfo();
return true;
}
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
if (alarmController.IsAlerting()) {
StopAlerting();
return true;
}
@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() {
bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
// Don't allow closing the screen by swiping while the alarm is alerting
return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown;
return alarmController.IsAlerting() && event == TouchEvents::SwipeDown;
}
void Alarm::OnValueChanged() {
@ -222,15 +223,10 @@ void Alarm::StopAlerting() {
}
void Alarm::SetSwitchState(lv_anim_enable_t anim) {
switch (alarmController.State()) {
case AlarmController::AlarmState::Set:
lv_switch_on(enableSwitch, anim);
break;
case AlarmController::AlarmState::Not_Set:
lv_switch_off(enableSwitch, anim);
break;
default:
break;
if (alarmController.IsEnabled()) {
lv_switch_on(enableSwitch, anim);
} else {
lv_switch_off(enableSwitch, anim);
}
}
@ -247,7 +243,7 @@ void Alarm::ShowInfo() {
txtMessage = lv_label_create(btnMessage, nullptr);
lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY);
if (alarmController.State() == AlarmController::AlarmState::Set) {
if (alarmController.IsEnabled()) {
auto timeToAlarm = alarmController.SecondsToAlarm();
auto daysToAlarm = timeToAlarm / 86400;

View file

@ -0,0 +1,126 @@
#include "displayapp/screens/FindMyPhone.h"
#include <lvgl/lvgl.h>
#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<FindMyPhone*>(obj->user_data);
screen->OnImmediateAlertEvent(obj, event);
}
void RestoreLabelTaskCallback(lv_task_t* task) {
auto* screen = static_cast<FindMyPhone*>(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);
}

View file

@ -0,0 +1,62 @@
#pragma once
#include <cstdint>
#include <chrono>
#include "displayapp/screens/Screen.h"
#include "Symbols.h"
#include "systemtask/SystemTask.h"
#include "components/ble/ImmediateAlertClient.h"
#include <lvgl/src/lv_core/lv_style.h>
#include <lvgl/src/lv_core/lv_obj.h>
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<Apps::FindMyPhone> {
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());
};
};
}
}

View file

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

View file

@ -40,8 +40,7 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel,
const Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: app {app},
dateTimeController {dateTimeController},
: dateTimeController {dateTimeController},
batteryController {batteryController},
brightnessController {brightnessController},
bleController {bleController},

View file

@ -35,7 +35,6 @@ namespace Pinetime {
bool OnTouchEvent(TouchEvents event) override;
private:
DisplayApp* app;
Pinetime::Controllers::DateTime& dateTimeController;
const Pinetime::Controllers::Battery& batteryController;
Pinetime::Controllers::BrightnessController& brightnessController;

View file

@ -24,8 +24,7 @@ namespace {
constexpr std::array<uint16_t, 6> SettingDisplay::options;
SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: app {app}, settingsController {settingsController} {
SettingDisplay::SettingDisplay(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} {
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr);

View file

@ -14,14 +14,13 @@ namespace Pinetime {
class SettingDisplay : public Screen {
public:
SettingDisplay(DisplayApp* app, Pinetime::Controllers::Settings& settingsController);
SettingDisplay(Pinetime::Controllers::Settings& settingsController);
~SettingDisplay() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
void ToggleAlwaysOn();
private:
DisplayApp* app;
static constexpr std::array<uint16_t, 6> options = {5000, 7000, 10000, 15000, 20000, 30000};
Controllers::Settings& settingsController;

View file

@ -15,8 +15,7 @@ bool SettingSetDateTime::OnTouchEvent(Pinetime::Applications::TouchEvents event)
SettingSetDateTime::SettingSetDateTime(Pinetime::Applications::DisplayApp* app,
Pinetime::Controllers::DateTime& dateTimeController,
Pinetime::Controllers::Settings& settingsController)
: app {app},
dateTimeController {dateTimeController},
: dateTimeController {dateTimeController},
settingsController {settingsController},
screens {app,
0,

View file

@ -20,7 +20,6 @@ namespace Pinetime {
void Quit();
private:
DisplayApp* app;
Controllers::DateTime& dateTimeController;
Controllers::Settings& settingsController;

View file

@ -54,8 +54,7 @@ SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem)
: app {app},
watchfaceItems {std::move(watchfaceItems)},
: watchfaceItems {std::move(watchfaceItems)},
settingsController {settingsController},
filesystem {filesystem},
screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} {

View file

@ -34,7 +34,6 @@ namespace Pinetime {
bool OnTouchEvent(TouchEvents event) override;
private:
DisplayApp* app;
auto CreateScreenList() const;
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;

View file

@ -104,7 +104,7 @@ Pinetime::Controllers::DateTime dateTimeController {settingsController};
Pinetime::Drivers::Watchdog watchdog;
Pinetime::Controllers::NotificationManager notificationManager;
Pinetime::Controllers::MotionController motionController;
Pinetime::Controllers::AlarmController alarmController {dateTimeController};
Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs};
Pinetime::Controllers::TouchHandler touchHandler;
Pinetime::Controllers::ButtonHandler buttonHandler;
Pinetime::Controllers::BrightnessController brightnessController {};

View file

@ -216,7 +216,7 @@ void SystemTask::Work() {
GoToSleep();
break;
case Messages::OnNewTime:
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) {
if (alarmController.IsEnabled()) {
alarmController.ScheduleAlarm();
}
break;
@ -317,8 +317,7 @@ void SystemTask::Work() {
case Messages::OnNewHour:
using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
alarmController.State() != AlarmController::AlarmState::Alerting) {
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && !alarmController.IsAlerting()) {
GoToRunning();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
}
@ -326,8 +325,7 @@ void SystemTask::Work() {
case Messages::OnNewHalfHour:
using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
alarmController.State() != AlarmController::AlarmState::Alerting) {
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && !alarmController.IsAlerting()) {
GoToRunning();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
}