Compare commits

...

39 commits

Author SHA1 Message Date
D. Scott Boggs cfd1ac4567 Add spell-check exception
Some checks failed
CI / build-firmware (push) Has been cancelled
CI / build-simulator (push) Has been cancelled
CI / get-base-ref-size (push) Has been cancelled
CI / Compare build size (push) Has been cancelled
2024-10-19 06:21:18 -04:00
D. Scott Boggs 6a9d13fe77 Bump version to 1.15.1
Some checks failed
CI / build-firmware (push) Has been cancelled
CI / build-simulator (push) Has been cancelled
CI / get-base-ref-size (push) Has been cancelled
CI / Compare build size (push) Has been cancelled
2024-09-15 12:00:38 -04:00
Davis Mosenkovs d689e5e874 Improve timer vibration
Some checks are pending
CI / build-firmware (push) Waiting to run
CI / build-simulator (push) Waiting to run
CI / get-base-ref-size (push) Waiting to run
CI / Compare build size (push) Blocked by required conditions
2024-09-15 11:57:35 -04:00
D. Scott Boggs 8abe9d428b Update flake for v1.15.0 2024-09-15 11:57:10 -04:00
D. Scott Boggs b7cf42156c bump version
Some checks are pending
CI / build-firmware (push) Waiting to run
CI / build-simulator (push) Waiting to run
CI / get-base-ref-size (push) Waiting to run
CI / Compare build size (push) Blocked by required conditions
2024-09-15 09:30:15 -04:00
D. Scott Boggs 7d8e58d863 Merge branch 'heartrate-measurements-in-background'
Some checks are pending
CI / build-firmware (push) Waiting to run
CI / build-simulator (push) Waiting to run
CI / get-base-ref-size (push) Waiting to run
CI / Compare build size (push) Blocked by required conditions
2024-09-15 09:07:20 -04:00
D. Scott Boggs fd15fd238d bump settings version 2024-09-15 08:43:08 -04:00
Dom Rodriguez a9a36793ad
feat: Introduce Flake for development and builds
Some checks are pending
CI / build-firmware (push) Waiting to run
CI / build-simulator (push) Waiting to run
CI / get-base-ref-size (push) Waiting to run
CI / Compare build size (push) Blocked by required conditions
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-09-14 14:27:43 +01:00
Patric Gruber 7df39994ab integrate code review 2024-08-31 20:49:19 +02:00
Patric Gruber 9501d36060 use better state names 2024-08-31 00:54:05 +02:00
Patric Gruber b846547f2f remove unnecessary file 2024-08-31 00:42:51 +02:00
Patric Gruber 71b31c78fb use switch case 2024-08-31 00:40:58 +02:00
Patric Gruber 0978964b7d Merge branch 'heartrate-measurements-in-background' of github.com:patricgruber/InfiniTime into heartrate-measurements-in-background 2024-08-31 00:40:10 +02:00
Patric Gruber cedca795e2 use switch case 2024-08-31 00:39:13 +02:00
Patric Gruber 78af44eafe
keep measuring when transitioning to background
Co-authored-by: Simon Effenberg <savar@schuldeigen.de>
2024-07-17 20:18:31 +02:00
Patric Gruber e6f0a89202 reduce RAM size 2024-07-11 15:53:18 +02:00
Patric Gruber 616926345e refactor heartrate task (switch cases, comments with explanation) 2024-07-11 15:06:27 +02:00
Patric Gruber 6a0276f164 fix settings screen 2024-07-11 15:06:27 +02:00
Patric Gruber 7cf4f6e1ec fix bug where settings open pair pin screen 2024-07-11 15:06:27 +02:00
Patric Gruber ffc5f96d9a bump settings version 2024-07-11 15:06:27 +02:00
Patric Gruber 3b432cd310 fix issues after rebase on main 2024-07-11 15:06:27 +02:00
Patric Gruber d78f26201b fix DisplayApp.cpp 2024-07-11 15:06:27 +02:00
Patric Gruber 4ed4d2cfcd use pdMS_TO_TICKS correctly, format using clang-format 2024-07-11 15:06:27 +02:00
Patric Gruber 50d88bbe84 bump settings version, fix types 2024-07-11 15:06:27 +02:00
Patric Gruber 520e50901a fix rebase mistakes 2024-07-11 15:06:27 +02:00
Patric Gruber 04ed068ff9 add settings screen to choose heartrate measurement background 2024-07-11 15:06:27 +02:00
Patric Gruber 69578a679a properly format using clang-format 2024-07-11 15:06:27 +02:00
Patric Gruber eeaf5374d4 stop background after 30s of no data from the heart rate sensor 2024-07-11 15:06:22 +02:00
Patric Gruber f94c074064 rebase on main 2024-07-11 15:06:09 +02:00
Patric Gruber a2edd931ec add heart rate measurments in the background 2024-07-11 15:05:59 +02:00
Patric Gruber d376a856b7 use enum instead of uint32_t for heartrater interval setting 2024-07-11 15:05:59 +02:00
Patric Gruber be1a519098 use different style for the heartrate settings and fix issues with settings file 2024-07-11 15:05:59 +02:00
Patric Gruber 27ee1eb2c8 add settings screen to choose heartrate measurement background 2024-07-11 15:05:59 +02:00
Patric Gruber 5dbe1f77b5 properly format using clang-format 2024-07-11 15:05:59 +02:00
Patric Gruber 7ae790bcdb stop background after 30s of no data from the heart rate sensor 2024-07-11 15:05:50 +02:00
Patric Gruber a5db54af27 rebase on main 2024-07-11 15:05:37 +02:00
Patric Gruber 0370e3cd65 remove background start timestamp reset on sleep 2024-07-11 15:05:37 +02:00
Patric Gruber 58c507ee45 increase task delay when waiting in the background to 10s 2024-07-11 15:05:02 +02:00
Patric Gruber f7b1111e05 add heart rate measurments in the background 2024-07-11 15:05:02 +02:00
18 changed files with 580 additions and 76 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

