From f1285ee54b532ae6d26aba022518d66dba99ae76 Mon Sep 17 00:00:00 2001 From: Asger Gitz-Johansen Date: Sat, 10 May 2025 10:09:41 +0200 Subject: [PATCH 1/4] fix: gitignore autogenerated files and the external libs This is just personal preference and the commit can be dropped if desired. Especially the sdk-toolchain part - that's just where I put the nRF5 SDK and gcc toolchain. CMakeUserPresets.json contains the various presets that I used, hence it's gitignored. node_modules and so on is just for the lv_font_conv dependency. --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index d6f917cfc4..13b939a3f9 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,10 @@ src/arm-none-eabi node_modules package.json package-lock.json + +# Toolchain and External dependencies +sdk-toolchain/ + +# Developer files +CMakeUserPresets.json +compile_commands.json From 6342fd33b7f84d7edd4c904d2ef65cb3a5beb06a Mon Sep 17 00:00:00 2001 From: Asger Gitz-Johansen Date: Sun, 11 May 2025 10:51:02 +0200 Subject: [PATCH 2/4] feat: add sleeptracking controller This is a reimplementation / reimagining of Sleep_TK https://github.com/thiswillbeyourgithub/SleepTk_pinetime_sleep_tracker/blob/main/sleep_tk.py Much of this was inspired by the efforts done in https://github.com/InfiniTimeOrg/InfiniTime/pull/2174 but architecturally this solution is signifigantly different. This has been tested on a sealed version of the InfiniTime over a couple of nights. --- src/CMakeLists.txt | 5 + .../sleeptracking/SleepTrackingController.cpp | 303 ++++++++++++++++++ .../sleeptracking/SleepTrackingController.h | 110 +++++++ src/displayapp/Controllers.h | 2 + src/displayapp/DisplayApp.cpp | 17 + src/displayapp/DisplayApp.h | 2 + src/displayapp/DisplayAppRecovery.cpp | 1 + src/displayapp/DisplayAppRecovery.h | 2 + src/displayapp/Messages.h | 2 + src/displayapp/apps/Apps.h.in | 1 + src/displayapp/apps/CMakeLists.txt | 1 + src/displayapp/fonts/fonts.json | 2 +- src/displayapp/screens/Sleep.cpp | 237 ++++++++++++++ src/displayapp/screens/Sleep.h | 93 ++++++ src/displayapp/screens/Symbols.h | 1 + src/main.cpp | 8 + src/systemtask/Messages.h | 2 + src/systemtask/SystemTask.cpp | 11 + src/systemtask/SystemTask.h | 3 + 19 files changed, 802 insertions(+), 1 deletion(-) create mode 100644 src/components/sleeptracking/SleepTrackingController.cpp create mode 100644 src/components/sleeptracking/SleepTrackingController.h create mode 100644 src/displayapp/screens/Sleep.cpp create mode 100644 src/displayapp/screens/Sleep.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5cd2e656a4..e9f50aa227 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -396,6 +396,7 @@ list(APPEND SOURCE_FILES displayapp/screens/PassKey.cpp displayapp/screens/Error.cpp displayapp/screens/Alarm.cpp + displayapp/screens/Sleep.cpp displayapp/screens/Styles.cpp displayapp/screens/WeatherSymbols.cpp displayapp/Colors.cpp @@ -469,6 +470,7 @@ list(APPEND SOURCE_FILES components/settings/Settings.cpp components/timer/Timer.cpp components/alarm/AlarmController.cpp + components/sleeptracking/SleepTrackingController.cpp components/fs/FS.cpp drivers/Cst816s.cpp FreeRTOS/port.c @@ -538,6 +540,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/settings/Settings.cpp components/timer/Timer.cpp components/alarm/AlarmController.cpp + components/sleeptracking/SleepTrackingController.cpp drivers/Cst816s.cpp FreeRTOS/port.c FreeRTOS/port_cmsis_systick.c @@ -615,6 +618,7 @@ set(INCLUDE_FILES displayapp/screens/Timer.h displayapp/screens/Dice.h displayapp/screens/Alarm.h + displayapp/screens/Sleep.h displayapp/Colors.h displayapp/widgets/Counter.h displayapp/widgets/PageIndicator.h @@ -657,6 +661,7 @@ set(INCLUDE_FILES components/settings/Settings.h components/timer/Timer.h components/alarm/AlarmController.h + components/sleeptracking/SleepTrackingController.h drivers/Cst816s.h FreeRTOS/portmacro.h FreeRTOS/portmacro_cmsis.h diff --git a/src/components/sleeptracking/SleepTrackingController.cpp b/src/components/sleeptracking/SleepTrackingController.cpp new file mode 100644 index 0000000000..b7c6900ce0 --- /dev/null +++ b/src/components/sleeptracking/SleepTrackingController.cpp @@ -0,0 +1,303 @@ +/* Copyright (C) 2025 Asger Gitz-Johansen + + 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 . +*/ +#include "SleepTrackingController.h" + +#include +#include +#include +#include + +#define pdSEC_TO_TICKS(n) pdMS_TO_TICKS(n * 1000) +#define pdMIN_TO_TICKS(n) pdSEC_TO_TICKS(n * 60) +#ifndef MIN // Wrap in an ifndef becuase the simulator doesn't autoinclude it. + #define MIN(a, b) ((a < b) ? (a) : (b)) +#endif + +namespace { + void MotionTrackingTimerTrigger(TimerHandle_t xTimer) { + static_cast(pvTimerGetTimerID(xTimer))->OnMotionTrackingTimerTrigger(); + } + + void HeartRateTrackingTimerTrigger(TimerHandle_t xTimer) { + static_cast(pvTimerGetTimerID(xTimer))->OnHeartRateTrackingTimerTrigger(); + } + + void StoreDataTimerTrigger(TimerHandle_t xTimer) { + static_cast(pvTimerGetTimerID(xTimer))->OnStoreDataTimerTrigger(); + } + + void GentleWakeupTimerTrigger(TimerHandle_t xTimer) { + static_cast(pvTimerGetTimerID(xTimer))->OnGentleWakeupTimerTrigger(); + } + + void WakeAlarmTimerTrigger(TimerHandle_t xTimer) { + static_cast(pvTimerGetTimerID(xTimer))->OnWakeAlarmTrigger(); + } +} + +namespace Pinetime::Controllers { + SleepTrackingController::SleepTrackingController(FS& filesystem, + DateTime& datetimeController, + Pinetime::Drivers::Bma421& motionSensor, + HeartRateController& heartRateController, + MotorController& motorController) + : settings {}, + systemTask {nullptr}, + filesystem {filesystem}, + datetimeController {datetimeController}, + + // Sleep tracking related + motionSensor {motionSensor}, + heartRateController {heartRateController}, + motionTrackingTimer {}, + heartRateTrackingTimer {}, + storeDataTimer {}, + currentDataPoint {}, + previousValues {}, + hasPreviousValues {false}, + + // Wakeup related + wakeupAlarmTimer {}, + gentleWakeupTimer {}, + motorController {motorController}, + vibrationDurationMillis {wakeAlarmVibrationDurationStart}, + isAlerting {false} { + } + + void SleepTrackingController::Init(System::SystemTask* systemTask) { + this->systemTask = systemTask; + motionTrackingTimer = xTimerCreate("sampleMotion", pdSEC_TO_TICKS(2), pdFALSE, this, MotionTrackingTimerTrigger); + heartRateTrackingTimer = xTimerCreate("sampleHR", pdMIN_TO_TICKS(2), pdFALSE, this, HeartRateTrackingTimerTrigger); + storeDataTimer = xTimerCreate("storeData", pdMIN_TO_TICKS(3), pdFALSE, this, StoreDataTimerTrigger); + wakeupAlarmTimer = xTimerCreate("wakeupAlarm", 1, pdFALSE, this, WakeAlarmTimerTrigger); + gentleWakeupTimer = xTimerCreate("gentleWakeup", pdSEC_TO_TICKS(10), pdFALSE, this, GentleWakeupTimerTrigger); + LoadSettings(); + + if (settings.isTracking) { + StartTracking(); + NRF_LOG_INFO("[SleepTrackingController] Sleep tracking resumed"); + } + } + + void SleepTrackingController::StartTracking() { + // Reset tracking data. + previousValues = {}; + hasPreviousValues = false; + currentDataPoint = {}; + settings.isTracking = true; + SaveSettings(); + ScheduleWakeAlarm(); + + // Start tracking timers. + xTimerStart(motionTrackingTimer, 0); + xTimerStart(heartRateTrackingTimer, 0); + xTimerStart(storeDataTimer, 0); + NRF_LOG_INFO("[SleepTrackingController] Sleep tracking started"); + } + + void SleepTrackingController::StopTracking() { + // Stop tracking timers. + xTimerStop(motionTrackingTimer, 0); + xTimerStop(heartRateTrackingTimer, 0); + xTimerStop(storeDataTimer, 0); + settings.isTracking = false; + settings.currentSession = (settings.currentSession + 1) % 10; + SaveSettings(); + DismissWakeAlarm(); + ClearTrackingFile(); + NRF_LOG_INFO("[SleepTrackingController] Sleep tracking stopped"); + } + + void SleepTrackingController::OnMotionTrackingTimerTrigger() { + auto data = motionSensor.Process(); + if (hasPreviousValues) { + currentDataPoint.xDiffSum += ABS(previousValues.x) - ABS(data.x); + currentDataPoint.yDiffSum += ABS(previousValues.y) - ABS(data.y); + currentDataPoint.zDiffSum += ABS(previousValues.z) - ABS(data.z); + } + previousValues = data; + hasPreviousValues = true; + xTimerStart(motionTrackingTimer, 0); + } + + void SleepTrackingController::OnHeartRateTrackingTimerTrigger() { + currentDataPoint.heartRate = heartRateController.HeartRate(); + xTimerStart(heartRateTrackingTimer, 0); + } + + void SleepTrackingController::OnStoreDataTimerTrigger() { + systemTask->PushMessage(Pinetime::System::Messages::OnSleepTrackingDataPoint); + } + + void SleepTrackingController::SaveDatapoint() { + lfs_file_t sleepDataFile; + auto day = datetimeController.Day(); + auto month = datetimeController.Month(); + auto year = datetimeController.Year(); + auto hours = datetimeController.Hours(); + auto minutes = datetimeController.Minutes(); + auto seconds = datetimeController.Seconds(); + + char filename[32] {}; + snprintf(filename, 32, "logs/sleep/session-%d.csv", settings.currentSession); + + // Ensure that the subdirectory exists. + lfs_dir logdir {}; + if (filesystem.DirOpen("logs", &logdir) != LFS_ERR_OK) { + filesystem.DirCreate("logs"); + } + filesystem.DirClose(&logdir); + if (filesystem.DirOpen("logs/sleep", &logdir) != LFS_ERR_OK) { + filesystem.DirCreate("logs/sleep"); + } + filesystem.DirClose(&logdir); + + if (filesystem.FileOpen(&sleepDataFile, filename, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) < 0) { + NRF_LOG_WARNING("[SleepTrackingController] Failed to open '%s' file", filename); + xTimerStart(storeDataTimer, 0); + return; + } + + char buffer[64]; + auto len = snprintf(buffer, + sizeof(buffer), + "%04d-%02d-%02dT%02d:%02d:%02d,%d,%d,%d,%d\n", + year, + static_cast(month), + day, + hours, + minutes, + seconds, + currentDataPoint.heartRate, + currentDataPoint.xDiffSum, + currentDataPoint.yDiffSum, + currentDataPoint.zDiffSum); + filesystem.FileWrite(&sleepDataFile, reinterpret_cast(buffer), len); + filesystem.FileClose(&sleepDataFile); + xTimerStart(storeDataTimer, 0); + } + + void SleepTrackingController::DismissWakeAlarm() { + isAlerting = false; + xTimerStop(wakeupAlarmTimer, 0); + xTimerStop(gentleWakeupTimer, 0); + vibrationDurationMillis = wakeAlarmVibrationDurationStart; + if (!isAlerting) { + return; + } + motorController.StopRinging(); + } + + void SleepTrackingController::LoadSettings() { + lfs_file_t settingsFile; + Settings settingsBuffer; + if (filesystem.FileOpen(&settingsFile, settingsFileName, LFS_O_RDONLY) < 0) { + NRF_LOG_WARNING("[SleepTrackingController] Failed to open settings file"); + return; + } + + filesystem.FileRead(&settingsFile, reinterpret_cast(&settingsBuffer), sizeof(settingsBuffer)); + filesystem.FileClose(&settingsFile); + if (settingsBuffer.version != sleeptrackingSettingsFormatVersion) { + NRF_LOG_WARNING("[SleepTrackingController] Loaded settings has version %u instead of %u, discarding", + settingsBuffer.version, + sleeptrackingSettingsFormatVersion); + return; + } + + settings = settingsBuffer; + NRF_LOG_INFO("[SleepTrackingController] Loaded settings from file"); + } + + void SleepTrackingController::SaveSettings() { + lfs_file_t settingsFile; + if (filesystem.FileOpen(&settingsFile, settingsFileName, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) != LFS_ERR_OK) { + NRF_LOG_WARNING("[SleepTrackingController] Failed to open settings file"); + return; + } + filesystem.FileWrite(&settingsFile, reinterpret_cast(&settings), sizeof(settings)); + filesystem.FileClose(&settingsFile); + NRF_LOG_INFO("[SleepTrackingController] Saved settings to file"); + } + + void SleepTrackingController::OnWakeAlarmTrigger() { + isAlerting = true; + // Notify the system that the wake alarm is triggered, so we can show the alarm dismissal screen. + systemTask->PushMessage(System::Messages::SetOffWakeAlarm); + } + + void SleepTrackingController::OnGentleWakeupTimerTrigger() { + // TODO: Also set intensity when motorcontroller supports it. (start low, end medium) + motorController.RunForDuration(vibrationDurationMillis); + vibrationDurationMillis = MIN(vibrationDurationMillis + 100, static_cast(1000)); + xTimerStart(gentleWakeupTimer, 0); + } + + void SleepTrackingController::ScheduleWakeAlarm() { + // Determine the next time the alarm needs to go off and set the timer + xTimerStop(wakeupAlarmTimer, 0); + auto now = datetimeController.CurrentDateTime(); + auto ttAlarmTime = std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(now)); + auto* tmAlarmTime = std::localtime(&ttAlarmTime); + // If the time being set has already passed today,the alarm should be set for tomorrow + if (settings.alarm.hours < datetimeController.Hours() || + (settings.alarm.hours == datetimeController.Hours() && settings.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 = settings.alarm.hours; + tmAlarmTime->tm_min = settings.alarm.minutes; + tmAlarmTime->tm_sec = 0; + tmAlarmTime->tm_isdst = -1; // use system timezone setting to determine DST + // now can convert back to a time_point + auto alarmTime = std::chrono::system_clock::from_time_t(std::mktime(tmAlarmTime)); + auto secondsToAlarm = std::chrono::duration_cast(alarmTime - now).count(); + xTimerChangePeriod(wakeupAlarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0); + xTimerStart(wakeupAlarmTimer, 0); + NRF_LOG_INFO("[SleepTrackingController] New alarm scheduled in %d seconds", secondsToAlarm); + } + + void SleepTrackingController::ClearTrackingFile() { + char filename[32] {}; + snprintf(filename, 32, "logs/sleep/session-%d.csv", settings.currentSession); + lfs_info info; + filesystem.Stat(filename, &info); + if (info.size > 0) { + lfs_file_t file; + filesystem.FileOpen(&file, filename, LFS_O_CREAT | LFS_O_WRONLY | LFS_O_TRUNC); // NOTE: TRUNC = truncate + filesystem.FileClose(&file); + } + } + + SleepTrackingController::Settings SleepTrackingController::GetSettings() { + return settings; + } + + void SleepTrackingController::SetSettings(const Settings& newSettings) { + settings = newSettings; + } + + bool SleepTrackingController::IsAlerting() const { + return isAlerting; + } + + bool SleepTrackingController::IsTracking() const { + return settings.isTracking; + } +} diff --git a/src/components/sleeptracking/SleepTrackingController.h b/src/components/sleeptracking/SleepTrackingController.h new file mode 100644 index 0000000000..c6caad21a9 --- /dev/null +++ b/src/components/sleeptracking/SleepTrackingController.h @@ -0,0 +1,110 @@ +/* Copyright (C) 2025 Asger Gitz-Johansen + + 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 . +*/ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Pinetime::Controllers { + class SleepTrackingController { + private: + using timepoint = std::chrono::time_point; + static constexpr uint8_t sleeptrackingSettingsFormatVersion = 1; + static constexpr uint8_t maxSavedSessions = 5; + static constexpr const char* settingsFileName = "sleeptracksettings.dat"; + static constexpr uint8_t wakeAlarmVibrationDurationStart = 50; + static constexpr uint8_t maxSessionNameLength = 36; + + struct Settings { + uint8_t version = sleeptrackingSettingsFormatVersion; + uint8_t currentSession = 0; + bool isTracking = false; + + struct Alarm { + uint8_t hours = 7; + uint8_t minutes = 0; + } alarm; + }; + + struct SleepDataPoint { + int16_t xDiffSum = 0; + int16_t yDiffSum = 0; + int16_t zDiffSum = 0; + uint8_t heartRate = 0; + }; + + public: + SleepTrackingController(FS& filesystem, + DateTime& datetimeController, + Pinetime::Drivers::Bma421& motionSensor, + HeartRateController& heartRateController, + MotorController& motorController); + + void Init(System::SystemTask* systemTask); + + void StartTracking(); + void StopTracking(); + void OnWakeAlarmTrigger(); + + void OnMotionTrackingTimerTrigger(); + void OnHeartRateTrackingTimerTrigger(); + void OnStoreDataTimerTrigger(); + void OnGentleWakeupTimerTrigger(); + + void ScheduleWakeAlarm(); + bool IsAlerting() const; + bool IsTracking() const; + void SaveDatapoint(); + + Settings GetSettings(); + void SetSettings(const Settings& newSettings); + void SaveSettings(); + + private: + void DismissWakeAlarm(); + void LoadSettings(); + void ClearTrackingFile(); + + // Dependencies + Settings settings; + System::SystemTask* systemTask; + FS& filesystem; + DateTime& datetimeController; + + // Sleep tracking + Pinetime::Drivers::Bma421& motionSensor; + HeartRateController& heartRateController; + TimerHandle_t motionTrackingTimer; + TimerHandle_t heartRateTrackingTimer; + TimerHandle_t storeDataTimer; + SleepDataPoint currentDataPoint; + Drivers::Bma421::Values previousValues; + bool hasPreviousValues; + + // Wakeup + TimerHandle_t wakeupAlarmTimer; + TimerHandle_t gentleWakeupTimer; + MotorController& motorController; + uint16_t vibrationDurationMillis; + bool isAlerting; + }; +} diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h index 9992426c5d..6aeb6ffbf3 100644 --- a/src/displayapp/Controllers.h +++ b/src/displayapp/Controllers.h @@ -20,6 +20,7 @@ namespace Pinetime { class MotionController; class AlarmController; class BrightnessController; + class SleepTrackingController; class SimpleWeatherService; class FS; class Timer; @@ -43,6 +44,7 @@ namespace Pinetime { Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::AlarmController& alarmController; Pinetime::Controllers::BrightnessController& brightnessController; + Pinetime::Controllers::SleepTrackingController& sleeptrackingController; Pinetime::Controllers::SimpleWeatherService* weatherController; Pinetime::Controllers::FS& filesystem; Pinetime::Controllers::Timer& timer; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index bfd7dbed6d..c17113eec0 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -1,9 +1,11 @@ #include "displayapp/DisplayApp.h" +#include #include #include "displayapp/screens/HeartRate.h" #include "displayapp/screens/Motion.h" #include "displayapp/screens/Timer.h" #include "displayapp/screens/Alarm.h" +#include "displayapp/screens/Sleep.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/datetime/DateTimeController.h" @@ -83,6 +85,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, + Pinetime::Controllers::SleepTrackingController& sleeptrackingController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, Pinetime::Drivers::SpiNorFlash& spiNorFlash) @@ -99,6 +102,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, motionController {motionController}, alarmController {alarmController}, brightnessController {brightnessController}, + sleeptrackingController {sleeptrackingController}, touchHandler {touchHandler}, filesystem {filesystem}, spiNorFlash {spiNorFlash}, @@ -114,6 +118,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, motionController, alarmController, brightnessController, + sleeptrackingController, nullptr, filesystem, timer, @@ -384,6 +389,18 @@ void DisplayApp::Refresh() { LoadNewScreen(Apps::Alarm, DisplayApp::FullRefreshDirections::None); } break; + case Messages::WakeAlarmTriggered: + if (currentApp == Apps::Sleep) { + auto* sleep = static_cast(currentScreen.get()); + sleep->StartAlerting(); + } else { + LoadNewScreen(Apps::Sleep, DisplayApp::FullRefreshDirections::None); + } + break; + case Messages::SleepSaveDataPoint: + sleeptrackingController.SaveDatapoint(); + PushMessage(Messages::GoToSleep); + break; case Messages::ShowPairingKey: LoadNewScreen(Apps::PassKey, DisplayApp::FullRefreshDirections::Up); motorController.RunForDuration(35); diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index dabed99ea7..0e4b1317fc 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -65,6 +65,7 @@ namespace Pinetime { Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, + Pinetime::Controllers::SleepTrackingController& sleeptrackingController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, Pinetime::Drivers::SpiNorFlash& spiNorFlash); @@ -95,6 +96,7 @@ namespace Pinetime { Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::AlarmController& alarmController; Pinetime::Controllers::BrightnessController& brightnessController; + Pinetime::Controllers::SleepTrackingController& sleeptrackingController; Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::FS& filesystem; Pinetime::Drivers::SpiNorFlash& spiNorFlash; diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index bcb8db0e9d..03d0beca29 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -23,6 +23,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::MotionController& /*motionController*/, Pinetime::Controllers::AlarmController& /*alarmController*/, Pinetime::Controllers::BrightnessController& /*brightnessController*/, + Pinetime::Controllers::SleepTrackingController& /*sleeptrackingController*/, Pinetime::Controllers::TouchHandler& /*touchHandler*/, Pinetime::Controllers::FS& /*filesystem*/, Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/) diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index 162ff2575e..f5c49505b9 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -33,6 +33,7 @@ namespace Pinetime { class MotorController; class AlarmController; class BrightnessController; + class SleepTrackingController; class FS; class SimpleWeatherService; class MusicService; @@ -59,6 +60,7 @@ namespace Pinetime { Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, + Pinetime::Controllers::SleepTrackingController& sleeptrackingController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, Pinetime::Drivers::SpiNorFlash& spiNorFlash); diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index 1fcd72d278..d3206ca556 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -22,6 +22,8 @@ namespace Pinetime { NotifyDeviceActivity, ShowPairingKey, AlarmTriggered, + WakeAlarmTriggered, + SleepSaveDataPoint, Chime, BleRadioEnableToggle, }; diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index f6feeb7b6d..57b3f1dc0c 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -14,6 +14,7 @@ namespace Pinetime { NotificationsPreview, Notifications, Timer, + Sleep, Alarm, FlashLight, BatteryInfo, diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 93196ed6a0..477acdbf65 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -15,6 +15,7 @@ else () set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Calculator") set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather") + set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Sleep") #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") endif () diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index fea3160572..f742fbc8a5 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, 0xf1ec, 0xf55a" + "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, 0xf1ec, 0xf55a, 0xf236" } ], "bpp": 1, diff --git a/src/displayapp/screens/Sleep.cpp b/src/displayapp/screens/Sleep.cpp new file mode 100644 index 0000000000..ab4f4e8004 --- /dev/null +++ b/src/displayapp/screens/Sleep.cpp @@ -0,0 +1,237 @@ +/* Copyright (C) 2025 Asger Gitz-Johansen + + 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 . +*/ +#include "Sleep.h" + +#include +#include + +namespace { + void OnValueChangedHandler(void* userData) { + static_cast(userData)->OnAlarmValueChanged(); + } + + void OnButtonPress(lv_obj_t* obj, lv_event_t event) { + static_cast(obj->user_data)->OnButtonEvent(obj, event); + } + + void StopAlarmTaskCallback(lv_task_t* task) { + static_cast(task->user_data)->StopAlerting(); + } +} + +namespace Pinetime::Applications::Screens { + Sleep::Sleep(Controllers::SleepTrackingController& sleeptrackingController, + Controllers::Settings::ClockType clockType, + Controllers::MotorController& motorController, + System::SystemTask& systemTask, + DisplayApp& displayApp) + : sleeptrackingController {sleeptrackingController}, + motorController {motorController}, + systemTask {systemTask}, + displayApp {displayApp}, + wakeLock {systemTask}, + state {}, + hourCounter {0, 23, jetbrains_mono_76}, + minuteCounter {0, 59, jetbrains_mono_76}, + lblampm {nullptr}, + startButton {nullptr}, + startText {nullptr}, + stopButton {nullptr}, + stopText {nullptr}, + dismissButton {nullptr}, + dismissText {nullptr}, + colonLabel {nullptr}, + wakeUpLabel {nullptr}, + sleepingLabel {nullptr}, + taskStopAlarm {nullptr} { + auto settings = sleeptrackingController.GetSettings(); + hourCounter.Create(); + lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); + if (clockType == Controllers::Settings::ClockType::H12) { + hourCounter.EnableTwelveHourMode(); + + lblampm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(lblampm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + lv_label_set_text_static(lblampm, "AM"); + lv_label_set_align(lblampm, LV_LABEL_ALIGN_CENTER); + lv_obj_align(lblampm, lv_scr_act(), LV_ALIGN_CENTER, 0, 30); + } + hourCounter.SetValue(settings.alarm.hours); + hourCounter.SetValueChangedEventCallback(this, OnValueChangedHandler); + + minuteCounter.Create(); + lv_obj_align(minuteCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); + minuteCounter.SetValue(settings.alarm.minutes); + minuteCounter.SetValueChangedEventCallback(this, OnValueChangedHandler); + + lv_obj_t* colonLabel = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(colonLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); + lv_label_set_text_static(colonLabel, ":"); + lv_obj_align(colonLabel, lv_scr_act(), LV_ALIGN_CENTER, 0, -29); + + stopButton = lv_btn_create(lv_scr_act(), nullptr); + stopButton->user_data = this; + lv_obj_set_event_cb(stopButton, OnButtonPress); + lv_obj_set_size(stopButton, 240, 50); + lv_obj_align(stopButton, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + lv_obj_set_style_local_bg_color(stopButton, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); + stopText = lv_label_create(stopButton, nullptr); + lv_label_set_text_static(stopText, Symbols::pause); + lv_obj_set_hidden(stopButton, true); + + dismissButton = lv_btn_create(lv_scr_act(), nullptr); + dismissButton->user_data = this; + lv_obj_set_event_cb(dismissButton, OnButtonPress); + lv_obj_set_size(dismissButton, 240, 50); + lv_obj_align(dismissButton, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + lv_obj_set_style_local_bg_color(dismissButton, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); + dismissText = lv_label_create(dismissButton, nullptr); + lv_label_set_text_static(dismissText, Symbols::stop); + lv_obj_set_hidden(dismissButton, true); + + startButton = lv_btn_create(lv_scr_act(), nullptr); + startButton->user_data = this; + lv_obj_set_event_cb(startButton, OnButtonPress); + lv_obj_set_size(startButton, 240, 50); + lv_obj_align(startButton, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); + lv_obj_set_style_local_bg_color(startButton, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN); + startText = lv_label_create(startButton, nullptr); + lv_label_set_text_static(startText, Symbols::bed); + lv_obj_set_hidden(startButton, false); + + UpdateWakeAlarmTime(); + + if (sleeptrackingController.IsAlerting()) { + StartAlerting(); + } else if (sleeptrackingController.IsTracking()) { + SetState(State::Tracking); + } else { + SetState(State::NotTracking); + } + } + + Sleep::~Sleep() { + if (sleeptrackingController.IsAlerting()) { + StopAlerting(); + } + lv_obj_clean(lv_scr_act()); + sleeptrackingController.SaveSettings(); + } + + void Sleep::StartAlerting() { + SetState(State::Alerting); + sleeptrackingController.OnGentleWakeupTimerTrigger(); + } + + void Sleep::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { + if (event != LV_EVENT_CLICKED) { + return; + } + if (obj == stopButton) { + sleeptrackingController.StopTracking(); + SetState(State::NotTracking); + return; + } + if (obj == dismissButton) { + sleeptrackingController.StopTracking(); + SetState(State::NotTracking); + return; + } + if (obj == startButton) { + sleeptrackingController.StartTracking(); + SetState(State::Tracking); + return; + } + } + + void Sleep::StopAlerting() { + sleeptrackingController.StopTracking(); + SetState(State::NotTracking); + } + + void Sleep::OnAlarmValueChanged() { + UpdateWakeAlarmTime(); + } + + void Sleep::UpdateWakeAlarmTime() { + if (lblampm != nullptr) { + if (hourCounter.GetValue() >= 12) { + lv_label_set_text_static(lblampm, "PM"); + } else { + lv_label_set_text_static(lblampm, "AM"); + } + } + auto settings = sleeptrackingController.GetSettings(); + settings.alarm.hours = hourCounter.GetValue(); + settings.alarm.minutes = minuteCounter.GetValue(); + sleeptrackingController.SetSettings(settings); + } + + bool Sleep::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + // Don't allow closing the screen by swiping while the wake alarm is alerting + return sleeptrackingController.IsAlerting() && event == TouchEvents::SwipeDown; + } + + void Sleep::SetState(const State& uistate) { + state = uistate; + switch (uistate) { + case State::NotTracking: + wakeLock.Release(); + lv_obj_set_hidden(stopButton, true); + lv_obj_set_hidden(dismissButton, true); + hourCounter.ShowControls(); + minuteCounter.ShowControls(); + lv_obj_set_hidden(startButton, false); + if (taskStopAlarm != nullptr) { + lv_task_del(taskStopAlarm); + taskStopAlarm = nullptr; + } + break; + case State::Tracking: + wakeLock.Release(); + lv_obj_set_hidden(startButton, true); + lv_obj_set_hidden(dismissButton, true); + hourCounter.HideControls(); + minuteCounter.HideControls(); + lv_obj_set_hidden(stopButton, false); + break; + case State::Alerting: + wakeLock.Lock(); + lv_obj_set_hidden(startButton, true); + lv_obj_set_hidden(stopButton, true); + hourCounter.HideControls(); + minuteCounter.HideControls(); + lv_obj_set_hidden(dismissButton, false); + if (taskStopAlarm != nullptr) { + lv_task_del(taskStopAlarm); + taskStopAlarm = nullptr; + } + taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(10 * 60 * 1000), LV_TASK_PRIO_MID, this); + break; + } + } + + bool Sleep::OnButtonPushed() { + if (sleeptrackingController.IsAlerting()) { + sleeptrackingController.StopTracking(); + SetState(State::NotTracking); + return true; + } + return false; + } +} diff --git a/src/displayapp/screens/Sleep.h b/src/displayapp/screens/Sleep.h new file mode 100644 index 0000000000..a5eef332ce --- /dev/null +++ b/src/displayapp/screens/Sleep.h @@ -0,0 +1,93 @@ +/* Copyright (C) 2025 Asger Gitz-Johansen + + 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 . +*/ +#pragma once + +#include "Apps.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Pinetime::Applications::Screens { + class Sleep : public Screen { + public: + enum class State : uint8_t { NotTracking, Tracking, Alerting }; + + Sleep(Controllers::SleepTrackingController& sleeptrackingController, + Controllers::Settings::ClockType clockType, + Controllers::MotorController& motorController, + System::SystemTask& systemTask, + DisplayApp& displayApp); + ~Sleep() override; + + void StartAlerting(); + + void SetState(const State& uistate); + + void OnButtonEvent(lv_obj_t* obj, lv_event_t event); + void OnAlarmValueChanged(); + + bool OnTouchEvent(TouchEvents event) override; + bool OnButtonPushed() override; + void StopAlerting(); + + private: + void UpdateWakeAlarmTime(); + + // Dependencies + Controllers::SleepTrackingController& sleeptrackingController; + Controllers::MotorController& motorController; + System::SystemTask& systemTask; + DisplayApp& displayApp; + System::WakeLock wakeLock; + + // UI + State state; + Widgets::Counter hourCounter; + Widgets::Counter minuteCounter; + lv_obj_t* lblampm; + lv_obj_t *startButton, *startText; + lv_obj_t *stopButton, *stopText; + lv_obj_t *dismissButton, *dismissText; + lv_obj_t *colonLabel, *wakeUpLabel, *sleepingLabel; + lv_task_t* taskStopAlarm; + }; +} + +namespace Pinetime::Applications { + template <> + struct AppTraits { + static constexpr Apps app = Apps::Sleep; + static constexpr const char* icon = Screens::Symbols::bed; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Sleep(controllers.sleeptrackingController, + controllers.settingsController.GetClockType(), + controllers.motorController, + *controllers.systemTask, + *controllers.displayApp); + } + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + }; + }; +} diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 40699b3d65..0395d63af2 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -41,6 +41,7 @@ namespace Pinetime { static constexpr const char* sleep = "\xEE\xBD\x84"; static constexpr const char* calculator = "\xEF\x87\xAC"; static constexpr const char* backspace = "\xEF\x95\x9A"; + static constexpr const char* bed = "\xEF\x88\xB6"; // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/main.cpp b/src/main.cpp index 24f13caddd..7fa2a9c06c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,7 @@ #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" #include "components/brightness/BrightnessController.h" +#include "components/sleeptracking/SleepTrackingController.h" #include "components/motor/MotorController.h" #include "components/datetime/DateTimeController.h" #include "components/heartrate/HeartRateController.h" @@ -108,6 +109,11 @@ Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs}; Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; +Pinetime::Controllers::SleepTrackingController sleeptrackingController {fs, + dateTimeController, + motionSensor, + heartRateController, + motorController}; Pinetime::Applications::DisplayApp displayApp(lcd, touchPanel, @@ -122,6 +128,7 @@ Pinetime::Applications::DisplayApp displayApp(lcd, motionController, alarmController, brightnessController, + sleeptrackingController, touchHandler, fs, spiNorFlash); @@ -134,6 +141,7 @@ Pinetime::System::SystemTask systemTask(spi, bleController, dateTimeController, alarmController, + sleeptrackingController, watchdog, notificationManager, heartRateSensor, diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index fee94bb747..d3cddd2bfa 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -24,7 +24,9 @@ namespace Pinetime { OnNewHalfHour, OnChargingEvent, OnPairing, + OnSleepTrackingDataPoint, SetOffAlarm, + SetOffWakeAlarm, MeasureBatteryTimerExpired, BatteryPercentageUpdated, StartFileTransfer, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 8e0435e372..97ff079793 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -40,6 +40,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, Controllers::Ble& bleController, Controllers::DateTime& dateTimeController, Controllers::AlarmController& alarmController, + Controllers::SleepTrackingController& sleeptrackingController, Drivers::Watchdog& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, Pinetime::Drivers::Hrs3300& heartRateSensor, @@ -60,6 +61,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, bleController {bleController}, dateTimeController {dateTimeController}, alarmController {alarmController}, + sleeptrackingController {sleeptrackingController}, watchdog {watchdog}, notificationManager {notificationManager}, heartRateSensor {heartRateSensor}, @@ -128,6 +130,7 @@ void SystemTask::Work() { batteryController.Register(this); motionSensor.SoftReset(); alarmController.Init(this); + sleeptrackingController.Init(this); // Reset the TWI device because the motion sensor chip most probably crashed it... twiMaster.Sleep(); @@ -218,6 +221,14 @@ void SystemTask::Work() { GoToRunning(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); break; + case Messages::SetOffWakeAlarm: + GoToRunning(); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::WakeAlarmTriggered); + break; + case Messages::OnSleepTrackingDataPoint: + GoToRunning(); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::SleepSaveDataPoint); + break; case Messages::BleConnected: displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); isBleDiscoveryTimerRunning = true; diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 0060e36096..5f69204947 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -16,6 +16,7 @@ #include "components/ble/NimbleController.h" #include "components/ble/NotificationManager.h" #include "components/alarm/AlarmController.h" +#include "components/sleeptracking/SleepTrackingController.h" #include "components/fs/FS.h" #include "touchhandler/TouchHandler.h" #include "buttonhandler/ButtonHandler.h" @@ -61,6 +62,7 @@ namespace Pinetime { Controllers::Ble& bleController, Controllers::DateTime& dateTimeController, Controllers::AlarmController& alarmController, + Controllers::SleepTrackingController& sleeptrackingController, Drivers::Watchdog& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, Pinetime::Drivers::Hrs3300& heartRateSensor, @@ -101,6 +103,7 @@ namespace Pinetime { Pinetime::Controllers::Ble& bleController; Pinetime::Controllers::DateTime& dateTimeController; Pinetime::Controllers::AlarmController& alarmController; + Pinetime::Controllers::SleepTrackingController& sleeptrackingController; QueueHandle_t systemTasksMsgQueue; Pinetime::Drivers::Watchdog& watchdog; Pinetime::Controllers::NotificationManager& notificationManager; From 35e9dc80c23ed090fd6724d79d81bf1f2f5eda86 Mon Sep 17 00:00:00 2001 From: Asger Gitz-Johansen Date: Sat, 24 May 2025 22:16:26 +0200 Subject: [PATCH 3/4] docs: add sleeptracking to getting started list --- doc/gettingStarted/Applications.md | 8 ++++++++ .../AppsScreenshots/Sleeptracking.png | Bin 0 -> 2620 bytes 2 files changed, 8 insertions(+) create mode 100644 doc/gettingStarted/AppsScreenshots/Sleeptracking.png diff --git a/doc/gettingStarted/Applications.md b/doc/gettingStarted/Applications.md index 8ca2b25287..1f48f1fd7a 100644 --- a/doc/gettingStarted/Applications.md +++ b/doc/gettingStarted/Applications.md @@ -16,6 +16,7 @@ InfiniTime has 13 apps on the `main` branch at the time of writing. - Metronome - Maps - Weather +- Sleeptracking ### Stopwatch ![Stopwatch UI](/doc/gettingStarted/AppsScreenshots/stopwatch.png) @@ -97,3 +98,10 @@ InfiniTime has 13 apps on the `main` branch at the time of writing. ![Weather UI](/doc/gettingStarted/AppsScreenshots/Weather.png) - This app shows weather info. - Please note that this app is not very useful without a device connected. + +### SleepTracking +![Sleep UI](/doc/gettingStarted/AppsScreenshots/Sleeptracking.png) +- This app records your body movement and heartrate and wakes you up at the specified time. +- Sleeptracking files can be accessed through the files API in the `/logs/sleep` directory. + - Session files are comma-separated data files of the format: `%YYYY-%MM-%ddT%hh:%mm:%ss,heartrate,motionx,motiony,motionz`. + - To save space session files are rotated, only max 10 sessions are saved at a time. diff --git a/doc/gettingStarted/AppsScreenshots/Sleeptracking.png b/doc/gettingStarted/AppsScreenshots/Sleeptracking.png new file mode 100644 index 0000000000000000000000000000000000000000..4d65bd0aa56d3bb435a1bfadae3ba98d5d6a0d0f GIT binary patch literal 2620 zcmbVO2{aqn8qQEmlcJ9rt)-(GiclW4Ok#_*DAATuZBhGDd!s{JOgd9bgEsb&O{k`! zjV*?XSSC?CTT~-T!_!(48d_4d6fb@6otZOl=AC!WyXV~dpYOl-{QrN?fB*lx*=G=^ zWPvI`001EC;_PrvEZ=>eUrLDAdh@hy001cI;$Z8Qj1%%EV^z6I&)#<*)5?v31i4bY z^>vLvlKVm2M<@x3tf=BU*$Eke=SGEL5rCn4rqlSogzjsNeG-)>Q8j`4Mnct$t3vLK zf?zrNg!#Jg6)W`KbdkGySi(Bl6njmdm%PHrouvP;+`PB5I=!$HXrf{oYch6qNp()v zH->D3BYYT|*o&ka%*y5rjfWszZO>3L;`UZ~QK?HJG=lQJN1ncSW49qm6FhinaN_!4 z5@kw&7hUwYNUgbo48!IOT^eKv1KyT!m#~JrFTcXjJK8U$)-1MKetpM>i5KD#KTkvPsQo(m%u|_T#hhrfCnrv$Vw*2THc$5S^icJ>qMvy8C+&AMK761w5&nmp z{c7p+&cLgZ;4bF3ucnxRVS(1u-gf37Xx|~egM86@O(#NOpf?WX-1VZh;irN^6rx_b z$0q2c$EahqTjTKC1xU?j$Bz3zUDio#FIU`y-1X>OCQg&jMyGENw6sJO z(84oc&9gfF^Q7=`pQ?VEv$nhy(HOcIJN$AQf9IixpOEpY`%baLewsNZJbt&)`p(tkPJyy!<8Gj6CT9dMTN~m=-rCbwS}%) z+C^?Uh(?`dwsj60S+>B4$Xzx;=fSr(edBH8d0`jLWl`(w=e@8#+Sa;0P(m1fbDa%& z;CpDFO2T93vMN66p;b>kEV9^6rp%lAC$7?T^dDsu@xN`R8-DlkpeYe}u+Kfke0Q2}ZSg-eze| zhiFHJz3Sk@A+yE_CxZRKS8|6Y-s#21w1)RrsBDg)AOk$Oluq3WF$GUCy>QusnbaO4EmI#Ocr10sv*f+&`Otvqz}9QfsJ zhF<^i{sP0iy`}^s#KFtHLk|v=e)+I%MMcGIrEYb;nnm3UM);YtiWr(;@?W zshUHD_Qiv+AZgf0;hPGM8lH8HA8FLzl+K;`1j#-U@e@(7A%rHC_jt!35A3^9W@Anr zpyY3$2w4?6Jhg9v%MI(VpI-ZR^)UXj#1sF!aB`R#dr@nu|BK|jKj~+`R{d}NLjjxF zjvaj16spS>Ue^;|-`?05Ptq!V!ijonKyv=JL~=sh#r~bB-(~Ip2j>#}ItQ6E0yG&!6d!zR^ka>MzU8v_?4X1WE2p@ z3cn7nd|o>=(a#A(BQ`4;Qu$&8ba=4V_NFyID88ZMFuwGe9^@cqeQmvN_;O5kU^@Rn zfH5q=&53Neo4(M}*vRmtY$UdK#APsN!inkgQG%SIAmvkJ%ao>3L39RIOkZcu?=Ip+ zN7FaQW5o1nS>i-C*-;@eEkTFD($@2OZli3aFFK^D$g zBlP(JC2IxU(;70jfIk9FF_QadA1=WFp0TbPbSfI)_Ob|G{aI36JL}>6vollI*?-1_ zQdDxJ+4~1~B>%F5e1*&30u|LK+FS}NWky=U4du*Jc^WWUU`c~2*<*A_ z7R+wf7oQwe@jS6EzR1phx@pliIqC=QOopVmm_IszVwwC?9EfdQT{gR0LL)W-N9QD@|zne9xng-2zA?}fyyxz=$k5B_D-Vb+st z`i0;YWHmdh*L$tJ?b^cFBXGF(be|766|)2;Ep2^YdIOxUw295?otL?J$vn-!HSJw& z@;1H;J=OdqT>Q!MQt3N^$#C|Y1^UoBZ5F_Lih{c|M}o615W Date: Sat, 5 Jul 2025 08:32:59 +0200 Subject: [PATCH 4/4] feat: increase max saved sessions to 14 days --- src/components/sleeptracking/SleepTrackingController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/sleeptracking/SleepTrackingController.h b/src/components/sleeptracking/SleepTrackingController.h index c6caad21a9..3162155547 100644 --- a/src/components/sleeptracking/SleepTrackingController.h +++ b/src/components/sleeptracking/SleepTrackingController.h @@ -29,7 +29,7 @@ namespace Pinetime::Controllers { private: using timepoint = std::chrono::time_point; static constexpr uint8_t sleeptrackingSettingsFormatVersion = 1; - static constexpr uint8_t maxSavedSessions = 5; + static constexpr uint8_t maxSavedSessions = 14; // A night is ~5.5KiB (pessimistic estimate) = max(ish) 77KiB usage. static constexpr const char* settingsFileName = "sleeptracksettings.dat"; static constexpr uint8_t wakeAlarmVibrationDurationStart = 50; static constexpr uint8_t maxSessionNameLength = 36;