Compare commits

..

17 commits

Author SHA1 Message Date
D. Scott Boggs 423dd489ef Bump version 2024-10-19 16:06:12 -04:00
D. Scott Boggs d93d809255 Merge branch 'timerv' into v1.15.3 2024-10-19 16:04:58 -04:00
D. Scott Boggs 6e57b178e8 Merge branch 'heartrate-measurements-in-background' into v1.15.3 2024-10-19 07:00:30 -04:00
D. Scott Boggs a2a2b6a64d bump settings version 2024-10-19 06:31:11 -04:00
Dom Rodriguez c613bc7afd
feat: Introduce Flake for development and builds
This PR introduces a Nix flake, allowing for InfiniTime to be built as a
Flake, including a FHS development environment.

It's derived from #1850 and
c57c57f3c6.

We also introduce `flake-compat`, allowing for non-Flake Nix mahcines to
use the project as-is, both for building (`default.nix`), and
development (`shell.nix`).

Additionally, we introduce `.envrc`, meaning that with `direnv`, the Nix
Flake is activated automatically.

Fixes #1850.

Signed-off-by: Dom Rodriguez <shymega@shymega.org.uk>
2024-10-13 16:13:37 +01:00
NeroBurner 8598142c27
Remove unused submodule QCBOR (#2138)
The submodule isn't used anymore. Remove the submodule reference
completely.
2024-10-09 20:26:08 +02:00
NeroBurner a2356f2f4a
MusicService: add missing includes for TickType_t and xTaskGetTickCount (#2130)
Add `FreeRTOS.h` include for the directly used data type `TickType_t` in the header
and the function `xTaskGetTickCount` from FreeRTOS's `task.h`
2024-09-29 21:10:32 +02:00
NeroBurner 3db4e012ce
Remove unused pointer to DisplayApp member variables (#2125)
In the screens that use `DisplayApp *app` and pass it to a child item,
or use the reference just in the constructor. Afterwards the `app`
member is not used. So remove it from the private member variables.

Completely remove `app` parameter from `SettingDisplay` constructor as
it is unused.
2024-09-29 19:39:14 +02:00
NeroBurner a0cd439efc
Alarm persist to flash (#1367)
* AlarmController: Add saving alarm time to file

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

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

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

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

* alarmController: close .system dir after usage

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

---------

Co-authored-by: Galdor Takacs <g@ldor.de>
2024-09-28 08:14:08 +02:00
Patric Gruber 462ea11bd4 use interval as interval, instead of wait time 2024-09-22 20:55:45 +02:00
Reinhold Gschweicher 997e4cee8c Hrs3300: fix includes for std::begin/std::end
Fix for Hrs3300 PR about Atomic HRS reads:
https://github.com/InfiniTimeOrg/InfiniTime/pull/1845

We use `std::begin` and `std::end`, but we don't include one of the
headers that define those functions.
See https://en.cppreference.com/w/cpp/iterator/begin for a list of
headers that define `std::begin` and `std::end`.

Starting with GCC 14 this leads to a compilation error presumably
because they cleaned up their headers.

Fix code by inlcuding `<iterator>`
2024-09-22 16:15:48 +02:00
mark9064 ad3bf49c7b
Atomic HRS reads (#1845)
- Combine the reading of all `HRS3300` registers into one I2C read so data is not partial
- Downsizes both HRS and ALS to 16bit as the sensor does not generate larger than
  16bit values in its current configuration
  - Increasing the resolution by 1 bit doubles the sensor acquisition time,
    since we are already at 10Hz we are never going to use a higher resolution
  - The PPG algorithm buffers for ALS/HRS are already 16bit anyway
- Remove functions for setting gain / drive that are unused throughout the codebase
- Calculate constants with constexpr
2024-09-22 00:29:15 +02:00
mark9064 7ca0418c82 Refactor doNotGoToSleep to a wakelock counter 2024-09-21 22:45:57 +02:00
mark9064 c3d05901a0 Refactor SystemTask state handling for resilience
State transitions now happen immediately where possible
This simplifies state management in general,
and prevents bugs such as the chime issue from occurring in the first place
2024-09-21 21:08:07 +02:00
mark9064 b3756e45fa Remove unused method declarations 2024-09-21 21:08:07 +02:00
Eli Tan a266202831
notifications: Dismiss to watchface when empty (#1716)
Set `running` to false to flag end of watchface when there are no more
notifications left to display.

I found it slightly annoying that dismissing all notifications leaves me with
a "No notification to display" message. Instead of dismissing to a relatively
useless message, dismiss to watchface.
2024-09-18 22:31:15 +02:00
Davis Mosenkovs 98455aa54d Improve timer vibration 2024-06-30 22:37:01 +03:00
47 changed files with 435 additions and 297 deletions

3
.gitmodules vendored
View file

@ -4,9 +4,6 @@
[submodule "src/libs/littlefs"] [submodule "src/libs/littlefs"]
path = src/libs/littlefs path = src/libs/littlefs
url = https://github.com/littlefs-project/littlefs.git url = https://github.com/littlefs-project/littlefs.git
[submodule "src/libs/QCBOR"]
path = src/libs/QCBOR
url = https://github.com/laurencelundblade/QCBOR.git
[submodule "src/libs/arduinoFFT"] [submodule "src/libs/arduinoFFT"]
path = src/libs/arduinoFFT path = src/libs/arduinoFFT
url = https://github.com/kosme/arduinoFFT.git url = https://github.com/kosme/arduinoFFT.git

View file

@ -66,8 +66,5 @@
"streambuf": "cpp", "streambuf": "cpp",
"cinttypes": "cpp", "cinttypes": "cpp",
"typeinfo": "cpp" "typeinfo": "cpp"
}, }
"cSpell.words": [
"Pinetime"
]
} }

View file

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release") set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
project(pinetime VERSION 1.15.1 LANGUAGES C CXX ASM) project(pinetime VERSION 1.15.3 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)

View file

@ -16,11 +16,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1725634671, "lastModified": 1728492678,
"narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=", "narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c", "rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -32,28 +32,26 @@
with pkgs; { with pkgs; {
default = stdenv.mkDerivation rec { default = stdenv.mkDerivation rec {
name = "infinitime"; name = "infinitime";
version = "1.14.1";
src = fetchFromGitea { src = fetchFromGitHub {
domain = "git.techwork.zone"; owner = "InfiniTimeOrg";
owner = "scott";
repo = "InfiniTime"; repo = "InfiniTime";
rev = "1.15.1"; rev = "refs/tags/${version}";
hash = "sha256-9cy7qYRZrg7qZZwQzYeMIZivGLZ5uGUoTyPgHEvdnZ8="; hash = "sha256-IrsN+9LgEjgfoRR6H7FhsdLMK+GLxc41IBnSbdpwv/E=";
fetchSubmodules = true; fetchSubmodules = true;
}; };
nativeBuildInputs = [ nativeBuildInputs = [
cmake adafruit-nrfutil
nodePackages.lv_font_conv nodePackages.lv_font_conv
patch
python3 python3
python3.pkgs.cbor python3.pkgs.cbor
python3.pkgs.click python3.pkgs.click
python3.pkgs.cryptography python3.pkgs.cryptography
python3.pkgs.intelhex python3.pkgs.intelhex
python3.pkgs.pillow python3.pkgs.pillow
adafruit-nrfutil
patch
git
]; ];
postPatch = '' postPatch = ''
@ -86,27 +84,17 @@
}; };
}); });
in in
{ with pkgs; {
default = default =
pkgs.buildFHSUserEnv { mkShell {
name = "infinitime-devenv"; packages = [
# build tools (writeShellScriptBin "cmake_infinitime" ''
targetPkgs = pkgs: (with pkgs; [ ${cmake}/bin/cmake -DARM_NONE_EABI_TOOLCHAIN_PATH="${gcc-arm-embedded-10}" \
cmake
nodePackages.lv_font_conv
python3
python3.pkgs.cbor
python3.pkgs.click
python3.pkgs.cryptography
python3.pkgs.intelhex
adafruit-nrfutil
(pkgs.writeShellScriptBin "cmake_infinitime" ''
cmake -DARM_NONE_EABI_TOOLCHAIN_PATH="${pkgs.gcc-arm-embedded-10}" \
-DNRF5_SDK_PATH="${infinitime-nrf5-sdk}/share/nRF5_SDK" \ -DNRF5_SDK_PATH="${infinitime-nrf5-sdk}/share/nRF5_SDK" \
"$@" "$@"
'') '')
]); ninja
] ++ self.packages.${pkgs.system}.default.nativeBuildInputs;
}; };
}); });
}; };