View file

@ -66,5 +66,8 @@
"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.14.0 LANGUAGES C CXX ASM) project(pinetime VERSION 1.15.1 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)

10
default.nix Normal file
View file

@ -0,0 +1,10 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{ src = ./.; }
).defaultNix

42
flake.lock Normal file
View file

@ -0,0 +1,42 @@
{
"nodes": {
"flake-compat": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1725634671,
"narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

114
flake.nix Normal file
View file

@ -0,0 +1,114 @@
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
};
outputs = { self, ... }@inputs:
let
forAllSystems = function:
inputs.nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
]
(system: function (import inputs.nixpkgs {
inherit system;
config.allowUnfree = true;
}));
in
{
packages = forAllSystems (pkgs:
let
infinitime-nrf5-sdk = pkgs.nrf5-sdk.overrideAttrs (old: {
version = "15.3.0";
src = pkgs.fetchzip {
url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip";
sha256 = "sha256-pfmhbpgVv5x2ju489XcivguwpnofHbgVA7bFUJRTj08=";
};
});
in
with pkgs; {
default = stdenv.mkDerivation rec {
name = "infinitime";
src = fetchFromGitea {
domain = "git.techwork.zone";
owner = "scott";
repo = "InfiniTime";
rev = "1.15.1";
hash = "sha256-9cy7qYRZrg7qZZwQzYeMIZivGLZ5uGUoTyPgHEvdnZ8=";
fetchSubmodules = true;
};
nativeBuildInputs = [
cmake
nodePackages.lv_font_conv
python3
python3.pkgs.cbor
python3.pkgs.click
python3.pkgs.cryptography
python3.pkgs.intelhex
python3.pkgs.pillow
adafruit-nrfutil
patch
git
];
postPatch = ''
# /usr/bin/env is not available in the build sandbox
substituteInPlace src/displayapp/fonts/generate.py --replace "'/usr/bin/env', 'patch'" "'patch'"
substituteInPlace tools/mcuboot/imgtool.py --replace "/usr/bin/env python3" "${python3}/bin/python3"
'';
cmakeFlags = [
''-DARM_NONE_EABI_TOOLCHAIN_PATH=${gcc-arm-embedded-10}''
''-DNRF5_SDK_PATH=${infinitime-nrf5-sdk}/share/nRF5_SDK''
''-DBUILD_DFU=1''
''-DBUILD_RESOURCES=1''
''-DCMAKE_SOURCE_DIR=${src}''
];
installPhase = ''
SOURCES_DIR=${src} BUILD_DIR=. OUTPUT_DIR=$out ./post_build.sh
'';
};
});
devShells = forAllSystems (pkgs:
let
infinitime-nrf5-sdk = pkgs.nrf5-sdk.overrideAttrs (old: {
version = "15.3.0";
src = pkgs.fetchzip {
url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip";
sha256 = "sha256-pfmhbpgVv5x2ju489XcivguwpnofHbgVA7bFUJRTj08=";
};
});
in
{
default =
pkgs.buildFHSUserEnv {
name = "infinitime-devenv";
# build tools
targetPkgs = pkgs: (with pkgs; [
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" \
"$@"
'')
]);
};
});
};
}

