diff --git a/.devcontainer.json b/.devcontainer.json index 95a27dac74..813f92cc3a 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -2,7 +2,8 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp { "build": { - "dockerfile": "docker/Dockerfile" + "dockerfile": "docker/Dockerfile", + "context": "docker", }, "customizations": { "vscode": { diff --git a/.gitattributes b/.gitattributes index 2257cac4e5..7adb0e69a6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,9 @@ # Set the default behavior, in case people don't have core.autocrlf set. -* text=auto +# Prevent build errors on non lf systems (like Windows), we need files with lf as newlines. +* text=auto eol=lf # Explicitly declare text files you want to always be normalized and converted -# to native line endings on checkout. +# to lf line endings on checkout. *.c text *.cpp text *.h text diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 71c05fa299..ceb37d5bdd 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -29,7 +29,7 @@ jobs: run: tests/test-format.sh - name: Upload patches - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: Patches diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 247bd4af6b..41f305b9ae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,6 +22,8 @@ jobs: text_size: ${{ steps.output-sizes.outputs.text_size }} data_size: ${{ steps.output-sizes.outputs.data_size }} bss_size: ${{ steps.output-sizes.outputs.bss_size }} + firmware_artifact: ${{ steps.upload-firmware.outputs.artifact-id }} + resources_artifact: ${{ steps.upload-resources.outputs.artifact-id }} env: # InfiniTime sources are downloaded to the current directory. # Override SOURCES_DIR in build.sh @@ -52,22 +54,24 @@ jobs: ref_name: ${{ github.head_ref || github.ref_name }} run: echo "REF_NAME=${ref_name//\//-}" >> $GITHUB_ENV - name: Upload DFU artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: InfiniTime DFU ${{ env.REF_NAME }} path: ./build/output/pinetime-mcuboot-app-dfu/* - name: Upload MCUBoot image artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: InfiniTime MCUBoot image ${{ env.REF_NAME }} path: ./build/output/pinetime-mcuboot-app-image-*.bin - name: Upload standalone ELF artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 + id: upload-firmware with: name: InfiniTime image ${{ env.REF_NAME }} path: ./build/output/src/pinetime-app-*.out - name: Upload resources artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 + id: upload-resources with: name: InfiniTime resources ${{ env.REF_NAME }} path: ./build/output/infinitime-resources-*.zip @@ -108,7 +112,7 @@ jobs: cmake --build build_lv_sim - name: Upload simulator executable - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: infinisim-${{ env.REF_NAME }} path: build_lv_sim/infinisim @@ -205,10 +209,12 @@ jobs: | text | ${{ needs.build-firmware.outputs.text_size }}B | ${{ steps.output-sizes-diff.outputs.text_diff }}B | | data | ${{ needs.build-firmware.outputs.data_size }}B | ${{ steps.output-sizes-diff.outputs.data_diff }}B | | bss | ${{ needs.build-firmware.outputs.bss_size }}B | ${{ steps.output-sizes-diff.outputs.bss_diff }}B | + + [Run in InfiniEmu](https://infiniemu.pipe01.net/?firmware=artifact://${{ github.repository }}/${{ needs.build-firmware.outputs.firmware_artifact }}&resources=artifact://${{ github.repository }}/${{ needs.build-firmware.outputs.resources_artifact }}) EOF - name: Upload comment - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: comment path: comment diff --git a/CMakeLists.txt b/CMakeLists.txt index 3250982d6a..6e0fbe3d4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,10 @@ if(BUILD_RESOURCES) set(BUILD_RESOURCES true) endif() +if(ENABLE_CCACHE) + include(cmake/ccache.cmake) +endif() + set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device") set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY_TFK5 MOY_TIN5 MOY_TON5 MOY_UNK) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000000..3d94774fd6 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,72 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 22, + "patch": 0 + }, + "configurePresets": [ + { + "name": "base", + "description": "Default build using devcontainer tools", + "generator": "Ninja", + "hidden": true, + "cacheVariables": { + "ARM_NONE_EABI_TOOLCHAIN_PATH": { + "type": "PATH", + "value": "/opt/gcc-arm-none-eabi-10.3-2021.10" + }, + "NRF5_SDK_PATH": { + "type": "PATH", + "value": "/opt/nRF5_SDK_15.3.0_59ac345" + }, + "BUILD_DFU": { + "type": "BOOL", + "value": "ON" + }, + "BUILD_RESOURCES": { + "type": "BOOL", + "value": "ON" + }, + "TARGET_DEVICE": { + "type": "STRING", + "value": "PINETIME" + }, + "ENABLE_CCACHE": { + "type": "BOOL", + "value": true + } + } + }, + { + "name": "pinetime-debug", + "inherits": "base", + "displayName": "Pinetime Debug", + "description": "Debug build using Ninja", + "binaryDir": "build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "pinetime-release", + "inherits": "base", + "displayName": "Pinetime Release", + "description": "Release build using Ninja", + "binaryDir": "build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ], + "buildPresets": [ + { + "name": "pinetime-release", + "configurePreset": "pinetime-release" + }, + { + "name": "pinetime-debug", + "configurePreset": "pinetime-debug" + } + ] +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000000..78dc9178b0 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set euo -pipefail + +cmake --preset pinetime-release +cmake --build --preset pinetime-release diff --git a/cmake/ccache.cmake b/cmake/ccache.cmake new file mode 100644 index 0000000000..e9dccaa9ed --- /dev/null +++ b/cmake/ccache.cmake @@ -0,0 +1,14 @@ +find_program(CCACHE_EXECUTABLE ccache) + +if (CCACHE_EXECUTABLE) + message(STATUS "Activating ccache compiler cache.") + set(ccacheEnv + CCACHE_SLOPPINESS=pch_defines + ) + foreach (lang IN ITEMS C CXX) + set(CMAKE_${lang}_COMPILER_LAUNCHER + ${CMAKE_COMMAND} -E env ${ccacheEnv} ${CCACHE_EXECUTABLE} + ) + endforeach () + message(WARNING "Ccache could not be activated because ccache executable was not found.") +endif () diff --git a/docker/Dockerfile b/docker/Dockerfile index e6d92aae96..95df5a64b8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -12,14 +12,17 @@ RUN apt-get update -qq \ # x86_64 / generic packages bash \ build-essential \ + ccache \ cmake \ git \ make \ + ninja-build \ nodejs \ python3 \ python3-pip \ python3-pil \ python-is-python3 \ + sudo \ tar \ unzip \ wget \ @@ -62,8 +65,8 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;" # McuBoot RUN bash -c "source /opt/build.sh; GetMcuBoot;" -# Add the infinitime user for connecting devcontainer -RUN adduser infinitime +# Add the infinitime user with sudo password "it" for developing in devcontainer +RUN adduser infinitime && echo "infinitime:it" | chpasswd && usermod -aG sudo infinitime # Configure Git to accept the /sources directory as safe RUN git config --global --add safe.directory /sources diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2b69b8b02..7e03d66386 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,7 @@ set(SDK_SOURCE_FILES "${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_gpiote.c" "${NRF5_SDK_PATH}/modules/nrfx/soc/nrfx_atomic.c" "${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_saadc.c" + "${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_pwm.c" # FreeRTOS ${NRF5_SDK_PATH}/external/freertos/source/croutine.c @@ -380,6 +381,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Metronome.cpp displayapp/screens/Motion.cpp displayapp/screens/Weather.cpp + displayapp/screens/Calculator.cpp displayapp/screens/FirmwareValidation.cpp displayapp/screens/ApplicationList.cpp displayapp/screens/Notifications.cpp @@ -395,6 +397,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 @@ -467,6 +470,7 @@ list(APPEND SOURCE_FILES components/settings/Settings.cpp components/timer/Timer.cpp components/alarm/AlarmController.cpp + components/infinisleep/InfiniSleepController.cpp components/fs/FS.cpp drivers/Cst816s.cpp FreeRTOS/port.c @@ -536,6 +540,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/settings/Settings.cpp components/timer/Timer.cpp components/alarm/AlarmController.cpp + components/infinisleep/InfiniSleepController.cpp drivers/Cst816s.cpp FreeRTOS/port.c FreeRTOS/port_cmsis_systick.c @@ -613,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 @@ -655,6 +661,7 @@ set(INCLUDE_FILES components/settings/Settings.h components/timer/Timer.h components/alarm/AlarmController.h + components/infinisleep/InfiniSleepController.h drivers/Cst816s.h FreeRTOS/portmacro.h FreeRTOS/portmacro_cmsis.h diff --git a/src/FreeRTOSConfig.h b/src/FreeRTOSConfig.h index d877705a70..67c33a34cc 100644 --- a/src/FreeRTOSConfig.h +++ b/src/FreeRTOSConfig.h @@ -62,6 +62,7 @@ #define configTICK_RATE_HZ 1024 #define configMAX_PRIORITIES (3) #define configMINIMAL_STACK_SIZE (120) +#define configTOTAL_HEAP_SIZE (1024 * 40) #define configMAX_TASK_NAME_LEN (4) #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 diff --git a/src/components/alarm/AlarmController.cpp b/src/components/alarm/AlarmController.cpp index 7dbfb0e307..4ae42c0830 100644 --- a/src/components/alarm/AlarmController.cpp +++ b/src/components/alarm/AlarmController.cpp @@ -111,7 +111,6 @@ uint32_t AlarmController::SecondsToAlarm() const { void AlarmController::DisableAlarm() { xTimerStop(alarmTimer, 0); - isAlerting = false; if (alarm.isEnabled) { alarm.isEnabled = false; alarmChanged = true; diff --git a/src/components/infinisleep/InfiniSleepController.cpp b/src/components/infinisleep/InfiniSleepController.cpp new file mode 100644 index 0000000000..106c3b5ca4 --- /dev/null +++ b/src/components/infinisleep/InfiniSleepController.cpp @@ -0,0 +1,384 @@ +#include "components/infinisleep/InfiniSleepController.h" +#include "systemtask/SystemTask.h" +#include "task.h" +#include +#include + +using namespace Pinetime::Controllers; +using namespace std::chrono_literals; + +InfiniSleepController::InfiniSleepController(Controllers::DateTime& dateTimeController, + Controllers::FS& fs, + Controllers::HeartRateController& heartRateController, + Controllers::BrightnessController& brightnessController) + : dateTimeController {dateTimeController}, fs {fs}, heartRateController {heartRateController}, brightnessController {brightnessController} { +} + +namespace { + void SetOffWakeAlarm(TimerHandle_t xTimer) { + auto* controller = static_cast(pvTimerGetTimerID(xTimer)); + controller->SetOffWakeAlarmNow(); + } + + void SetOffGradualWake(TimerHandle_t xTimer) { + auto* controller = static_cast(pvTimerGetTimerID(xTimer)); + if (controller->GetInfiniSleepSettings().graddualWake == false) { + return; + } + controller->SetOffGradualWakeNow(); + } + + void SetOffTrackerUpdate(TimerHandle_t xTimer) { + auto* controller = static_cast(pvTimerGetTimerID(xTimer)); + controller->UpdateTracker(); + } +} + +void InfiniSleepController::Init(System::SystemTask* systemTask) { + this->systemTask = systemTask; + wakeAlarmTimer = xTimerCreate("WakeAlarm", 1, pdFALSE, this, SetOffWakeAlarm); + gradualWakeTimer = xTimerCreate("GradualWake", 1, pdFALSE, this, SetOffGradualWake); + + LoadSettingsFromFile(); + LoadPrevSessionData(); + if (infiniSleepSettings.pushesToStopAlarm == 0) { + infiniSleepSettings.pushesToStopAlarm = PUSHES_TO_STOP_ALARM; + settingsChanged = true; + } + pushesLeftToStopWakeAlarm = infiniSleepSettings.pushesToStopAlarm; + if (wakeAlarm.isEnabled) { + NRF_LOG_INFO("[InfiniSleepController] Loaded wake alarm was enabled, scheduling"); + ScheduleWakeAlarm(); + } + + prevBrightnessLevel = brightnessController.Level(); +} + +void InfiniSleepController::EnableTracker() { + // DisableTracker(); + NRF_LOG_INFO("[InfiniSleepController] Enabling tracker"); + isEnabled = true; + trackerUpdateTimer = + xTimerCreate("TrackerUpdate", pdMS_TO_TICKS(TRACKER_UPDATE_INTERVAL_MINS * 60 * 1000), pdFALSE, this, SetOffTrackerUpdate); + xTimerStart(trackerUpdateTimer, 0); +} + +void InfiniSleepController::DisableTracker() { + NRF_LOG_INFO("[InfiniSleepController] Disabling tracker"); + xTimerStop(trackerUpdateTimer, 0); + isEnabled = false; +} + +void InfiniSleepController::UpdateTracker() { + NRF_LOG_INFO("[InfiniSleepController] Updating tracker"); + + if (infiniSleepSettings.heartRateTracking) { + // UpdateBPM(); + } + systemTask->PushMessage(System::Messages::SleepTrackerUpdate); + + xTimerStop(trackerUpdateTimer, 0); + xTimerStart(trackerUpdateTimer, 0); +} + +void InfiniSleepController::SaveWakeAlarm() { + // verify is save needed + if (wakeAlarmChanged) { + SaveSettingsToFile(); + } + wakeAlarmChanged = false; +} + +void InfiniSleepController::SaveInfiniSleepSettings() { + // verify is save needed + if (settingsChanged) { + SaveSettingsToFile(); + } + settingsChanged = false; +} + +void InfiniSleepController::SetWakeAlarmTime(uint8_t wakeAlarmHr, uint8_t wakeAlarmMin) { + if (wakeAlarm.hours == wakeAlarmHr && wakeAlarm.minutes == wakeAlarmMin) { + return; + } + wakeAlarm.hours = wakeAlarmHr; + wakeAlarm.minutes = wakeAlarmMin; + wakeAlarmChanged = true; +} + +void InfiniSleepController::ScheduleWakeAlarm() { + // This line essentially removes the ability to change recurrance type and sets it to daily + // SetRecurrence(RecurType::Daily); + + // Determine the next time the wake alarm needs to go off and set the timer + xTimerStop(wakeAlarmTimer, 0); + xTimerStop(gradualWakeTimer, 0); + + pushesLeftToStopWakeAlarm = infiniSleepSettings.pushesToStopAlarm; + + gradualWakeStep = 9; + + auto now = dateTimeController.CurrentDateTime(); + wakeAlarmTime = now; + time_t ttWakeAlarmTime = + std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(wakeAlarmTime)); + tm* tmWakeAlarmTime = std::localtime(&ttWakeAlarmTime); + + // If the time being set has already passed today, the wake alarm should be set for tomorrow + if (wakeAlarm.hours < dateTimeController.Hours() || + (wakeAlarm.hours == dateTimeController.Hours() && wakeAlarm.minutes <= dateTimeController.Minutes())) { + tmWakeAlarmTime->tm_mday += 1; + // tm_wday doesn't update automatically + tmWakeAlarmTime->tm_wday = (tmWakeAlarmTime->tm_wday + 1) % 7; + } + + tmWakeAlarmTime->tm_hour = wakeAlarm.hours; + tmWakeAlarmTime->tm_min = wakeAlarm.minutes; + tmWakeAlarmTime->tm_sec = 0; + + tmWakeAlarmTime->tm_isdst = -1; // use system timezone setting to determine DST + + // now can convert back to a time_point + wakeAlarmTime = std::chrono::system_clock::from_time_t(std::mktime(tmWakeAlarmTime)); + int64_t secondsToWakeAlarm = std::chrono::duration_cast(wakeAlarmTime - now).count(); + xTimerChangePeriod(wakeAlarmTimer, secondsToWakeAlarm * configTICK_RATE_HZ, 0); + xTimerStart(wakeAlarmTimer, 0); + + // make sure graudal wake steps are possible + while (gradualWakeStep > 0 && secondsToWakeAlarm <= gradualWakeSteps[gradualWakeStep - 1]) { + gradualWakeStep--; + // gradualWakeVibration = gradualWakeStep; + } + + // Calculate the period for the gradualWakeTimer + if (infiniSleepSettings.graddualWake && gradualWakeStep > 0) { + int64_t gradualWakePeriod = ((secondsToWakeAlarm - gradualWakeSteps[-1 + gradualWakeStep])) * (configTICK_RATE_HZ); + xTimerChangePeriod(gradualWakeTimer, gradualWakePeriod, 0); + xTimerStart(gradualWakeTimer, 0); + } + + if (!wakeAlarm.isEnabled) { + wakeAlarm.isEnabled = true; + wakeAlarmChanged = true; + } +} + +uint32_t InfiniSleepController::SecondsToWakeAlarm() const { + return std::chrono::duration_cast(wakeAlarmTime - dateTimeController.CurrentDateTime()).count(); +} + +void InfiniSleepController::DisableWakeAlarm() { + xTimerStop(wakeAlarmTimer, 0); + xTimerStop(gradualWakeTimer, 0); + gradualWakeStep = 9; + isAlerting = false; + if (wakeAlarm.isEnabled) { + wakeAlarm.isEnabled = false; + wakeAlarmChanged = true; + } +} + +void InfiniSleepController::EnableWakeAlarm() { + wakeAlarm.isEnabled = true; + wakeAlarmChanged = true; + ScheduleWakeAlarm(); +} + +void InfiniSleepController::SetOffWakeAlarmNow() { + isAlerting = true; + systemTask->PushMessage(System::Messages::SetOffWakeAlarm); +} + +void InfiniSleepController::SetOffGradualWakeNow() { + // isGradualWakeAlerting = true; + + systemTask->PushMessage(System::Messages::SetOffGradualWake); +} + +void InfiniSleepController::UpdateGradualWake() { + // make sure graudal wake steps are possible + while (gradualWakeStep > 0 && SecondsToWakeAlarm() <= gradualWakeSteps[gradualWakeStep - 1]) { + gradualWakeStep--; + } + + // Calculate the period for the gradualWakeTimer + if (infiniSleepSettings.graddualWake && gradualWakeStep > 0) { + uint64_t gradualWakePeriod = ((SecondsToWakeAlarm() - gradualWakeSteps[-1 + gradualWakeStep])) * (configTICK_RATE_HZ); + xTimerChangePeriod(gradualWakeTimer, gradualWakePeriod, 0); + xTimerStart(gradualWakeTimer, 0); + } else { + xTimerStop(gradualWakeTimer, 0); + } +} + +void InfiniSleepController::StopAlerting() { + isAlerting = false; + wakeAlarm.isEnabled = false; + wakeAlarmChanged = true; +} + +/* Sleep Tracking Section */ + +// void InfiniSleepController::UpdateBPM() { +// // Get the heart rate from the controller +// prevBpm = bpm; +// bpm = heartRateController.HeartRate(); + +// if (prevBpm != 0) +// rollingBpm = (rollingBpm + bpm) / 2; +// else +// rollingBpm = bpm; + +// // Get the current time from DateTimeController +// int hours = dateTimeController.Hours(); +// int minutes = dateTimeController.Minutes(); +// int seconds = dateTimeController.Seconds(); + +// // Log the BPM and current time +// NRF_LOG_INFO("BPM: %d at %02d:%02d:%02d", rollingBpm, hours, minutes, seconds); + +// // Write data to CSV +// // const int motion = 0; // Placeholder for motion data +// // std::tuple data[1] = {std::make_tuple(hours, minutes, seconds, bpm, motion)}; +// // WriteDataCSV(TRACKER_DATA_FILE_NAME, data, 1); +// } + +// void InfiniSleepController::WriteDataCSV(const char* fileName, const std::tuple* data, int dataSize) const { +// lfs_file_t file; +// int err = fs.FileOpen(&file, fileName, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND); +// if (err < 0) { +// // Handle error +// NRF_LOG_INFO("Error opening file: %d", err); +// return; +// } + +// for (int i = 0; i < dataSize; ++i) { +// int hours, minutes, seconds, bpm, motion; +// std::tie(hours, minutes, seconds, bpm, motion) = data[i]; +// char buffer[64]; +// int len = snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d,%d,%d\n", hours, minutes, seconds, bpm, motion); +// err = fs.FileWrite(&file, reinterpret_cast(buffer), len); +// if (err < 0) { +// // Handle error +// NRF_LOG_INFO("Error writing to file: %d", err); +// fs.FileClose(&file); + +// return; +// } +// } + +// fs.FileClose(&file); +// } + +// Clear data in CSV +// void InfiniSleepController::ClearDataCSV(const char* filename) const { +// lfs_file_t file; +// int err = fs.FileOpen(&file, filename, LFS_O_WRONLY | LFS_O_TRUNC); +// if (err < 0) { +// // Handle error +// NRF_LOG_INFO("Error opening file: %d", err); +// return; +// } + +// fs.FileClose(&file); +// NRF_LOG_INFO("CSV data cleared"); +// } + +/* Sleep Tracking Section End */ + +void InfiniSleepController::LoadSettingsFromFile() { + lfs_file_t wakeAlarmFile; + WakeAlarmSettings wakeAlarmBuffer; + + if (fs.FileOpen(&wakeAlarmFile, "wakeAlarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) { + NRF_LOG_WARNING("[InfiniSleepController] Failed to open alarm data file"); + return; + } + + fs.FileRead(&wakeAlarmFile, reinterpret_cast(&wakeAlarmBuffer), sizeof(wakeAlarmBuffer)); + fs.FileClose(&wakeAlarmFile); + if (wakeAlarmBuffer.version != wakeAlarmFormatVersion) { + NRF_LOG_WARNING("[InfiniSleepController] Loaded alarm settings has version %u instead of %u, discarding", + wakeAlarmBuffer.version, + wakeAlarmFormatVersion); + return; + } + + wakeAlarm = wakeAlarmBuffer; + NRF_LOG_INFO("[InfiniSleepController] Loaded alarm settings from file"); + + lfs_file_t infiniSleepSettingsFile; + InfiniSleepSettings infiniSleepSettingsBuffer; + + if (fs.FileOpen(&infiniSleepSettingsFile, "infiniSleepSettings.dat", LFS_O_RDONLY) != LFS_ERR_OK) { + NRF_LOG_WARNING("[InfiniSleepController] Failed to open InfiniSleep settings file"); + return; + } + + fs.FileRead(&infiniSleepSettingsFile, reinterpret_cast(&infiniSleepSettingsBuffer), sizeof(infiniSleepSettingsBuffer)); + fs.FileClose(&infiniSleepSettingsFile); + + infiniSleepSettings = infiniSleepSettingsBuffer; + NRF_LOG_INFO("[InfiniSleepController] Loaded InfiniSleep settings from file"); +} + +void InfiniSleepController::SaveSettingsToFile() const { + lfs_file_t alarmFile; + WakeAlarmSettings tempWakeAlarm = wakeAlarm; + if (isSnoozing) { + tempWakeAlarm.hours = preSnnoozeHours; + tempWakeAlarm.minutes = preSnoozeMinutes; + } + if (fs.FileOpen(&alarmFile, "wakeAlarm.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) { + NRF_LOG_WARNING("[InfiniSleepController] Failed to open alarm data file for saving"); + return; + } + + fs.FileWrite(&alarmFile, reinterpret_cast(&tempWakeAlarm), sizeof(tempWakeAlarm)); + fs.FileClose(&alarmFile); + NRF_LOG_INFO("[InfiniSleepController] Saved alarm settings with format version %u to file", tempWakeAlarm.version); + + lfs_file_t settingsFile; + if (fs.FileOpen(&settingsFile, "infiniSleepSettings.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) { + NRF_LOG_WARNING("[InfiniSleepController] Failed to open InfiniSleep settings file for saving"); + return; + } + + fs.FileWrite(&settingsFile, reinterpret_cast(&infiniSleepSettings), sizeof(infiniSleepSettings)); + fs.FileClose(&settingsFile); + NRF_LOG_INFO("[InfiniSleepController] Saved InfiniSleep settings"); +} + +void InfiniSleepController::SavePrevSessionData() const { + lfs_file_t prevSessionFile; + if (fs.FileOpen(&prevSessionFile, PREV_SESSION_DATA_FILE_NAME, LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) { + NRF_LOG_WARNING("[InfiniSleepController] Failed to open previous session data file for saving"); + return; + } + + fs.FileWrite(&prevSessionFile, reinterpret_cast(&prevSessionData), sizeof(prevSessionData)); + fs.FileClose(&prevSessionFile); + NRF_LOG_INFO("[InfiniSleepController] Saved previous session data"); +} + +void InfiniSleepController::LoadPrevSessionData() { + lfs_file_t prevSessionFile; + if (fs.FileOpen(&prevSessionFile, PREV_SESSION_DATA_FILE_NAME, LFS_O_RDONLY) != LFS_ERR_OK) { + NRF_LOG_WARNING("[InfiniSleepController] Failed to open previous session data file"); + return; + } + + InfiniSleepControllerTypes::SessionData tmpSessionData; + + fs.FileRead(&prevSessionFile, reinterpret_cast(&tmpSessionData), sizeof(tmpSessionData)); + fs.FileClose(&prevSessionFile); + + if (tmpSessionData.version != SESSION_DATA_VERSION) { + NRF_LOG_WARNING("[InfiniSleepController] Loaded previous session data has version %u instead of %u, discarding", + tmpSessionData.version, + SESSION_DATA_VERSION); + return; + } + prevSessionData = tmpSessionData; + NRF_LOG_INFO("[InfiniSleepController] Loaded previous session data"); +} \ No newline at end of file diff --git a/src/components/infinisleep/InfiniSleepController.h b/src/components/infinisleep/InfiniSleepController.h new file mode 100644 index 0000000000..9e2ff3fb8f --- /dev/null +++ b/src/components/infinisleep/InfiniSleepController.h @@ -0,0 +1,276 @@ +#pragma once + +#include +#include +#include +#include "components/datetime/DateTimeController.h" +#include "components/fs/FS.h" +#include "components/heartrate/HeartRateController.h" +#include "components/alarm/AlarmController.h" + +#include + +#define SNOOZE_MINUTES 3 +#define PUSHES_TO_STOP_ALARM 5 +#define TRACKER_UPDATE_INTERVAL_MINS 5 +#define TRACKER_DATA_FILE_NAME "SleepTracker_Data.csv" +#define PREV_SESSION_DATA_FILE_NAME "SleepTracker_PrevSession.csv" +#define SLEEP_CYCLE_DURATION 90 // sleep cycle duration in minutes +#define DESIRED_CYCLES 5 // desired number of sleep cycles +#define PUSHES_TO_STOP_ALARM_TIMEOUT 2 // in seconds +#define SESSION_DATA_VERSION 2 // Version of the session data struct + +namespace Pinetime { + namespace System { + class SystemTask; + } + + namespace Controllers { + namespace InfiniSleepControllerTypes { + // Struct for sessions + struct SessionData { + uint8_t day = 0; + uint8_t month = 0; + uint16_t year = 0; + + uint8_t startTimeHours = 0; + uint8_t startTimeMinutes = 0; + uint8_t endTimeHours = 0; + uint8_t endTimeMinutes = 0; + + uint16_t totalSleepMinutes = 0; + + uint32_t startTimeStamp = 0; + + uint8_t version = SESSION_DATA_VERSION; + }; + } + + class InfiniSleepController { + public: + InfiniSleepController(Controllers::DateTime& dateTimeCOntroller, + Controllers::FS&, + Controllers::HeartRateController& heartRateController, + Controllers::BrightnessController& brightnessController); + + void Init(System::SystemTask* systemTask); + void SaveWakeAlarm(); + void SaveInfiniSleepSettings(); + void SetWakeAlarmTime(uint8_t wakeAlarmHr, uint8_t wakeAlarmMin); + void ScheduleWakeAlarm(); + void DisableWakeAlarm(); + void EnableWakeAlarm(); + void SetOffWakeAlarmNow(); + void SetOffGradualWakeNow(); + void UpdateGradualWake(); + uint32_t SecondsToWakeAlarm() const; + void StopAlerting(); + + uint8_t pushesLeftToStopWakeAlarm = PUSHES_TO_STOP_ALARM; + + bool isSnoozing = false; + uint8_t preSnoozeMinutes = 255; + uint8_t preSnnoozeHours = 255; + + InfiniSleepControllerTypes::SessionData prevSessionData; + + void SetPreSnoozeTime() { + if (preSnoozeMinutes != 255 || preSnnoozeHours != 255) { + return; + } + preSnoozeMinutes = wakeAlarm.minutes; + preSnnoozeHours = wakeAlarm.hours; + } + + void RestorePreSnoozeTime() { + if (preSnoozeMinutes == 255 || preSnnoozeHours == 255) { + return; + } + wakeAlarm.minutes = preSnoozeMinutes; + wakeAlarm.hours = preSnnoozeHours; + preSnoozeMinutes = 255; + preSnnoozeHours = 255; + } + + uint8_t Hours() const { + return wakeAlarm.hours; + } + + uint8_t Minutes() const { + return wakeAlarm.minutes; + } + + bool IsAlerting() const { + return isAlerting; + } + + bool IsEnabled() const { + return isEnabled; + } + + void EnableTracker(); + void DisableTracker(); + void UpdateTracker(); + + void SetSettingsChanged() { + settingsChanged = true; + } + + // Versions 255 is reserved for now, so the version field can be made + // bigger, should it ever be needed. + static constexpr uint8_t wakeAlarmFormatVersion = 1; + + struct WakeAlarmSettings { + static constexpr uint8_t version = wakeAlarmFormatVersion; + uint8_t hours = 7; + uint8_t minutes = 0; + AlarmController::RecurType recurrence = AlarmController::RecurType::Daily; + bool isEnabled = false; + }; + + WakeAlarmSettings wakeAlarm; + + // Dertermine the steps for the gradual wake alarm, the corresponding vibration durations determine the power of the vibration + static constexpr uint16_t gradualWakeSteps[9] = {30, 60, 90, 120, 180, 240, 300, 350, 600}; // In seconds + + uint8_t gradualWakeStep = 9; // used to keep track of which step to use, in position form not idex + + uint16_t GetSleepCycles() const { + return (GetTotalSleep() * 100 / infiniSleepSettings.sleepCycleDuration); + } + + uint16_t GetTotalSleep() const { + uint8_t endHours = IsEnabled() ? GetCurrentHour() : prevSessionData.endTimeHours; + uint8_t endMinutes = IsEnabled() ? GetCurrentMinute() : prevSessionData.endTimeMinutes; + + // Calculate total minutes for start and end times + uint16_t startTotalMinutes = prevSessionData.startTimeHours * 60 + prevSessionData.startTimeMinutes; + uint16_t endTotalMinutes = endHours * 60 + endMinutes; + + // If end time is before start time, add 24 hours to end time (handle crossing midnight) + if (endTotalMinutes < startTotalMinutes) { + endTotalMinutes += 24 * 60; + } + + uint16_t sleepMinutes = endTotalMinutes - startTotalMinutes; + + return sleepMinutes; + } + + uint16_t GetSuggestedSleepTime() const { + return infiniSleepSettings.desiredCycles * infiniSleepSettings.sleepCycleDuration; + } + + WakeAlarmSettings GetWakeAlarm() const { + return wakeAlarm; + } + + struct InfiniSleepSettings { + bool bodyTracking = false; + bool heartRateTracking = true; + bool graddualWake = false; + bool smartAlarm = false; + uint8_t sleepCycleDuration = SLEEP_CYCLE_DURATION; + uint8_t desiredCycles = DESIRED_CYCLES; + uint8_t motorStrength = 100; + bool naturalWake = false; + uint8_t pushesToStopAlarm = PUSHES_TO_STOP_ALARM; + }; + + InfiniSleepSettings infiniSleepSettings; + + InfiniSleepSettings GetInfiniSleepSettings() const { + return infiniSleepSettings; + } + + BrightnessController::Levels prevBrightnessLevel; + + bool ToggleTracker() { + if (isEnabled) { + prevSessionData.endTimeHours = GetCurrentHour(); + prevSessionData.endTimeMinutes = GetCurrentMinute(); + + // Calculate total sleep time + uint16_t startTotalMinutes = prevSessionData.startTimeHours * 60 + prevSessionData.startTimeMinutes; + uint16_t endTotalMinutes = GetCurrentHour() * 60 + GetCurrentMinute(); + + // If end time is before start time, add 24 hours to end time (handle crossing midnight) + if (endTotalMinutes < startTotalMinutes) { + endTotalMinutes += 24 * 60; + } + + prevSessionData.totalSleepMinutes = endTotalMinutes - startTotalMinutes; + + SavePrevSessionData(); + DisableTracker(); + } else { + // ClearDataCSV(TRACKER_DATA_FILE_NAME); + prevSessionData.totalSleepMinutes = 0; + prevSessionData.endTimeHours = 255; + prevSessionData.endTimeMinutes = 255; + prevSessionData.startTimeHours = GetCurrentHour(); + prevSessionData.startTimeMinutes = GetCurrentMinute(); + prevSessionData.day = dateTimeController.Day(); + prevSessionData.month = static_cast(dateTimeController.Month()); + prevSessionData.year = dateTimeController.Year(); + prevSessionData.startTimeStamp = dateTimeController.CurrentDateTime().time_since_epoch().count(); + EnableTracker(); + } + return isEnabled; + } + + bool IsTrackerEnabled() const { + return isEnabled; + } + + uint8_t GetCurrentHour() const { + return dateTimeController.Hours(); + } + + uint8_t GetCurrentMinute() const { + return dateTimeController.Minutes(); + } + + void UpdateBPM(); + + uint8_t GetGradualWakeStep() const { + return (9 - gradualWakeStep) + 1; + } + + BrightnessController& GetBrightnessController() { + return brightnessController; + } + + private: + bool isAlerting = false; + bool isGradualWakeAlerting = false; + bool wakeAlarmChanged = false; + bool isEnabled = false; + bool settingsChanged = false; + + // uint8_t bpm = 0; + // uint8_t prevBpm = 0; + // uint8_t rollingBpm = 0; + + Controllers::DateTime& dateTimeController; + Controllers::FS& fs; + Controllers::HeartRateController& heartRateController; + Controllers::BrightnessController& brightnessController; + System::SystemTask* systemTask = nullptr; + TimerHandle_t wakeAlarmTimer; + TimerHandle_t gradualWakeTimer; + TimerHandle_t trackerUpdateTimer; + std::chrono::time_point wakeAlarmTime; + + void LoadSettingsFromFile(); + void SaveSettingsToFile() const; + void LoadPrevSessionData(); + void SavePrevSessionData() const; + + // For File IO + // void WriteDataCSV(const char* fileName, const std::tuple* data, int dataSize) const; + // void ClearDataCSV(const char* fileName) const; + }; + } + +} \ No newline at end of file diff --git a/src/components/motor/MotorController.cpp b/src/components/motor/MotorController.cpp index 4e392416ae..d6c64bfd80 100644 --- a/src/components/motor/MotorController.cpp +++ b/src/components/motor/MotorController.cpp @@ -2,15 +2,54 @@ #include #include "systemtask/SystemTask.h" #include "drivers/PinMap.h" +#include "nrf_pwm.h" using namespace Pinetime::Controllers; +static uint16_t pwmValue = 0; // Declare the variable for PWM value + void MotorController::Init() { + // Configure the motor pin as an output nrf_gpio_cfg_output(PinMap::Motor); nrf_gpio_pin_set(PinMap::Motor); + // Configure the PWM sequence + static nrf_pwm_sequence_t seq; + seq.values.p_common = &pwmValue; // Use the PWM value array + seq.length = NRF_PWM_VALUES_LENGTH(pwmValue); + seq.repeats = 0; + seq.end_delay = 0; + + // Configure the PWM pins + uint32_t out_pins[] = {PinMap::Motor, NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED, NRF_PWM_PIN_NOT_CONNECTED}; + nrf_pwm_pins_set(NRF_PWM2, out_pins); + + // Enable and configure the PWM peripheral + nrf_pwm_enable(NRF_PWM2); + nrf_pwm_configure(NRF_PWM2, NRF_PWM_CLK_1MHz, NRF_PWM_MODE_UP, 255); // Top value determines the resolution + nrf_pwm_loop_set(NRF_PWM2, 0); // Infinite loop + nrf_pwm_decoder_set(NRF_PWM2, NRF_PWM_LOAD_COMMON, NRF_PWM_STEP_AUTO); + nrf_pwm_sequence_set(NRF_PWM2, 0, &seq); + + // Start the PWM with an initial value of 0 + pwmValue = 0; + nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART0); + + // Initialize timers for motor actions shortVib = xTimerCreate("shortVib", 1, pdFALSE, nullptr, StopMotor); longVib = xTimerCreate("longVib", pdMS_TO_TICKS(1000), pdTRUE, this, Ring); + wakeAlarmVib = xTimerCreate("wakeAlarmVib", pdMS_TO_TICKS(1000), pdTRUE, this, WakeAlarmRing); + naturalWakeAlarmVib = xTimerCreate("natWakeVib", pdMS_TO_TICKS(30 * 1000), pdTRUE, this, NaturalWakeAlarmRing); +} + +void MotorController::SetMotorStrength(uint8_t strength) { + // Ensure strength is within bounds (0-100) + // if (strength > 100) + // strength = 100; + + // Map the strength to the PWM value (0-100 -> 0-top_value) + // pwmValue = (strength * 255) / 100; + pwmValue = strength; } void MotorController::Ring(TimerHandle_t xTimer) { @@ -18,22 +57,94 @@ void MotorController::Ring(TimerHandle_t xTimer) { motorController->RunForDuration(50); } -void MotorController::RunForDuration(uint8_t motorDuration) { +void MotorController::RunForDuration(uint16_t motorDuration) { if (motorDuration > 0 && xTimerChangePeriod(shortVib, pdMS_TO_TICKS(motorDuration), 0) == pdPASS && xTimerStart(shortVib, 0) == pdPASS) { - nrf_gpio_pin_clear(PinMap::Motor); + if (pwmValue == 0) { + SetMotorStrength(255); + } + // nrf_gpio_pin_clear(PinMap::Motor); + nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_SEQSTART0); // Restart the PWM sequence with the updated value } } void MotorController::StartRinging() { + SetMotorStrength(100); RunForDuration(50); xTimerStart(longVib, 0); } void MotorController::StopRinging() { xTimerStop(longVib, 0); + nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_STOP); // Stop the PWM sequence + pwmValue = 0; // Reset the PWM value nrf_gpio_pin_set(PinMap::Motor); } -void MotorController::StopMotor(TimerHandle_t /*xTimer*/) { +void MotorController::StartWakeAlarm() { + wakeAlarmStrength = (80 * infiniSleepMotorStrength) / 100; + wakeAlarmDuration = 100; + SetMotorStrength(wakeAlarmStrength); + RunForDuration(wakeAlarmDuration); + xTimerStart(wakeAlarmVib, 0); +} + +void MotorController::WakeAlarmRing(TimerHandle_t xTimer) { + auto* motorController = static_cast(pvTimerGetTimerID(xTimer)); + if (motorController->wakeAlarmStrength > (40 * motorController->infiniSleepMotorStrength) / 100) { + motorController->wakeAlarmStrength -= (1 * motorController->infiniSleepMotorStrength) / 100; + } + if (motorController->wakeAlarmDuration < 500) { + motorController->wakeAlarmDuration += 6; + } + motorController->SetMotorStrength(motorController->wakeAlarmStrength); + motorController->RunForDuration(motorController->wakeAlarmDuration); +} + +void MotorController::StopWakeAlarm() { + xTimerStop(wakeAlarmVib, 0); + nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_STOP); // Stop the PWM sequence + pwmValue = 0; // Reset the PWM value nrf_gpio_pin_set(PinMap::Motor); } + +void MotorController::StartNaturalWakeAlarm() { + wakeAlarmStrength = (80 * infiniSleepMotorStrength) / 100; + wakeAlarmDuration = 100; + SetMotorStrength(wakeAlarmStrength); + RunForDuration(wakeAlarmDuration); + xTimerStart(naturalWakeAlarmVib, 0); +} + +void MotorController::NaturalWakeAlarmRing(TimerHandle_t xTimer) { + auto* motorController = static_cast(pvTimerGetTimerID(xTimer)); + if (motorController->wakeAlarmStrength > (40 * motorController->infiniSleepMotorStrength) / 100) { + motorController->wakeAlarmStrength -= (15 * motorController->infiniSleepMotorStrength) / 100; + } else { + motorController->wakeAlarmStrength += (30 * motorController->infiniSleepMotorStrength) / 100; + } + if (motorController->wakeAlarmDuration < 500) { + motorController->wakeAlarmDuration += 90; + } else { + motorController->wakeAlarmDuration -= 90; + } + motorController->SetMotorStrength(motorController->wakeAlarmStrength); + motorController->RunForDuration(motorController->wakeAlarmDuration); +} + +void MotorController::StopNaturalWakeAlarm() { + xTimerStop(naturalWakeAlarmVib, 0); + nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_STOP); // Stop the PWM sequence + pwmValue = 0; // Reset the PWM value + nrf_gpio_pin_set(PinMap::Motor); +} + +void MotorController::StopMotor(TimerHandle_t /*xTimer*/) { + nrf_pwm_task_trigger(NRF_PWM2, NRF_PWM_TASK_STOP); // Stop the PWM sequence + pwmValue = 0; // Reset the PWM value + nrf_gpio_pin_set(PinMap::Motor); // Set the motor pin to the off state +} + +void MotorController::GradualWakeBuzz() { + SetMotorStrength((60 * infiniSleepMotorStrength) / 100); + RunForDuration(100); +} diff --git a/src/components/motor/MotorController.h b/src/components/motor/MotorController.h index 6dea6d1f9e..cfeeb22a25 100644 --- a/src/components/motor/MotorController.h +++ b/src/components/motor/MotorController.h @@ -12,15 +12,32 @@ namespace Pinetime { MotorController() = default; void Init(); - void RunForDuration(uint8_t motorDuration); + void RunForDuration(uint16_t motorDuration); void StartRinging(); void StopRinging(); + void StartWakeAlarm(); + void StopWakeAlarm(); + void StartNaturalWakeAlarm(); + void StopNaturalWakeAlarm(); + void GradualWakeBuzz(); + void StopGradualWakeBuzz(); + void SetMotorStrength(uint8_t strength); + + uint8_t wakeAlarmStrength = 80; + uint16_t wakeAlarmDuration = 100; + uint8_t infiniSleepMotorStrength = 100; private: static void Ring(TimerHandle_t xTimer); + static void WakeAlarmRing(TimerHandle_t xTimer); + static void NaturalWakeAlarmRing(TimerHandle_t xTimer); static void StopMotor(TimerHandle_t xTimer); + TimerHandle_t shortVib; TimerHandle_t longVib; + + TimerHandle_t wakeAlarmVib; + TimerHandle_t naturalWakeAlarmVib; }; } } diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h index 9992426c5d..5116398e2e 100644 --- a/src/displayapp/Controllers.h +++ b/src/displayapp/Controllers.h @@ -19,6 +19,7 @@ namespace Pinetime { class MotorController; class MotionController; class AlarmController; + class InfiniSleepController; class BrightnessController; class SimpleWeatherService; class FS; @@ -42,6 +43,7 @@ namespace Pinetime { Pinetime::Controllers::MotorController& motorController; Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::AlarmController& alarmController; + Pinetime::Controllers::InfiniSleepController& infiniSleepController; Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::SimpleWeatherService* weatherController; Pinetime::Controllers::FS& filesystem; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index b1594f197c..1d82d17fbe 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -30,6 +30,8 @@ #include "displayapp/screens/Weather.h" #include "displayapp/screens/PassKey.h" #include "displayapp/screens/Error.h" +#include "displayapp/screens/Sleep.h" +#include "displayapp/screens/Calculator.h" #include "drivers/Cst816s.h" #include "drivers/St7789.h" @@ -82,7 +84,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, - Pinetime::Drivers::SpiNorFlash& spiNorFlash) + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::Controllers::InfiniSleepController& infiniSleepController) : lcd {lcd}, touchPanel {touchPanel}, batteryController {batteryController}, @@ -99,6 +102,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, touchHandler {touchHandler}, filesystem {filesystem}, spiNorFlash {spiNorFlash}, + infiniSleepController {infiniSleepController}, lvgl {lcd, filesystem}, timer(this, TimerCallback), controllers {batteryController, @@ -110,6 +114,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, motorController, motionController, alarmController, + infiniSleepController, brightnessController, nullptr, filesystem, @@ -350,6 +355,11 @@ void DisplayApp::Refresh() { lcd.LowPowerOff(); } else { lcd.Wakeup(); + if (infiniSleepController.IsEnabled()) { + if (currentApp != Apps::Sleep) { + LoadNewScreen(Apps::Sleep, DisplayApp::FullRefreshDirections::Up); + } + } } lv_disp_trig_activity(nullptr); ApplyBrightness(); @@ -372,7 +382,11 @@ void DisplayApp::Refresh() { } else { LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up); } - motorController.RunForDuration(35); + motorController.RunForDuration(100); + vTaskDelay(pdMS_TO_TICKS(500)); + motorController.RunForDuration(100); + vTaskDelay(pdMS_TO_TICKS(500)); + motorController.RunForDuration(100); break; case Messages::AlarmTriggered: if (currentApp == Apps::Alarm) { @@ -382,6 +396,37 @@ void DisplayApp::Refresh() { LoadNewScreen(Apps::Alarm, DisplayApp::FullRefreshDirections::None); } break; + case Messages::WakeAlarmTriggered: + if (currentApp == Apps::Sleep) { + auto* sleep = static_cast(currentScreen.get()); + sleep->SetAlerting(); + } else { + LoadNewScreen(Apps::Sleep, DisplayApp::FullRefreshDirections::None); + } + break; + case Messages::GradualWakeTriggered: + if (currentApp == Apps::Sleep) { + // auto* sleep = static_cast(currentScreen.get()); + // sleep->SetGradualWakeAlerting(); + } else { + // LoadNewScreen(Apps::Sleep, DisplayApp::FullRefreshDirections::None); + } + // motorController.RunForDuration(infiniSleepController.gradualWakeVibrationDurations[-1 + infiniSleepController.gradualWakeStep]); + + if (infiniSleepController.isSnoozing == false) { + motorController.GradualWakeBuzz(); + NRF_LOG_INFO("Gradual wake triggered"); + } + + infiniSleepController.UpdateGradualWake(); + + break; + case Messages::SleepTrackerUpdate: + if (currentApp == Apps::Sleep) { + auto* sleep = static_cast(currentScreen.get()); + sleep->UpdateDisplay(); + } + break; case Messages::ShowPairingKey: LoadNewScreen(Apps::PassKey, DisplayApp::FullRefreshDirections::Up); motorController.RunForDuration(35); @@ -460,7 +505,7 @@ void DisplayApp::Refresh() { LoadNewScreen(Apps::SysInfo, DisplayApp::FullRefreshDirections::Up); break; case Messages::ButtonDoubleClicked: - if (currentApp != Apps::Notifications && currentApp != Apps::NotificationsPreview) { + if (!infiniSleepController.IsAlerting() && currentApp != Apps::Notifications && currentApp != Apps::NotificationsPreview) { LoadNewScreen(Apps::Notifications, DisplayApp::FullRefreshDirections::Down); } break; @@ -526,6 +571,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio settingsController, batteryController, bleController, + alarmController, dateTimeController, filesystem, std::move(apps)); @@ -580,7 +626,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio brightnessController, motorController, settingsController, - bleController); + bleController, + alarmController); break; case Apps::Settings: currentScreen = std::make_unique(this, settingsController); @@ -720,6 +767,10 @@ void DisplayApp::Register(Pinetime::Controllers::NavigationService* NavigationSe } void DisplayApp::ApplyBrightness() { + if (infiniSleepController.IsEnabled() || currentApp == Apps::Sleep) { + brightnessController.Set(Controllers::BrightnessController::Levels::Low); + return; + } auto brightness = settingsController.GetBrightness(); if (brightness != Controllers::BrightnessController::Levels::Low && brightness != Controllers::BrightnessController::Levels::Medium && brightness != Controllers::BrightnessController::Levels::High) { diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 2f276eaf9e..0be91e848a 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -14,6 +14,7 @@ #include "displayapp/screens/Screen.h" #include "components/timer/Timer.h" #include "components/alarm/AlarmController.h" +#include "components/infinisleep/InfiniSleepController.h" #include "touchhandler/TouchHandler.h" #include "displayapp/Messages.h" @@ -67,7 +68,8 @@ namespace Pinetime { Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, - Pinetime::Drivers::SpiNorFlash& spiNorFlash); + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::Controllers::InfiniSleepController& infiniSleepController); void Start(System::BootErrors error); void PushMessage(Display::Messages msg); @@ -98,6 +100,7 @@ namespace Pinetime { Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::FS& filesystem; Pinetime::Drivers::SpiNorFlash& spiNorFlash; + Pinetime::Controllers::InfiniSleepController& infiniSleepController; Pinetime::Controllers::FirmwareValidator validator; Pinetime::Components::LittleVgl lvgl; diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index bcb8db0e9d..2500dcad32 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -7,6 +7,7 @@ #include "touchhandler/TouchHandler.h" #include "displayapp/icons/infinitime/infinitime-nb.c" #include "components/ble/BleController.h" +#include "displayapp/screens/Sleep.h" using namespace Pinetime::Applications; @@ -25,7 +26,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::BrightnessController& /*brightnessController*/, Pinetime::Controllers::TouchHandler& /*touchHandler*/, Pinetime::Controllers::FS& /*filesystem*/, - Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/) + Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/, + Pinetime::Controllers::InfiniSleepController& /*infiniSleepController*/) : lcd {lcd}, bleController {bleController} { } diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index 162ff2575e..dc5108b66f 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -37,6 +37,7 @@ namespace Pinetime { class SimpleWeatherService; class MusicService; class NavigationService; + class InfiniSleepController; } namespace System { @@ -61,7 +62,8 @@ namespace Pinetime { Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem, - Pinetime::Drivers::SpiNorFlash& spiNorFlash); + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::Controllers::InfiniSleepController& infiniSleepController); void Start(); void Start(Pinetime::System::BootErrors) { diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index d2abc8e58d..ce25df5a7b 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -22,9 +22,12 @@ namespace Pinetime { NotifyDeviceActivity, ShowPairingKey, AlarmTriggered, + WakeAlarmTriggered, + GradualWakeTriggered, Chime, BleRadioEnableToggle, OnChargingEvent, + SleepTrackerUpdate, }; } } diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267c0..0598bf7974 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -22,10 +22,12 @@ namespace Pinetime { Paddle, Twos, HeartRate, + Sleep, Navigation, StopWatch, Metronome, Motion, + Calculator, Steps, Dice, Weather, diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index d78587609e..b278c1568f 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -13,7 +13,9 @@ 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::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 41c383c0d4..7e34eeff51 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, 0xf201, 0xf06e, 0xf015, 0xf00c, 0xf1ec, 0xf743, 0xf55a, 0xf0f3, 0xf522, 0xf236" } ], "bpp": 1, diff --git a/src/displayapp/screens/Alarm.cpp b/src/displayapp/screens/Alarm.cpp index b1e673639a..4cf4392157 100644 --- a/src/displayapp/screens/Alarm.cpp +++ b/src/displayapp/screens/Alarm.cpp @@ -77,7 +77,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController, btnStop = lv_btn_create(lv_scr_act(), nullptr); btnStop->user_data = this; lv_obj_set_event_cb(btnStop, btnEventHandler); - lv_obj_set_size(btnStop, 115, 50); + lv_obj_set_size(btnStop, 240, 70); lv_obj_align(btnStop, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); lv_obj_set_style_local_bg_color(btnStop, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); txtStop = lv_label_create(btnStop, nullptr); @@ -203,6 +203,10 @@ void Alarm::UpdateAlarmTime() { void Alarm::SetAlerting() { lv_obj_set_hidden(enableSwitch, true); + lv_obj_set_hidden(btnRecur, true); + lv_obj_set_hidden(btnInfo, true); + hourCounter.HideControls(); + minuteCounter.HideControls(); lv_obj_set_hidden(btnStop, false); taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this); motorController.StartRinging(); @@ -218,8 +222,12 @@ void Alarm::StopAlerting() { taskStopAlarm = nullptr; } wakeLock.Release(); - lv_obj_set_hidden(enableSwitch, false); lv_obj_set_hidden(btnStop, true); + hourCounter.ShowControls(); + minuteCounter.ShowControls(); + lv_obj_set_hidden(btnInfo, false); + lv_obj_set_hidden(btnRecur, false); + lv_obj_set_hidden(enableSwitch, false); } void Alarm::SetSwitchState(lv_anim_enable_t anim) { diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp index 41735349da..fb46b41384 100644 --- a/src/displayapp/screens/ApplicationList.cpp +++ b/src/displayapp/screens/ApplicationList.cpp @@ -21,6 +21,7 @@ ApplicationList::ApplicationList(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, + const Pinetime::Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, Pinetime::Controllers::FS& filesystem, std::array&& apps) @@ -28,6 +29,7 @@ ApplicationList::ApplicationList(DisplayApp* app, settingsController {settingsController}, batteryController {batteryController}, bleController {bleController}, + alarmController {alarmController}, dateTimeController {dateTimeController}, filesystem {filesystem}, apps {std::move(apps)}, @@ -59,6 +61,7 @@ std::unique_ptr ApplicationList::CreateScreen(unsigned int screenNum) co settingsController, batteryController, bleController, + alarmController, dateTimeController, pageApps); } diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h index 41a413af1a..4a57d7c034 100644 --- a/src/displayapp/screens/ApplicationList.h +++ b/src/displayapp/screens/ApplicationList.h @@ -18,6 +18,7 @@ namespace Pinetime { Pinetime::Controllers::Settings& settingsController, const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Ble& bleController, + const Pinetime::Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, Pinetime::Controllers::FS& filesystem, std::array&& apps); @@ -32,6 +33,7 @@ namespace Pinetime { Controllers::Settings& settingsController; const Pinetime::Controllers::Battery& batteryController; const Pinetime::Controllers::Ble& bleController; + const Pinetime::Controllers::AlarmController& alarmController; Controllers::DateTime& dateTimeController; Pinetime::Controllers::FS& filesystem; std::array apps; diff --git a/src/displayapp/screens/Calculator.cpp b/src/displayapp/screens/Calculator.cpp new file mode 100644 index 0000000000..a1f093830c --- /dev/null +++ b/src/displayapp/screens/Calculator.cpp @@ -0,0 +1,375 @@ +#include +#include +#include "Calculator.h" +#include "displayapp/InfiniTimeTheme.h" +#include "Symbols.h" + +using namespace Pinetime::Applications::Screens; + +static void eventHandler(lv_obj_t* obj, lv_event_t event) { + auto app = static_cast(obj->user_data); + app->OnButtonEvent(obj, event); +} + +Calculator::~Calculator() { + lv_obj_clean(lv_scr_act()); +} + +constexpr const char* const buttonMap[] = { + "7", "8", "9", Symbols::backspace, "\n", "4", "5", "6", "+ -", "\n", "1", "2", "3", "* /", "\n", "0", ".", "(-)", "=", ""}; + +Calculator::Calculator() { + resultLabel = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(resultLabel, LV_LABEL_LONG_CROP); + lv_label_set_align(resultLabel, LV_LABEL_ALIGN_RIGHT); + lv_label_set_text_fmt(resultLabel, "%" PRId64, result); + lv_obj_set_size(resultLabel, 200, 20); + lv_obj_set_pos(resultLabel, 10, 5); + + valueLabel = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_long_mode(valueLabel, LV_LABEL_LONG_CROP); + lv_label_set_align(valueLabel, LV_LABEL_ALIGN_RIGHT); + lv_label_set_text_fmt(valueLabel, "%" PRId64, value); + lv_obj_set_size(valueLabel, 200, 20); + lv_obj_set_pos(valueLabel, 10, 35); + + buttonMatrix = lv_btnmatrix_create(lv_scr_act(), nullptr); + buttonMatrix->user_data = this; + lv_obj_set_event_cb(buttonMatrix, eventHandler); + lv_btnmatrix_set_map(buttonMatrix, const_cast(buttonMap)); + lv_btnmatrix_set_one_check(buttonMatrix, true); + lv_obj_set_size(buttonMatrix, 238, 180); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_pad_inner(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_top(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_bottom(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_left(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_set_style_local_pad_right(buttonMatrix, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 1); + lv_obj_align(buttonMatrix, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + lv_obj_set_style_local_bg_opa(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_OPA_COVER); + lv_obj_set_style_local_bg_grad_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128); + lv_obj_set_style_local_bg_main_stop(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, 128); +} + +void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { + if ((obj == buttonMatrix) && (event == LV_EVENT_PRESSED)) { + HandleInput(); + } +} + +void Calculator::HandleInput() { + const char* buttonText = lv_btnmatrix_get_active_btn_text(buttonMatrix); + + if (buttonText == nullptr) { + return; + } + + if ((equalSignPressedBefore && (*buttonText != '=')) || (error != Error::None)) { + ResetInput(); + UpdateOperation(); + } + + // we only compare the first char because it is enough + switch (*buttonText) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + // *buttonText is the first char in buttonText + // "- '0'" results in the int value of the char + uint8_t digit = (*buttonText) - '0'; + int8_t sign = (value < 0) ? -1 : 1; + + // if this is true, we already pressed the . button + if (offset < FIXED_POINT_OFFSET) { + value += sign * offset * digit; + offset /= 10; + } else if (value <= MAX_VALUE / 10) { + value *= 10; + value += sign * offset * digit; + } + } break; + + // unary minus + case '(': + value = -value; + break; + + case '.': + if (offset == FIXED_POINT_OFFSET) { + offset /= 10; + } + break; + + // for every operator we: + // - eval the current operator if value > FIXED_POINT_OFFSET + // - then set the new operator + // - + and - as well as * and / cycle on the same button + case '+': + if (value != 0) { + Eval(); + ResetInput(); + } + + switch (operation) { + case '+': + operation = '-'; + break; + case '-': + operation = ' '; + break; + default: + operation = '+'; + break; + } + UpdateOperation(); + break; + + case '*': + if (value != 0) { + Eval(); + ResetInput(); + } + + switch (operation) { + case '*': + operation = '/'; + break; + case '/': + operation = ' '; + break; + default: + operation = '*'; + break; + } + UpdateOperation(); + break; + + // this is a little hacky because it matches only the first char + case Symbols::backspace[0]: + if (value != 0) { + // delete one value digit + if (offset < FIXED_POINT_OFFSET) { + if (offset == 0) { + offset = 1; + } else { + offset *= 10; + } + } else { + value /= 10; + } + if (offset < FIXED_POINT_OFFSET) { + value -= value % (10 * offset); + } else { + value -= value % offset; + } + } else if (offset < FIXED_POINT_OFFSET) { + if (offset == 0) { + offset = 1; + } else { + offset *= 10; + } + } else { + // reset the result + result = 0; + } + + if (value == 0) { + operation = ' '; + UpdateOperation(); + } + break; + + case '=': + equalSignPressedBefore = true; + Eval(); + // If the operation is ' ' then we move the value to the result. + // We reset the input after this. + // This seems more convenient. + if (operation == ' ') { + ResetInput(); + } + break; + } + + UpdateValueLabel(); + UpdateResultLabel(); +} + +void Calculator::UpdateOperation() const { + switch (operation) { + case '+': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '-': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 7, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '*': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + case '/': + lv_obj_set_style_local_bg_grad_dir(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, LV_GRAD_DIR_HOR); + lv_obj_set_style_local_bg_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::bgAlt); + lv_obj_set_style_local_bg_grad_color(buttonMatrix, LV_BTNMATRIX_PART_BTN, LV_STATE_CHECKED, Colors::deepOrange); + lv_btnmatrix_set_btn_ctrl(buttonMatrix, 11, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + default: + lv_btnmatrix_clear_btn_ctrl_all(buttonMatrix, LV_BTNMATRIX_CTRL_CHECK_STATE); + break; + } +} + +void Calculator::ResetInput() { + value = 0; + offset = FIXED_POINT_OFFSET; + operation = ' '; + equalSignPressedBefore = false; + error = Error::None; +} + +void Calculator::UpdateResultLabel() const { + int64_t integer = result / FIXED_POINT_OFFSET; + int64_t remainder = result % FIXED_POINT_OFFSET; + bool negative = (remainder < 0); + + if (remainder == 0) { + lv_label_set_text_fmt(resultLabel, "%" PRId64, integer); + return; + } + + if (remainder < 0) { + remainder = -remainder; + } + + uint8_t minWidth = N_DECIMALS; + + // cut "0"-digits on the right + while ((remainder > 0) && (remainder % 10 == 0)) { + remainder /= 10; + minWidth--; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(resultLabel, "-0.%0*" PRId64, minWidth, remainder); + } else { + lv_label_set_text_fmt(resultLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, remainder); + } +} + +void Calculator::UpdateValueLabel() { + switch (error) { + case Error::TooLarge: + lv_label_set_text_static(valueLabel, "too large"); + break; + case Error::ZeroDivision: + lv_label_set_text_static(valueLabel, "zero division"); + break; + case Error::None: + default: { + int64_t integer = value / FIXED_POINT_OFFSET; + int64_t remainder = value % FIXED_POINT_OFFSET; + bool negative = (remainder < 0); + + int64_t printRemainder = remainder < 0 ? -remainder : remainder; + + uint8_t minWidth = 0; + int64_t tmpOffset = offset; + + if (tmpOffset == 0) { + tmpOffset = 1; + minWidth = 1; + } + while (tmpOffset < FIXED_POINT_OFFSET) { + tmpOffset *= 10; + minWidth++; + } + minWidth--; + + for (uint8_t i = minWidth; i < N_DECIMALS; i++) { + printRemainder /= 10; + } + + if ((integer == 0) && negative) { + lv_label_set_text_fmt(valueLabel, "-0.%0*" PRId64, minWidth, printRemainder); + } else if (offset == FIXED_POINT_OFFSET) { + lv_label_set_text_fmt(valueLabel, "%" PRId64, integer); + } else if ((offset == (FIXED_POINT_OFFSET / 10)) && (remainder == 0)) { + lv_label_set_text_fmt(valueLabel, "%" PRId64 ".", integer); + } else { + lv_label_set_text_fmt(valueLabel, "%" PRId64 ".%0*" PRId64, integer, minWidth, printRemainder); + } + } break; + } +} + +// update the result based on value and operation +void Calculator::Eval() { + switch (operation) { + case ' ': + result = value; + break; + + case '+': + // check for overflow + if (((result > 0) && (value > (MAX_VALUE - result))) || ((result < 0) && (value < (MIN_VALUE - result)))) { + error = Error::TooLarge; + break; + } + + result += value; + break; + case '-': + // check for overflow + if (((result < 0) && (value > (MAX_VALUE + result))) || ((result > 0) && (value < (MIN_VALUE + result)))) { + error = Error::TooLarge; + break; + } + + result -= value; + break; + case '*': + // check for overflow + // while dividing we eliminate the fixed point offset + // therefore we have to multiply it again for the comparison with value + // we also assume here that MAX_VALUE == -MIN_VALUE + if ((result != 0) && (std::abs(value) > (FIXED_POINT_OFFSET * (MAX_VALUE / std::abs(result))))) { + error = Error::TooLarge; + break; + } + + result *= value; + // fixed point offset was multiplied too + result /= FIXED_POINT_OFFSET; + break; + case '/': + // check for zero division + if (value == 0) { + error = Error::ZeroDivision; + break; + } + + // fixed point offset will be divided too + result *= FIXED_POINT_OFFSET; + result /= value; + break; + + default: + break; + } +} diff --git a/src/displayapp/screens/Calculator.h b/src/displayapp/screens/Calculator.h new file mode 100644 index 0000000000..9971f275a4 --- /dev/null +++ b/src/displayapp/screens/Calculator.h @@ -0,0 +1,83 @@ +#pragma once + +#include "displayapp/screens/Screen.h" +#include "displayapp/apps/Apps.h" +#include "displayapp/Controllers.h" +#include "Symbols.h" + +namespace { + constexpr int64_t powi(int64_t base, uint8_t exponent) { + int64_t value = 1; + while (exponent) { + value *= base; + exponent--; + } + return value; + } +} + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Calculator : public Screen { + public: + ~Calculator() override; + + Calculator(); + + void OnButtonEvent(lv_obj_t* obj, lv_event_t event); + + private: + lv_obj_t* buttonMatrix {}; + lv_obj_t* valueLabel {}; + lv_obj_t* resultLabel {}; + + void Eval(); + void ResetInput(); + void HandleInput(); + void UpdateValueLabel(); + void UpdateResultLabel() const; + void UpdateOperation() const; + + // change this if you want to change the number of decimals + // keep in mind, that we only have 12 digits in total (see MAX_DIGITS) + // due to the fixed point implementation + static constexpr uint8_t N_DECIMALS = 3; + // this is the constant default offset + static constexpr int64_t FIXED_POINT_OFFSET = powi(10, N_DECIMALS); + // this is the current offset, may vary after pressing '.' + int64_t offset = FIXED_POINT_OFFSET; + + // the screen can show 12 chars + // but two are needed for '.' and '-' + static constexpr uint8_t MAX_DIGITS = 12; + static constexpr int64_t MAX_VALUE = powi(10, MAX_DIGITS) - 1; + // this is assumed in the multiplication overflow! + static constexpr int64_t MIN_VALUE = -MAX_VALUE; + + int64_t value = 0; + int64_t result = 0; + char operation = ' '; + bool equalSignPressedBefore = false; + + enum Error { + TooLarge, + ZeroDivision, + None, + }; + + Error error = Error::None; + }; + } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::Calculator; + static constexpr const char* icon = Screens::Symbols::calculator; + + static Screens::Screen* Create(AppControllers& /* controllers */) { + return new Screens::Calculator(); + }; + }; + } +} diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp index 45f72f2e20..837c4683aa 100644 --- a/src/displayapp/screens/Notifications.cpp +++ b/src/displayapp/screens/Notifications.cpp @@ -246,8 +246,8 @@ namespace { Notifications::NotificationItem::NotificationItem(Pinetime::Controllers::AlertNotificationService& alertNotificationService, Pinetime::Controllers::MotorController& motorController) - : NotificationItem("Notification", - "No notification to display", + : NotificationItem("Notifications", + "No notifications to display", 0, Controllers::NotificationManager::Categories::Unknown, 0, diff --git a/src/displayapp/screens/Sleep.cpp b/src/displayapp/screens/Sleep.cpp new file mode 100644 index 0000000000..ccd1ea635a --- /dev/null +++ b/src/displayapp/screens/Sleep.cpp @@ -0,0 +1,745 @@ +#include "displayapp/screens/Sleep.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" +#include "displayapp/InfiniTimeTheme.h" +#include "components/settings/Settings.h" +#include "components/infinisleep/InfiniSleepController.h" +#include "components/motor/MotorController.h" +#include "systemtask/SystemTask.h" + +#include +#include + +using namespace Pinetime::Applications::Screens; + +namespace { + void ValueChangedHandler(void* userData) { + auto* screen = static_cast(userData); + screen->OnValueChanged(); + } + + void btnEventHandler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + screen->OnButtonEvent(obj, event); + } + + void SnoozeAlarmTaskCallback(lv_task_t* task) { + lv_task_set_prio(task, LV_TASK_PRIO_OFF); + auto* screen = static_cast(task->user_data); + screen->ignoreButtonPush = true; + screen->OnButtonEvent(screen->btnSnooze, LV_EVENT_CLICKED); + screen->ignoreButtonPush = false; + } + + void PressesToStopAlarmTimeoutCallback(lv_task_t* task) { + auto* screen = static_cast(task->user_data); + screen->infiniSleepController.pushesLeftToStopWakeAlarm = screen->infiniSleepController.infiniSleepSettings.pushesToStopAlarm; + screen->UpdateDisplay(); + } +} + +Sleep::Sleep(Controllers::InfiniSleepController& infiniSleepController, + Controllers::Settings::ClockType clockType, + System::SystemTask& systemTask, + Controllers::MotorController& motorController, + DisplayApp& displayApp) + : infiniSleepController {infiniSleepController}, + wakeLock(systemTask), + motorController {motorController}, + clockType {clockType}, + displayApp {displayApp} { + + infiniSleepController.infiniSleepSettings.heartRateTracking = false; + infiniSleepController.SetSettingsChanged(); + UpdateDisplay(); + taskRefresh = lv_task_create(RefreshTaskCallback, 2000, LV_TASK_PRIO_MID, this); + taskPressesToStopAlarmTimeout = + lv_task_create(PressesToStopAlarmTimeoutCallback, PUSHES_TO_STOP_ALARM_TIMEOUT * 1000, LV_TASK_PRIO_MID, this); + infiniSleepController.infiniSleepSettings.sleepCycleDuration = 90; + infiniSleepController.SetSettingsChanged(); + + if (!infiniSleepController.IsEnabled()) { + infiniSleepController.prevBrightnessLevel = infiniSleepController.GetBrightnessController().Level(); + } + infiniSleepController.GetBrightnessController().Set(Controllers::BrightnessController::Levels::Low); +} + +Sleep::~Sleep() { + if (infiniSleepController.IsAlerting()) { + StopAlerting(); + } + lv_task_del(taskRefresh); + lv_task_del(taskPressesToStopAlarmTimeout); + if (taskSnoozeWakeAlarm != nullptr) { + lv_task_del(taskSnoozeWakeAlarm); + } + lv_obj_clean(lv_scr_act()); + infiniSleepController.SaveWakeAlarm(); + infiniSleepController.SaveInfiniSleepSettings(); + if (!infiniSleepController.IsEnabled()) { + infiniSleepController.GetBrightnessController().Set(infiniSleepController.prevBrightnessLevel); + } +} + +void Sleep::DisableWakeAlarm() { + if (infiniSleepController.GetWakeAlarm().isEnabled) { + infiniSleepController.DisableWakeAlarm(); + lv_switch_off(enableSwitch, LV_ANIM_ON); + } +} + +void Sleep::Refresh() { + UpdateDisplay(); +} + +void Sleep::UpdateDisplay() { + if (infiniSleepController.IsAlerting() != true && lastDisplayState == displayState && displayState == SleepDisplayState::Alarm) { + return; + } + + lv_task_reset(taskRefresh); + + // Clear the screen + lv_obj_clean(lv_scr_act()); + if (infiniSleepController.IsAlerting()) { + displayState = SleepDisplayState::Alarm; + } + // Draw the screen + switch (displayState) { + case SleepDisplayState::Alarm: + DrawAlarmScreen(); + pageIndicator2.Create(); + break; + case SleepDisplayState::Info: + DrawInfoScreen(); + pageIndicator1.Create(); + break; + case SleepDisplayState::Settings: + DrawSettingsScreen(); + pageIndicator3.Create(); + break; + } + + if (alreadyAlerting) { + RedrawSetAlerting(); + return; + } + + if (infiniSleepController.IsAlerting()) { + SetAlerting(); + } else if (displayState == SleepDisplayState::Alarm) { + SetSwitchState(LV_ANIM_OFF); + } +} + +void Sleep::DrawAlarmScreen() { + 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(infiniSleepController.GetWakeAlarm().hours); + hourCounter.SetValueChangedEventCallback(this, ValueChangedHandler); + + minuteCounter.Create(); + lv_obj_align(minuteCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); + minuteCounter.SetValue(infiniSleepController.GetWakeAlarm().minutes); + minuteCounter.SetValueChangedEventCallback(this, ValueChangedHandler); + + 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); + + if (infiniSleepController.IsAlerting()) { + lv_obj_set_hidden(hourCounter.GetObject(), true); + lv_obj_set_hidden(minuteCounter.GetObject(), true); + lv_obj_set_hidden(colonLabel, true); + + lv_obj_t* lblTime = lv_label_create(lv_scr_act(), nullptr); + if (clockType == Controllers::Settings::ClockType::H24) { + lv_label_set_text_fmt(lblTime, "%02d:%02d", infiniSleepController.GetCurrentHour(), infiniSleepController.GetCurrentMinute()); + } else { + lv_label_set_text_fmt(lblTime, + "%02d:%02d", + (infiniSleepController.GetCurrentHour() % 12 == 0) ? 12 : infiniSleepController.GetCurrentHour() % 12, + infiniSleepController.GetCurrentMinute()); + } + lv_obj_align(lblTime, lv_scr_act(), LV_ALIGN_CENTER, -87, -100); + lv_obj_set_style_local_text_color(lblTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_label_set_align(lblTime, LV_LABEL_ALIGN_CENTER); + lv_obj_set_style_local_text_font(lblTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); + + lv_obj_t* lblWaketxt = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(lblWaketxt, "Wake Up!"); + lv_obj_align(lblWaketxt, lv_scr_act(), LV_ALIGN_CENTER, 0, -22); + lv_label_set_align(lblWaketxt, LV_LABEL_ALIGN_CENTER); + lv_obj_set_style_local_text_font(lblWaketxt, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + lv_obj_set_style_local_text_color(lblWaketxt, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + } + + btnSnooze = lv_btn_create(lv_scr_act(), nullptr); + btnSnooze->user_data = this; + lv_obj_set_event_cb(btnSnooze, btnEventHandler); + lv_obj_set_size(btnSnooze, 200, 63); + lv_obj_align(btnSnooze, lv_scr_act(), LV_ALIGN_CENTER, 0, 28); + lv_obj_set_style_local_bg_color(btnSnooze, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); + txtSnooze = lv_label_create(btnSnooze, nullptr); + lv_label_set_text_static(txtSnooze, "Snooze"); + lv_obj_set_hidden(btnSnooze, true); + + btnStop = lv_btn_create(lv_scr_act(), nullptr); + btnStop->user_data = this; + lv_obj_set_event_cb(btnStop, btnEventHandler); + lv_obj_set_size(btnStop, 130, 50); + lv_obj_align(btnStop, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0); + lv_obj_set_style_local_bg_color(btnStop, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); + txtStop = lv_label_create(btnStop, nullptr); + lv_label_set_text_fmt(txtStop, + "Stop: %d/%d", + infiniSleepController.infiniSleepSettings.pushesToStopAlarm - infiniSleepController.pushesLeftToStopWakeAlarm, + infiniSleepController.infiniSleepSettings.pushesToStopAlarm); + lv_obj_set_hidden(btnStop, true); + + static constexpr lv_color_t bgColor = Colors::bgAlt; + + btnSuggestedAlarm = lv_btn_create(lv_scr_act(), nullptr); + btnSuggestedAlarm->user_data = this; + lv_obj_set_event_cb(btnSuggestedAlarm, btnEventHandler); + lv_obj_set_size(btnSuggestedAlarm, 115, 50); + lv_obj_align(btnSuggestedAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); + // txtSuggestedAlarm = lv_label_create(btnSuggestedAlarm, nullptr); + // lv_label_set_text_static(txtSuggestedAlarm, "Use Sugg.\nAlarmTime"); + + txtSuggestedAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_align(txtSuggestedAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -13); + lv_label_set_text_static(txtSuggestedAlarm, "Auto"); + + iconSuggestedAlarm = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(iconSuggestedAlarm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_align(iconSuggestedAlarm, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -50, -13); + lv_label_set_text_static(iconSuggestedAlarm, Symbols::sun); + + enableSwitch = lv_switch_create(lv_scr_act(), nullptr); + enableSwitch->user_data = this; + lv_obj_set_event_cb(enableSwitch, btnEventHandler); + lv_obj_set_size(enableSwitch, 100, 50); + // Align to the center of 115px from edge + lv_obj_align(enableSwitch, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 7, 0); + lv_obj_set_style_local_bg_color(enableSwitch, LV_SWITCH_PART_BG, LV_STATE_DEFAULT, bgColor); + + UpdateWakeAlarmTime(); +} + +void Sleep::DrawInfoScreen() { + lv_obj_t* lblTime = lv_label_create(lv_scr_act(), nullptr); + if (clockType == Controllers::Settings::ClockType::H24) { + lv_label_set_text_fmt(lblTime, "%02d:%02d", infiniSleepController.GetCurrentHour(), infiniSleepController.GetCurrentMinute()); + } else { + lv_label_set_text_fmt(lblTime, + "%02d:%02d", + (infiniSleepController.GetCurrentHour() % 12 == 0) ? 12 : infiniSleepController.GetCurrentHour() % 12, + infiniSleepController.GetCurrentMinute()); + } + lv_obj_align(lblTime, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 5); + lv_obj_set_style_local_text_color(lblTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + + // Total sleep time + label_total_sleep = lv_label_create(lv_scr_act(), nullptr); + + const uint16_t totalMinutes = infiniSleepController.GetTotalSleep(); + + lv_label_set_text_fmt(label_total_sleep, "Time Asleep: %dh%dm", totalMinutes / 60, totalMinutes % 60); + lv_obj_align(label_total_sleep, lv_scr_act(), LV_ALIGN_CENTER, 0, -60); + lv_obj_set_style_local_text_color(label_total_sleep, + LV_LABEL_PART_MAIN, + LV_STATE_DEFAULT, + infiniSleepController.IsEnabled() ? LV_COLOR_RED : LV_COLOR_WHITE); + + // Sleep Cycles Info + label_sleep_cycles = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_fmt(label_sleep_cycles, + "Sleep Cycles: %d.%02d", + infiniSleepController.GetSleepCycles() / 100, + infiniSleepController.GetSleepCycles() % 100); + lv_obj_align(label_sleep_cycles, lv_scr_act(), LV_ALIGN_CENTER, 0, -40); + lv_obj_set_style_local_text_color(label_sleep_cycles, + LV_LABEL_PART_MAIN, + LV_STATE_DEFAULT, + infiniSleepController.IsEnabled() ? LV_COLOR_RED : LV_COLOR_WHITE); + + // Start time + if (infiniSleepController.IsEnabled()) { + label_start_time = lv_label_create(lv_scr_act(), nullptr); + if (clockType == Controllers::Settings::ClockType::H24) { + lv_label_set_text_fmt(label_start_time, + "Began at: %02d:%02d", + infiniSleepController.prevSessionData.startTimeHours, + infiniSleepController.prevSessionData.startTimeMinutes); + } else { + lv_label_set_text_fmt( + label_start_time, + "Began at: %02d:%02d", + (infiniSleepController.prevSessionData.startTimeHours % 12 == 0) ? 12 : infiniSleepController.prevSessionData.startTimeHours % 12, + infiniSleepController.prevSessionData.startTimeMinutes); + } + lv_obj_align(label_start_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_text_color(label_start_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); + } + + // The alarm info + label_alarm_time = lv_label_create(lv_scr_act(), nullptr); + if (infiniSleepController.GetWakeAlarm().isEnabled) { + if (clockType == Controllers::Settings::ClockType::H24) { + lv_label_set_text_fmt(label_alarm_time, + "Alarm at: %02d:%02d", + infiniSleepController.GetWakeAlarm().hours, + infiniSleepController.GetWakeAlarm().minutes); + } else { + lv_label_set_text_fmt(label_alarm_time, + "Alarm at: %02d:%02d", + (infiniSleepController.GetWakeAlarm().hours % 12 == 0) ? 12 : infiniSleepController.GetWakeAlarm().hours % 12, + infiniSleepController.GetWakeAlarm().minutes); + } + } else { + lv_label_set_text_static(label_alarm_time, "Alarm is not set."); + } + lv_obj_align(label_alarm_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 20); + lv_obj_set_style_local_text_color(label_alarm_time, + LV_LABEL_PART_MAIN, + LV_STATE_DEFAULT, + infiniSleepController.IsEnabled() ? LV_COLOR_RED : LV_COLOR_WHITE); + + // Wake Mode info + if (infiniSleepController.GetWakeAlarm().isEnabled) { + label_gradual_wake = lv_label_create(lv_scr_act(), nullptr); + if (infiniSleepController.infiniSleepSettings.graddualWake && infiniSleepController.infiniSleepSettings.naturalWake) { + lv_label_set_text_static(label_gradual_wake, "Wake Mode: Both"); + } else if (infiniSleepController.infiniSleepSettings.graddualWake) { + lv_label_set_text_static(label_gradual_wake, "Wake Mode: PreWake"); + } else if (infiniSleepController.infiniSleepSettings.naturalWake) { + lv_label_set_text_static(label_gradual_wake, "Wake Mode: Natural"); + } else { + lv_label_set_text_static(label_gradual_wake, "Wake Mode: Normal"); + } + lv_obj_align(label_gradual_wake, lv_scr_act(), LV_ALIGN_CENTER, 0, 40); + lv_obj_set_style_local_text_color(label_gradual_wake, + LV_LABEL_PART_MAIN, + LV_STATE_DEFAULT, + infiniSleepController.IsEnabled() ? LV_COLOR_RED : LV_COLOR_WHITE); + } + + // Start/Stop button + trackerToggleBtn = lv_btn_create(lv_scr_act(), nullptr); + trackerToggleBtn->user_data = this; + lv_obj_set_height(trackerToggleBtn, 50); + lv_obj_align(trackerToggleBtn, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + // Tracker toggle button + trackerToggleLabel = lv_label_create(trackerToggleBtn, nullptr); + if (infiniSleepController.IsTrackerEnabled()) { + lv_label_set_text_static(trackerToggleLabel, "Stop"); + lv_obj_set_style_local_bg_color(trackerToggleBtn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); + } else { + lv_label_set_text_static(trackerToggleLabel, "Start"); + lv_obj_set_style_local_bg_color(trackerToggleBtn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN); + } + lv_obj_set_event_cb(trackerToggleBtn, btnEventHandler); +} + +void Sleep::DrawSettingsScreen() { + // lv_obj_t* lblSettings = lv_label_create(lv_scr_act(), nullptr); + // lv_label_set_text_static(lblSettings, "Settings"); + // lv_obj_align(lblSettings, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 10); + + if (infiniSleepController.wakeAlarm.isEnabled) { + lv_obj_t* lblWarning = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(lblWarning, "Disable alarm to\nchange settings."); + lv_obj_align(lblWarning, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_local_text_color(lblWarning, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED); + return; + } + + uint8_t y_offset = 10; + + lblWakeMode = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(lblWakeMode, "Wake\nMode"); + lv_obj_align(lblWakeMode, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, y_offset); + + btnWakeMode = lv_btn_create(lv_scr_act(), nullptr); + lv_obj_set_size(btnWakeMode, 100, 50); + lv_obj_align(btnWakeMode, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 130, y_offset); + btnWakeMode->user_data = this; + lv_obj_set_event_cb(btnWakeMode, btnEventHandler); + const char* mode = (infiniSleepController.infiniSleepSettings.graddualWake && infiniSleepController.infiniSleepSettings.naturalWake) + ? "Both" + : infiniSleepController.infiniSleepSettings.graddualWake ? "Pre." + : infiniSleepController.infiniSleepSettings.naturalWake ? "Nat." + : "Norm."; + lblWakeModeValue = lv_label_create(btnWakeMode, nullptr); + lv_label_set_text_static(lblWakeModeValue, mode); + lv_obj_align(lblWakeModeValue, nullptr, LV_ALIGN_CENTER, 0, 0); + + y_offset += 60; // Adjust the offset for the next UI element + + lblCycles = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(lblCycles, "Desired\nCycles"); + lv_obj_align(lblCycles, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, y_offset); + + btnCycles = lv_btn_create(lv_scr_act(), nullptr); + lv_obj_set_size(btnCycles, 100, 50); + lv_obj_align(btnCycles, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 130, y_offset); + btnCycles->user_data = this; + lv_obj_set_event_cb(btnCycles, btnEventHandler); + + lblCycleValue = lv_label_create(btnCycles, nullptr); + lv_label_set_text_fmt(lblCycleValue, "%d", infiniSleepController.infiniSleepSettings.desiredCycles); + lv_obj_align(lblCycleValue, nullptr, LV_ALIGN_CENTER, 0, 0); + + infiniSleepController.infiniSleepSettings.sleepCycleDuration = 90; + infiniSleepController.SetSettingsChanged(); + + y_offset += 60; // Adjust the offset for the next UI element + + btnTestMotorGradual = lv_btn_create(lv_scr_act(), nullptr); + lv_obj_set_size(btnTestMotorGradual, 110, 50); + lv_obj_align(btnTestMotorGradual, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, y_offset); + btnTestMotorGradual->user_data = this; + lv_obj_set_event_cb(btnTestMotorGradual, btnEventHandler); + + lblMotorStrength = lv_label_create(btnTestMotorGradual, nullptr); + lv_label_set_text_static(lblMotorStrength, "Motor\nPower"); + lv_obj_align(lblMotorStrength, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 0, 0); + + btnMotorStrength = lv_btn_create(lv_scr_act(), nullptr); + lv_obj_set_size(btnMotorStrength, 100, 50); + lv_obj_align(btnMotorStrength, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 130, y_offset); + btnMotorStrength->user_data = this; + lv_obj_set_event_cb(btnMotorStrength, btnEventHandler); + + lblMotorStrengthValue = lv_label_create(btnMotorStrength, nullptr); + lv_label_set_text_fmt(lblMotorStrengthValue, "%d", infiniSleepController.infiniSleepSettings.motorStrength); + motorController.infiniSleepMotorStrength = infiniSleepController.infiniSleepSettings.motorStrength; + lv_obj_align(lblMotorStrengthValue, nullptr, LV_ALIGN_CENTER, 0, 0); + + y_offset += 60; // Adjust the offset for the next UI element + + lblPushesToStop = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(lblPushesToStop, "Pushes\nto Stop"); + lv_obj_align(lblPushesToStop, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, y_offset); + + btnPushesToStop = lv_btn_create(lv_scr_act(), nullptr); + lv_obj_set_size(btnPushesToStop, 100, 50); + lv_obj_align(btnPushesToStop, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 130, y_offset); + btnPushesToStop->user_data = this; + lv_obj_set_event_cb(btnPushesToStop, btnEventHandler); + + lblPushesToStopValue = lv_label_create(btnPushesToStop, nullptr); + lv_label_set_text_fmt(lblPushesToStopValue, "%d", infiniSleepController.infiniSleepSettings.pushesToStopAlarm); + lv_obj_align(lblPushesToStopValue, nullptr, LV_ALIGN_CENTER, 0, 0); +} + +void Sleep::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_CLICKED) { + if (obj == btnSnooze) { + StopAlerting(); + UpdateDisplay(); + SnoozeWakeAlarm(); + displayState = SleepDisplayState::Info; + UpdateDisplay(); + return; + } + if (obj == btnStop) { + StopAlarmPush(); + return; + } + if (obj == enableSwitch) { + if (lv_switch_get_state(enableSwitch)) { + infiniSleepController.ScheduleWakeAlarm(); + } else { + infiniSleepController.DisableWakeAlarm(); + } + if (infiniSleepController.isSnoozing) { + infiniSleepController.RestorePreSnoozeTime(); + } + infiniSleepController.isSnoozing = false; + return; + } + if (obj == trackerToggleBtn) { + infiniSleepController.ToggleTracker(); + UpdateDisplay(); + return; + } + if (obj == btnSuggestedAlarm) { + // Set the suggested time + const uint16_t totalSuggestedMinutes = infiniSleepController.GetSuggestedSleepTime(); + const uint8_t suggestedHours = totalSuggestedMinutes / 60; + const uint8_t suggestedMinutes = totalSuggestedMinutes % 60; + + // Time for alarm, current time + suggested sleep time + const uint8_t alarmHour = (infiniSleepController.GetCurrentHour() + suggestedHours) % 24; + const uint8_t alarmMinute = (infiniSleepController.GetCurrentMinute() + suggestedMinutes) % 60; + + infiniSleepController.SetWakeAlarmTime(alarmHour, alarmMinute); + + hourCounter.SetValue(alarmHour); + minuteCounter.SetValue(alarmMinute); + + OnValueChanged(); + infiniSleepController.ScheduleWakeAlarm(); + SetSwitchState(LV_ANIM_OFF); + return; + } + if (obj == btnWakeMode) { + if (infiniSleepController.infiniSleepSettings.graddualWake && infiniSleepController.infiniSleepSettings.naturalWake) { + infiniSleepController.infiniSleepSettings.graddualWake = false; + infiniSleepController.infiniSleepSettings.naturalWake = false; + } else if (infiniSleepController.infiniSleepSettings.graddualWake) { + infiniSleepController.infiniSleepSettings.graddualWake = false; + infiniSleepController.infiniSleepSettings.naturalWake = true; + } else if (infiniSleepController.infiniSleepSettings.naturalWake) { + infiniSleepController.infiniSleepSettings.naturalWake = true; + infiniSleepController.infiniSleepSettings.graddualWake = true; + } else if (!infiniSleepController.infiniSleepSettings.graddualWake && !infiniSleepController.infiniSleepSettings.naturalWake) { + infiniSleepController.infiniSleepSettings.graddualWake = true; + infiniSleepController.infiniSleepSettings.naturalWake = false; + } + infiniSleepController.SetSettingsChanged(); + const char* mode = (infiniSleepController.infiniSleepSettings.graddualWake && infiniSleepController.infiniSleepSettings.naturalWake) + ? "Both" + : infiniSleepController.infiniSleepSettings.graddualWake ? "Pre." + : infiniSleepController.infiniSleepSettings.naturalWake ? "Nat." + : "Norm."; + lv_label_set_text_static(lv_obj_get_child(obj, nullptr), mode); + return; + } + if (obj == btnCycles) { + uint8_t value = infiniSleepController.infiniSleepSettings.desiredCycles; + value = (value % 10) + 1; // Cycle through values 1 to 10 + infiniSleepController.infiniSleepSettings.desiredCycles = value; + infiniSleepController.SetSettingsChanged(); + lv_label_set_text_fmt(lv_obj_get_child(obj, nullptr), "%d", value); + return; + } + if (obj == btnTestMotorGradual) { + motorController.GradualWakeBuzz(); + return; + } + if (obj == btnMotorStrength) { + uint8_t value = infiniSleepController.infiniSleepSettings.motorStrength; + value += 25; + if (value > 200) { + value = 100; + } + infiniSleepController.infiniSleepSettings.motorStrength = value; + infiniSleepController.SetSettingsChanged(); + lv_label_set_text_fmt(lv_obj_get_child(obj, nullptr), "%d", value); + motorController.infiniSleepMotorStrength = value; + motorController.GradualWakeBuzz(); + return; + } + if (obj == btnPushesToStop) { + uint8_t value = infiniSleepController.infiniSleepSettings.pushesToStopAlarm; + value = (value % 10) + 1; // Cycle through values 1 to 10 + infiniSleepController.infiniSleepSettings.pushesToStopAlarm = value; + infiniSleepController.SetSettingsChanged(); + lv_label_set_text_fmt(lv_obj_get_child(obj, nullptr), "%d", value); + return; + } + } +} + +bool Sleep::OnButtonPushed() { + if (ignoreButtonPush) { + return true; + } + // Side button to snooze + if (infiniSleepController.IsAlerting() && displayState == SleepDisplayState::Alarm) { + OnButtonEvent(btnSnooze, LV_EVENT_CLICKED); + return true; + } + if (displayState != SleepDisplayState::Info) { + displayState = SleepDisplayState::Info; + UpdateDisplay(); + return true; + } + return false; +} + +bool Sleep::StopAlarmPush() { + if (infiniSleepController.pushesLeftToStopWakeAlarm > 1) { + lv_task_reset(taskPressesToStopAlarmTimeout); + infiniSleepController.pushesLeftToStopWakeAlarm--; + UpdateDisplay(); + return true; + } + + if (infiniSleepController.isSnoozing) { + infiniSleepController.RestorePreSnoozeTime(); + } + infiniSleepController.isSnoozing = false; + StopAlerting(); + if (infiniSleepController.IsTrackerEnabled()) { + displayState = SleepDisplayState::Info; + UpdateDisplay(); + infiniSleepController.ToggleTracker(); + UpdateDisplay(); + return true; + } + displayState = SleepDisplayState::Info; + UpdateDisplay(); + return true; +} + +bool Sleep::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + + // Swiping should be ignored when in alerting state + if (infiniSleepController.IsAlerting() && (event == TouchEvents::SwipeDown || event == TouchEvents::SwipeUp || + event == TouchEvents::SwipeLeft || event == TouchEvents::SwipeRight)) { + return true; + } + + lastDisplayState = displayState; + NRF_LOG_INFO("Last Display State: %d", static_cast(lastDisplayState)); + + // The cases for swiping to change page on app + switch (event) { + case TouchEvents::SwipeDown: + if (static_cast(displayState) != 0) { + displayApp.SetFullRefresh(Pinetime::Applications::DisplayApp::FullRefreshDirections::Down); + displayState = static_cast(static_cast(displayState) - 1); + UpdateDisplay(); + } else { + return false; + } + NRF_LOG_INFO("SwipeDown: %d", static_cast(displayState)); + return true; + case TouchEvents::SwipeUp: + if (static_cast(displayState) != 2) { + displayApp.SetFullRefresh(Pinetime::Applications::DisplayApp::FullRefreshDirections::Up); + displayState = static_cast(static_cast(displayState) + 1); + UpdateDisplay(); + } + NRF_LOG_INFO("SwipeUp: %d", static_cast(displayState)); + return true; + default: + break; + } + + // Don't allow closing the screen by swiping while the alarm is alerting + return infiniSleepController.IsAlerting() && event == TouchEvents::SwipeDown; +} + +void Sleep::OnValueChanged() { + DisableWakeAlarm(); + UpdateWakeAlarmTime(); +} + +// Currently snoozes baeed on define statement in InfiniSleepController.h +void Sleep::SnoozeWakeAlarm() { + if (taskSnoozeWakeAlarm != nullptr) { + lv_task_del(taskSnoozeWakeAlarm); + taskSnoozeWakeAlarm = nullptr; + } + + NRF_LOG_INFO("Snoozing alarm for %d minutes", SNOOZE_MINUTES); + + const uint16_t totalAlarmMinutes = infiniSleepController.GetCurrentHour() * 60 + infiniSleepController.GetCurrentMinute(); + const uint16_t newSnoozeMinutes = totalAlarmMinutes + SNOOZE_MINUTES; + + if (infiniSleepController.isSnoozing != true) { + infiniSleepController.SetPreSnoozeTime(); + } + infiniSleepController.isSnoozing = true; + + infiniSleepController.SetWakeAlarmTime(newSnoozeMinutes / 60, newSnoozeMinutes % 60); + + hourCounter.SetValue(newSnoozeMinutes / 60); + minuteCounter.SetValue(newSnoozeMinutes % 60); + + infiniSleepController.ScheduleWakeAlarm(); +} + +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"); + } + } + infiniSleepController.SetWakeAlarmTime(hourCounter.GetValue(), minuteCounter.GetValue()); + SetSwitchState(LV_ANIM_OFF); +} + +void Sleep::SetAlerting() { + lv_obj_set_hidden(enableSwitch, true); + lv_obj_set_hidden(btnSnooze, false); + lv_obj_set_hidden(btnStop, false); + lv_obj_set_hidden(btnSuggestedAlarm, true); + lv_obj_set_hidden(txtSuggestedAlarm, true); + lv_obj_set_hidden(iconSuggestedAlarm, true); + NRF_LOG_INFO("Alarm is alerting"); + if (!infiniSleepController.infiniSleepSettings.naturalWake) { + if (taskSnoozeWakeAlarm != nullptr) { + lv_task_del(taskSnoozeWakeAlarm); + taskSnoozeWakeAlarm = nullptr; + } + taskSnoozeWakeAlarm = lv_task_create(SnoozeAlarmTaskCallback, 120 * 1000, LV_TASK_PRIO_MID, this); + } + if (infiniSleepController.infiniSleepSettings.naturalWake) { + motorController.StartNaturalWakeAlarm(); + } else { + motorController.StartWakeAlarm(); + } + wakeLock.Lock(); + alreadyAlerting = true; +} + +void Sleep::RedrawSetAlerting() { + lv_obj_set_hidden(enableSwitch, true); + lv_obj_set_hidden(btnSnooze, false); + lv_obj_set_hidden(btnStop, false); + lv_obj_set_hidden(btnSuggestedAlarm, true); + lv_obj_set_hidden(txtSuggestedAlarm, true); + lv_obj_set_hidden(iconSuggestedAlarm, true); + wakeLock.Lock(); +} + +void Sleep::StopAlerting(bool setSwitch) { + if (taskSnoozeWakeAlarm != nullptr) { + lv_task_del(taskSnoozeWakeAlarm); + taskSnoozeWakeAlarm = nullptr; + } + infiniSleepController.StopAlerting(); + if (infiniSleepController.infiniSleepSettings.naturalWake) { + motorController.StopNaturalWakeAlarm(); + } else { + motorController.StopWakeAlarm(); + } + if (setSwitch) { + SetSwitchState(LV_ANIM_OFF); + } + wakeLock.Release(); + lv_obj_set_hidden(enableSwitch, false); + lv_obj_set_hidden(btnSnooze, true); + lv_obj_set_hidden(btnStop, true); + lv_obj_set_hidden(btnSuggestedAlarm, false); + lv_obj_set_hidden(txtSuggestedAlarm, false); + lv_obj_set_hidden(iconSuggestedAlarm, false); + alreadyAlerting = false; +} + +void Sleep::SetSwitchState(lv_anim_enable_t anim) { + if (displayState == SleepDisplayState::Alarm && infiniSleepController.GetWakeAlarm().isEnabled) { + lv_switch_on(enableSwitch, anim); + } else { + lv_switch_off(enableSwitch, anim); + } +} \ No newline at end of file diff --git a/src/displayapp/screens/Sleep.h b/src/displayapp/screens/Sleep.h new file mode 100644 index 0000000000..13f8816798 --- /dev/null +++ b/src/displayapp/screens/Sleep.h @@ -0,0 +1,107 @@ +#pragma once + +#include "displayapp/apps/Apps.h" +#include "components/settings/Settings.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/widgets/Counter.h" +#include "displayapp/widgets/PageIndicator.h" +#include "displayapp/Controllers.h" +#include "systemtask/SystemTask.h" +#include "systemtask/WakeLock.h" +#include "Symbols.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Sleep : public Screen { + public: + explicit Sleep(Controllers::InfiniSleepController& infiniSleepController, + Controllers::Settings::ClockType clockType, + System::SystemTask& systemTask, + Controllers::MotorController& motorController, + DisplayApp& displayApp); + ~Sleep() override; + void Refresh() override; + void SetAlerting(); + void RedrawSetAlerting(); + void OnButtonEvent(lv_obj_t* obj, lv_event_t event); + bool OnButtonPushed() override; + bool OnTouchEvent(TouchEvents event) override; + void OnValueChanged(); + void StopAlerting(bool setSwitch = true); + void SnoozeWakeAlarm(); + void UpdateDisplay(); + enum class SleepDisplayState { Info, Alarm, Settings }; + SleepDisplayState displayState = SleepDisplayState::Info; + SleepDisplayState lastDisplayState = SleepDisplayState::Info; + + Controllers::InfiniSleepController& infiniSleepController; + + bool ignoreButtonPush = false; + + lv_obj_t* btnSnooze; + + private: + System::WakeLock wakeLock; + Controllers::MotorController& motorController; + Controllers::Settings::ClockType clockType; + DisplayApp& displayApp; + + lv_obj_t *btnStop, *txtStop, *txtSnooze, /**btnRecur, *txtRecur,*/ *btnInfo, *enableSwitch; + lv_obj_t *trackerToggleBtn, *trackerToggleLabel; + lv_obj_t* lblampm = nullptr; + lv_obj_t* txtMessage = nullptr; + lv_obj_t* btnMessage = nullptr; + lv_task_t* taskSnoozeWakeAlarm = nullptr; + + lv_task_t* taskRefresh = nullptr; + + lv_task_t* taskPressesToStopAlarmTimeout = nullptr; + + // enum class EnableButtonState { On, Off, Alerting }; + void DisableWakeAlarm(); + void SetSwitchState(lv_anim_enable_t anim); + void SetWakeAlarm(); + void UpdateWakeAlarmTime(); + Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76); + Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); + + void DrawAlarmScreen(); + void DrawInfoScreen(); + void DrawSettingsScreen(); + bool StopAlarmPush(); + + bool alreadyAlerting = false; + + lv_obj_t* label_hr; + lv_obj_t* label_start_time; + lv_obj_t* label_alarm_time; + lv_obj_t* label_gradual_wake; + lv_obj_t* label_total_sleep; + lv_obj_t* label_sleep_cycles; + lv_obj_t *btnSuggestedAlarm, *txtSuggestedAlarm, *iconSuggestedAlarm; + + lv_obj_t *lblWakeMode, *btnWakeMode, *lblWakeModeValue, *lblCycles, *btnCycles, *lblCycleValue, *btnTestMotorGradual, + *lblMotorStrength, *btnMotorStrength, *lblMotorStrengthValue, *lblPushesToStop, *btnPushesToStop, *lblPushesToStopValue; + + Widgets::PageIndicator pageIndicator1 = Widgets::PageIndicator(0, 3); + Widgets::PageIndicator pageIndicator2 = Widgets::PageIndicator(1, 3); + Widgets::PageIndicator pageIndicator3 = Widgets::PageIndicator(2, 3); + }; + } + + 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.infiniSleepController, + controllers.settingsController.GetClockType(), + *controllers.systemTask, + controllers.motorController, + *controllers.displayApp); + } + }; + } +} \ No newline at end of file diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index bd958b285f..01e2c40e6c 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -39,6 +39,9 @@ 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* bed = "\xEF\x88\xB6"; + static constexpr const char* calculator = "\xEF\x87\xAC"; + static constexpr const char* backspace = "\xEF\x95\x9A"; // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; diff --git a/src/displayapp/screens/Tile.cpp b/src/displayapp/screens/Tile.cpp index 7c392c59e5..7c585a4b97 100644 --- a/src/displayapp/screens/Tile.cpp +++ b/src/displayapp/screens/Tile.cpp @@ -29,9 +29,13 @@ Tile::Tile(uint8_t screenID, Controllers::Settings& settingsController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, std::array& applications) - : app {app}, dateTimeController {dateTimeController}, pageIndicator(screenID, numScreens), statusIcons(batteryController, bleController) { + : app {app}, + dateTimeController {dateTimeController}, + pageIndicator(screenID, numScreens), + statusIcons(batteryController, bleController, alarmController) { settingsController.SetAppMenu(screenID); diff --git a/src/displayapp/screens/Tile.h b/src/displayapp/screens/Tile.h index f1b86246ce..c16151d0e1 100644 --- a/src/displayapp/screens/Tile.h +++ b/src/displayapp/screens/Tile.h @@ -28,6 +28,7 @@ namespace Pinetime { Controllers::Settings& settingsController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::DateTime& dateTimeController, std::array& applications); diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index d944117dd7..3163c6e750 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -19,6 +19,7 @@ using namespace Pinetime::Applications::Screens; WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, @@ -31,7 +32,7 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController, heartRateController {heartRateController}, motionController {motionController}, weatherService {weatherService}, - statusIcons(batteryController, bleController) { + statusIcons(batteryController, bleController, alarmController) { statusIcons.Create(); diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h index 7bb713cbb8..3005cea56f 100644 --- a/src/displayapp/screens/WatchFaceDigital.h +++ b/src/displayapp/screens/WatchFaceDigital.h @@ -17,6 +17,7 @@ namespace Pinetime { class Settings; class Battery; class Ble; + class AlarmController; class NotificationManager; class HeartRateController; class MotionController; @@ -30,6 +31,7 @@ namespace Pinetime { WatchFaceDigital(Controllers::DateTime& dateTimeController, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController, Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::HeartRateController& heartRateController, @@ -84,6 +86,7 @@ namespace Pinetime { return new Screens::WatchFaceDigital(controllers.dateTimeController, controllers.batteryController, controllers.bleController, + controllers.alarmController, controllers.notificationManager, controllers.settingsController, controllers.heartRateController, diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp index 4c6fc196ac..40f2abbbbc 100644 --- a/src/displayapp/screens/WatchFaceInfineat.cpp +++ b/src/displayapp/screens/WatchFaceInfineat.cpp @@ -434,12 +434,15 @@ void WatchFaceInfineat::Refresh() { batteryPercentRemaining = batteryController.PercentRemaining(); isCharging = batteryController.IsCharging(); - if (batteryController.IsCharging()) { // Charging battery animation - chargingBatteryPercent += 1; + // Charging battery animation + if (batteryController.IsCharging() && (xTaskGetTickCount() - chargingAnimationTick > pdMS_TO_TICKS(150))) { + // Dividing 100 by the height gives the battery percentage required to shift the animation by 1 pixel + chargingBatteryPercent += 100 / lv_obj_get_height(logoPine); if (chargingBatteryPercent > 100) { chargingBatteryPercent = batteryPercentRemaining.Get(); } SetBatteryLevel(chargingBatteryPercent); + chargingAnimationTick = xTaskGetTickCount(); } else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) { chargingBatteryPercent = batteryPercentRemaining.Get(); SetBatteryLevel(chargingBatteryPercent); diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h index 55c43f98e0..78d020f10b 100644 --- a/src/displayapp/screens/WatchFaceInfineat.h +++ b/src/displayapp/screens/WatchFaceInfineat.h @@ -47,6 +47,7 @@ namespace Pinetime { private: uint32_t savedTick = 0; uint8_t chargingBatteryPercent = 101; // not a mistake ;) + TickType_t chargingAnimationTick = 0; Utility::DirtyValue batteryPercentRemaining {}; Utility::DirtyValue isCharging {}; diff --git a/src/displayapp/screens/settings/QuickSettings.cpp b/src/displayapp/screens/settings/QuickSettings.cpp index 0548488855..c5c3071aef 100644 --- a/src/displayapp/screens/settings/QuickSettings.cpp +++ b/src/displayapp/screens/settings/QuickSettings.cpp @@ -33,13 +33,14 @@ QuickSettings::QuickSettings(Pinetime::Applications::DisplayApp* app, Controllers::BrightnessController& brightness, Controllers::MotorController& motorController, Pinetime::Controllers::Settings& settingsController, - const Controllers::Ble& bleController) + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController) : app {app}, dateTimeController {dateTimeController}, brightness {brightness}, motorController {motorController}, settingsController {settingsController}, - statusIcons(batteryController, bleController) { + statusIcons(batteryController, bleController, alarmController) { statusIcons.Create(); diff --git a/src/displayapp/screens/settings/QuickSettings.h b/src/displayapp/screens/settings/QuickSettings.h index 55da617629..87c126b7fa 100644 --- a/src/displayapp/screens/settings/QuickSettings.h +++ b/src/displayapp/screens/settings/QuickSettings.h @@ -23,7 +23,8 @@ namespace Pinetime { Controllers::BrightnessController& brightness, Controllers::MotorController& motorController, Pinetime::Controllers::Settings& settingsController, - const Controllers::Ble& bleController); + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController); ~QuickSettings() override; diff --git a/src/displayapp/widgets/StatusIcons.cpp b/src/displayapp/widgets/StatusIcons.cpp index 423b53d97a..777731a59f 100644 --- a/src/displayapp/widgets/StatusIcons.cpp +++ b/src/displayapp/widgets/StatusIcons.cpp @@ -1,10 +1,13 @@ #include "displayapp/widgets/StatusIcons.h" #include "displayapp/screens/Symbols.h" +#include "components/alarm/AlarmController.h" using namespace Pinetime::Applications::Widgets; -StatusIcons::StatusIcons(const Controllers::Battery& batteryController, const Controllers::Ble& bleController) - : batteryIcon(true), batteryController {batteryController}, bleController {bleController} { +StatusIcons::StatusIcons(const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController) + : batteryIcon(true), batteryController {batteryController}, bleController {bleController}, alarmController {alarmController} { } void StatusIcons::Create() { @@ -20,6 +23,9 @@ void StatusIcons::Create() { batteryPlug = lv_label_create(container, nullptr); lv_label_set_text_static(batteryPlug, Screens::Symbols::plug); + alarmIcon = lv_label_create(container, nullptr); + lv_label_set_text_static(alarmIcon, Screens::Symbols::bell); + batteryIcon.Create(container); lv_obj_align(container, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0); @@ -37,6 +43,11 @@ void StatusIcons::Update() { batteryIcon.SetBatteryPercentage(batteryPercent); } + alarmEnabled = alarmController.IsEnabled(); + if (alarmEnabled.IsUpdated()) { + lv_obj_set_hidden(alarmIcon, !alarmEnabled.Get()); + } + bleState = bleController.IsConnected(); bleRadioEnabled = bleController.IsRadioEnabled(); if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) { diff --git a/src/displayapp/widgets/StatusIcons.h b/src/displayapp/widgets/StatusIcons.h index 9e21d3add0..5524e996c5 100644 --- a/src/displayapp/widgets/StatusIcons.h +++ b/src/displayapp/widgets/StatusIcons.h @@ -5,6 +5,7 @@ #include "displayapp/screens/Screen.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" +#include "components/alarm/AlarmController.h" #include "displayapp/screens/BatteryIcon.h" #include "utility/DirtyValue.h" @@ -13,7 +14,9 @@ namespace Pinetime { namespace Widgets { class StatusIcons { public: - StatusIcons(const Controllers::Battery& batteryController, const Controllers::Ble& bleController); + StatusIcons(const Controllers::Battery& batteryController, + const Controllers::Ble& bleController, + const Controllers::AlarmController& alarmController); void Align(); void Create(); @@ -27,13 +30,16 @@ namespace Pinetime { Screens::BatteryIcon batteryIcon; const Pinetime::Controllers::Battery& batteryController; const Controllers::Ble& bleController; + const Controllers::AlarmController& alarmController; Utility::DirtyValue batteryPercentRemaining {}; Utility::DirtyValue powerPresent {}; Utility::DirtyValue bleState {}; Utility::DirtyValue bleRadioEnabled {}; + Utility::DirtyValue alarmEnabled {}; lv_obj_t* bleIcon; + lv_obj_t* alarmIcon; lv_obj_t* batteryPlug; lv_obj_t* container; }; diff --git a/src/main.cpp b/src/main.cpp index 24f13caddd..f60f592c63 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,10 +105,14 @@ Pinetime::Drivers::Watchdog watchdog; Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::MotionController motionController; Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs}; + Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; +Pinetime::Controllers::InfiniSleepController infiniSleepController {dateTimeController, fs, heartRateController, brightnessController}; + + Pinetime::Applications::DisplayApp displayApp(lcd, touchPanel, batteryController, @@ -124,7 +128,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd, brightnessController, touchHandler, fs, - spiNorFlash); + spiNorFlash, + infiniSleepController); Pinetime::System::SystemTask systemTask(spi, spiNorFlash, @@ -145,7 +150,8 @@ Pinetime::System::SystemTask systemTask(spi, heartRateApp, fs, touchHandler, - buttonHandler); + buttonHandler, + infiniSleepController); int mallocFailedCount = 0; int stackOverflowCount = 0; extern "C" { diff --git a/src/sdk_config.h b/src/sdk_config.h index b42b39244f..da44682d4b 100644 --- a/src/sdk_config.h +++ b/src/sdk_config.h @@ -5633,7 +5633,7 @@ // PWM_ENABLED - nrf_drv_pwm - PWM peripheral driver - legacy layer //========================================================== #ifndef PWM_ENABLED - #define PWM_ENABLED 0 + #define PWM_ENABLED 1 #endif // PWM_DEFAULT_CONFIG_OUT0_PIN - Out0 pin <0-31> @@ -5739,7 +5739,7 @@ // PWM2_ENABLED - Enable PWM2 instance #ifndef PWM2_ENABLED - #define PWM2_ENABLED 0 + #define PWM2_ENABLED 1 #endif // PWM_NRF52_ANOMALY_109_WORKAROUND_ENABLED - Enables nRF52 Anomaly 109 workaround for PWM. diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index fee94bb747..1f61239608 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -25,11 +25,14 @@ namespace Pinetime { OnChargingEvent, OnPairing, SetOffAlarm, + SetOffWakeAlarm, + SetOffGradualWake, MeasureBatteryTimerExpired, BatteryPercentageUpdated, StartFileTransfer, StopFileTransfer, - BleRadioEnableToggle + BleRadioEnableToggle, + SleepTrackerUpdate, }; } } diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index eb013d6d1a..df69091bef 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -51,7 +51,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, Pinetime::Applications::HeartRateTask& heartRateApp, Pinetime::Controllers::FS& fs, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::ButtonHandler& buttonHandler) + Pinetime::Controllers::ButtonHandler& buttonHandler, + Pinetime::Controllers::InfiniSleepController& infiniSleepController) : spi {spi}, spiNorFlash {spiNorFlash}, twiMaster {twiMaster}, @@ -80,7 +81,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, spiNorFlash, heartRateController, motionController, - fs) { + fs), + infiniSleepController {infiniSleepController} { } void SystemTask::Start() { @@ -128,6 +130,7 @@ void SystemTask::Work() { batteryController.Register(this); motionSensor.SoftReset(); alarmController.Init(this); + infiniSleepController.Init(this); // Reset the TWI device because the motion sensor chip most probably crashed it... twiMaster.Sleep(); @@ -199,12 +202,16 @@ void SystemTask::Work() { GoToRunning(); break; case Messages::GoToSleep: + infiniSleepController.pushesLeftToStopWakeAlarm = infiniSleepController.infiniSleepSettings.pushesToStopAlarm; GoToSleep(); break; case Messages::OnNewTime: if (alarmController.IsEnabled()) { alarmController.ScheduleAlarm(); } + if (infiniSleepController.GetWakeAlarm().isEnabled) { + infiniSleepController.ScheduleWakeAlarm(); + } break; case Messages::OnNewNotification: if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) { @@ -218,6 +225,15 @@ void SystemTask::Work() { GoToRunning(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); break; + case Messages::SetOffWakeAlarm: + // Code the screen trigger here + GoToRunning(); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::WakeAlarmTriggered); + break; + case Messages::SetOffGradualWake: + // GoToRunning(); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GradualWakeTriggered); + break; case Messages::BleConnected: displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); isBleDiscoveryTimerRunning = true; @@ -358,6 +374,9 @@ void SystemTask::Work() { nimbleController.DisableRadio(); } break; + case Messages::SleepTrackerUpdate: + displayApp.PushMessage(Pinetime::Applications::Display::Messages::SleepTrackerUpdate); + break; default: break; } diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 0060e36096..f456a8fd3e 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/infinisleep/InfiniSleepController.h" #include "components/fs/FS.h" #include "touchhandler/TouchHandler.h" #include "buttonhandler/ButtonHandler.h" @@ -72,7 +73,8 @@ namespace Pinetime { Pinetime::Applications::HeartRateTask& heartRateApp, Pinetime::Controllers::FS& fs, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::ButtonHandler& buttonHandler); + Pinetime::Controllers::ButtonHandler& buttonHandler, + Pinetime::Controllers::InfiniSleepController& infiniSleepController); void Start(); void PushMessage(Messages msg); @@ -116,6 +118,7 @@ namespace Pinetime { Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::ButtonHandler& buttonHandler; Pinetime::Controllers::NimbleController nimbleController; + Pinetime::Controllers::InfiniSleepController& infiniSleepController; static void Process(void* instance); void Work();