View file

@ -479,6 +479,7 @@ list(APPEND SOURCE_FILES
systemtask/SystemTask.cpp systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp drivers/TwiMaster.cpp
heartratetask/HeartRateTask.cpp heartratetask/HeartRateTask.cpp
@ -543,6 +544,7 @@ list(APPEND RECOVERY_SOURCE_FILES
systemtask/SystemTask.cpp systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp systemtask/SystemMonitor.cpp
systemtask/WakeLock.cpp
drivers/TwiMaster.cpp drivers/TwiMaster.cpp
components/rle/RleDecoder.cpp components/rle/RleDecoder.cpp
components/heartrate/HeartRateController.cpp components/heartrate/HeartRateController.cpp
@ -661,6 +663,7 @@ set(INCLUDE_FILES
displayapp/InfiniTimeTheme.h displayapp/InfiniTimeTheme.h
systemtask/SystemTask.h systemtask/SystemTask.h
systemtask/SystemMonitor.h systemtask/SystemMonitor.h
systemtask/WakeLock.h
displayapp/screens/Symbols.h displayapp/screens/Symbols.h
drivers/TwiMaster.h drivers/TwiMaster.h
heartratetask/HeartRateTask.h heartratetask/HeartRateTask.h

View file

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

View file

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

View file

@ -124,9 +124,11 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) {
bootloaderSize, bootloaderSize,
applicationSize); applicationSize);
// wait until SystemTask has finished waking up all devices // Wait until SystemTask has disabled sleeping
while (systemTask.IsSleeping()) { // This isn't quite correct, as we don't actually know
vTaskDelay(50); // 50ms // if BleFirmwareUpdateStarted has been received yet
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
} }
dfuImage.Erase(); dfuImage.Erase();

View file

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

View file

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

View file

@ -454,9 +454,15 @@ void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
/* Wakeup Spi and SpiNorFlash before accessing the file system /* Wakeup Spi and SpiNorFlash before accessing the file system
* This should be fixed in the FS driver * This should be fixed in the FS driver
*/ */
systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
vTaskDelay(10);
// This isn't quite correct
// SystemTask could receive EnableSleeping right after passing this check
// We need some guarantee that the SystemTask has processed the above message
// before we can continue
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
}
lfs_file_t file_p; lfs_file_t file_p;

View file