10
shell.nix Normal file
View file

@ -0,0 +1,10 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{ src = ./.; }
).shellNix

View file

@ -411,6 +411,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/settings/SettingWeatherFormat.cpp displayapp/screens/settings/SettingWeatherFormat.cpp
displayapp/screens/settings/SettingWakeUp.cpp displayapp/screens/settings/SettingWakeUp.cpp
displayapp/screens/settings/SettingDisplay.cpp displayapp/screens/settings/SettingDisplay.cpp
displayapp/screens/settings/SettingHeartRate.cpp
displayapp/screens/settings/SettingSteps.cpp displayapp/screens/settings/SettingSteps.cpp
displayapp/screens/settings/SettingSetDateTime.cpp displayapp/screens/settings/SettingSetDateTime.cpp
displayapp/screens/settings/SettingSetDate.cpp displayapp/screens/settings/SettingSetDate.cpp

View file

@ -8,13 +8,11 @@ Settings::Settings(Pinetime::Controllers::FS& fs) : fs {fs} {
} }
void Settings::Init() { void Settings::Init() {
// Load default settings from Flash // Load default settings from Flash
LoadSettingsFromFile(); LoadSettingsFromFile();
} }
void Settings::SaveSettings() { void Settings::SaveSettings() {
// verify if is necessary to save // verify if is necessary to save
if (settingsChanged) { if (settingsChanged) {
SaveSettingsToFile(); SaveSettingsToFile();

View file

@ -50,6 +50,17 @@ namespace Pinetime {
int colorIndex = 0; int colorIndex = 0;
}; };
enum class HeartRateBackgroundMeasurementInterval : uint8_t {
Off,
Continuous,
TenSeconds,
ThirtySeconds,
OneMinute,
FiveMinutes,
TenMinutes,
ThirtyMinutes,
};
Settings(Pinetime::Controllers::FS& fs); Settings(Pinetime::Controllers::FS& fs);
Settings(const Settings&) = delete; Settings(const Settings&) = delete;
@ -298,10 +309,21 @@ namespace Pinetime {
return bleRadioEnabled; return bleRadioEnabled;
}; };
HeartRateBackgroundMeasurementInterval GetHeartRateBackgroundMeasurementInterval() const {
return settings.heartRateBackgroundMeasurementInterval;
}
void SetHeartRateBackgroundMeasurementInterval(HeartRateBackgroundMeasurementInterval newHeartRateBackgroundMeasurementInterval) {
if (newHeartRateBackgroundMeasurementInterval != settings.heartRateBackgroundMeasurementInterval) {
settingsChanged = true;
}
settings.heartRateBackgroundMeasurementInterval = newHeartRateBackgroundMeasurementInterval;
}
private: private:
Pinetime::Controllers::FS& fs; Pinetime::Controllers::FS& fs;
static constexpr uint32_t settingsVersion = 0x0008; static constexpr uint32_t settingsVersion = 0x0009;
struct SettingsData { struct SettingsData {
uint32_t version = settingsVersion; uint32_t version = settingsVersion;
@ -325,6 +347,8 @@ namespace Pinetime {
uint16_t shakeWakeThreshold = 150; uint16_t shakeWakeThreshold = 150;
Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium; Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
HeartRateBackgroundMeasurementInterval heartRateBackgroundMeasurementInterval = HeartRateBackgroundMeasurementInterval::Off;
}; };
SettingsData settings; SettingsData settings;

View file

@ -47,6 +47,7 @@
#include "displayapp/screens/settings/SettingSteps.h" #include "displayapp/screens/settings/SettingSteps.h"
#include "displayapp/screens/settings/SettingSetDateTime.h" #include "displayapp/screens/settings/SettingSetDateTime.h"
#include "displayapp/screens/settings/SettingChimes.h" #include "displayapp/screens/settings/SettingChimes.h"
#include "displayapp/screens/settings/SettingHeartRate.h"
#include "displayapp/screens/settings/SettingShakeThreshold.h" #include "displayapp/screens/settings/SettingShakeThreshold.h"
#include "displayapp/screens/settings/SettingBluetooth.h" #include "displayapp/screens/settings/SettingBluetooth.h"
@ -333,7 +334,7 @@ void DisplayApp::Refresh() {
} else { } else {
LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up); LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up);
} }
motorController.RunForDuration(35); motorController.StartRinging();
break; break;
case Messages::AlarmTriggered: case Messages::AlarmTriggered:
if (currentApp == Apps::Alarm) { if (currentApp == Apps::Alarm) {
@ -564,6 +565,9 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
case Apps::SettingWakeUp: case Apps::SettingWakeUp:
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController); currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
break; break;
case Apps::SettingHeartRate:
currentScreen = std::make_unique<Screens::SettingHeartRate>(settingsController);
break;
case Apps::SettingDisplay: case Apps::SettingDisplay:
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController); currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController);
break; break;

View file

@ -35,6 +35,7 @@ namespace Pinetime {
SettingWatchFace, SettingWatchFace,
SettingTimeFormat, SettingTimeFormat,
SettingWeatherFormat, SettingWeatherFormat,
SettingHeartRate,
SettingDisplay, SettingDisplay,
SettingWakeUp, SettingWakeUp,
SettingSteps, SettingSteps,

View file

@ -0,0 +1,75 @@
#include "displayapp/screens/settings/SettingHeartRate.h"
#include <lvgl/lvgl.h>
#include "displayapp/screens/Styles.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
#include <array>
#include <algorithm>
using namespace Pinetime::Applications::Screens;
namespace {
void event_handler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<SettingHeartRate*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
}
constexpr std::array<Option, 8> SettingHeartRate::options;
SettingHeartRate::SettingHeartRate(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} {
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5);
lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5);
lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0);
lv_obj_set_pos(container1, 10, 60);
lv_obj_set_width(container1, LV_HOR_RES - 20);
lv_obj_set_height(container1, LV_VER_RES - 50);
lv_cont_set_layout(container1, LV_LAYOUT_PRETTY_TOP);
lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(title, "Backg. Interval");
lv_label_set_text(title, "Backg. Interval");
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 10, 15);
lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
lv_label_set_text_static(icon, Symbols::heartBeat);
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
for (unsigned int i = 0; i < options.size(); i++) {
cbOption[i] = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text(cbOption[i], options[i].name);
cbOption[i]->user_data = this;
lv_obj_set_event_cb(cbOption[i], event_handler);
SetRadioButtonStyle(cbOption[i]);
if (settingsController.GetHeartRateBackgroundMeasurementInterval() == options[i].interval) {
lv_checkbox_set_checked(cbOption[i], true);
}
}
}
SettingHeartRate::~SettingHeartRate() {
lv_obj_clean(lv_scr_act());
settingsController.SaveSettings();
}
void SettingHeartRate::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) {
for (unsigned int i = 0; i < options.size(); i++) {
if (object == cbOption[i]) {
lv_checkbox_set_checked(cbOption[i], true);
settingsController.SetHeartRateBackgroundMeasurementInterval(options[i].interval);
} else {
lv_checkbox_set_checked(cbOption[i], false);
}
}
}
}

View file

@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include <lvgl/lvgl.h>
#include "components/settings/Settings.h"
#include "displayapp/screens/ScreenList.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/CheckboxList.h"
namespace Pinetime {
namespace Applications {
namespace Screens {
struct Option {
const Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval interval;
const char* name;
};
class SettingHeartRate : public Screen {
public:
SettingHeartRate(Pinetime::Controllers::Settings& settings);
~SettingHeartRate() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
private:
Pinetime::Controllers::Settings& settingsController;
static constexpr std::array<Option, 8> options = {{
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off, " Off"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous, "Cont"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds, " 10s"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds, " 30s"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute, " 1m"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes, " 5m"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenMinutes, " 10m"},
{Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtyMinutes, " 30m"},
}};
lv_obj_t* cbOption[options.size()];
};
}
}
}

View file