@ -142,7 +142,7 @@ Ppg::Ppg() {
spectrum.fill(0.0f); spectrum.fill(0.0f);
} }
int8_t Ppg::Preprocess(uint32_t hrs, uint32_t als) { int8_t Ppg::Preprocess(uint16_t hrs, uint16_t als) {
if (dataIndex < dataLength) { if (dataIndex < dataLength) {
dataHRS[dataIndex++] = hrs; dataHRS[dataIndex++] = hrs;
} }

View file

@ -14,7 +14,7 @@ namespace Pinetime {
class Ppg { class Ppg {
public: public:
Ppg(); Ppg();
int8_t Preprocess(uint32_t hrs, uint32_t als); int8_t Preprocess(uint16_t hrs, uint16_t als);
int HeartRate(); int HeartRate();
void Reset(bool resetDaqBuffer); void Reset(bool resetDaqBuffer);
static constexpr int deltaTms = 100; static constexpr int deltaTms = 100;

View file

@ -53,7 +53,7 @@ namespace Pinetime {
enum class HeartRateBackgroundMeasurementInterval : uint8_t { enum class HeartRateBackgroundMeasurementInterval : uint8_t {
Off, Off,
Continuous, Continuous,
TenSeconds, FifteenSeconds,
ThirtySeconds, ThirtySeconds,
OneMinute, OneMinute,
FiveMinutes, FiveMinutes,

View file

@ -256,9 +256,20 @@ void DisplayApp::Refresh() {
isDimmed = true; isDimmed = true;
brightnessController.Set(Controllers::BrightnessController::Levels::Low); brightnessController.Set(Controllers::BrightnessController::Levels::Low);
} }
if (IsPastSleepTime()) { if (IsPastSleepTime() && uxQueueMessagesWaiting(msgQueue) == 0) {
systemTask->PushMessage(System::Messages::GoToSleep); PushMessageToSystemTask(System::Messages::GoToSleep);
state = States::Idle; // Can't set state to Idle here, something may send
// DisableSleeping before this GoToSleep arrives
// Instead we check we have no messages queued before sending GoToSleep
// This works as the SystemTask is higher priority than DisplayApp
// As soon as we send GoToSleep, SystemTask pre-empts DisplayApp
// Whenever DisplayApp is running again, it is guaranteed that
// SystemTask has handled the message
// If it responded, we will have a GoToSleep waiting in the queue
// By checking that there are no messages in the queue, we avoid
// resending GoToSleep when we already have a response
// SystemTask is resilient to duplicate messages, this is an
// optimisation to reduce pressure on the message queues
} }
} else if (isDimmed) { } else if (isDimmed) {
isDimmed = false; isDimmed = false;
@ -274,6 +285,9 @@ void DisplayApp::Refresh() {
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) { if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
switch (msg) { switch (msg) {
case Messages::GoToSleep: case Messages::GoToSleep:
if (state != States::Running) {
break;
}
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) { while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
brightnessController.Lower(); brightnessController.Lower();
vTaskDelay(100); vTaskDelay(100);
@ -308,6 +322,9 @@ void DisplayApp::Refresh() {
lv_disp_trig_activity(nullptr); lv_disp_trig_activity(nullptr);
break; break;
case Messages::GoToRunning: case Messages::GoToRunning:
if (state == States::Running) {
break;
}
if (settingsController.GetAlwaysOnDisplay()) { if (settingsController.GetAlwaysOnDisplay()) {
lcd.LowPowerOff(); lcd.LowPowerOff();
} else { } else {
@ -569,7 +586,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::SettingHeartRate>(settingsController); currentScreen = std::make_unique<Screens::SettingHeartRate>(settingsController);
break; break;
case Apps::SettingDisplay: case Apps::SettingDisplay:
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController); currentScreen = std::make_unique<Screens::SettingDisplay>(settingsController);
break; break;
case Apps::SettingSteps: case Apps::SettingSteps:
currentScreen = std::make_unique<Screens::SettingSteps>(settingsController); currentScreen = std::make_unique<Screens::SettingSteps>(settingsController);

View file

@ -48,7 +48,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
Controllers::Settings::ClockType clockType, Controllers::Settings::ClockType clockType,
System::SystemTask& systemTask, System::SystemTask& systemTask,
Controllers::MotorController& motorController) Controllers::MotorController& motorController)
: alarmController {alarmController}, systemTask {systemTask}, motorController {motorController} { : alarmController {alarmController}, wakeLock(systemTask), motorController {motorController} {
hourCounter.Create(); hourCounter.Create();
lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
UpdateAlarmTime(); UpdateAlarmTime();
if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
SetAlerting(); SetAlerting();
} else { } else {
SetSwitchState(LV_ANIM_OFF); SetSwitchState(LV_ANIM_OFF);
@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
} }
Alarm::~Alarm() { Alarm::~Alarm() {
if (alarmController.State() == AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
StopAlerting(); StopAlerting();
} }
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
alarmController.SaveAlarm();
} }
void Alarm::DisableAlarm() { void Alarm::DisableAlarm() {
if (alarmController.State() == AlarmController::AlarmState::Set) { if (alarmController.IsEnabled()) {
alarmController.DisableAlarm(); alarmController.DisableAlarm();
lv_switch_off(enableSwitch, LV_ANIM_ON); lv_switch_off(enableSwitch, LV_ANIM_ON);
} }
@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() {
HideInfo(); HideInfo();
return true; return true;
} }
if (alarmController.State() == AlarmController::AlarmState::Alerting) { if (alarmController.IsAlerting()) {
StopAlerting(); StopAlerting();
return true; return true;
} }
@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() {
bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) { bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
// Don't allow closing the screen by swiping while the alarm is alerting // Don't allow closing the screen by swiping while the alarm is alerting
return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown; return alarmController.IsAlerting() && event == TouchEvents::SwipeDown;
} }
void Alarm::OnValueChanged() { void Alarm::OnValueChanged() {
@ -205,7 +206,7 @@ void Alarm::SetAlerting() {
lv_obj_set_hidden(btnStop, false); lv_obj_set_hidden(btnStop, false);
taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this); taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this);
motorController.StartRinging(); motorController.StartRinging();
systemTask.PushMessage(System::Messages::DisableSleeping); wakeLock.Lock();
} }
void Alarm::StopAlerting() { void Alarm::StopAlerting() {
@ -216,21 +217,16 @@ void Alarm::StopAlerting() {
lv_task_del(taskStopAlarm); lv_task_del(taskStopAlarm);
taskStopAlarm = nullptr; taskStopAlarm = nullptr;
} }
systemTask.PushMessage(System::Messages::EnableSleeping); wakeLock.Release();
lv_obj_set_hidden(enableSwitch, false); lv_obj_set_hidden(enableSwitch, false);
lv_obj_set_hidden(btnStop, true); lv_obj_set_hidden(btnStop, true);
} }
void Alarm::SetSwitchState(lv_anim_enable_t anim) { void Alarm::SetSwitchState(lv_anim_enable_t anim) {
switch (alarmController.State()) { if (alarmController.IsEnabled()) {
case AlarmController::AlarmState::Set:
lv_switch_on(enableSwitch, anim); lv_switch_on(enableSwitch, anim);
break; } else {
case AlarmController::AlarmState::Not_Set:
lv_switch_off(enableSwitch, anim); lv_switch_off(enableSwitch, anim);
break;
default:
break;
} }
} }
@ -247,7 +243,7 @@ void Alarm::ShowInfo() {
txtMessage = lv_label_create(btnMessage, nullptr); txtMessage = lv_label_create(btnMessage, nullptr);
lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY); lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY);
if (alarmController.State() == AlarmController::AlarmState::Set) { if (alarmController.IsEnabled()) {
auto timeToAlarm = alarmController.SecondsToAlarm(); auto timeToAlarm = alarmController.SecondsToAlarm();
auto daysToAlarm = timeToAlarm / 86400; auto daysToAlarm = timeToAlarm / 86400;

View file

@ -22,6 +22,7 @@
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/widgets/Counter.h" #include "displayapp/widgets/Counter.h"
#include "displayapp/Controllers.h" #include "displayapp/Controllers.h"
#include "systemtask/WakeLock.h"
#include "Symbols.h" #include "Symbols.h"
namespace Pinetime { namespace Pinetime {
@ -43,7 +44,7 @@ namespace Pinetime {
private: private:
Controllers::AlarmController& alarmController; Controllers::AlarmController& alarmController;
System::SystemTask& systemTask; System::WakeLock wakeLock;
Controllers::MotorController& motorController; Controllers::MotorController& motorController;
lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch; lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch;

View file

@ -15,7 +15,7 @@ namespace {
} }
FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController) FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController)
: systemTask {systemTask}, brightnessController {brightnessController} { : wakeLock(systemTask), brightnessController {brightnessController} {
previousBrightnessLevel = brightnessController.Level(); previousBrightnessLevel = brightnessController.Level();
brightnessController.Set(Controllers::BrightnessController::Levels::Low); brightnessController.Set(Controllers::BrightnessController::Levels::Low);
@ -47,14 +47,13 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo
backgroundAction->user_data = this; backgroundAction->user_data = this;
lv_obj_set_event_cb(backgroundAction, EventHandler); lv_obj_set_event_cb(backgroundAction, EventHandler);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); wakeLock.Lock();
} }
FlashLight::~FlashLight() { FlashLight::~FlashLight() {
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
brightnessController.Set(previousBrightnessLevel); brightnessController.Set(previousBrightnessLevel);
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
} }
void FlashLight::SetColors() { void FlashLight::SetColors() {

View file

@ -3,6 +3,7 @@
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "components/brightness/BrightnessController.h" #include "components/brightness/BrightnessController.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include <cstdint> #include <cstdint>
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
@ -23,7 +24,7 @@ namespace Pinetime {
void SetIndicators(); void SetIndicators();
void SetColors(); void SetColors();
Pinetime::System::SystemTask& systemTask; Pinetime::System::WakeLock wakeLock;
Controllers::BrightnessController& brightnessController; Controllers::BrightnessController& brightnessController;
Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High; Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High;

View file

@ -29,7 +29,7 @@ namespace {
} }
HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, System::SystemTask& systemTask) HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, System::SystemTask& systemTask)
: heartRateController {heartRateController}, systemTask {systemTask} { : heartRateController {heartRateController}, wakeLock(systemTask) {
bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped; bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
label_hr = lv_label_create(lv_scr_act(), nullptr); label_hr = lv_label_create(lv_scr_act(), nullptr);
@ -63,7 +63,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
label_startStop = lv_label_create(btn_startStop, nullptr); label_startStop = lv_label_create(btn_startStop, nullptr);
UpdateStartStopButton(isHrRunning); UpdateStartStopButton(isHrRunning);
if (isHrRunning) { if (isHrRunning) {
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); wakeLock.Lock();
} }
taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this); taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this);
@ -72,7 +72,6 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
HeartRate::~HeartRate() { HeartRate::~HeartRate() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
} }
void HeartRate::Refresh() { void HeartRate::Refresh() {
@ -101,12 +100,12 @@ void HeartRate::OnStartStopEvent(lv_event_t event) {
if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) { if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) {
heartRateController.Start(); heartRateController.Start();
UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); wakeLock.Lock();
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight);
} else { } else {
heartRateController.Stop(); heartRateController.Stop();
UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped); UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped);
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); wakeLock.Release();
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
} }
} }