@ -38,15 +38,16 @@ namespace Pinetime {
{Symbols::home, "Watch face", Apps::SettingWatchFace}, {Symbols::home, "Watch face", Apps::SettingWatchFace},
{Symbols::shoe, "Steps", Apps::SettingSteps}, {Symbols::shoe, "Steps", Apps::SettingSteps},
{Symbols::heartBeat, "Heartrate", Apps::SettingHeartRate},
{Symbols::clock, "Date&Time", Apps::SettingSetDateTime}, {Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
{Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat}, {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
{Symbols::clock, "Chimes", Apps::SettingChimes}, {Symbols::clock, "Chimes", Apps::SettingChimes},
{Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold}, {Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold},
{Symbols::check, "Firmware", Apps::FirmwareValidation}, {Symbols::check, "Firmware", Apps::FirmwareValidation},
{Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth},
{Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth},
{Symbols::list, "About", Apps::SysInfo}, {Symbols::list, "About", Apps::SysInfo},
// {Symbols::none, "None", Apps::None}, // {Symbols::none, "None", Apps::None},

View file

@ -5,8 +5,23 @@
using namespace Pinetime::Applications; using namespace Pinetime::Applications;
HeartRateTask::HeartRateTask(Drivers::Hrs3300& heartRateSensor, Controllers::HeartRateController& controller) TickType_t CurrentTaskDelay(HeartRateTask::States state, TickType_t ppgDeltaTms) {
: heartRateSensor {heartRateSensor}, controller {controller} { switch (state) {
case HeartRateTask::States::ScreenOnAndMeasuring:
case HeartRateTask::States::ScreenOffAndMeasuring:
return ppgDeltaTms;
case HeartRateTask::States::ScreenOffAndWaiting:
return pdMS_TO_TICKS(1000);
default:
return portMAX_DELAY;
}
}
HeartRateTask::HeartRateTask(Drivers::Hrs3300& heartRateSensor,
Controllers::HeartRateController& controller,
Controllers::Settings& settings)
: heartRateSensor {heartRateSensor}, controller {controller}, settings {settings} {
} }
void HeartRateTask::Start() { void HeartRateTask::Start() {
@ -25,77 +40,40 @@ void HeartRateTask::Process(void* instance) {
void HeartRateTask::Work() { void HeartRateTask::Work() {
int lastBpm = 0; int lastBpm = 0;
while (true) {
Messages msg;
uint32_t delay;
if (state == States::Running) {
if (measurementStarted) {
delay = ppg.deltaTms;
} else {
delay = 100;
}
} else {
delay = portMAX_DELAY;
}
if (xQueueReceive(messageQueue, &msg, delay)) { while (true) {
TickType_t delay = CurrentTaskDelay(state, ppg.deltaTms);
Messages msg;
if (xQueueReceive(messageQueue, &msg, delay) == pdTRUE) {
switch (msg) { switch (msg) {
case Messages::GoToSleep: case Messages::GoToSleep:
StopMeasurement(); HandleGoToSleep();
state = States::Idle;
break; break;
case Messages::WakeUp: case Messages::WakeUp:
state = States::Running; HandleWakeUp();
if (measurementStarted) {
lastBpm = 0;
StartMeasurement();
}
break; break;
case Messages::StartMeasurement: case Messages::StartMeasurement:
if (measurementStarted) { HandleStartMeasurement(&lastBpm);
break;
}
lastBpm = 0;
StartMeasurement();
measurementStarted = true;
break; break;
case Messages::StopMeasurement: case Messages::StopMeasurement:
if (!measurementStarted) { HandleStopMeasurement();
break;
}
StopMeasurement();
measurementStarted = false;
break; break;
} }
} }
if (measurementStarted) { switch (state) {
int8_t ambient = ppg.Preprocess(heartRateSensor.ReadHrs(), heartRateSensor.ReadAls()); case States::ScreenOffAndWaiting:
int bpm = ppg.HeartRate(); HandleBackgroundWaiting();
break;
// If ambient light detected or a reset requested (bpm < 0) case States::ScreenOffAndMeasuring:
if (ambient > 0) { case States::ScreenOnAndMeasuring:
// Reset all DAQ buffers HandleSensorData(&lastBpm);
ppg.Reset(true); break;
// Force state to NotEnoughData (below) case States::ScreenOffAndStopped:
lastBpm = 0; case States::ScreenOnAndStopped:
bpm = 0; // nothing to do -> ignore
} else if (bpm < 0) { break;
// Reset all DAQ buffers except HRS buffer
ppg.Reset(false);
// Set HR to zero and update
bpm = 0;
controller.Update(Controllers::HeartRateController::States::Running, bpm);
}
if (lastBpm == 0 && bpm == 0) {
controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm);
}
if (bpm != 0) {
lastBpm = bpm;
controller.Update(Controllers::HeartRateController::States::Running, lastBpm);
}
} }
} }
} }
@ -110,6 +88,7 @@ void HeartRateTask::StartMeasurement() {
heartRateSensor.Enable(); heartRateSensor.Enable();
ppg.Reset(true); ppg.Reset(true);
vTaskDelay(100); vTaskDelay(100);
measurementStart = xTaskGetTickCount();
} }
void HeartRateTask::StopMeasurement() { void HeartRateTask::StopMeasurement() {
@ -117,3 +96,165 @@ void HeartRateTask::StopMeasurement() {
ppg.Reset(true); ppg.Reset(true);
vTaskDelay(100); vTaskDelay(100);
} }
void HeartRateTask::StartWaiting() {
StopMeasurement();
backgroundWaitingStart = xTaskGetTickCount();
}
void HeartRateTask::HandleGoToSleep() {
switch (state) {
case States::ScreenOnAndStopped:
state = States::ScreenOffAndStopped;
break;
case States::ScreenOnAndMeasuring:
state = States::ScreenOffAndMeasuring;
break;
case States::ScreenOffAndStopped:
case States::ScreenOffAndWaiting:
case States::ScreenOffAndMeasuring:
// shouldn't happen -> ignore
break;
}
}
void HeartRateTask::HandleWakeUp() {
switch (state) {
case States::ScreenOffAndStopped:
state = States::ScreenOnAndStopped;
break;
case States::ScreenOffAndMeasuring:
state = States::ScreenOnAndMeasuring;
break;
case States::ScreenOffAndWaiting:
state = States::ScreenOnAndMeasuring;
StartMeasurement();
break;
case States::ScreenOnAndStopped:
case States::ScreenOnAndMeasuring:
// shouldn't happen -> ignore
break;
}
}
void HeartRateTask::HandleStartMeasurement(int* lastBpm) {
switch (state) {
case States::ScreenOffAndStopped:
case States::ScreenOnAndStopped:
state = States::ScreenOnAndMeasuring;
*lastBpm = 0;
StartMeasurement();
break;
case States::ScreenOnAndMeasuring:
case States::ScreenOffAndMeasuring:
case States::ScreenOffAndWaiting:
// shouldn't happen -> ignore
break;
}
}
void HeartRateTask::HandleStopMeasurement() {
switch (state) {
case States::ScreenOnAndMeasuring:
state = States::ScreenOnAndStopped;
StopMeasurement();
break;
case States::ScreenOffAndMeasuring:
case States::ScreenOffAndWaiting:
state = States::ScreenOffAndStopped;
StopMeasurement();
break;
case States::ScreenOnAndStopped:
case States::ScreenOffAndStopped:
// shouldn't happen -> ignore
break;
}
}
void HeartRateTask::HandleBackgroundWaiting() {
if (!IsBackgroundMeasurementActivated()) {
return;
}
TickType_t ticksSinceWaitingStart = xTaskGetTickCount() - backgroundWaitingStart;
if (ticksSinceWaitingStart >= GetHeartRateBackgroundMeasurementIntervalInTicks()) {
state = States::ScreenOffAndMeasuring;
StartMeasurement();
}
}
void HeartRateTask::HandleSensorData(int* lastBpm) {
int8_t ambient = ppg.Preprocess(heartRateSensor.ReadHrs(), heartRateSensor.ReadAls());
int bpm = ppg.HeartRate();
// If ambient light detected or a reset requested (bpm < 0)
if (ambient > 0) {
// Reset all DAQ buffers
ppg.Reset(true);
} else if (bpm < 0) {
// Reset all DAQ buffers except HRS buffer
ppg.Reset(false);
// Set HR to zero and update
bpm = 0;
}
if (*lastBpm == 0 && bpm == 0) {
controller.Update(Controllers::HeartRateController::States::NotEnoughData, bpm);
}
if (bpm != 0) {
*lastBpm = bpm;
controller.Update(Controllers::HeartRateController::States::Running, bpm);
if (state == States::ScreenOnAndMeasuring || IsContinuousModeActivated()) {
return;
}
if (state == States::ScreenOffAndMeasuring) {
state = States::ScreenOffAndWaiting;
StartWaiting();
}
}
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() {
int ms;
switch (settings.GetHeartRateBackgroundMeasurementInterval()) {
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenSeconds:
ms = 10 * 1000;
break;
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtySeconds:
ms = 30 * 1000;
break;
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::OneMinute:
ms = 60 * 1000;
break;
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::FiveMinutes:
ms = 5 * 60 * 1000;
break;
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::TenMinutes:
ms = 10 * 60 * 1000;
break;
case Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::ThirtyMinutes:
ms = 30 * 60 * 1000;
break;
default:
ms = 0;
break;
}
return pdMS_TO_TICKS(ms);
}
bool HeartRateTask::IsContinuousModeActivated() {
return settings.GetHeartRateBackgroundMeasurementInterval() ==
Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Continuous;
}
bool HeartRateTask::IsBackgroundMeasurementActivated() {
return settings.GetHeartRateBackgroundMeasurementInterval() !=
Pinetime::Controllers::Settings::HeartRateBackgroundMeasurementInterval::Off;
}

View file

@ -3,6 +3,9 @@
#include <task.h> #include <task.h>
#include <queue.h> #include <queue.h>
#include <components/heartrate/Ppg.h> #include <components/heartrate/Ppg.h>
#include "components/settings/Settings.h"
#define DURATION_UNTIL_BACKGROUND_MEASUREMENT_IS_STOPPED pdMS_TO_TICKS(30 * 1000)
namespace Pinetime { namespace Pinetime {
namespace Drivers { namespace Drivers {
@ -16,10 +19,24 @@ namespace Pinetime {
namespace Applications { namespace Applications {
class HeartRateTask { class HeartRateTask {
public: public:
enum class Messages : uint8_t { GoToSleep, WakeUp, StartMeasurement, StopMeasurement }; enum class Messages : uint8_t {
enum class States { Idle, Running }; GoToSleep,
WakeUp,
StartMeasurement,
StopMeasurement
};
explicit HeartRateTask(Drivers::Hrs3300& heartRateSensor, Controllers::HeartRateController& controller); enum class States {
ScreenOnAndStopped,
ScreenOnAndMeasuring,
ScreenOffAndStopped,
ScreenOffAndWaiting,
ScreenOffAndMeasuring
};
explicit HeartRateTask(Drivers::Hrs3300& heartRateSensor,
Controllers::HeartRateController& controller,
Controllers::Settings& settings);
void Start(); void Start();
void Work(); void Work();
void PushMessage(Messages msg); void PushMessage(Messages msg);
@ -28,14 +45,29 @@ 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 HandleWakeUp();
void HandleStartMeasurement(int* lastBpm);
void HandleStopMeasurement();
void HandleBackgroundWaiting();
void HandleSensorData(int* lastBpm);
TickType_t GetHeartRateBackgroundMeasurementIntervalInTicks();
bool IsContinuousModeActivated();
bool IsBackgroundMeasurementActivated();
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
QueueHandle_t messageQueue; QueueHandle_t messageQueue;
States state = States::Running; States state = States::ScreenOnAndStopped;
Drivers::Hrs3300& heartRateSensor; Drivers::Hrs3300& heartRateSensor;
Controllers::HeartRateController& controller; Controllers::HeartRateController& controller;
Controllers::Settings& settings;
Controllers::Ppg ppg; Controllers::Ppg ppg;
bool measurementStarted = false; TickType_t backgroundWaitingStart = 0;
TickType_t measurementStart = 0;
}; };
} }

View file

@ -93,13 +93,13 @@ TimerHandle_t debounceChargeTimer;
Pinetime::Controllers::Battery batteryController; Pinetime::Controllers::Battery batteryController;
Pinetime::Controllers::Ble bleController; Pinetime::Controllers::Ble bleController;
Pinetime::Controllers::HeartRateController heartRateController;
Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController);
Pinetime::Controllers::FS fs {spiNorFlash}; Pinetime::Controllers::FS fs {spiNorFlash};
Pinetime::Controllers::Settings settingsController {fs}; Pinetime::Controllers::Settings settingsController {fs};
Pinetime::Controllers::MotorController motorController {}; Pinetime::Controllers::MotorController motorController {};
Pinetime::Controllers::HeartRateController heartRateController;
Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController, settingsController);
Pinetime::Controllers::DateTime dateTimeController {settingsController}; Pinetime::Controllers::DateTime dateTimeController {settingsController};
Pinetime::Drivers::Watchdog watchdog; Pinetime::Drivers::Watchdog watchdog;
Pinetime::Controllers::NotificationManager notificationManager; Pinetime::Controllers::NotificationManager notificationManager;