View file

@ -4,6 +4,7 @@
#include <chrono> #include <chrono>
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "Symbols.h" #include "Symbols.h"
#include <lvgl/src/lv_core/lv_style.h> #include <lvgl/src/lv_core/lv_style.h>
#include <lvgl/src/lv_core/lv_obj.h> #include <lvgl/src/lv_core/lv_obj.h>
@ -27,7 +28,7 @@ namespace Pinetime {
private: private:
Controllers::HeartRateController& heartRateController; Controllers::HeartRateController& heartRateController;
Pinetime::System::SystemTask& systemTask; Pinetime::System::WakeLock wakeLock;
void UpdateStartStopButton(bool isRunning); void UpdateStartStopButton(bool isRunning);
lv_obj_t* label_hr; lv_obj_t* label_hr;
lv_obj_t* label_bpm; lv_obj_t* label_bpm;

View file

@ -22,7 +22,7 @@ namespace {
} }
Metronome::Metronome(Controllers::MotorController& motorController, System::SystemTask& systemTask) Metronome::Metronome(Controllers::MotorController& motorController, System::SystemTask& systemTask)
: motorController {motorController}, systemTask {systemTask} { : motorController {motorController}, wakeLock(systemTask) {
bpmArc = lv_arc_create(lv_scr_act(), nullptr); bpmArc = lv_arc_create(lv_scr_act(), nullptr);
bpmArc->user_data = this; bpmArc->user_data = this;
@ -72,7 +72,6 @@ Metronome::Metronome(Controllers::MotorController& motorController, System::Syst
Metronome::~Metronome() { Metronome::~Metronome() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
systemTask.PushMessage(System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
@ -128,12 +127,12 @@ void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) {
metronomeStarted = !metronomeStarted; metronomeStarted = !metronomeStarted;
if (metronomeStarted) { if (metronomeStarted) {
lv_label_set_text_static(lblPlayPause, Symbols::pause); lv_label_set_text_static(lblPlayPause, Symbols::pause);
systemTask.PushMessage(System::Messages::DisableSleeping); wakeLock.Lock();
startTime = xTaskGetTickCount(); startTime = xTaskGetTickCount();
counter = 1; counter = 1;
} else { } else {
lv_label_set_text_static(lblPlayPause, Symbols::play); lv_label_set_text_static(lblPlayPause, Symbols::play);
systemTask.PushMessage(System::Messages::EnableSleeping); wakeLock.Release();
} }
} }
break; break;

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "Symbols.h" #include "Symbols.h"
@ -21,7 +22,7 @@ namespace Pinetime {
TickType_t startTime = 0; TickType_t startTime = 0;
TickType_t tappedTime = 0; TickType_t tappedTime = 0;
Controllers::MotorController& motorController; Controllers::MotorController& motorController;
System::SystemTask& systemTask; System::WakeLock wakeLock;
int16_t bpm = 120; int16_t bpm = 120;
uint8_t bpb = 4; uint8_t bpb = 4;
uint8_t counter = 1; uint8_t counter = 1;

View file

@ -20,7 +20,7 @@ Notifications::Notifications(DisplayApp* app,
notificationManager {notificationManager}, notificationManager {notificationManager},
alertNotificationService {alertNotificationService}, alertNotificationService {alertNotificationService},
motorController {motorController}, motorController {motorController},
systemTask {systemTask}, wakeLock(systemTask),
mode {mode} { mode {mode} {
notificationManager.ClearNewNotificationFlag(); notificationManager.ClearNewNotificationFlag();
@ -40,7 +40,7 @@ Notifications::Notifications(DisplayApp* app,
validDisplay = false; validDisplay = false;
} }
if (mode == Modes::Preview) { if (mode == Modes::Preview) {
systemTask.PushMessage(System::Messages::DisableSleeping); wakeLock.Lock();
if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) { if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) {
motorController.StartRinging(); motorController.StartRinging();
} else { } else {
@ -65,7 +65,6 @@ Notifications::~Notifications() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
// make sure we stop any vibrations before exiting // make sure we stop any vibrations before exiting
motorController.StopRinging(); motorController.StopRinging();
systemTask.PushMessage(System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
@ -82,7 +81,6 @@ void Notifications::Refresh() {
} else if (mode == Modes::Preview && dismissingNotification) { } else if (mode == Modes::Preview && dismissingNotification) {
running = false; running = false;
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController);
} else if (dismissingNotification) { } else if (dismissingNotification) {
dismissingNotification = false; dismissingNotification = false;
@ -113,15 +111,15 @@ void Notifications::Refresh() {
alertNotificationService, alertNotificationService,
motorController); motorController);
} else { } else {
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController); running = false;
} }
} }
running = currentItem->IsRunning() && running; running = running && currentItem->IsRunning();
} }
void Notifications::OnPreviewInteraction() { void Notifications::OnPreviewInteraction() {
systemTask.PushMessage(System::Messages::EnableSleeping); wakeLock.Release();
motorController.StopRinging(); motorController.StopRinging();
if (timeoutLine != nullptr) { if (timeoutLine != nullptr) {
lv_obj_del(timeoutLine); lv_obj_del(timeoutLine);
@ -173,7 +171,9 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
} else if (nextMessage.valid) { } else if (nextMessage.valid) {
currentId = nextMessage.id; currentId = nextMessage.id;
} else { } else {
// don't update id, won't be found be refresh and try to load latest message or no message box // don't update id, notification manager will try to fetch
// but not find it. Refresh will try to load latest message
// or dismiss to watchface
} }
DismissToBlack(); DismissToBlack();
return true; return true;

View file

@ -8,6 +8,7 @@
#include "components/ble/NotificationManager.h" #include "components/ble/NotificationManager.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@ -73,7 +74,7 @@ namespace Pinetime {
Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Controllers::AlertNotificationService& alertNotificationService; Pinetime::Controllers::AlertNotificationService& alertNotificationService;
Pinetime::Controllers::MotorController& motorController; Pinetime::Controllers::MotorController& motorController;
System::SystemTask& systemTask; System::WakeLock wakeLock;
Modes mode = Modes::Normal; Modes mode = Modes::Normal;
std::unique_ptr<NotificationItem> currentItem; std::unique_ptr<NotificationItem> currentItem;
Pinetime::Controllers::NotificationManager::Notification::Id currentId; Pinetime::Controllers::NotificationManager::Notification::Id currentId;

View file

@ -34,7 +34,7 @@ namespace {
constexpr TickType_t blinkInterval = pdMS_TO_TICKS(1000); constexpr TickType_t blinkInterval = pdMS_TO_TICKS(1000);
} }
StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} { StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) {
static constexpr uint8_t btnWidth = 115; static constexpr uint8_t btnWidth = 115;
static constexpr uint8_t btnHeight = 80; static constexpr uint8_t btnHeight = 80;
btnPlayPause = lv_btn_create(lv_scr_act(), nullptr); btnPlayPause = lv_btn_create(lv_scr_act(), nullptr);
@ -79,7 +79,6 @@ StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} {
StopWatch::~StopWatch() { StopWatch::~StopWatch() {
lv_task_del(taskRefresh); lv_task_del(taskRefresh);
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
lv_obj_clean(lv_scr_act()); lv_obj_clean(lv_scr_act());
} }
@ -135,7 +134,7 @@ void StopWatch::Start() {
SetInterfaceRunning(); SetInterfaceRunning();
startTime = xTaskGetTickCount(); startTime = xTaskGetTickCount();
currentState = States::Running; currentState = States::Running;
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping); wakeLock.Lock();
} }
void StopWatch::Pause() { void StopWatch::Pause() {
@ -145,7 +144,7 @@ void StopWatch::Pause() {
oldTimeElapsed = laps[lapsDone]; oldTimeElapsed = laps[lapsDone];
blinkTime = xTaskGetTickCount() + blinkInterval; blinkTime = xTaskGetTickCount() + blinkInterval;
currentState = States::Halted; currentState = States::Halted;
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); wakeLock.Release();
} }
void StopWatch::Refresh() { void StopWatch::Refresh() {

View file

@ -7,6 +7,7 @@
#include "portmacro_cmsis.h" #include "portmacro_cmsis.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "systemtask/WakeLock.h"
#include "displayapp/apps/Apps.h" #include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h" #include "displayapp/Controllers.h"
#include "Symbols.h" #include "Symbols.h"
@ -43,7 +44,7 @@ namespace Pinetime {
void Start(); void Start();
void Pause(); void Pause();
Pinetime::System::SystemTask& systemTask; Pinetime::System::WakeLock wakeLock;
States currentState = States::Init; States currentState = States::Init;
TickType_t startTime; TickType_t startTime;
TickType_t oldTimeElapsed = 0; TickType_t oldTimeElapsed = 0;

View file

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

View file

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

View file

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

View file

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

View file

@ -32,7 +32,7 @@ namespace Pinetime {
static constexpr std::array<Option, 8> options = {{ static constexpr std::array<Option, 8> options = {{
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off, " Off"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off, " Off"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous, "Cont"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous, "Cont"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds, " 10s"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FifteenSeconds, " 15s"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds, " 30s"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds, " 30s"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute, " 1m"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute, " 1m"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes, " 5m"}, {Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes, " 5m"},

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@
#include "drivers/Hrs3300.h" #include "drivers/Hrs3300.h"
#include <algorithm> #include <algorithm>
#include <iterator>
#include <nrf_gpio.h> #include <nrf_gpio.h>
#include <FreeRTOS.h> #include <FreeRTOS.h>
@ -67,40 +68,37 @@ void Hrs3300::Disable() {
WriteRegister(static_cast<uint8_t>(Registers::PDriver), 0); WriteRegister(static_cast<uint8_t>(Registers::PDriver), 0);
} }
uint32_t Hrs3300::ReadHrs() { Hrs3300::PackedHrsAls Hrs3300::ReadHrsAls() {
auto m = ReadRegister(static_cast<uint8_t>(Registers::C0DataM)); constexpr Registers dataRegisters[] =
auto h = ReadRegister(static_cast<uint8_t>(Registers::C0DataH)); {Registers::C1dataM, Registers::C0DataM, Registers::C0DataH, Registers::C1dataH, Registers::C1dataL, Registers::C0dataL};
auto l = ReadRegister(static_cast<uint8_t>(Registers::C0dataL)); // Calculate smallest register address
return ((l & 0x30) << 12) | (m << 8) | ((h & 0x0f) << 4) | (l & 0x0f); constexpr uint8_t baseOffset = static_cast<uint8_t>(*std::min_element(std::begin(dataRegisters), std::end(dataRegisters)));
} // Calculate largest address to determine length of read needed
// Add one to largest relative index to find the length
constexpr uint8_t length = static_cast<uint8_t>(*std::max_element(std::begin(dataRegisters), std::end(dataRegisters))) - baseOffset + 1;
uint32_t Hrs3300::ReadAls() { Hrs3300::PackedHrsAls res;
auto m = ReadRegister(static_cast<uint8_t>(Registers::C1dataM)); uint8_t buf[length];
auto h = ReadRegister(static_cast<uint8_t>(Registers::C1dataH)); auto ret = twiMaster.Read(twiAddress, baseOffset, buf, length);
auto l = ReadRegister(static_cast<uint8_t>(Registers::C1dataL)); if (ret != TwiMaster::ErrorCodes::NoError) {
return ((h & 0x3f) << 11) | (m << 3) | (l & 0x07); NRF_LOG_INFO("READ ERROR");
}
void Hrs3300::SetGain(uint8_t gain) {
constexpr uint8_t maxGain = 64U;
gain = std::min(gain, maxGain);
uint8_t hgain = 0;
while ((1 << hgain) < gain) {
++hgain;
} }
// hrs
uint8_t m = static_cast<uint8_t>(Registers::C0DataM) - baseOffset;
uint8_t h = static_cast<uint8_t>(Registers::C0DataH) - baseOffset;
uint8_t l = static_cast<uint8_t>(Registers::C0dataL) - baseOffset;
// There are two extra bits (17 and 18) but they are not read here
// as resolutions >16bit aren't practically useful (too slow) and
// all hrs values throughout InfiniTime are 16bit
res.hrs = (buf[m] << 8) | ((buf[h] & 0x0f) << 4) | (buf[l] & 0x0f);
WriteRegister(static_cast<uint8_t>(Registers::Hgain), hgain << 2); // als
} m = static_cast<uint8_t>(Registers::C1dataM) - baseOffset;
h = static_cast<uint8_t>(Registers::C1dataH) - baseOffset;
l = static_cast<uint8_t>(Registers::C1dataL) - baseOffset;
res.als = ((buf[h] & 0x3f) << 11) | (buf[m] << 3) | (buf[l] & 0x07);
void Hrs3300::SetDrive(uint8_t drive) { return res;
auto en = ReadRegister(static_cast<uint8_t>(Registers::Enable));
auto pd = ReadRegister(static_cast<uint8_t>(Registers::PDriver));
en = (en & 0xf7) | ((drive & 2) << 2);
pd = (pd & 0xbf) | ((drive & 1) << 6);
WriteRegister(static_cast<uint8_t>(Registers::Enable), en);
WriteRegister(static_cast<uint8_t>(Registers::PDriver), pd);
} }
void Hrs3300::WriteRegister(uint8_t reg, uint8_t data) { void Hrs3300::WriteRegister(uint8_t reg, uint8_t data) {

View file

@ -21,6 +21,11 @@ namespace Pinetime {
Hgain = 0x17 Hgain = 0x17
}; };
struct PackedHrsAls {
uint16_t hrs;
uint16_t als;
};
Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress); Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress);
Hrs3300(const Hrs3300&) = delete; Hrs3300(const Hrs3300&) = delete;
Hrs3300& operator=(const Hrs3300&) = delete; Hrs3300& operator=(const Hrs3300&) = delete;
@ -30,10 +35,7 @@ namespace Pinetime {
void Init(); void Init();
void Enable(); void Enable();
void Disable(); void Disable();
uint32_t ReadHrs(); PackedHrsAls ReadHrsAls();
uint32_t ReadAls();
void SetGain(uint8_t gain);
void SetDrive(uint8_t drive);
private: private:
TwiMaster& twiMaster; TwiMaster& twiMaster;

View file

@ -97,11 +97,6 @@ void HeartRateTask::StopMeasurement() {
vTaskDelay(100); vTaskDelay(100);
} }
void HeartRateTask::StartWaiting() {
StopMeasurement();
backgroundWaitingStart = xTaskGetTickCount();
}
void HeartRateTask::HandleGoToSleep() { void HeartRateTask::HandleGoToSleep() {
switch (state) { switch (state) {
case States::ScreenOnAndStopped: case States::ScreenOnAndStopped:
@ -176,8 +171,7 @@ void HeartRateTask::HandleBackgroundWaiting() {
return; return;
} }
TickType_t ticksSinceWaitingStart = xTaskGetTickCount() - backgroundWaitingStart; if (ShouldStartBackgroundMeasuring()) {
if (ticksSinceWaitingStart >= GetHeartRateBackgroundMeasurementIntervalInTicks()) {
state = States::ScreenOffAndMeasuring; state = States::ScreenOffAndMeasuring;
StartMeasurement(); StartMeasurement();
} }
@ -198,34 +192,45 @@ void HeartRateTask::HandleSensorData(int* lastBpm) {
bpm = 0; bpm = 0;
} }
if (*lastBpm == 0 && bpm == 0) { bool notEnoughData = *lastBpm == 0 && bpm == 0;
if (notEnoughData) {
controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm); controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm);
} }
if (bpm != 0) { if (bpm != 0) {
*lastBpm = bpm; *lastBpm = bpm;
controller.Update(Controllers::HeartRateController::States::Running, bpm); controller.Update(Controllers::HeartRateController::States::Running, bpm);
}
if (state == States::ScreenOnAndMeasuring || IsContinuousModeActivated()) { if (state == States::ScreenOnAndMeasuring || IsContinuousModeActivated()) {
return; return;
} }
if (state == States::ScreenOffAndMeasuring) {
// state == States::ScreenOffAndMeasuring
// (because state != ScreenOnAndMeasuring and the only state that enables measuring is ScreenOffAndMeasuring)
// !IsContinuousModeActivated()
if (ShouldStartBackgroundMeasuring()) {
// This doesn't change the state but resets the measurment timer, which basically starts the next measurment without resetting the sensor.
// This is basically a fall back to continuous mode, when measurments take too long.
measurementStart = xTaskGetTickCount();
return;
}
bool noDataWithinTimeLimit = bpm == 0 && ShoudStopTryingToGetData();
bool dataWithinTimeLimit = bpm != 0;
if (dataWithinTimeLimit || noDataWithinTimeLimit) {
state = States::ScreenOffAndWaiting; state = States::ScreenOffAndWaiting;
StartWaiting(); StopMeasurement();
}
}
TickType_t ticksSinceMeasurementStart = xTaskGetTickCount() - measurementStart;
if (bpm == 0 && state == States::ScreenOffAndMeasuring && !IsContinuousModeActivated() &&
ticksSinceMeasurementStart >= DURATION_UNTIL_BACKGROUND_MEASUREMENT_IS_STOPPED) {
state = States::ScreenOffAndWaiting;
StartWaiting();
} }
} }
TickType_t HeartRateTask::GetHeartRateBackgroundMeasurementIntervalInTicks() { TickType_t HeartRateTask::GetHeartRateBackgroundMeasurementIntervalInTicks() {
int ms; int ms;
switch (settings.GetHeartRateBackgroundMeasurementInterval()) { switch (settings.GetHeartRateBackgroundMeasurementInterval()) {
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds: case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FifteenSeconds:
ms = 10 * 1000; ms = 15 * 1000;
break; break;
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds: case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds:
ms = 30 * 1000; ms = 30 * 1000;
@ -258,3 +263,15 @@ bool HeartRateTask::IsBackgroundMeasurementActivated() {
return settings.GetHeartRateBackgroundMeasurementInterval() != return settings.GetHeartRateBackgroundMeasurementInterval() !=
Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off; Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off;
} }
TickType_t HeartRateTask::GetTicksSinceLastMeasurementStarted() {
return xTaskGetTickCount() - measurementStart;
}
bool HeartRateTask::ShoudStopTryingToGetData() {
return GetTicksSinceLastMeasurementStarted() >= DURATION_UNTIL_BACKGROUND_MEASUREMENT_IS_STOPPED;
}
bool HeartRateTask::ShouldStartBackgroundMeasuring() {
return GetTicksSinceLastMeasurementStarted() >= GetHeartRateBackgroundMeasurementIntervalInTicks();
}

View file

@ -45,7 +45,6 @@ namespace Pinetime {
static void Process(void* instance); static void Process(void* instance);
void StartMeasurement(); void StartMeasurement();
void StopMeasurement(); void StopMeasurement();
void StartWaiting();
void HandleGoToSleep(); void HandleGoToSleep();
void HandleWakeUp(); void HandleWakeUp();
@ -59,6 +58,10 @@ namespace Pinetime {
bool IsContinuousModeActivated(); bool IsContinuousModeActivated();
bool IsBackgroundMeasurementActivated(); bool IsBackgroundMeasurementActivated();
TickType_t GetTicksSinceLastMeasurementStarted();
bool ShoudStopTryingToGetData();
bool ShouldStartBackgroundMeasuring();
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
QueueHandle_t messageQueue; QueueHandle_t messageQueue;
States state = States::ScreenOnAndStopped; States state = States::ScreenOnAndStopped;
@ -66,7 +69,6 @@ namespace Pinetime {
Controllers::HeartRateController& controller; Controllers::HeartRateController& controller;
Controllers::Settings& settings; Controllers::Settings& settings;
Controllers::Ppg ppg; Controllers::Ppg ppg;
TickType_t backgroundWaitingStart = 0;
TickType_t measurementStart = 0; TickType_t measurementStart = 0;
}; };

@ -1 +0,0 @@
Subproject commit 56b17bf9f74096774944bcac0829adcd887d391e

View file

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

View file

@ -189,36 +189,14 @@ void SystemTask::Work() {
if (xQueueReceive(systemTasksMsgQueue, &msg, 100) == pdTRUE) { if (xQueueReceive(systemTasksMsgQueue, &msg, 100) == pdTRUE) {
switch (msg) { switch (msg) {
case Messages::EnableSleeping: case Messages::EnableSleeping:
// Make sure that exiting an app doesn't enable sleeping, wakeLocksHeld--;
// if the exiting was caused by a firmware update
if (!bleController.IsFirmwareUpdating()) {
doNotGoToSleep = false;
}
break; break;
case Messages::DisableSleeping: case Messages::DisableSleeping:
doNotGoToSleep = true; GoToRunning();
wakeLocksHeld++;
break; break;
case Messages::GoToRunning: case Messages::GoToRunning:
// SPI doesn't go to sleep for always on mode GoToRunning();
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Wakeup();
}
// Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
touchPanel.Wakeup();
}
spiNorFlash.Wakeup();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
if (bleController.IsRadioEnabled() && !bleController.IsConnected()) {
nimbleController.RestartFastAdv();
}
state = SystemTaskState::Running;
break; break;
case Messages::TouchWakeUp: { case Messages::TouchWakeUp: {
if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) { if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) {
@ -235,31 +213,23 @@ void SystemTask::Work() {
break; break;
} }
case Messages::GoToSleep: case Messages::GoToSleep:
if (doNotGoToSleep) { GoToSleep();
break;
}
state = SystemTaskState::GoingToSleep; // Already set in PushMessage()
NRF_LOG_INFO("[systemtask] Going to sleep");
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
break; break;
case Messages::OnNewTime: case Messages::OnNewTime:
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) { if (alarmController.IsEnabled()) {
alarmController.ScheduleAlarm(); alarmController.ScheduleAlarm();
} }
break; break;
case Messages::OnNewNotification: case Messages::OnNewNotification:
if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) { if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) {
if (state == SystemTaskState::Sleeping) { if (IsSleeping()) {
GoToRunning(); GoToRunning();
} }
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification); displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
} }
break; break;
case Messages::SetOffAlarm: case Messages::SetOffAlarm:
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered);
break; break;
case Messages::BleConnected: case Messages::BleConnected:
@ -268,29 +238,25 @@ void SystemTask::Work() {
bleDiscoveryTimer = 5; bleDiscoveryTimer = 5;
break; break;
case Messages::BleFirmwareUpdateStarted: case Messages::BleFirmwareUpdateStarted:
doNotGoToSleep = true;
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
} wakeLocksHeld++;
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted); displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
break; break;
case Messages::BleFirmwareUpdateFinished: case Messages::BleFirmwareUpdateFinished:
if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) { if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) {
NVIC_SystemReset(); NVIC_SystemReset();
} }
doNotGoToSleep = false; wakeLocksHeld--;
break; break;
case Messages::StartFileTransfer: case Messages::StartFileTransfer:
NRF_LOG_INFO("[systemtask] FS Started"); NRF_LOG_INFO("[systemtask] FS Started");
doNotGoToSleep = true;
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
} wakeLocksHeld++;
// TODO add intent of fs access icon or something // TODO add intent of fs access icon or something
break; break;
case Messages::StopFileTransfer: case Messages::StopFileTransfer:
NRF_LOG_INFO("[systemtask] FS Stopped"); NRF_LOG_INFO("[systemtask] FS Stopped");
doNotGoToSleep = false; wakeLocksHeld--;
// TODO add intent of fs access icon or something // TODO add intent of fs access icon or something
break; break;
case Messages::OnTouchEvent: case Messages::OnTouchEvent:
@ -318,6 +284,13 @@ void SystemTask::Work() {
HandleButtonAction(action); HandleButtonAction(action);
} break; } break;
case Messages::OnDisplayTaskSleeping: case Messages::OnDisplayTaskSleeping:
// The state was set to GoingToSleep when GoToSleep() was called
// If the state is no longer GoingToSleep, we have since transitioned back to Running
// In this case absorb the OnDisplayTaskSleeping
// as DisplayApp is about to receive GoToRunning
if (state != SystemTaskState::GoingToSleep) {
break;
}
if (BootloaderVersion::IsValid()) { if (BootloaderVersion::IsValid()) {
// First versions of the bootloader do not expose their version and cannot initialize the SPI NOR FLASH // First versions of the bootloader do not expose their version and cannot initialize the SPI NOR FLASH
// if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions. // if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions.
@ -344,39 +317,23 @@ void SystemTask::Work() {
case Messages::OnNewHour: case Messages::OnNewHour:
using Pinetime::Controllers::AlarmController; using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && !alarmController.IsAlerting()) {
alarmController.State() != AlarmController::AlarmState::Alerting) {
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
// request running first and repush the chime message
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
PushMessage(msg);
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
} }
}
break; break;
case Messages::OnNewHalfHour: case Messages::OnNewHalfHour:
using Pinetime::Controllers::AlarmController; using Pinetime::Controllers::AlarmController;
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && !alarmController.IsAlerting()) {
alarmController.State() != AlarmController::AlarmState::Alerting) {
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
// request running first and repush the chime message
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
PushMessage(msg);
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
} }
}
break; break;
case Messages::OnChargingEvent: case Messages::OnChargingEvent:
batteryController.ReadPowerState(); batteryController.ReadPowerState();
displayApp.PushMessage(Applications::Display::Messages::OnChargingEvent);
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
} displayApp.PushMessage(Applications::Display::Messages::OnChargingEvent);
break; break;
case Messages::MeasureBatteryTimerExpired: case Messages::MeasureBatteryTimerExpired:
batteryController.MeasureVoltage(); batteryController.MeasureVoltage();
@ -385,9 +342,7 @@ void SystemTask::Work() {
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining()); nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
break; break;
case Messages::OnPairing: case Messages::OnPairing:
if (state == SystemTaskState::Sleeping) {
GoToRunning(); GoToRunning();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey); displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey);
break; break;
case Messages::BleRadioEnableToggle: case Messages::BleRadioEnableToggle:
@ -422,12 +377,48 @@ void SystemTask::Work() {
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }
void SystemTask::UpdateMotion() { void SystemTask::GoToRunning() {
if (state == SystemTaskState::GoingToSleep || state == SystemTaskState::WakingUp) { if (state == SystemTaskState::Running) {
return; return;
} }
// SPI doesn't go to sleep for always on mode
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Wakeup();
}
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) || // Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
touchPanel.Wakeup();
}
spiNorFlash.Wakeup();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
if (bleController.IsRadioEnabled() && !bleController.IsConnected()) {
nimbleController.RestartFastAdv();
}
state = SystemTaskState::Running;
};
void SystemTask::GoToSleep() {
if (IsSleeping()) {
return;
}
if (IsSleepDisabled()) {
return;
}
NRF_LOG_INFO("[systemtask] Going to sleep");
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
state = SystemTaskState::GoingToSleep;
};
void SystemTask::UpdateMotion() {
if (IsSleeping() && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) || settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
motionController.GetService()->IsMotionNotificationSubscribed())) { motionController.GetService()->IsMotionNotificationSubscribed())) {
return; return;
@ -452,7 +443,7 @@ void SystemTask::UpdateMotion() {
} }
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running && if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running &&
motionController.ShouldLowerSleep()) { motionController.ShouldLowerSleep()) {
PushMessage(Messages::GoToSleep); GoToSleep();
} }
} }
@ -468,7 +459,7 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
switch (action) { switch (action) {
case Actions::Click: case Actions::Click:
// If the first action after fast wakeup is a click, it should be ignored. // If the first action after fast wakeup is a click, it should be ignored.
if (!fastWakeUpDone && state != SystemTaskState::GoingToSleep) { if (!fastWakeUpDone) {
displayApp.PushMessage(Applications::Display::Messages::ButtonPushed); displayApp.PushMessage(Applications::Display::Messages::ButtonPushed);
} }
break; break;
@ -488,17 +479,10 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
fastWakeUpDone = false; fastWakeUpDone = false;
} }
void SystemTask::GoToRunning() {
if (state == SystemTaskState::Sleeping) {
state = SystemTaskState::WakingUp;
PushMessage(Messages::GoToRunning);
}
}
void SystemTask::OnTouchEvent() { void SystemTask::OnTouchEvent() {
if (state == SystemTaskState::Running) { if (state == SystemTaskState::Running) {
PushMessage(Messages::OnTouchEvent); PushMessage(Messages::OnTouchEvent);
} else if (state == SystemTaskState::Sleeping) { } else {
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
PushMessage(Messages::TouchWakeUp); PushMessage(Messages::TouchWakeUp);
@ -507,10 +491,6 @@ void SystemTask::OnTouchEvent() {
} }
void SystemTask::PushMessage(System::Messages msg) { void SystemTask::PushMessage(System::Messages msg) {
if (msg == Messages::GoToSleep && !doNotGoToSleep) {
state = SystemTaskState::GoingToSleep;
}
if (in_isr()) { if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken); xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);

View file

@ -52,7 +52,7 @@ namespace Pinetime {
namespace System { namespace System {
class SystemTask { class SystemTask {
public: public:
enum class SystemTaskState { Sleeping, Running, GoingToSleep, WakingUp }; enum class SystemTaskState { Sleeping, Running, GoingToSleep };
SystemTask(Drivers::SpiMaster& spi, SystemTask(Drivers::SpiMaster& spi,
Pinetime::Drivers::SpiNorFlash& spiNorFlash, Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Drivers::TwiMaster& twiMaster, Drivers::TwiMaster& twiMaster,
@ -79,11 +79,8 @@ namespace Pinetime {
void OnTouchEvent(); void OnTouchEvent();
void OnIdle();
void OnDim();
bool IsSleepDisabled() { bool IsSleepDisabled() {
return doNotGoToSleep; return wakeLocksHeld > 0;
} }
Pinetime::Controllers::NimbleController& nimble() { Pinetime::Controllers::NimbleController& nimble() {
@ -91,7 +88,7 @@ namespace Pinetime {
}; };
bool IsSleeping() const { bool IsSleeping() const {
return state == SystemTaskState::Sleeping || state == SystemTaskState::WakingUp; return state != SystemTaskState::Running;
} }
private: private:
@ -127,13 +124,14 @@ namespace Pinetime {
bool isBleDiscoveryTimerRunning = false; bool isBleDiscoveryTimerRunning = false;
uint8_t bleDiscoveryTimer = 0; uint8_t bleDiscoveryTimer = 0;
TimerHandle_t measureBatteryTimer; TimerHandle_t measureBatteryTimer;
bool doNotGoToSleep = false; uint8_t wakeLocksHeld = 0;
SystemTaskState state = SystemTaskState::Running; SystemTaskState state = SystemTaskState::Running;
void HandleButtonAction(Controllers::ButtonActions action); void HandleButtonAction(Controllers::ButtonActions action);
bool fastWakeUpDone = false; bool fastWakeUpDone = false;
void GoToRunning(); void GoToRunning();
void GoToSleep();
void UpdateMotion(); void UpdateMotion();
bool stepCounterMustBeReset = false; bool stepCounterMustBeReset = false;
static constexpr TickType_t batteryMeasurementPeriod = pdMS_TO_TICKS(10 * 60 * 1000); static constexpr TickType_t batteryMeasurementPeriod = pdMS_TO_TICKS(10 * 60 * 1000);

View file

@ -0,0 +1,27 @@
#include "systemtask/WakeLock.h"
using namespace Pinetime::System;
WakeLock::WakeLock(SystemTask& systemTask) : systemTask {systemTask} {
lockHeld = false;
}
WakeLock::~WakeLock() {
Release();
}
void WakeLock::Lock() {
if (lockHeld) {
return;
}
systemTask.PushMessage(Messages::DisableSleeping);
lockHeld = true;
}
void WakeLock::Release() {
if (!lockHeld) {
return;
}
systemTask.PushMessage(Messages::EnableSleeping);
lockHeld = false;
}

19
src/systemtask/WakeLock.h Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "systemtask/SystemTask.h"
namespace Pinetime {
namespace System {
class WakeLock {
public:
WakeLock(SystemTask& systemTask);
~WakeLock();
void Lock();
void Release();
private:
bool lockHeld;
SystemTask& systemTask;
};
}
}