├── .gitignore
├── TestHrv
├── monkey.jungle
├── resources
│ ├── strings
│ │ ├── strings.xml
│ │ └── hrvFitContributionsStrings.xml
│ ├── drawables
│ │ ├── drawables.xml
│ │ └── appIcon.png
│ ├── constants.xml
│ └── hrvFitContributions.xml
├── resources-round-218x218
│ └── constants.xml
├── .project
├── source
│ ├── TimeFormatter.mc
│ ├── testHrv
│ │ ├── TestHrvModel.mc
│ │ ├── TestHrvDelegate.mc
│ │ ├── ElapsedDurationRenderer.mc
│ │ ├── TestHrvActivity.mc
│ │ └── TestHrvView.mc
│ ├── Vibe.mc
│ ├── TestHrvApp.mc
│ ├── summary
│ │ ├── SummaryModel.mc
│ │ └── SummaryViewDelegate.mc
│ └── waitingHrv
│ │ └── WaitingHrvDelegate.mc
├── barrels.jungle
├── resources-round-390x390
│ └── constants.xml
└── manifest.xml
├── StatusIconFonts
├── monkey.jungle
├── .settings
│ └── IQ_IDE.prefs
├── resources
│ ├── fonts
│ │ ├── meditateIcons
│ │ │ ├── yoga.png
│ │ │ ├── meditateIcons.png
│ │ │ └── meditateIcons.fnt
│ │ └── fontAwesome
│ │ │ ├── fontAwesomeFreeSolid_0.png
│ │ │ ├── fontAwesomeFreeRegular_0.png
│ │ │ ├── fontAwesomeFreeRegular.fnt
│ │ │ └── fontAwesomeFreeSolid.fnt
│ ├── resources.xml
│ └── strings
│ │ └── strings.xml
├── source
│ └── StatusIconFonts.mc
├── .project
└── manifest.xml
├── HrvAlgorithms
├── monkey.jungle
├── .settings
│ └── IQ_IDE.prefs
├── sources
│ └── activity
│ │ ├── ActivitySummary.mc
│ │ ├── HrvTracking.mc
│ │ ├── HrSummary.mc
│ │ ├── hrv
│ │ ├── HrvSummary.mc
│ │ ├── tests
│ │ │ ├── HrvAlgorithmsSampleOutput.xlsx
│ │ │ ├── HrvCalculatorFixture.mc
│ │ │ ├── HrvSdrrLastNSecTests.mc
│ │ │ └── HrvSdrrFirstNSecTests.mc
│ │ ├── lombScarglePeriodogram
│ │ │ ├── Lomb-Scargle Periodogram.xlsm
│ │ │ └── Lomb-Scargle_Periodogram.pdf
│ │ ├── HrvSuccessive.mc
│ │ ├── HrvRmssd.mc
│ │ ├── HrvPnnx.mc
│ │ ├── HrvSdrrFirstNSec.mc
│ │ ├── HrvSdrrLastNSec.mc
│ │ ├── HrvRmssdRolling.mc
│ │ ├── HrvMonitorDefault.mc
│ │ └── HrvMonitorDetailed.mc
│ │ ├── stress
│ │ ├── tests
│ │ │ └── StressAlgorithmsSampleOutput.xlsx
│ │ ├── HrPeaksWindow.mc
│ │ └── StressMonitor.mc
│ │ ├── FitSessionSpec.mc
│ │ ├── HeartbeatIntervalsSensor.mc
│ │ ├── ShortDetailedHrvActivity.mc
│ │ ├── HrvAndStressActivity.mc
│ │ └── HrActivity.mc
├── resources
│ ├── readme.txt
│ ├── strings
│ │ └── hrvFitContributionsStrings.xml
│ └── hrvFitContributions.xml
├── .project
└── manifest.xml
├── ScreenPicker
├── monkey.jungle
├── .settings
│ └── IQ_IDE.prefs
├── .project
├── sources
│ ├── ScreenPickerDetailsSinglePageView.mc
│ ├── ScreenPickerDetailsView.mc
│ ├── StressIcon.mc
│ ├── HrvIcon.mc
│ ├── ScreenPickerView.mc
│ ├── ScreenPickerDelegate.mc
│ ├── DetailsViewRenderer.mc
│ └── DetailsModel.mc
└── manifest.xml
├── TestHrvDemo.gif
├── Screenshots
├── Graphs.png
├── SummaryStats.png
└── TestHrvOverviewActivity.png
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | .metadata
3 |
--------------------------------------------------------------------------------
/TestHrv/monkey.jungle:
--------------------------------------------------------------------------------
1 | project.manifest = manifest.xml
2 |
--------------------------------------------------------------------------------
/StatusIconFonts/monkey.jungle:
--------------------------------------------------------------------------------
1 | project.manifest = manifest.xml
--------------------------------------------------------------------------------
/HrvAlgorithms/monkey.jungle:
--------------------------------------------------------------------------------
1 | project.manifest = manifest.xml
2 |
3 |
--------------------------------------------------------------------------------
/ScreenPicker/monkey.jungle:
--------------------------------------------------------------------------------
1 | project.manifest = manifest.xml
2 |
3 |
--------------------------------------------------------------------------------
/TestHrvDemo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/TestHrvDemo.gif
--------------------------------------------------------------------------------
/HrvAlgorithms/.settings/IQ_IDE.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | project_manifest=manifest.xml
3 |
--------------------------------------------------------------------------------
/Screenshots/Graphs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/Screenshots/Graphs.png
--------------------------------------------------------------------------------
/StatusIconFonts/.settings/IQ_IDE.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | project_manifest=manifest.xml
3 |
--------------------------------------------------------------------------------
/TestHrv/resources/strings/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Test HRV
3 |
4 |
--------------------------------------------------------------------------------
/Screenshots/SummaryStats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/Screenshots/SummaryStats.png
--------------------------------------------------------------------------------
/TestHrv/resources/drawables/drawables.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Screenshots/TestHrvOverviewActivity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/Screenshots/TestHrvOverviewActivity.png
--------------------------------------------------------------------------------
/TestHrv/resources/drawables/appIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/TestHrv/resources/drawables/appIcon.png
--------------------------------------------------------------------------------
/TestHrv/resources-round-218x218/constants.xml:
--------------------------------------------------------------------------------
1 |
2 | -7
3 |
--------------------------------------------------------------------------------
/ScreenPicker/.settings/IQ_IDE.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | project_compiler_options=
3 | project_jungle=monkey.jungle
4 | project_manifest=manifest.xml
5 |
--------------------------------------------------------------------------------
/StatusIconFonts/resources/fonts/meditateIcons/yoga.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/StatusIconFonts/resources/fonts/meditateIcons/yoga.png
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/ActivitySummary.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class ActivitySummary {
3 | var hrSummary;
4 | var hrvSummary;
5 | var stress;
6 | }
7 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/HrvTracking.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | module HrvTracking {
3 | enum {
4 | Off = 0,
5 | On = 1,
6 | OnDetailed = 2
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/HrSummary.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrSummary {
3 | var maxHr;
4 | var averageHr;
5 | var minHr;
6 | var elapsedTimeSeconds;
7 | }
8 | }
--------------------------------------------------------------------------------
/StatusIconFonts/resources/fonts/meditateIcons/meditateIcons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/StatusIconFonts/resources/fonts/meditateIcons/meditateIcons.png
--------------------------------------------------------------------------------
/StatusIconFonts/resources/fonts/fontAwesome/fontAwesomeFreeSolid_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/StatusIconFonts/resources/fonts/fontAwesome/fontAwesomeFreeSolid_0.png
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvSummary.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvSummary {
3 | var rmssd;
4 | var pnn50;
5 | var pnn20;
6 | var first5MinSdrr;
7 | var last5MinSdrr;
8 | }
9 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/tests/HrvAlgorithmsSampleOutput.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/HrvAlgorithms/sources/activity/hrv/tests/HrvAlgorithmsSampleOutput.xlsx
--------------------------------------------------------------------------------
/StatusIconFonts/resources/fonts/fontAwesome/fontAwesomeFreeRegular_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/StatusIconFonts/resources/fonts/fontAwesome/fontAwesomeFreeRegular_0.png
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/stress/tests/StressAlgorithmsSampleOutput.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/HrvAlgorithms/sources/activity/stress/tests/StressAlgorithmsSampleOutput.xlsx
--------------------------------------------------------------------------------
/HrvAlgorithms/resources/readme.txt:
--------------------------------------------------------------------------------
1 | Copy hrvFitContributions.xml and strings\hrvFitContributionsStrings.xml to the actual project that is using the barrel.
2 | Without doing this the extra Fit fields will not be shown in Connect IQ.
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/lombScarglePeriodogram/Lomb-Scargle Periodogram.xlsm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/HrvAlgorithms/sources/activity/hrv/lombScarglePeriodogram/Lomb-Scargle Periodogram.xlsm
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/lombScarglePeriodogram/Lomb-Scargle_Periodogram.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtrifonov-esfiddle/TestHrv/HEAD/HrvAlgorithms/sources/activity/hrv/lombScarglePeriodogram/Lomb-Scargle_Periodogram.pdf
--------------------------------------------------------------------------------
/StatusIconFonts/source/StatusIconFonts.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 |
3 | module StatusIconFonts {
4 | var fontMeditateIcons = Ui.loadResource(Rez.Fonts.fontMeditateIcons);
5 | var fontAwesomeFreeSolid = Ui.loadResource(Rez.Fonts.fontAwesomeFreeSolid);
6 | var fontAwesomeFreeRegular = Ui.loadResource(Rez.Fonts.fontAwesomeFreeRegular);
7 | }
--------------------------------------------------------------------------------
/StatusIconFonts/resources/resources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TestHrv/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | TestHrv
4 |
5 |
6 |
7 |
8 |
9 | connectiq.builder
10 |
11 |
12 |
13 |
14 |
15 | connectiq.projectNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/ScreenPicker/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | ScreenPicker
4 |
5 |
6 |
7 |
8 |
9 | connectiq.barrelBuilder
10 |
11 |
12 |
13 |
14 |
15 | connectiq.barrelProjectNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/TestHrv/source/TimeFormatter.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Lang;
2 |
3 | class TimeFormatter {
4 | static function format(timeInSec) {
5 | var timeCalc = timeInSec;
6 | var seconds = timeCalc % 60;
7 | timeCalc /= 60;
8 | var minutes = timeCalc % 60;
9 | timeCalc /= 60;
10 | var hours = timeCalc % 24;
11 |
12 | var formattedTime = Lang.format("$1$:$2$", [minutes.format("%1d"), seconds.format("%02d")]);
13 | return formattedTime;
14 | }
15 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | HrvAlgorithms
4 |
5 |
6 |
7 |
8 |
9 | connectiq.barrelBuilder
10 |
11 |
12 |
13 |
14 |
15 | connectiq.barrelProjectNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/StatusIconFonts/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | StatusIconFonts
4 |
5 |
6 |
7 |
8 |
9 | connectiq.barrelBuilder
10 |
11 |
12 |
13 |
14 |
15 | connectiq.barrelProjectNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/TestHrv/source/testHrv/TestHrvModel.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Graphics as Gfx;
2 |
3 | class TestHrvModel {
4 | function initialize() {
5 | me.elapsedTimeSec = 0;
6 | }
7 |
8 | var elapsedTimeSec;
9 | const SessionTimeSec = 180;
10 | const ElapsedTimeColor = Gfx.COLOR_BLUE;
11 |
12 | function isHrvTestFinished() {
13 | return me.elapsedTimeSec >= SessionTimeSec;
14 | }
15 |
16 | function getRemainingTime() {
17 | return SessionTimeSec - me.elapsedTimeSec;
18 | }
19 | }
--------------------------------------------------------------------------------
/TestHrv/barrels.jungle:
--------------------------------------------------------------------------------
1 | # Do not hand edit this file. To make changes right click
2 | # on the project and select "Configure Monkey Barrels".
3 |
4 | StatusIconFonts = [..\StatusIconFonts\monkey.jungle]
5 | base.barrelPath = $(base.barrelPath);$(StatusIconFonts)
6 |
7 | ScreenPicker = [..\ScreenPicker\monkey.jungle]
8 | base.barrelPath = $(base.barrelPath);$(ScreenPicker)
9 |
10 | HrvAlgorithms = [..\HrvAlgorithms\monkey.jungle]
11 | base.barrelPath = $(base.barrelPath);$(HrvAlgorithms)
12 |
13 |
--------------------------------------------------------------------------------
/TestHrv/source/Vibe.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Attention;
2 |
3 | class Vibe {
4 | static function vibrateLongContinuous() {
5 | var vibeProfile = [
6 | new Attention.VibeProfile(100, 4000)
7 | ];
8 | Attention.vibrate(vibeProfile);
9 | }
10 |
11 | static function vibrateMediumPulsating() {
12 | var vibeProfile = [
13 | new Attention.VibeProfile(100, 1000),
14 | new Attention.VibeProfile(0, 1000),
15 | new Attention.VibeProfile(100, 1000)
16 | ];
17 | Attention.vibrate(vibeProfile);
18 | }
19 | }
--------------------------------------------------------------------------------
/ScreenPicker/sources/ScreenPickerDetailsSinglePageView.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Graphics as Gfx;
3 |
4 | module ScreenPicker {
5 | class ScreenPickerDetailsSinglePageView extends Ui.View {
6 | private var mRenderer;
7 |
8 | function initialize(detailsModel) {
9 | View.initialize();
10 | me.mRenderer = new DetailsViewRenderer(detailsModel);
11 | }
12 |
13 | function onUpdate(dc) {
14 | View.onUpdate(dc);
15 | me.mRenderer.renderBackgroundColor(dc);
16 | me.mRenderer.renderDetailsView(dc);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/ScreenPicker/sources/ScreenPickerDetailsView.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Graphics as Gfx;
3 |
4 | module ScreenPicker {
5 | class ScreenPickerDetailsView extends ScreenPickerView {
6 | private var mRenderer;
7 |
8 | function initialize(detailsModel) {
9 | ScreenPickerView.initialize(detailsModel.color);
10 | me.mRenderer = new DetailsViewRenderer(detailsModel);
11 | }
12 |
13 | function onUpdate(dc) {
14 | me.mRenderer.renderBackgroundColor(dc);
15 | ScreenPickerView.onUpdate(dc);
16 | me.mRenderer.renderDetailsView(dc);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/ScreenPicker/sources/StressIcon.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Graphics as Gfx;
2 |
3 | module ScreenPicker {
4 | class StressIcon extends Icon {
5 | function initialize(icon) {
6 | icon[:font] = StatusIconFonts.fontMeditateIcons;
7 | icon[:symbol] = StatusIconFonts.Rez.Strings.meditateFontStress;
8 |
9 | Icon.initialize(icon);
10 | }
11 |
12 | function setNoStress() {
13 | me.setColor(Gfx.COLOR_DK_GREEN);
14 | }
15 |
16 | function setLowStress() {
17 | me.setColor(Gfx.COLOR_ORANGE);
18 | }
19 |
20 | function setHighStress() {
21 | me.setColor(Gfx.COLOR_DK_RED);
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/TestHrv/resources/constants.xml:
--------------------------------------------------------------------------------
1 |
2 | 30
3 | 60
4 | 60
5 | 80
6 | 50
7 | 70
8 | 0
9 | 32
10 | 32
11 |
--------------------------------------------------------------------------------
/TestHrv/resources-round-390x390/constants.xml:
--------------------------------------------------------------------------------
1 |
2 | 70
3 | 110
4 | 80
5 | 110
6 | 80
7 | 110
8 | 0
9 | 50
10 | 52
11 |
--------------------------------------------------------------------------------
/TestHrv/source/testHrv/TestHrvDelegate.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi;
2 |
3 | class TestHrvDelegate extends WatchUi.BehaviorDelegate {
4 |
5 | function initialize(heartbeatIntervalsSensor) {
6 | BehaviorDelegate.initialize();
7 |
8 | me.mTestHrvModel = new TestHrvModel();
9 | me.mTestHrvActivity = new TestHrvActivity(me.mTestHrvModel, heartbeatIntervalsSensor);
10 | }
11 |
12 | private var mTestHrvModel;
13 | private var mTestHrvActivity;
14 |
15 | function createTestHrvView() {
16 | return new TestHrvView(me.mTestHrvModel);
17 | }
18 |
19 | function onBack() {
20 | if (me.mTestHrvActivity != null) {
21 | me.mTestHrvActivity.discardSession();
22 | me.mTestHrvActivity = null;
23 | }
24 | return false;//exit
25 | }
26 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvSuccessive.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvSuccessive {
3 | function initialize() {
4 | me.mPreviousBeatToBeatInterval = null;
5 | me.mCurrentBeatToBeatInterval = null;
6 | }
7 |
8 | private var mPreviousBeatToBeatInterval;
9 | private var mCurrentBeatToBeatInterval;
10 |
11 | function addBeatToBeatInterval(beatToBeatInterval) {
12 | me.mPreviousBeatToBeatInterval = me.mCurrentBeatToBeatInterval;
13 | me.mCurrentBeatToBeatInterval = beatToBeatInterval;
14 | }
15 |
16 | function calculate() {
17 | if (me.mPreviousBeatToBeatInterval == null || me.mCurrentBeatToBeatInterval == null) {
18 | return null;
19 | }
20 | return me.mCurrentBeatToBeatInterval - me.mPreviousBeatToBeatInterval;
21 | }
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/TestHrv/source/TestHrvApp.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Application;
2 | using Toybox.WatchUi;
3 |
4 | class TestHrvApp extends Application.AppBase {
5 |
6 | function initialize() {
7 | AppBase.initialize();
8 | }
9 |
10 | // onStart() is called on application start up
11 | function onStart(state) {
12 | }
13 |
14 | // onStop() is called when your application is exiting
15 | function onStop(state) {
16 | }
17 |
18 | // Return the initial view of your application here
19 | function getInitialView() {
20 | var heartbeatIntervalsSensor = new HrvAlgorithms.HeartbeatIntervalsSensor();
21 | var waitingHrvDelegate = new WaitingHrvDelegate(heartbeatIntervalsSensor);
22 | return [ waitingHrvDelegate.createScreenPickerView(), waitingHrvDelegate ];
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/FitSessionSpec.mc:
--------------------------------------------------------------------------------
1 | using Toybox.ActivityRecording;
2 |
3 | module HrvAlgorithms {
4 | class FitSessionSpec {
5 | private static const SUB_SPORT_YOGA = 43;
6 | private static const SUB_SPORT_BREATHWORKS = 62;
7 |
8 | static function createYoga() {
9 | return {
10 | :name => "Yoga",
11 | :sport => ActivityRecording.SPORT_TRAINING,
12 | :subSport => SUB_SPORT_YOGA
13 | };
14 | }
15 |
16 | static function createBreathworks(sessionName) {
17 | return {
18 | :name => sessionName,
19 | :sport => ActivityRecording.SPORT_TRAINING,
20 | :subSport => SUB_SPORT_BREATHWORKS
21 | };
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/ScreenPicker/sources/HrvIcon.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Graphics as Gfx;
2 |
3 | module ScreenPicker {
4 | class HrvIcon extends Icon {
5 | function initialize(icon) {
6 | icon[:font] = StatusIconFonts.fontAwesomeFreeSolid;
7 | icon[:symbol] = StatusIconFonts.Rez.Strings.faHeartbeat;
8 | if (icon[:color] == null) {
9 | icon[:color] = HeartBeatPurpleColor;
10 | }
11 |
12 | Icon.initialize(icon);
13 | }
14 |
15 | const HeartBeatPurpleColor = 0xFF00FF;
16 |
17 | function setStatusDefault() {
18 | me.setColor(HeartBeatPurpleColor);
19 | }
20 |
21 | function setStatusOn() {
22 | me.setColor(HeartBeatPurpleColor);
23 | }
24 |
25 | function setStatusOnDetailed() {
26 | me.setColor(Gfx.COLOR_BLUE);
27 | }
28 |
29 | function setStatusOff() {
30 | me.setColor(Gfx.COLOR_LT_GRAY);
31 | }
32 |
33 | function setStatusWarning() {
34 | me.setColor(Gfx.COLOR_YELLOW);
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/StatusIconFonts/resources/strings/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 1
16 | 3
17 | 2
18 | 4
19 | 5
20 | 6
21 | 7
22 | 8
23 |
24 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvRmssd.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvRmssd {
3 | function initialize() {
4 | me.mSquareOfSuccessiveBtbDifferences = 0.0;
5 | me.mPreviousBeatToBeatInterval = null;
6 | me.mBeatToBeatIntervalsCount = 0;
7 | }
8 |
9 | private var mSquareOfSuccessiveBtbDifferences;
10 | private var mPreviousBeatToBeatInterval;
11 | private var mBeatToBeatIntervalsCount;
12 |
13 | function addBeatToBeatInterval(beatToBeatInterval) {
14 | if (me.mPreviousBeatToBeatInterval != null) {
15 | me.mBeatToBeatIntervalsCount++;
16 |
17 | me.mSquareOfSuccessiveBtbDifferences += Math.pow(beatToBeatInterval - me.mPreviousBeatToBeatInterval, 2);
18 | }
19 | me.mPreviousBeatToBeatInterval = beatToBeatInterval;
20 | }
21 |
22 | function calculate() {
23 | if (me.mBeatToBeatIntervalsCount < 1) {
24 | return null;
25 | }
26 |
27 | var rootMeanSquareOfSuccessiveBtbDifferences = Math.sqrt(me.mSquareOfSuccessiveBtbDifferences / me.mBeatToBeatIntervalsCount);
28 | return rootMeanSquareOfSuccessiveBtbDifferences;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/TestHrv/resources/strings/hrvFitContributionsStrings.xml:
--------------------------------------------------------------------------------
1 |
2 | Min HR
3 | bpm
4 |
5 | HRV SDRR
6 | HRV SDRR First 5 min
7 | HRV SDRR Last 5 min
8 |
9 | ms
10 |
11 | HRV Successive Differences(ms)
12 | HRV RMSSD
13 | HRV pNN20
14 | HRV pNN50
15 |
16 | HRV RMSSD 30 Sec Window(ms)
17 |
18 | %
19 | %
20 |
21 | HRV beat-to-beat interval(ms)
22 |
23 | HR Peaks 10 Sec Window(bpm)
24 | Stress
25 | HR from heartbeat(bpm)
26 |
27 |
--------------------------------------------------------------------------------
/HrvAlgorithms/resources/strings/hrvFitContributionsStrings.xml:
--------------------------------------------------------------------------------
1 |
2 | Min HR
3 | bpm
4 |
5 | HRV SDRR
6 | HRV SDRR First 5 min
7 | HRV SDRR Last 5 min
8 |
9 | ms
10 |
11 | HRV Successive Differences(ms)
12 | HRV RMSSD
13 | HRV pNN20
14 | HRV pNN50
15 |
16 | HRV RMSSD 30 Sec Window(ms)
17 |
18 | %
19 | %
20 |
21 | HRV beat-to-beat interval(ms)
22 |
23 | HR Peaks 10 Sec Window(bpm)
24 | Stress
25 | HR from heartbeat(bpm)
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 vtrifonov-esfiddle
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/StatusIconFonts/resources/fonts/meditateIcons/meditateIcons.fnt:
--------------------------------------------------------------------------------
1 | info face="Meditate Icons" size=32 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
2 | common lineHeight=32 base=28 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0
3 | page id=0 file="meditateIcons.png"
4 | chars count=2
5 | char id=49 x=0 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
6 | char id=50 x=33 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
7 | char id=51 x=65 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
8 | char id=52 x=97 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
9 | char id=53 x=129 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
10 | char id=54 x=161 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
11 | char id=55 x=193 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
12 | char id=56 x=225 y=0 width=32 height=32 xoffset=0 yoffset=1 xadvance=32 page=0 chnl=15
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvPnnx.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvPnnx {
3 | function initialize(differenceThreshold) {
4 | me.mPreviousBeatToBeatInterval = null;
5 | me.mTotalIntervalsCount = 0;
6 | me.mOverThresholdIntervalsCount = 0;
7 | me.mDifferenceThreshold = differenceThreshold;
8 | }
9 |
10 | private var mPreviousBeatToBeatInterval;
11 | private var mTotalIntervalsCount;
12 | private var mOverThresholdIntervalsCount;
13 | private var mDifferenceThreshold;
14 |
15 | function addBeatToBeatInterval(beatToBeatInterval) {
16 | me.mTotalIntervalsCount++;
17 | if (beatToBeatInterval == null) {
18 | return;
19 | }
20 | if (me.mPreviousBeatToBeatInterval == null) {
21 | me.mPreviousBeatToBeatInterval = beatToBeatInterval;
22 | }
23 | var intervalsDifference = (beatToBeatInterval - me.mPreviousBeatToBeatInterval).abs();
24 | me.mPreviousBeatToBeatInterval = beatToBeatInterval;
25 | if (intervalsDifference > me.mDifferenceThreshold) {
26 | me.mOverThresholdIntervalsCount++;
27 | }
28 | }
29 |
30 | function calculate() {
31 | if (me.mTotalIntervalsCount == 0) {
32 | return null;
33 | }
34 | return (me.mOverThresholdIntervalsCount.toFloat() / me.mTotalIntervalsCount.toFloat()) * 100.0;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/TestHrv/source/testHrv/ElapsedDurationRenderer.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Graphics as Gfx;
2 | using Toybox.Lang;
3 |
4 | class ElapsedDuationRenderer {
5 | function initialize(color, radius, width) {
6 | me.mColor = color;
7 | me.mRadius = radius;
8 | me.mWidth = width;
9 | }
10 |
11 | private var mColor;
12 | private var mRadius;
13 | private var mWidth;
14 | private const StartDegree = 90;
15 |
16 | function drawOverallElapsedTime(dc, overallElapsedTime, alarmTime) {
17 | var progressPercentage;
18 | if (overallElapsedTime >= alarmTime) {
19 | progressPercentage = 100;
20 | }
21 | else {
22 | progressPercentage = 100 * (overallElapsedTime.toFloat() / alarmTime.toFloat());
23 | if (progressPercentage == 0) {
24 | progressPercentage = 0.05;
25 | }
26 | }
27 | me.drawDuration(dc, progressPercentage);
28 | }
29 | private function drawDuration(dc, progressPercentage) {
30 | dc.setColor(me.mColor, Gfx.COLOR_TRANSPARENT);
31 | dc.setPenWidth(me.mWidth);
32 | var endDegree = StartDegree + percentageToArcDegree(progressPercentage);
33 | dc.drawArc(dc.getWidth() / 2, dc.getHeight() / 2, me.mRadius , Gfx.ARC_CLOCKWISE, me.StartDegree, endDegree);
34 | }
35 |
36 | private static function percentageToArcDegree(percentage) {
37 | return - percentage * 3.6;
38 | }
39 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/HeartbeatIntervalsSensor.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Sensor;
2 |
3 | module HrvAlgorithms {
4 | class HeartbeatIntervalsSensor {
5 | private const SessionSamplePeriodSeconds = 1;
6 |
7 | function initialize() {
8 | me.enableHrSensor();
9 | }
10 | function start() {
11 | Sensor.unregisterSensorDataListener();
12 | Sensor.registerSensorDataListener(method(:onSessionSensorData), {
13 | :period => SessionSamplePeriodSeconds,
14 | :heartBeatIntervals => {
15 | :enabled => true
16 | }
17 | });
18 | }
19 |
20 | private function enableHrSensor() {
21 | Sensor.setEnabledSensors([Sensor.SENSOR_HEARTRATE]);
22 | }
23 |
24 | function disableHrSensor() {
25 | Sensor.setEnabledSensors([]);
26 | }
27 |
28 | function setOneSecBeatToBeatIntervalsSensorListener(listener) {
29 | me.mSensorListener = listener;
30 | }
31 |
32 | private var mSensorListener;
33 |
34 | function onSessionSensorData(sensorData) {
35 | if (!(sensorData has :heartRateData) || sensorData.heartRateData == null || mSensorListener == null) {
36 | return;
37 | }
38 |
39 | var heartBeatIntervals = sensorData.heartRateData.heartBeatIntervals;
40 | me.mSensorListener.invoke(heartBeatIntervals);
41 | }
42 |
43 | function stop() {
44 | Sensor.unregisterSensorDataListener();
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/ScreenPicker/sources/ScreenPickerView.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Graphics as Gfx;
3 |
4 | module ScreenPicker {
5 | class ScreenPickerView extends Ui.View {
6 | function initialize(upDownArrowsColor) {
7 | View.initialize();
8 | me.mUpArrow = new Icon({
9 | :font => StatusIconFonts.fontAwesomeFreeSolid,
10 | :symbol => StatusIconFonts.Rez.Strings.faSortUp,
11 | :color=>upDownArrowsColor,
12 | :justify => Gfx.TEXT_JUSTIFY_CENTER
13 | });
14 | me.mDownArrow = new Icon({
15 | :font => StatusIconFonts.fontAwesomeFreeSolid,
16 | :symbol => StatusIconFonts.Rez.Strings.faSortDown,
17 | :color=>upDownArrowsColor,
18 | :justify => Gfx.TEXT_JUSTIFY_CENTER
19 | });
20 | }
21 |
22 | private var mUpArrow;
23 | private var mDownArrow;
24 |
25 | protected function setArrowsColor(color) {
26 | me.mUpArrow.setColor(color);
27 | me.mDownArrow.setColor(color);
28 | }
29 |
30 | function onUpdate(dc) {
31 | var centerXPos = dc.getWidth() / 2;
32 | me.mUpArrow.setXPos(centerXPos);
33 | me.mUpArrow.setYPos(0);
34 | me.mUpArrow.draw(dc);
35 | me.mDownArrow.setXPos(centerXPos);
36 | me.mDownArrow.setYPos(dc.getHeight() - dc.getFontHeight(StatusIconFonts.fontAwesomeFreeSolid));
37 | me.mDownArrow.draw(dc);
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/ScreenPicker/sources/ScreenPickerDelegate.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 |
3 | module ScreenPicker {
4 | class ScreenPickerDelegate extends Ui.BehaviorDelegate {
5 | protected var mSelectedPageIndex;
6 | protected var mPagesCount;
7 |
8 | function initialize(selectedPageIndex, pagesCount) {
9 | BehaviorDelegate.initialize();
10 | me.mSelectedPageIndex = selectedPageIndex;
11 | me.mPagesCount = pagesCount;
12 | }
13 |
14 | function createScreenPickerView() {
15 | }
16 |
17 | function setPagesCount(pagesCount) {
18 | me.mPagesCount = pagesCount;
19 | }
20 |
21 | function select(pageIndex) {
22 | me.mSelectedPageIndex = pageIndex;
23 | Ui.switchToView(me.createScreenPickerView(), me, Ui.SLIDE_IMMEDIATE);
24 | }
25 |
26 | function onNextPage() {
27 | if (me.mPagesCount == 1) {
28 | me.mSelectedPageIndex = 0;
29 | }
30 | else {
31 | me.mSelectedPageIndex = (me.mSelectedPageIndex + 1) % me.mPagesCount;
32 | }
33 | Ui.switchToView(me.createScreenPickerView(), me, Ui.SLIDE_UP);
34 | return true;
35 | }
36 |
37 | function onPreviousPage() {
38 | if (me.mSelectedPageIndex == 0) {
39 | me.mSelectedPageIndex = me.mPagesCount - 1;
40 | }
41 | else {
42 | me.mSelectedPageIndex -= 1;
43 | }
44 | Ui.switchToView(me.createScreenPickerView(), me, Ui.SLIDE_DOWN);
45 | return true;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvSdrrFirstNSec.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvSdrrFirstNSec {
3 | function initialize(maxIntervalsCount) {
4 | me.mMaxIntervalsCount = maxIntervalsCount;
5 | me.mBeatToBeatIntervalsCount = 0;
6 | me.mIntervals = new [maxIntervalsCount];
7 | }
8 |
9 | private var mMaxIntervalsCount;
10 | private var mBeatToBeatIntervalsCount;
11 | private var mIntervals;
12 |
13 | function addBeatToBeatInterval(beatToBeatInterval) {
14 | if (me.mBeatToBeatIntervalsCount >= me.mMaxIntervalsCount) {
15 | return;
16 | }
17 |
18 | if (beatToBeatInterval != null) {
19 | me.mBeatToBeatIntervalsCount++;
20 |
21 | var intervalsIndex = me.mBeatToBeatIntervalsCount - 1;
22 | me.mIntervals[intervalsIndex] = beatToBeatInterval.toFloat();
23 | }
24 | }
25 |
26 | function calculate() {
27 | if (me.mBeatToBeatIntervalsCount < 2) {
28 | return null;
29 | }
30 |
31 | var sumBeatToBeat = 0.0;
32 | for (var i = 0; i < me.mBeatToBeatIntervalsCount; i++) {
33 | sumBeatToBeat += me.mIntervals[i];
34 | }
35 | var meanBeatToBeat = sumBeatToBeat / me.mBeatToBeatIntervalsCount.toFloat();
36 | var sumSquaredDeviations = 0.0;
37 | for (var i = 0; i < me.mBeatToBeatIntervalsCount; i++) {
38 | sumSquaredDeviations += Math.pow(me.mIntervals[i] - meanBeatToBeat, 2);
39 | }
40 | return Math.sqrt(sumSquaredDeviations / me.mBeatToBeatIntervalsCount.toFloat());
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/TestHrv/source/summary/SummaryModel.mc:
--------------------------------------------------------------------------------
1 | using Toybox.System;
2 |
3 | class SummaryModel {
4 | function initialize(activitySummary) {
5 | me.maxHr = me.initializeHeartRate(activitySummary.hrSummary.maxHr);
6 | me.avgHr = me.initializeHeartRate(activitySummary.hrSummary.averageHr);
7 | me.minHr = me.initializeHeartRate(activitySummary.hrSummary.minHr);
8 |
9 | if (activitySummary.hrvSummary != null) {
10 | me.hrvRmssd = me.initializeHeartRateVariability(activitySummary.hrvSummary.rmssd);
11 | me.hrvSdrr = me.initializeHeartRateVariability(activitySummary.hrvSummary.first5MinSdrr);
12 | me.hrvPnn50 = me.initializePercentageValue(activitySummary.hrvSummary.pnn50);
13 | me.hrvPnn20 = me.initializePercentageValue(activitySummary.hrvSummary.pnn20);
14 | }
15 | }
16 |
17 | private function initializeHeartRate(heartRate) {
18 | if (heartRate == null || heartRate == 0) {
19 | return me.InvalidHeartRate;
20 | }
21 | else {
22 | return heartRate;
23 | }
24 | }
25 |
26 | private function initializePercentageValue(stressScore) {
27 | if (stressScore == null) {
28 | return me.InvalidHeartRate;
29 | }
30 | else {
31 | return stressScore.format("%3.2f");
32 | }
33 | }
34 |
35 | private function initializeHeartRateVariability(hrv) {
36 | if (hrv == null) {
37 | return me.InvalidHeartRate;
38 | }
39 | else {
40 | return hrv.format("%3.2f");
41 | }
42 | }
43 |
44 | private const InvalidHeartRate = "--";
45 |
46 | var maxHr;
47 | var avgHr;
48 | var minHr;
49 |
50 | var hrvRmssd;
51 | var hrvSdrr;
52 | var hrvPnn50;
53 | var hrvPnn20;
54 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvSdrrLastNSec.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvSdrrLastNSec {
3 | function initialize(intervalsCount) {
4 | me.mIntervalsCount = intervalsCount;
5 | me.mBeatToBeatIntervalsCount = 0;
6 | me.mIntervals = new [intervalsCount];
7 | me.mBufferCurrentIndex = 0;
8 | }
9 |
10 | private var mIntervalsCount;
11 | private var mBeatToBeatIntervalsCount;
12 | private var mIntervals;
13 |
14 | private var mBufferCurrentIndex;
15 |
16 | function addBeatToBeatInterval(beatToBeatInterval) {
17 | if (beatToBeatInterval != null) {
18 | me.mBeatToBeatIntervalsCount++;
19 |
20 | me.storeBeatToBeatInterval(beatToBeatInterval);
21 | }
22 | }
23 |
24 | private function storeBeatToBeatInterval(beatToBeatInterval) {
25 | me.mIntervals[me.mBufferCurrentIndex] = beatToBeatInterval.toFloat();
26 | var bufferIndexRewinded = (me.mBufferCurrentIndex + 1) % me.mIntervalsCount;
27 | me.mBufferCurrentIndex = bufferIndexRewinded;
28 | }
29 |
30 | function calculate() {
31 | if (me.mBeatToBeatIntervalsCount < me.mIntervalsCount) {
32 | return null;
33 | }
34 |
35 | var sumBeatToBeat = 0.0;
36 | for (var i = 0; i < me.mIntervalsCount; i++) {
37 | sumBeatToBeat += me.mIntervals[i];
38 | }
39 | var meanBeatToBeat = sumBeatToBeat / me.mIntervalsCount.toFloat();
40 |
41 | var sumSquaredDeviations = 0.0;
42 | for (var i = 0; i < me.mIntervalsCount; i++) {
43 | sumSquaredDeviations += Math.pow(me.mIntervals[i] - meanBeatToBeat, 2);
44 | }
45 | return Math.sqrt(sumSquaredDeviations / me.mIntervalsCount.toFloat());
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/ShortDetailedHrvActivity.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class ShortDetailedHrvActivity extends HrActivity {
3 | function initialize(fitSession, heartbeatIntervalsSensor) {
4 | me.mHeartbeatIntervalsSensor = heartbeatIntervalsSensor;
5 | HrActivity.initialize(fitSession);
6 | }
7 |
8 | protected var mHeartbeatIntervalsSensor;
9 | private var mHrvMonitor;
10 |
11 |
12 | protected function onBeforeStart(fitSession) {
13 | me.mHeartbeatIntervalsSensor.setOneSecBeatToBeatIntervalsSensorListener(method(:onOneSecBeatToBeatIntervals));
14 | me.mHrvMonitor = new HrvMonitorDetailed(fitSession, false);
15 | }
16 |
17 | function onOneSecBeatToBeatIntervals(heartBeatIntervals) {
18 | me.mHrvMonitor.addOneSecBeatToBeatIntervals(heartBeatIntervals);
19 | }
20 |
21 | protected function onBeforeStop() {
22 | me.mHeartbeatIntervalsSensor.setOneSecBeatToBeatIntervalsSensorListener(null);
23 | }
24 |
25 | private var mHrvSuccessive;
26 |
27 | protected function onRefreshHrActivityStats(activityInfo, minHr) {
28 | me.mHrvSuccessive = me.mHrvMonitor.calculateHrvSuccessive();
29 | me.onRefreshHrvActivityStats(activityInfo, minHr, me.mHrvSuccessive);
30 | }
31 |
32 | protected function onRefreshHrvActivityStats(activityInfo, minHr, hrvSuccessive) {
33 | }
34 |
35 | function calculateSummaryFields() {
36 | var hrSummary = HrActivity.calculateSummaryFields();
37 | var activitySummary = new ActivitySummary();
38 | activitySummary.hrSummary = hrSummary;
39 | activitySummary.hrvSummary = me.mHrvMonitor.calculateHrvSummary();
40 | return activitySummary;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/tests/HrvCalculatorFixture.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Math;
2 |
3 | module HrvAlgorithms {
4 | class HrvCalculatorFixture{
5 | function initialize(hrvCalculator) {
6 | me.mHrvCalculator = hrvCalculator;
7 | }
8 |
9 | private var mHrvCalculator;
10 |
11 | function add1NormalInterval() {
12 | mHrvCalculator.addBeatToBeatInterval(1090.9);
13 | }
14 |
15 | function add6NormalIntervals() {
16 | mHrvCalculator.addBeatToBeatInterval(1090.9);
17 | mHrvCalculator.addBeatToBeatInterval(1016.9);
18 | mHrvCalculator.addBeatToBeatInterval(1016.9);
19 | mHrvCalculator.addBeatToBeatInterval(1034.4);
20 | mHrvCalculator.addBeatToBeatInterval(1016.9);
21 | mHrvCalculator.addBeatToBeatInterval(1052.6);
22 | }
23 |
24 | function add5MinNormalIntervals() {
25 | for (var i=1; i <= 5 * 10; i++) {
26 | add6NormalIntervals();
27 | }
28 | }
29 |
30 | function addOutlierInterval() {
31 | mHrvCalculator.addBeatToBeatInterval(1400.0);
32 | }
33 |
34 | function calculate() {
35 | var result = mHrvCalculator.calculate();
36 | return result;
37 | }
38 |
39 | function isResultExpected(actual, expected) {
40 | System.println("Hrv Calculator result: actual: " + actual + "; expected: " + expected);
41 | var actualRound = Math.floor(actual * 100.0);
42 | var expectedRound = Math.floor(expected * 100.0);
43 | return actualRound == expectedRound;
44 | }
45 | }
46 |
47 | class ExpectedHrvSdrr {
48 | const Expected6NormalIntervals = 26.95;
49 | const Expected5MinNormalIntervals = 26.95;
50 | const Expected6NormalIntervals1Outlier = 129.07;
51 | const Expected5NormalIntervals1Outlier = 139.41;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvRmssdRolling.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvRmssdRolling {
3 | function initialize(rollingIntervalSeconds) {
4 | me.mRollingIntervalSeconds = rollingIntervalSeconds;
5 | me.reset();
6 | }
7 |
8 | private var mRollingIntervalSeconds;
9 | private var mSecondsCount;
10 | private var mSquareOfSuccessiveBtbDifferences;
11 | private var mPreviousBeatToBeatInterval;
12 | private var mBeatToBeatIntervalsCount;
13 |
14 | private function reset() {
15 | me.mSecondsCount = 0;
16 | me.mSquareOfSuccessiveBtbDifferences = 0.0;
17 | me.mBeatToBeatIntervalsCount = 0;
18 | me.mPreviousBeatToBeatInterval = null;
19 | }
20 |
21 | function addOneSecBeatToBeatIntervals(beatToBeatIntervals) {
22 | for (var i = 0; i < beatToBeatIntervals.size(); i++) {
23 | me.addBeatToBeatInterval(beatToBeatIntervals[i]);
24 | }
25 | me.mSecondsCount++;
26 | return me.calculate();
27 | }
28 |
29 | private function addBeatToBeatInterval(beatToBeatInterval) {
30 | if (me.mPreviousBeatToBeatInterval != null) {
31 | me.mBeatToBeatIntervalsCount++;
32 |
33 | me.mSquareOfSuccessiveBtbDifferences += Math.pow(beatToBeatInterval - me.mPreviousBeatToBeatInterval, 2);
34 | }
35 | me.mPreviousBeatToBeatInterval = beatToBeatInterval;
36 | }
37 |
38 | private function calculate() {
39 | if (me.mSecondsCount < me.mRollingIntervalSeconds || me.mBeatToBeatIntervalsCount < 1) {
40 | return null;
41 | }
42 |
43 | var rootMeanSquareOfSuccessiveBtbDifferences = Math.sqrt(me.mSquareOfSuccessiveBtbDifferences / me.mBeatToBeatIntervalsCount);
44 | me.reset();
45 | return rootMeanSquareOfSuccessiveBtbDifferences;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/TestHrv/source/testHrv/TestHrvActivity.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Timer;
3 | using Toybox.FitContributor;
4 | using Toybox.Timer;
5 | using Toybox.Math;
6 | using Toybox.Sensor;
7 | using HrvAlgorithms.HrvTracking;
8 |
9 | class TestHrvActivity extends HrvAlgorithms.ShortDetailedHrvActivity {
10 | private var mTestHrvModel;
11 |
12 | function initialize(testHrvModel, heartbeatIntervalsSensor) {
13 | var fitSessionSpec = HrvAlgorithms.FitSessionSpec.createBreathworks("Test HRV");
14 |
15 | me.mTestHrvModel = testHrvModel;
16 | HrvAlgorithms.ShortDetailedHrvActivity.initialize(fitSessionSpec, heartbeatIntervalsSensor);
17 | }
18 |
19 | protected function onRefreshHrvActivityStats(activityInfo, minHr, hrvSuccessive) {
20 | if (activityInfo.elapsedTime != null) {
21 | me.mTestHrvModel.elapsedTimeSec = activityInfo.elapsedTime / 1000;
22 | }
23 |
24 | Ui.requestUpdate();
25 |
26 | if (me.mTestHrvModel.isHrvTestFinished()) {
27 | me.finishSession();
28 | }
29 | }
30 |
31 | private function finishSession() {
32 | Vibe.vibrateLongContinuous();
33 | me.stop();
34 | var summary = me.calculateSummaryFields();
35 | me.finish();
36 | me.mHeartbeatIntervalsSensor.stop();
37 | me.mHeartbeatIntervalsSensor.disableHrSensor();
38 |
39 | var summaryViewDelegate = new SummaryViewDelegate(summary);
40 | Ui.switchToView(summaryViewDelegate.createScreenPickerView(), summaryViewDelegate, Ui.SLIDE_LEFT);
41 | }
42 |
43 | function discardSession() {
44 | me.stop();
45 | me.mHeartbeatIntervalsSensor.disableHrSensor();
46 | me.discard();
47 | me.mHeartbeatIntervalsSensor.stop();
48 | }
49 |
50 | function calculateSummaryFields() {
51 | var activitySummary = HrvAlgorithms.ShortDetailedHrvActivity.calculateSummaryFields();
52 | var summaryModel = new SummaryModel(activitySummary);
53 | return summaryModel;
54 | }
55 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Test HRV
2 |
3 | A Garmin app that measures [HRV](https://en.wikipedia.org/wiki/Heart_rate_variability)(Heart Rate Variability) in a 3 minute test. Saves the output as a Connect IQ activity.
4 |
5 | 
6 |
7 | ## [Download](https://apps.garmin.com/en-US/apps/0bdc0e75-9baa-417a-8c9f-e976662a5d2e)
8 |
9 | Link to the Garmin Connect IQ app store.
10 |
11 | ## Measurements
12 |
13 | - [HRV](https://en.wikipedia.org/wiki/Heart_rate_variability) (Heart Rate Variability)
14 | - RMSSD - Root Mean Square of Successive Differences (beat-to-beat intervals)
15 | - pNN20 - % of successive beat-to-beat intervals that differ by more than 20 ms
16 | - pNN50 - % of successive beat-to-beat intervals that differ by more than 50 ms
17 | - beat-to-beat interval - reading coming directly from the watch sensor
18 | - HRV Successive Differences - difference between current and previous beat-to-beat intervals
19 | - SDRR - [Standard Deviation](https://en.wikipedia.org/wiki/Standard_deviation) of beat-to-beat intervals
20 | - HRV RMSSD 30 Sec Window - RMSSD calculated for consecutive 30 second intervals
21 | - HR from heartbeat - beat-to-beat interval converted to HR
22 | - HR (Heart Rate)
23 | - minimum
24 | - average
25 | - maximum
26 |
27 | ## Viewing Results in Garmin Connect
28 |
29 | Results are saved as a Garmin Connect activity category **Other** named **Test HRV**: 
30 |
31 | The summary data is saved under Stats, section Connect IQ:
32 |
33 | 
34 |
35 | The extra graphs are shown next to the built-in Garmin graphs:
36 | 
37 |
38 | ## Requirements
39 |
40 | - Connect IQ 3+ compatible device that tracks Heart Beat Interval data
41 | - Measurements taken from the built-in optical HR sensor on the watch
42 | - For reliable readings minimise wrist movement
43 | - No strap required
44 | - HR straps not supported
45 | - The app starts measurements automatically as soon as stable sensor readings are detected
46 |
47 | ## Dependencies
48 |
49 | - Status Icons - [Font Awesome free](https://fontawesome.com/license) (SIL OFL 1.1 License)
50 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/stress/HrPeaksWindow.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Math;
2 |
3 | module HrvAlgorithms {
4 | class HrPeaksWindow {
5 | function initialize(samplesCount) {
6 | me.mSamples = new [samplesCount];
7 | me.mStoreIndex = 0;
8 | me.mStoredSamplesCount = 0;
9 |
10 | me.mAverageHrPeak = 0.0;
11 | me.mHrPeaksCount = 0;
12 | }
13 |
14 | private var mSamples;
15 | private var mStoredSamplesCount;
16 | private var mStoreIndex;
17 |
18 | private var mAverageHrPeak;
19 | private var mHrPeaksCount;
20 |
21 | function addOneSecBeatToBeatIntervals(beatToBeatIntervals) {
22 | me.mSamples[me.mStoreIndex] = beatToBeatIntervals;
23 | me.mStoreIndex = (me.mStoreIndex + 1) % me.mSamples.size();
24 | me.mStoredSamplesCount++;
25 | }
26 |
27 | function calculateCurrentPeak() {
28 | var hrPeak = me.calculateHrPeak();
29 | if (hrPeak != null) {
30 | me.addHrPeak(hrPeak);
31 | }
32 | return hrPeak;
33 | }
34 |
35 | function calculateAverageStress(minHr) {
36 | if (me.mHrPeaksCount == 0 || minHr == null || minHr == 0) {
37 | return 0.0;
38 | }
39 | var averageHrPeaksDiff = me.mAverageHrPeak / me.mHrPeaksCount.toFloat() - minHr;
40 | if (averageHrPeaksDiff < 0.0) {
41 | return 0.0;
42 | }
43 |
44 | var averageStress = (averageHrPeaksDiff * 100.0) / minHr.toFloat();
45 | if (averageStress > 100.0) {
46 | return 100.0;
47 | }
48 | return averageStress;
49 | }
50 |
51 | private function addHrPeak(hrPeak) {
52 | me.mAverageHrPeak += hrPeak;
53 | me.mHrPeaksCount++;
54 | }
55 |
56 | private function calculateHrPeak() {
57 | if (me.mStoredSamplesCount < me.mSamples.size()) {
58 | return null;
59 | }
60 | var maxHr = null;
61 | for (var i = 0; i < me.mSamples.size(); i++) {
62 | if (me.mSamples[i].size() > 0) {
63 | for (var s = 0; s < me.mSamples[i].size(); s++) {
64 | var beatToBeatSample = me.mSamples[i][s];
65 | var bpmSample = Math.round(60000 / beatToBeatSample.toFloat()).toNumber();
66 |
67 | if (maxHr == null || bpmSample > maxHr) {
68 | maxHr = bpmSample;
69 | }
70 | }
71 | }
72 | }
73 |
74 | if (maxHr == null) {
75 | return null;
76 | }
77 | return maxHr;
78 | }
79 | }
80 |
81 | }
--------------------------------------------------------------------------------
/StatusIconFonts/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ScreenPicker/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/HrvAlgorithms/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/HrvAndStressActivity.mc:
--------------------------------------------------------------------------------
1 | module HrvAlgorithms {
2 | class HrvAndStressActivity extends HrActivity {
3 | function initialize(fitSession, hrvTracking, heartbeatIntervalsSensor) {
4 | me.mHrvTracking = hrvTracking;
5 | me.mHeartbeatIntervalsSensor = heartbeatIntervalsSensor;
6 | HrActivity.initialize(fitSession);
7 | }
8 |
9 | private var mHrvTracking;
10 | private var mHeartbeatIntervalsSensor;
11 |
12 | private var mHrvMonitor;
13 | private var mStressMonitor;
14 |
15 | private function isHrvOn() {
16 | return me.mHrvTracking != HrvTracking.Off;
17 | }
18 |
19 | protected function onBeforeStart(fitSession) {
20 | if (me.isHrvOn()) {
21 | me.mHeartbeatIntervalsSensor.setOneSecBeatToBeatIntervalsSensorListener(method(:onOneSecBeatToBeatIntervals));
22 | me.mStressMonitor = new StressMonitor(fitSession, me.mHrvTracking);
23 | if (me.mHrvTracking == HrvTracking.OnDetailed) {
24 | me.mHrvMonitor = new HrvMonitorDetailed(fitSession, true);
25 | }
26 | else {
27 | me.mHrvMonitor = new HrvMonitorDefault(fitSession);
28 | }
29 | }
30 | }
31 |
32 | function onOneSecBeatToBeatIntervals(heartBeatIntervals) {
33 | if (me.isHrvOn()) {
34 | me.mHrvMonitor.addOneSecBeatToBeatIntervals(heartBeatIntervals);
35 | me.mStressMonitor.addOneSecBeatToBeatIntervals(heartBeatIntervals);
36 | }
37 | }
38 |
39 | protected function onBeforeStop() {
40 | if (me.isHrvOn()) {
41 | me.mHeartbeatIntervalsSensor.setOneSecBeatToBeatIntervalsSensorListener(null);
42 | }
43 | }
44 |
45 | private var mHrvSuccessive;
46 |
47 | protected function onRefreshHrActivityStats(activityInfo, minHr) {
48 | if (me.isHrvOn()) {
49 | me.mHrvSuccessive = me.mHrvMonitor.calculateHrvSuccessive();
50 | }
51 | me.onRefreshHrvActivityStats(activityInfo, minHr, me.mHrvSuccessive);
52 | }
53 |
54 | protected function onRefreshHrvActivityStats(activityInfo, minHr, hrvSuccessive) {
55 | }
56 |
57 | function calculateSummaryFields() {
58 | var hrSummary = HrActivity.calculateSummaryFields();
59 | var activitySummary = new ActivitySummary();
60 | activitySummary.hrSummary = hrSummary;
61 | if (me.isHrvOn()) {
62 | activitySummary.hrvSummary = me.mHrvMonitor.calculateHrvSummary();
63 | activitySummary.stress = me.mStressMonitor.calculateStress(hrSummary.minHr);
64 | }
65 | return activitySummary;
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/StatusIconFonts/resources/fonts/fontAwesome/fontAwesomeFreeRegular.fnt:
--------------------------------------------------------------------------------
1 | info face="Font Awesome 5 Free Regular" size=26 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
2 | common lineHeight=26 base=23 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0
3 | page id=0 file="fontAwesomeFreeRegular_0.png"
4 | chars count=19
5 | char id=61444 x=159 y=28 width=26 height=23 xoffset=0 yoffset=2 xadvance=26 page=0 chnl=15
6 | char id=61445 x=30 y=0 width=28 height=27 xoffset=0 yoffset=0 xadvance=29 page=0 chnl=15
7 | char id=61447 x=136 y=0 width=23 height=27 xoffset=0 yoffset=0 xadvance=23 page=0 chnl=15
8 | char id=61463 x=229 y=0 width=26 height=25 xoffset=0 yoffset=1 xadvance=26 page=0 chnl=15
9 | char id=61527 x=0 y=28 width=26 height=25 xoffset=0 yoffset=1 xadvance=26 page=0 chnl=15
10 | char id=61528 x=27 y=28 width=26 height=25 xoffset=0 yoffset=1 xadvance=26 page=0 chnl=15
11 | char id=61529 x=54 y=28 width=26 height=25 xoffset=0 yoffset=1 xadvance=26 page=0 chnl=15
12 | char id=61557 x=186 y=28 width=26 height=23 xoffset=0 yoffset=2 xadvance=26 page=0 chnl=15
13 | char id=61683 x=160 y=0 width=23 height=27 xoffset=0 yoffset=0 xadvance=23 page=0 chnl=15
14 | char id=61720 x=81 y=28 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
15 | char id=61721 x=107 y=28 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
16 | char id=61722 x=133 y=28 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
17 | char id=61796 x=86 y=0 width=24 height=27 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
18 | char id=61797 x=111 y=0 width=24 height=27 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
19 | char id=61942 x=0 y=0 width=29 height=27 xoffset=0 yoffset=0 xadvance=29 page=0 chnl=15
20 | char id=62036 x=208 y=0 width=20 height=27 xoffset=0 yoffset=0 xadvance=20 page=0 chnl=15
21 | char id=62074 x=59 y=0 width=26 height=27 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
22 | char id=62189 x=184 y=0 width=23 height=27 xoffset=0 yoffset=0 xadvance=23 page=0 chnl=15
23 | char id=62637 x=213 y=28 width=26 height=23 xoffset=0 yoffset=2 xadvance=26 page=0 chnl=15
24 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/stress/StressMonitor.mc:
--------------------------------------------------------------------------------
1 | using Toybox.FitContributor;
2 | using Toybox.Math;
3 | using Toybox.Application as App;
4 | module HrvAlgorithms {
5 | class StressMonitor {
6 | function initialize(activitySession, hrvTracking) {
7 | me.mHrvTracking = hrvTracking;
8 | if (me.mHrvTracking == HrvTracking.OnDetailed) {
9 | me.mHrPeaksWindow10DataField = StressMonitor.createHrPeaksWindow10DataField(activitySession);
10 | }
11 | if (me.mHrvTracking != HrvTracking.Off) {
12 | me.mHrPeaksAverageDataField = StressMonitor.createHrPeaksAverageDataField(activitySession);
13 | me.mHrPeaksWindow10 = new HrPeaksWindow(10);
14 | }
15 | }
16 |
17 | private var mHrvTracking;
18 |
19 | private var mHrPeaksWindow10;
20 |
21 | private var mHrPeaksWindow10DataField;
22 | private var mHrPeaksAverageDataField;
23 |
24 | private static const HrPeaksWindow10DataFieldId = 15;
25 | private static const HrPeaksAverageDataFieldId = 17;
26 |
27 | private static function createHrPeaksAverageDataField(activitySession) {
28 | return activitySession.createField(
29 | "stress_hrpa",
30 | HrPeaksAverageDataFieldId,
31 | FitContributor.DATA_TYPE_FLOAT,
32 | {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"%"}
33 | );
34 | }
35 |
36 | private static function createHrPeaksWindow10DataField(activitySession) {
37 | return activitySession.createField(
38 | "stress_hrp",
39 | HrPeaksWindow10DataFieldId,
40 | FitContributor.DATA_TYPE_FLOAT,
41 | {:mesgType=>FitContributor.MESG_TYPE_RECORD, :units=>"bpm"}
42 | );
43 | }
44 |
45 | function addOneSecBeatToBeatIntervals(beatToBeatIntervals) {
46 | if (me.mHrvTracking != HrvTracking.Off) {
47 | me.mHrPeaksWindow10.addOneSecBeatToBeatIntervals(beatToBeatIntervals);
48 | me.calculateHrPeaksWindow10();
49 | }
50 | }
51 |
52 | private function calculateHrPeaksWindow10() {
53 | if (me.mHrvTracking == HrvTracking.Off) {
54 | return;
55 | }
56 |
57 | var result = me.mHrPeaksWindow10.calculateCurrentPeak();
58 | if (result != null) {
59 | if (me.mHrPeaksWindow10DataField != null) {
60 | me.mHrPeaksWindow10DataField.setData(result);
61 | }
62 | }
63 | }
64 |
65 | public function calculateStress(minHr) {
66 | if (me.mHrvTracking == HrvTracking.Off) {
67 | return null;
68 | }
69 | var averageStress = me.mHrPeaksWindow10.calculateAverageStress(minHr);
70 | me.mHrPeaksAverageDataField.setData(averageStress);
71 | return averageStress;
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/TestHrv/source/testHrv/TestHrvView.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Graphics as Gfx;
3 |
4 | class TestHrvView extends Ui.View {
5 | private var mTestHrvModel;
6 | private var mElapsedDurationRenderer;
7 | private var mElapsedTimeText;
8 | private var mMeasuringHrvText;
9 |
10 | function initialize(testHrvModel) {
11 | View.initialize();
12 |
13 | me.mTestHrvModel = testHrvModel;
14 | }
15 |
16 | function onLayout(dc) {
17 | renderBackground(dc);
18 | renderLayoutElapsedTime(dc);
19 | renderTestingHrvText(dc);
20 |
21 | var durationArcRadius = dc.getWidth() / 2;
22 | var elapsedDurationArcWidth = dc.getWidth() / 4;
23 | me.mElapsedDurationRenderer = new ElapsedDuationRenderer(me.mTestHrvModel.ElapsedTimeColor, durationArcRadius, elapsedDurationArcWidth);
24 | }
25 |
26 | private function renderBackground(dc) {
27 | dc.setColor(Gfx.COLOR_TRANSPARENT, Gfx.COLOR_BLACK);
28 | dc.clear();
29 | }
30 |
31 | private static const TextFont = Gfx.FONT_MEDIUM;
32 |
33 | private function getYPosOffsetFromCenter(dc, lineOffset) {
34 | return dc.getHeight() / 2 + lineOffset * dc.getFontHeight(TextFont);
35 | }
36 |
37 | private function renderTestingHrvText(dc) {
38 | var xPosCenter = dc.getWidth() / 2;
39 | var yPosCenter = dc.getHeight() / 2;
40 | me.mMeasuringHrvText = new Ui.Text({
41 | :text => "Testing HRV",
42 | :font => TextFont,
43 | :color => Gfx.COLOR_WHITE,
44 | :justification =>Gfx.TEXT_JUSTIFY_CENTER,
45 | :locX => xPosCenter,
46 | :locY => yPosCenter
47 | });
48 | }
49 |
50 | private function renderLayoutElapsedTime(dc) {
51 | var xPosCenter = dc.getWidth() / 2;
52 | var yPosCenter = getYPosOffsetFromCenter(dc, -1);
53 | me.mElapsedTimeText = new Ui.Text({
54 | :text => "",
55 | :font => TextFont,
56 | :color => me.mTestHrvModel.ElapsedTimeColor,
57 | :justification => Gfx.TEXT_JUSTIFY_CENTER,
58 | :locX => xPosCenter,
59 | :locY => yPosCenter
60 | });
61 | }
62 |
63 | function onUpdate(dc) {
64 | View.onUpdate(dc);
65 |
66 | me.mMeasuringHrvText.draw(dc);
67 |
68 | me.mElapsedTimeText.setText(TimeFormatter.format(me.mTestHrvModel.getRemainingTime()));
69 | me.mElapsedTimeText.draw(dc);
70 |
71 | me.mElapsedDurationRenderer.drawOverallElapsedTime(dc, me.mTestHrvModel.elapsedTimeSec, me.mTestHrvModel.SessionTimeSec);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvMonitorDefault.mc:
--------------------------------------------------------------------------------
1 | using Toybox.FitContributor;
2 | using Toybox.Math;
3 | using Toybox.Application as App;
4 |
5 | module HrvAlgorithms {
6 | class HrvMonitorDefault {
7 | function initialize(activitySession) {
8 | me.mHrvSuccessiveDataField = HrvMonitorDefault.createHrvSuccessiveDataField(activitySession);
9 | me.mHrvRmssdDataField = HrvMonitorDefault.createHrvRmssdDataField(activitySession);
10 |
11 | me.mHrvRmssd = new HrvRmssd();
12 | me.mHrvSuccessive = new HrvSuccessive();
13 | }
14 |
15 | private const HrvRmssd30Sec = 30;
16 |
17 | private var mHrvRmssd;
18 | private var mHrvSuccessive;
19 |
20 | private var mHrvSuccessiveDataField;
21 | private var mHrvRmssdDataField;
22 |
23 | private static const HrvSuccessiveFieldId = 6;
24 | private static const HrvRmssdFieldId = 7;
25 |
26 | private static function createHrvSuccessiveDataField(activitySession) {
27 | return activitySession.createField(
28 | "hrv_s",
29 | HrvMonitorDefault.HrvSuccessiveFieldId,
30 | FitContributor.DATA_TYPE_FLOAT,
31 | {:mesgType=>FitContributor.MESG_TYPE_RECORD, :units=>"ms"}
32 | );
33 | }
34 |
35 | private static function createHrvRmssdDataField(activitySession) {
36 | return activitySession.createField(
37 | "hrv_rmssd",
38 | HrvMonitorDefault.HrvRmssdFieldId,
39 | FitContributor.DATA_TYPE_FLOAT,
40 | {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"ms"}
41 | );
42 | }
43 |
44 | function addOneSecBeatToBeatIntervals(beatToBeatIntervals) {
45 | for (var i = 0; i < beatToBeatIntervals.size(); i++) {
46 | var beatToBeatInterval = beatToBeatIntervals[i];
47 | if (beatToBeatInterval != null) {
48 | me.addValidBeatToBeatInterval(beatToBeatInterval);
49 | }
50 | }
51 | }
52 |
53 | protected function addValidBeatToBeatInterval(beatToBeatInterval) {
54 | me.mHrvSuccessive.addBeatToBeatInterval(beatToBeatInterval);
55 | me.mHrvRmssd.addBeatToBeatInterval(beatToBeatInterval);
56 | }
57 |
58 | public function calculateHrvSuccessive() {
59 | var hrvSuccessive = me.mHrvSuccessive.calculate();
60 | if (hrvSuccessive != null) {
61 | me.mHrvSuccessiveDataField.setData(hrvSuccessive);
62 | }
63 | return hrvSuccessive;
64 | }
65 |
66 | public function calculateHrvSummary() {
67 | var hrvSummary = new HrvSummary();
68 | hrvSummary.rmssd = me.mHrvRmssd.calculate();
69 | if (hrvSummary.rmssd != null) {
70 | me.mHrvRmssdDataField.setData(hrvSummary.rmssd);
71 | }
72 | return hrvSummary;
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/TestHrv/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | eng
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/ScreenPicker/sources/DetailsViewRenderer.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Graphics as Gfx;
3 | using Toybox.Lang;
4 | using Toybox.Application as App;
5 |
6 | module ScreenPicker {
7 | class DetailsViewRenderer {
8 | private const TitlePosY = 20;
9 |
10 | private var mDetailsModel;
11 |
12 | function initialize(detailsModel) {
13 | me.mDetailsModel = detailsModel;
14 | me.progressBarWidth = App.getApp().getProperty("progressBarWidth");
15 | }
16 |
17 | function renderBackgroundColor(dc) {
18 | dc.setColor(Gfx.COLOR_TRANSPARENT, me.mDetailsModel.backgroundColor);
19 | dc.clear();
20 | }
21 |
22 | function renderDetailsView(dc) {
23 | dc.setColor(me.mDetailsModel.titleColor, Gfx.COLOR_TRANSPARENT);
24 | me.displayTitle(dc, me.mDetailsModel.title, me.mDetailsModel.titleFont);
25 |
26 | for (var lineNumber = 1; lineNumber <= me.mDetailsModel.detailLines.size(); lineNumber++) {
27 | dc.setColor(me.mDetailsModel.color, Gfx.COLOR_TRANSPARENT);
28 | var line = me.mDetailsModel.detailLines[lineNumber];
29 | if (line.icon != null) {
30 | me.displayFontIcon(dc, line.icon, line.getIconYPos());
31 | }
32 | if (line.value instanceof TextValue) {
33 | me.displayText(dc, line.value, line.getYPos());
34 | }
35 | else if (line.value instanceof PercentageHighlightLine) {
36 | var alertsLineYPos = line.getYPos() + line.value.yOffset;
37 | me.drawPercentageHighlightLine(dc, line.value.getHighlights(), line.value.backgroundColor, line.value.startPosX, alertsLineYPos);
38 | }
39 | }
40 | }
41 |
42 | private var progressBarWidth;
43 | private const ProgressBarHeight = 16;
44 |
45 | private function drawPercentageHighlightLine(dc, highlights, backgroundColor, startPosX, posY) {
46 | dc.setColor(backgroundColor, Gfx.COLOR_TRANSPARENT);
47 | dc.fillRectangle(startPosX, posY, progressBarWidth, ProgressBarHeight);
48 |
49 | var highlightWidth = 0.03 * progressBarWidth;
50 | for (var i = 0; i < highlights.size(); i++) {
51 | var highlight = highlights[i];
52 | var valuePosX = startPosX + highlight.progressPercentage * progressBarWidth;
53 | dc.setColor(highlight.color, Gfx.COLOR_TRANSPARENT);
54 | dc.fillRectangle(valuePosX, posY, highlightWidth, ProgressBarHeight);
55 | }
56 | }
57 |
58 | private function displayTitle(dc, title, titleFont) {
59 | var textX = dc.getWidth() / 2;
60 | dc.drawText(textX, TitlePosY, titleFont, title, Gfx.TEXT_JUSTIFY_CENTER);
61 | }
62 |
63 | private function displayFontIcon(dc, icon, yPos) {
64 | icon.setYPos(yPos);
65 | icon.draw(dc);
66 | }
67 |
68 | private function displayText(dc, value, yPos) {
69 | dc.setColor(value.color, Gfx.COLOR_TRANSPARENT);
70 | dc.drawText(value.xPos, yPos, value.font, value.text, Gfx.TEXT_JUSTIFY_LEFT);
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/tests/HrvSdrrLastNSecTests.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Test;
2 |
3 | module HrvAlgorithms {
4 | class HrvSdrrLastNSecTests {
5 | (:test)
6 | static function noBeatToBeatIntervalCalculate(logger) {
7 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(10));
8 | var result = calculatorFixture.calculate();
9 | return result == null;
10 | }
11 |
12 | (:test)
13 | static function oneBeatToBeatIntervalCalculate(logger) {
14 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(10));
15 | calculatorFixture.add1NormalInterval();
16 | var result = calculatorFixture.calculate();
17 | return result == null;
18 | }
19 |
20 | (:test)
21 | static function oneLessThanTheIntervalsCount(logger) {
22 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(7));
23 | calculatorFixture.add6NormalIntervals();
24 | var result = calculatorFixture.calculate();
25 | return result == null;
26 | }
27 |
28 | (:test)
29 | static function FiveMinNormalIntervals(logger) {
30 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(5 * 60));
31 | calculatorFixture.add5MinNormalIntervals();
32 |
33 | var result = calculatorFixture.calculate();
34 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected5MinNormalIntervals);
35 | }
36 |
37 | (:test)
38 | static function FiveMinIntervalsCountOneOutlierBefore5MinIntervals(logger) {
39 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(5 * 60));
40 | calculatorFixture.addOutlierInterval();
41 | calculatorFixture.add5MinNormalIntervals();
42 |
43 | var result = calculatorFixture.calculate();
44 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected5MinNormalIntervals);
45 | }
46 |
47 | (:test)
48 | static function FiveMinIntervalsCount10MinNormalIntervals(logger) {
49 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(5 * 60));
50 | calculatorFixture.add5MinNormalIntervals();
51 | calculatorFixture.add5MinNormalIntervals();
52 |
53 | var result = calculatorFixture.calculate();
54 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected5MinNormalIntervals);
55 | }
56 |
57 | (:test)
58 | static function SixSec6NormalIntervals(logger) {
59 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(6));
60 | calculatorFixture.add6NormalIntervals();
61 | var result = calculatorFixture.calculate();
62 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected6NormalIntervals);
63 | }
64 |
65 | (:test)
66 | static function SixSec6NormalIntervalsOneOutlier(logger) {
67 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrLastNSec(6));
68 | calculatorFixture.add6NormalIntervals();
69 | calculatorFixture.addOutlierInterval();
70 | var result = calculatorFixture.calculate();
71 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected5NormalIntervals1Outlier);
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/TestHrv/resources/hrvFitContributions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/HrvAlgorithms/resources/hrvFitContributions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/tests/HrvSdrrFirstNSecTests.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Test;
2 |
3 | module HrvAlgorithms {
4 | class HrvSdrrFirstNSecTests {
5 | (:test)
6 | static function noBeatToBeatIntervalCalculate(logger) {
7 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(10));
8 | var result = calculatorFixture.calculate();
9 | return result == null;
10 | }
11 |
12 | (:test)
13 | static function oneBeatToBeatIntervalCalculate(logger) {
14 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(10));
15 | calculatorFixture.add1NormalInterval();
16 | var result = calculatorFixture.calculate();
17 | return result == null;
18 | }
19 |
20 | (:test)
21 | static function FiveMinNormalIntervals(logger) {
22 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(5 * 60));
23 | calculatorFixture.add5MinNormalIntervals();
24 |
25 | var result = calculatorFixture.calculate();
26 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected5MinNormalIntervals);
27 | }
28 |
29 | (:test)
30 | static function FiveMinNormalIntervalsMaxIntervals5Min(logger) {
31 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(3 * 5 * 60));
32 | calculatorFixture.add5MinNormalIntervals();
33 |
34 | var result = calculatorFixture.calculate();
35 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected5MinNormalIntervals);
36 | }
37 |
38 | (:test)
39 | static function FiveMinCalculator10MinNormalIntervals(logger) {
40 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(10 * 5 * 60));
41 | calculatorFixture.add5MinNormalIntervals();
42 | calculatorFixture.add5MinNormalIntervals();
43 |
44 | var result = calculatorFixture.calculate();
45 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected5MinNormalIntervals);
46 | }
47 |
48 | (:test)
49 | static function SixSec6NormalIntervals(logger) {
50 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(6));
51 | calculatorFixture.add6NormalIntervals();
52 | var result = calculatorFixture.calculate();
53 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected6NormalIntervals);
54 | }
55 |
56 | (:test)
57 | static function SixSec6NormalIntervalsOneOutlier(logger) {
58 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(6));
59 | calculatorFixture.add6NormalIntervals();
60 | calculatorFixture.addOutlierInterval();
61 | var result = calculatorFixture.calculate();
62 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected6NormalIntervals);
63 | }
64 |
65 | (:test)
66 | static function SevenSec6NormalAndOneOutlierIntervals(logger) {
67 | var calculatorFixture = new HrvCalculatorFixture(new HrvSdrrFirstNSec(7));
68 | calculatorFixture.add6NormalIntervals();
69 | calculatorFixture.addOutlierInterval();
70 | var result = calculatorFixture.calculate();
71 | return calculatorFixture.isResultExpected(result, ExpectedHrvSdrr.Expected6NormalIntervals1Outlier);
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/TestHrv/source/waitingHrv/WaitingHrvDelegate.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Graphics as Gfx;
3 | using Toybox.Application as App;
4 |
5 | class WaitingHrvDelegate extends ScreenPicker.ScreenPickerDelegate {
6 | function initialize(heartbeatIntervalsSensor) {
7 | ScreenPickerDelegate.initialize(0, 1);
8 |
9 | me.mNoHrvSeconds = MinSecondsNoHrvDetected;
10 | me.mHrvReadySuccessCount = 0;
11 | me.mHeartbeatIntervalsSensor = heartbeatIntervalsSensor;
12 | me.mHeartbeatIntervalsSensor.setOneSecBeatToBeatIntervalsSensorListener(method(:onIsHrvReadyListener));
13 | me.mHeartbeatIntervalsSensor.start();
14 | me.isStartInProgress = false;
15 | }
16 |
17 | private var mHeartbeatIntervalsSensor;
18 | private var mNoHrvSeconds;
19 | private var mHrvReadySuccessCount;
20 | private const MinSecondsNoHrvDetected = 3;
21 | private const MinHrvReadySuccessCount = 2;
22 |
23 | function onIsHrvReadyListener(heartBeatIntervals) {
24 | if (heartBeatIntervals.size() == 0) {
25 | me.mNoHrvSeconds++;
26 | }
27 | else {
28 | me.mNoHrvSeconds = 0;
29 | }
30 | if (me.mNoHrvSeconds < MinSecondsNoHrvDetected) {
31 | me.mHrvReadySuccessCount++;
32 | }
33 | else {
34 | me.mHrvReadySuccessCount = 0;
35 | }
36 | if (me.mHrvReadySuccessCount >= MinHrvReadySuccessCount) {
37 | me.autoStartTestHrvActivity();
38 | }
39 | }
40 |
41 | private function autoStartTestHrvActivity() {
42 | Vibe.vibrateMediumPulsating();
43 | startTestHrvActivity();
44 | }
45 |
46 | private function startTestHrvActivity() {
47 | if (me.isStartInProgress == false) {
48 | me.isStartInProgress = true;
49 | me.mHeartbeatIntervalsSensor.setOneSecBeatToBeatIntervalsSensorListener(null);
50 | var testHrvDelegate = new TestHrvDelegate(me.mHeartbeatIntervalsSensor);
51 | Ui.switchToView(testHrvDelegate.createTestHrvView(), testHrvDelegate, Ui.SLIDE_LEFT);
52 | }
53 | }
54 |
55 | private var isStartInProgress;
56 |
57 | private function createWaitingHrvDetailsModel() {
58 | var details = new ScreenPicker.DetailsModel();
59 |
60 | details.titleColor = Gfx.COLOR_TRANSPARENT;
61 | details.color = Gfx.COLOR_WHITE;
62 | details.backgroundColor = Gfx.COLOR_BLACK;
63 |
64 | var hrvIcon = new ScreenPicker.HrvIcon({});
65 | hrvIcon.setStatusWarning();
66 | details.detailLines[2].icon = hrvIcon;
67 | details.detailLines[2].value.text = "Waiting sensor";
68 | details.detailLines[3].value.text = "Keep still";
69 |
70 | var iconsXPos = App.getApp().getProperty("waitingHrvIconsXPos");
71 | var textXPos = App.getApp().getProperty("waitingHrvTextXPos");
72 | details.setAllIconsXPos(iconsXPos);
73 | details.setAllValuesXPos(textXPos);
74 | return details;
75 | }
76 |
77 | function createScreenPickerView() {
78 | var details = me.createWaitingHrvDetailsModel();
79 | return new ScreenPicker.ScreenPickerDetailsSinglePageView(details);
80 | }
81 |
82 | function onKey(keyEvent) {
83 | if (keyEvent.getKey() == Ui.KEY_ENTER) {
84 | me.startTestHrvActivity();
85 | return true;
86 | }
87 | return false;
88 | }
89 |
90 | function onBack() {
91 | me.mHeartbeatIntervalsSensor.stop();
92 | return false;//exit
93 | }
94 | }
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/HrActivity.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Timer;
2 | using Toybox.FitContributor;
3 | using Toybox.ActivityRecording;
4 | using Toybox.Sensor;
5 |
6 | module HrvAlgorithms {
7 | class HrActivity {
8 | function initialize(fitSessionSpec) {
9 | me.mFitSession = ActivityRecording.createSession(fitSessionSpec);
10 | me.createMinHrDataField();
11 | me.onBeforeStart(me.mFitSession);
12 | me.mFitSession.start();
13 | me.mRefreshActivityTimer = new Timer.Timer();
14 | me.mRefreshActivityTimer.start(method(:refreshActivityStats), RefreshActivityInterval, true);
15 | }
16 |
17 | private var mFitSession;
18 | private const RefreshActivityInterval = 1000;
19 | private var mRefreshActivityTimer;
20 |
21 | protected function onBeforeStart(fitSession) {
22 | }
23 |
24 | function stop() {
25 | if (me.mFitSession.isRecording() == false) {
26 | return;
27 | }
28 | me.onBeforeStop();
29 | me.mFitSession.stop();
30 | me.mRefreshActivityTimer.stop();
31 | me.mRefreshActivityTimer = null;
32 | }
33 |
34 | protected function onBeforeStop() {
35 | }
36 |
37 | private function createMinHrDataField() {
38 | me.mMinHrField = me.mFitSession.createField(
39 | "min_hr",
40 | me.MinHrFieldId,
41 | FitContributor.DATA_TYPE_UINT16,
42 | {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"bpm"}
43 | );
44 |
45 | me.mMinHrField.setData(0);
46 | }
47 |
48 | private const MinHrFieldId = 0;
49 | private var mMinHrField;
50 | private var mMinHr;
51 |
52 | function refreshActivityStats() {
53 | if (me.mFitSession.isRecording() == false) {
54 | return;
55 | }
56 |
57 | var activityInfo = Activity.getActivityInfo();
58 | if (activityInfo == null) {
59 | return;
60 | }
61 |
62 | if (activityInfo.currentHeartRate != null && (me.mMinHr == null || me.mMinHr > activityInfo.currentHeartRate)) {
63 | me.mMinHr = activityInfo.currentHeartRate;
64 | }
65 | me.onRefreshHrActivityStats(activityInfo, me.mMinHr);
66 | }
67 |
68 | protected function onRefreshHrActivityStats(activityInfo, minHr) {
69 | }
70 |
71 | function calculateSummaryFields() {
72 | var activityInfo = Activity.getActivityInfo();
73 | if (me.mMinHr != null) {
74 | me.mMinHrField.setData(me.mMinHr);
75 | }
76 |
77 | var summary = new HrSummary();
78 | summary.maxHr = activityInfo.maxHeartRate;
79 | summary.averageHr = activityInfo.averageHeartRate;
80 | summary.minHr = me.mMinHr;
81 | summary.elapsedTimeSeconds = activityInfo.elapsedTime / 1000;
82 | return summary;
83 | }
84 |
85 | function finish() {
86 | me.mFitSession.save();
87 | me.mFitSession = null;
88 | }
89 |
90 | function discard() {
91 | me.mFitSession.discard();
92 | me.mFitSession = null;
93 | }
94 |
95 | function discardDanglingActivity() {
96 | var isDangling = me.mFitSession != null && !me.mFitSession.isRecording();
97 | if (isDangling) {
98 | me.discard();
99 | }
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/ScreenPicker/sources/DetailsModel.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Graphics as Gfx;
2 | using Toybox.WatchUi as Ui;
3 | using Toybox.Application as App;
4 |
5 | module ScreenPicker {
6 | class PercentageHighlightLine {
7 | function initialize(highlightsCount) {
8 | if (highlightsCount > 0) {
9 | me.highlights = new [MaxHighlightsCount];
10 | }
11 | else {
12 | me.highlights = [];
13 | }
14 | me.latestAddedIndex = -1;
15 | me.backgroundColor = Gfx.COLOR_WHITE;
16 | }
17 |
18 | private const MaxHighlightsCount = 50;
19 | private var highlights;
20 | private var latestAddedIndex;
21 |
22 | var backgroundColor;
23 | var startPosX;
24 | var yOffset;
25 |
26 | function addHighlight(color, progressPercentage) {
27 | if (latestAddedIndex + 1 < MaxHighlightsCount) {
28 | me.latestAddedIndex++;
29 | me.highlights[me.latestAddedIndex] = new LineHighlight(color, progressPercentage);
30 | }
31 | }
32 |
33 | function getHighlights() {
34 | if (me.highlights.size() == 0) {
35 | return [];
36 | }
37 | else {
38 | return me.highlights.slice(0, me.latestAddedIndex + 1);
39 | }
40 | }
41 | }
42 |
43 | class LineHighlight {
44 | function initialize(color, progressPercentage) {
45 | me.color = color;
46 | me.progressPercentage = progressPercentage;
47 | }
48 | var color;
49 | var progressPercentage;
50 | }
51 |
52 | class TextValue {
53 | function initialize() {
54 | me.text = "";
55 | me.font = Gfx.FONT_SYSTEM_TINY;
56 | me.color = Gfx.COLOR_WHITE;
57 | me.xPos = 0;
58 | }
59 |
60 | var text;
61 | var font;
62 | var color;
63 | var xPos;
64 | }
65 |
66 | class Icon {
67 | function initialize(icon) {
68 | var iconDrawableParams = {};
69 | if (icon[:symbol] != null) {
70 | iconDrawableParams[:text] = Ui.loadResource(icon[:symbol]);
71 | }
72 | else {
73 | iconDrawableParams[:text] = "";
74 | }
75 | if (icon[:color] != null) {
76 | iconDrawableParams[:color] = icon[:color];
77 | }
78 | else {
79 | iconDrawableParams[:color] = Gfx.COLOR_WHITE;
80 | }
81 | if (icon[:font] != null) {
82 | iconDrawableParams[:font] = icon[:font];
83 | }
84 | if (icon[:xPos] != null) {
85 | iconDrawableParams[:locX] = icon[:xPos];
86 | }
87 | else {
88 | iconDrawableParams[:locX] = 0;
89 | }
90 | if (icon[:yPos] != null) {
91 | iconDrawableParams[:locY] = icon[:yPos];
92 | }
93 | iconDrawableParams[:justification] = Gfx.TEXT_JUSTIFY_CENTER;
94 | me.mIconDrawable = new Ui.Text(iconDrawableParams);
95 | }
96 |
97 | private var mIconDrawable;
98 |
99 | function setXPos(xPos) {
100 | me.mIconDrawable.locX = xPos;
101 | }
102 |
103 | function setYPos(yPos) {
104 | me.mIconDrawable.locY = yPos;
105 | }
106 |
107 | function setColor(color) {
108 | me.mIconDrawable.setColor(color);
109 | }
110 |
111 | function draw(dc) {
112 | me.mIconDrawable.draw(dc);
113 | }
114 | }
115 |
116 | class DetailsLine extends DetailsLineBase {
117 | function initialize(lineNumber) {
118 | DetailsLineBase.initialize(lineNumber);
119 | me.icon = null;
120 | me.value = new TextValue();
121 | }
122 |
123 | var icon;
124 | var value;
125 | }
126 |
127 | class DetailsLineBase {
128 | function initialize(lineNumber) {
129 | me.mLineNumber = lineNumber;
130 | me.yLineOffset = 0;
131 | me.lineHeight = App.getApp().getProperty("detailsModelLineHeight");
132 | me.iconHeight = App.getApp().getProperty("detailsModelIconHeight");
133 | }
134 |
135 | var yLineOffset;
136 |
137 | private var mLineNumber;
138 |
139 | function getYPos() {
140 | return InitialPosY + me.mLineNumber * me.lineHeight + me.yLineOffset;
141 | }
142 |
143 | function getIconYPos() {
144 | return InitialPosY + me.mLineNumber * me.iconHeight + me.yLineOffset;
145 | }
146 |
147 | private var lineHeight;
148 | private var iconHeight;
149 | private const InitialPosY = 30;
150 | }
151 |
152 | class DetailsModel{
153 | function initialize() {
154 | me.title = "";
155 | me.titleFont = Gfx.FONT_SYSTEM_MEDIUM;
156 | me.color = null;
157 | me.titleColor = null;
158 | me.backgroundColor = null;
159 | me.detailLines = {
160 | 1 => new DetailsLine(1),
161 | 2 => new DetailsLine(2),
162 | 3 => new DetailsLine(3),
163 | 4 => new DetailsLine(4),
164 | 5 => new DetailsLine(5)
165 | };
166 | }
167 |
168 | const LinesCount = 5;
169 |
170 | function setAllIconsXPos(xPos) {
171 | for (var i = 1; i <= LinesCount; i++) {
172 | if (me.detailLines[i] instanceof DetailsLine && detailLines[i].icon instanceof Icon) {
173 | me.detailLines[i].icon.setXPos(xPos);
174 | }
175 | }
176 | }
177 |
178 | function setAllValuesXPos(xPos) {
179 | for (var i = 1; i <= LinesCount; i++) {
180 | if (me.detailLines[i] instanceof DetailsLine && me.detailLines[i].value instanceof TextValue) {
181 | me.detailLines[i].value.xPos = xPos;
182 | }
183 | }
184 | }
185 |
186 | function setAllLinesYOffset(yOffset) {
187 | for (var i = 1; i <= LinesCount; i++) {
188 | if (me.detailLines[i]) {
189 | me.detailLines[i].yLineOffset = yOffset;
190 | }
191 | }
192 | }
193 |
194 | var title;
195 | var titleFont;
196 | var titleColor;
197 | var detailLines;
198 | var color;
199 | var backgroundColor;
200 | }
201 | }
--------------------------------------------------------------------------------
/StatusIconFonts/resources/fonts/fontAwesome/fontAwesomeFreeSolid.fnt:
--------------------------------------------------------------------------------
1 | info face="Font Awesome 5 Free Solid" size=26 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
2 | common lineHeight=26 base=23 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0
3 | page id=0 file="fontAwesomeFreeSolid_0.png"
4 | chars count=47
5 | char id=61444 x=172 y=80 width=26 height=23 xoffset=0 yoffset=2 xadvance=25 page=0 chnl=15
6 | char id=61447 x=108 y=27 width=22 height=26 xoffset=0 yoffset=1 xadvance=22 page=0 chnl=15
7 | char id=61452 x=226 y=80 width=26 height=19 xoffset=0 yoffset=4 xadvance=25 page=0 chnl=15
8 | char id=61453 x=87 y=104 width=18 height=18 xoffset=0 yoffset=5 xadvance=17 page=0 chnl=15
9 | char id=61459 x=196 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
10 | char id=61463 x=170 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
11 | char id=61473 x=27 y=27 width=26 height=26 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
12 | char id=61527 x=222 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
13 | char id=61528 x=0 y=81 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
14 | char id=61529 x=26 y=81 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
15 | char id=61557 x=118 y=80 width=26 height=23 xoffset=0 yoffset=2 xadvance=25 page=0 chnl=15
16 | char id=61561 x=0 y=107 width=32 height=20 xoffset=0 yoffset=4 xadvance=31 page=0 chnl=15
17 | char id=61655 x=149 y=104 width=16 height=9 xoffset=0 yoffset=10 xadvance=16 page=0 chnl=15
18 | char id=61656 x=132 y=104 width=16 height=9 xoffset=0 yoffset=10 xadvance=16 page=0 chnl=15
19 | char id=61657 x=240 y=27 width=9 height=15 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=15
20 | char id=61658 x=106 y=104 width=9 height=15 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=15
21 | char id=61661 x=166 y=104 width=15 height=9 xoffset=0 yoffset=15 xadvance=16 page=0 chnl=15
22 | char id=61662 x=116 y=104 width=15 height=10 xoffset=0 yoffset=3 xadvance=16 page=0 chnl=15
23 | char id=61683 x=131 y=27 width=22 height=26 xoffset=0 yoffset=1 xadvance=22 page=0 chnl=15
24 | char id=61720 x=40 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=24 page=0 chnl=15
25 | char id=61721 x=66 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=24 page=0 chnl=15
26 | char id=61722 x=92 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=24 page=0 chnl=15
27 | char id=61880 x=54 y=27 width=26 height=26 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
28 | char id=61914 x=118 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
29 | char id=61942 x=165 y=0 width=29 height=26 xoffset=0 yoffset=1 xadvance=28 page=0 chnl=15
30 | char id=61950 x=33 y=107 width=26 height=19 xoffset=0 yoffset=4 xadvance=25 page=0 chnl=15
31 | char id=61952 x=225 y=0 width=28 height=26 xoffset=0 yoffset=1 xadvance=28 page=0 chnl=15
32 | char id=61953 x=60 y=104 width=26 height=19 xoffset=0 yoffset=4 xadvance=25 page=0 chnl=15
33 | char id=61982 x=199 y=80 width=26 height=23 xoffset=0 yoffset=2 xadvance=25 page=0 chnl=15
34 | char id=62033 x=200 y=27 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=15
35 | char id=62034 x=20 y=54 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=15
36 | char id=62035 x=0 y=54 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=15
37 | char id=62036 x=220 y=27 width=19 height=26 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=15
38 | char id=62074 x=81 y=27 width=26 height=26 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
39 | char id=62189 x=154 y=27 width=22 height=26 xoffset=0 yoffset=1 xadvance=22 page=0 chnl=15
40 | char id=62193 x=144 y=54 width=25 height=25 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
41 | char id=62194 x=177 y=27 width=22 height=26 xoffset=0 yoffset=1 xadvance=22 page=0 chnl=15
42 | char id=62337 x=52 y=80 width=32 height=23 xoffset=0 yoffset=2 xadvance=31 page=0 chnl=15
43 | char id=62338 x=85 y=80 width=32 height=23 xoffset=0 yoffset=2 xadvance=31 page=0 chnl=15
44 | char id=62470 x=0 y=27 width=26 height=26 xoffset=0 yoffset=1 xadvance=25 page=0 chnl=15
45 | char id=62637 x=145 y=80 width=26 height=23 xoffset=0 yoffset=2 xadvance=25 page=0 chnl=15
46 | char id=62643 x=0 y=0 width=32 height=26 xoffset=0 yoffset=1 xadvance=31 page=0 chnl=15
47 | char id=62654 x=195 y=0 width=29 height=26 xoffset=0 yoffset=1 xadvance=28 page=0 chnl=15
48 | char id=62716 x=66 y=0 width=32 height=26 xoffset=0 yoffset=1 xadvance=31 page=0 chnl=15
49 | char id=62717 x=99 y=0 width=32 height=26 xoffset=0 yoffset=1 xadvance=31 page=0 chnl=15
50 | char id=62718 x=132 y=0 width=32 height=26 xoffset=0 yoffset=1 xadvance=31 page=0 chnl=15
51 | char id=62719 x=33 y=0 width=32 height=26 xoffset=0 yoffset=1 xadvance=31 page=0 chnl=15
52 |
--------------------------------------------------------------------------------
/HrvAlgorithms/sources/activity/hrv/HrvMonitorDetailed.mc:
--------------------------------------------------------------------------------
1 | using Toybox.FitContributor;
2 | using Toybox.Math;
3 | using Toybox.Application as App;
4 |
5 | module HrvAlgorithms {
6 | class HrvMonitorDetailed extends HrvMonitorDefault {
7 | function initialize(activitySession, isSessionTimeLongerThan5min) {
8 | HrvMonitorDefault.initialize(activitySession);
9 |
10 | me.mHrvBeatToBeatIntervalsDataField = HrvMonitorDetailed.createHrvBeatToBeatIntervalsDataField(activitySession);
11 | me.mHrvSdrrFirst5MinDataField = HrvMonitorDetailed.createHrvSdrrFirst5MinDataField(activitySession, isSessionTimeLongerThan5min);
12 | me.mHrvSdrrLast5MinDataField = HrvMonitorDetailed.createHrvSdrrLast5MinDataField(activitySession);
13 | me.mHrFromHeartbeatDataField = HrvMonitorDetailed.createHrFromHeartbeatDataField(activitySession);
14 | me.mHrvRmssd30SecDataField = HrvMonitorDetailed.createHrvRmssd30SecDataField(activitySession);
15 | me.mHrvPnn50DataField = HrvMonitorDetailed.createHrvPnn50DataField(activitySession);
16 | me.mHrvPnn20DataField = HrvMonitorDetailed.createHrvPnn20DataField(activitySession);
17 | me.mHrvSdrrFirst5Min = new HrvSdrrFirstNSec(Buffer5MinLength);
18 | me.mHrvSdrrLast5Min = new HrvSdrrLastNSec(Buffer5MinLength);
19 |
20 | me.mHrvPnn50 = new HrvPnnx(50);
21 | me.mHrvPnn20 = new HrvPnnx(20);
22 | me.mHrvRmssd30Sec = new HrvRmssdRolling(HrvRmssd30Sec);
23 |
24 | }
25 |
26 | private const HrvRmssd30Sec = 30;
27 | private const Buffer5MinLength = 300;
28 |
29 | private var mHrvSdrrFirst5Min;
30 | private var mHrvSdrrLast5Min;
31 | private var mHrvRmssd30Sec;
32 | private var mHrvPnn50;
33 | private var mHrvPnn20;
34 |
35 | private var mHrvBeatToBeatIntervalsDataField;
36 | private var mHrvSdrrFirst5MinDataField;
37 | private var mHrvSdrrLast5MinDataField;
38 | private var mHrvRmssd30SecDataField;
39 | private var mHrvPnn50DataField;
40 | private var mHrvPnn20DataField;
41 | private var mHrFromHeartbeatDataField;
42 |
43 | private static const HrvBeatToBeatIntervalsFieldId = 8;
44 | private static const HrvSdrrFieldId = 1;
45 | private static const HrvSdrrFirst5MinFieldId = 9;
46 | private static const HrvSdrrLast5MinFieldId = 10;
47 | private static const HrvPnn50FieldId = 11;
48 | private static const HrvPnn20FieldId = 12;
49 | private static const HrvRmssd30SecFieldId = 13;
50 | private static const HrFromHeartbeatField = 16;
51 |
52 | private static function createHrvSdrrFirst5MinDataField(activitySession, isSessionTimeLongerThan5min) {
53 | var fieldId;
54 | if (isSessionTimeLongerThan5min) {
55 | fieldId = HrvMonitorDetailed.HrvSdrrFirst5MinFieldId;
56 | }
57 | else {
58 | fieldId = HrvMonitorDetailed.HrvSdrrFieldId;
59 | }
60 | return activitySession.createField(
61 | "hrv_sdrr_f",
62 | fieldId,
63 | FitContributor.DATA_TYPE_FLOAT,
64 | {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"ms"}
65 | );
66 | }
67 |
68 | private static function createHrvSdrrLast5MinDataField(activitySession) {
69 | return activitySession.createField(
70 | "hrv_sdrr_l",
71 | HrvMonitorDetailed.HrvSdrrLast5MinFieldId,
72 | FitContributor.DATA_TYPE_FLOAT,
73 | {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"ms"}
74 | );
75 | }
76 |
77 | private static function createHrvBeatToBeatIntervalsDataField(activitySession) {
78 | return activitySession.createField(
79 | "hrv_btb",
80 | HrvMonitorDetailed.HrvBeatToBeatIntervalsFieldId,
81 | FitContributor.DATA_TYPE_UINT16,
82 | {:mesgType=>FitContributor.MESG_TYPE_RECORD, :units=>"ms"}
83 | );
84 | }
85 |
86 | private static function createHrvRmssd30SecDataField(activitySession) {
87 | return activitySession.createField(
88 | "hrv_rmssd30s",
89 | HrvMonitorDetailed.HrvRmssd30SecFieldId,
90 | FitContributor.DATA_TYPE_FLOAT,
91 | {:mesgType=>FitContributor.MESG_TYPE_RECORD, :units=>"ms"}
92 | );
93 | }
94 |
95 | private static function createHrFromHeartbeatDataField(activitySession) {
96 | return activitySession.createField(
97 | "hrv_hr",
98 | HrvMonitorDetailed.HrFromHeartbeatField,
99 | FitContributor.DATA_TYPE_UINT16,
100 | {:mesgType=>FitContributor.MESG_TYPE_RECORD, :units=>"bpm"}
101 | );
102 | }
103 |
104 | private static function createHrvPnn50DataField(activitySession) {
105 | return activitySession.createField(
106 | "hrv_pnn50",
107 | HrvMonitorDetailed.HrvPnn50FieldId,
108 | FitContributor.DATA_TYPE_FLOAT,
109 | {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"%"}
110 | );
111 | }
112 |
113 | private static function createHrvPnn20DataField(activitySession) {
114 | return activitySession.createField(
115 | "hrv_pnn20",
116 | HrvMonitorDetailed.HrvPnn20FieldId,
117 | FitContributor.DATA_TYPE_FLOAT,
118 | {:mesgType=>FitContributor.MESG_TYPE_SESSION, :units=>"%"}
119 | );
120 | }
121 |
122 | function addOneSecBeatToBeatIntervals(beatToBeatIntervals) {
123 | HrvMonitorDefault.addOneSecBeatToBeatIntervals(beatToBeatIntervals);
124 |
125 | var rmssd30Sec = me.mHrvRmssd30Sec.addOneSecBeatToBeatIntervals(beatToBeatIntervals);
126 | if (rmssd30Sec != null) {
127 | me.mHrvRmssd30SecDataField.setData(rmssd30Sec);
128 | }
129 | }
130 |
131 | protected function addValidBeatToBeatInterval(beatToBeatInterval) {
132 | HrvMonitorDefault.addValidBeatToBeatInterval(beatToBeatInterval);
133 |
134 | me.mHrvBeatToBeatIntervalsDataField.setData(beatToBeatInterval.toNumber());
135 |
136 | var hrFromHeartbeat = Math.round(60000 / beatToBeatInterval.toFloat()).toNumber();
137 | me.mHrFromHeartbeatDataField.setData(hrFromHeartbeat);
138 |
139 | me.mHrvSdrrFirst5Min.addBeatToBeatInterval(beatToBeatInterval);
140 | me.mHrvSdrrLast5Min.addBeatToBeatInterval(beatToBeatInterval);
141 |
142 | me.mHrvPnn50.addBeatToBeatInterval(beatToBeatInterval);
143 | me.mHrvPnn20.addBeatToBeatInterval(beatToBeatInterval);
144 | }
145 |
146 | public function calculateHrvSummary() {
147 | var hrvSummary = HrvMonitorDefault.calculateHrvSummary();
148 |
149 | hrvSummary.pnn50 = me.mHrvPnn50.calculate();
150 | if (hrvSummary.pnn50 != null) {
151 | me.mHrvPnn50DataField.setData(hrvSummary.pnn50);
152 | }
153 | hrvSummary.pnn20 = me.mHrvPnn20.calculate();
154 | if (hrvSummary.pnn20 != null) {
155 | me.mHrvPnn20DataField.setData(hrvSummary.pnn20);
156 | }
157 | hrvSummary.first5MinSdrr = me.mHrvSdrrFirst5Min.calculate();
158 | if (hrvSummary.first5MinSdrr != null) {
159 | me.mHrvSdrrFirst5MinDataField.setData(hrvSummary.first5MinSdrr);
160 | }
161 | hrvSummary.last5MinSdrr = me.mHrvSdrrLast5Min.calculate();
162 | if (hrvSummary.last5MinSdrr != null) {
163 | me.mHrvSdrrLast5MinDataField.setData(hrvSummary.last5MinSdrr);
164 | }
165 | return hrvSummary;
166 | }
167 | }
168 | }
--------------------------------------------------------------------------------
/TestHrv/source/summary/SummaryViewDelegate.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi as Ui;
2 | using Toybox.Graphics as Gfx;
3 | using Toybox.Application as App;
4 | using Toybox.Lang;
5 |
6 | class SummaryViewDelegate extends ScreenPicker.ScreenPickerDelegate {
7 | private var mSummaryModel;
8 |
9 | function initialize(summaryModel) {
10 | me.mPagesCount = PagesCount;
11 | setPageIndexes();
12 |
13 | ScreenPickerDelegate.initialize(0, me.mPagesCount);
14 | me.mSummaryModel = summaryModel;
15 | me.mSummaryLinesYOffset = App.getApp().getProperty("summaryLinesYOffset");
16 | }
17 |
18 | private var mSummaryLinesYOffset;
19 |
20 | private const PagesCount = 4;
21 |
22 | private function setPageIndexes() {
23 | me.mHrvRmssdPageIndex = 0;
24 | me.mHrvPnnxPageIndex = 1;
25 | me.mHrvSdrrPageIndex = 2;
26 | me.mHrPageIndex = 3;
27 | }
28 |
29 | private var mPagesCount;
30 |
31 | private var mHrvRmssdPageIndex;
32 | private var mHrvSdrrPageIndex;
33 | private var mHrvPnnxPageIndex;
34 | private var mStressPageIndex;
35 | private var mHrPageIndex;
36 |
37 | private const InvalidPageIndex = -1;
38 |
39 | function onBack() {
40 | return false;
41 | }
42 |
43 | function createScreenPickerView() {
44 | var details;
45 | if (me.mSelectedPageIndex == mHrPageIndex) {
46 | details = me.createDetailsPageHr();
47 | }
48 | else if (me.mSelectedPageIndex == mHrvRmssdPageIndex){
49 | details = me.createDetailsPageHrvRmssd();
50 | }
51 | else if (me.mSelectedPageIndex == mHrvPnnxPageIndex){
52 | details = me.createDetailsPageHrvPnnx();
53 | }
54 | else if (me.mSelectedPageIndex == mHrvSdrrPageIndex){
55 | details = me.createDetailsPageHrvSdrr();
56 | }
57 | else {
58 | details = me.createDetailsPageHrvRmssd();
59 | }
60 | return new ScreenPicker.ScreenPickerDetailsView(details);
61 | }
62 |
63 | private function formatHr(hr) {
64 | return hr + " bpm";
65 | }
66 |
67 | private function createDetailsPageHr() {
68 | var details = new ScreenPicker.DetailsModel();
69 | details.color = Gfx.COLOR_BLACK;
70 | details.backgroundColor = Gfx.COLOR_WHITE;
71 | details.title = "Summary HR";
72 | details.titleColor = Gfx.COLOR_BLACK;
73 |
74 | var hrMinIcon = new ScreenPicker.Icon({
75 | :font => StatusIconFonts.fontMeditateIcons,
76 | :symbol => StatusIconFonts.Rez.Strings.meditateFontHrMin,
77 | :color=>Graphics.COLOR_RED
78 | });
79 | details.detailLines[2].icon = hrMinIcon;
80 | details.detailLines[2].value.color = Gfx.COLOR_BLACK;
81 | details.detailLines[2].value.text = me.formatHr(me.mSummaryModel.minHr);
82 |
83 | var hrAvgIcon = new ScreenPicker.Icon({
84 | :font => StatusIconFonts.fontMeditateIcons,
85 | :symbol => StatusIconFonts.Rez.Strings.meditateFontHrAvg,
86 | :color=>Graphics.COLOR_RED
87 | });
88 | details.detailLines[3].icon = hrAvgIcon;
89 | details.detailLines[3].value.color = Gfx.COLOR_BLACK;
90 | details.detailLines[3].value.text = me.formatHr(me.mSummaryModel.avgHr);
91 |
92 | var hrMaxIcon = new ScreenPicker.Icon({
93 | :font => StatusIconFonts.fontMeditateIcons,
94 | :symbol => StatusIconFonts.Rez.Strings.meditateFontHrMax,
95 | :color=>Graphics.COLOR_RED
96 | });
97 | details.detailLines[4].icon = hrMaxIcon;
98 | details.detailLines[4].value.color = Gfx.COLOR_BLACK;
99 | details.detailLines[4].value.text = me.formatHr(me.mSummaryModel.maxHr);
100 |
101 | var hrIconsXPos = App.getApp().getProperty("summaryHrIconsXPos");
102 | var hrValueXPos = App.getApp().getProperty("summaryHrValueXPos");
103 | details.setAllIconsXPos(hrIconsXPos);
104 | details.setAllValuesXPos(hrValueXPos);
105 | details.setAllLinesYOffset(me.mSummaryLinesYOffset);
106 |
107 | return details;
108 | }
109 |
110 | private function createDetailsPageHrvRmssd() {
111 | var details = new ScreenPicker.DetailsModel();
112 | details.color = Gfx.COLOR_BLACK;
113 | details.backgroundColor = Gfx.COLOR_WHITE;
114 | details.title = "Summary\n HRV RMSSD";
115 | details.titleColor = Gfx.COLOR_BLACK;
116 |
117 | details.detailLines[3].icon = new ScreenPicker.HrvIcon({});
118 | details.detailLines[3].value.color = Gfx.COLOR_BLACK;
119 | details.detailLines[3].value.text = Lang.format("$1$ ms", [me.mSummaryModel.hrvRmssd]);
120 |
121 | var hrvIconsXPos = App.getApp().getProperty("summaryHrvIconsXPos");
122 | var hrvValueXPos = App.getApp().getProperty("summaryHrvValueXPos");
123 | details.setAllIconsXPos(hrvIconsXPos);
124 | details.setAllValuesXPos(hrvValueXPos);
125 | details.setAllLinesYOffset(me.mSummaryLinesYOffset);
126 |
127 | return details;
128 | }
129 |
130 | private function createDetailsPageHrvPnnx() {
131 | var details = new ScreenPicker.DetailsModel();
132 | details.color = Gfx.COLOR_BLACK;
133 | details.backgroundColor = Gfx.COLOR_WHITE;
134 | details.title = "Summary\n HRV pNNx";
135 | details.titleColor = Gfx.COLOR_BLACK;
136 |
137 | var hrvIcon = new ScreenPicker.HrvIcon({});
138 | details.detailLines[2].icon = hrvIcon;
139 | details.detailLines[2].value.color = Gfx.COLOR_BLACK;
140 | details.detailLines[2].value.text = "pNN20";
141 |
142 | details.detailLines[3].value.color = Gfx.COLOR_BLACK;
143 | details.detailLines[3].value.text = Lang.format("$1$ %", [me.mSummaryModel.hrvPnn20]);
144 |
145 | details.detailLines[4].icon = hrvIcon;
146 | details.detailLines[4].value.color = Gfx.COLOR_BLACK;
147 | details.detailLines[4].value.text = "pNN50";
148 | details.detailLines[5].value.color = Gfx.COLOR_BLACK;
149 | details.detailLines[5].value.text = Lang.format("$1$ %", [me.mSummaryModel.hrvPnn50]);
150 |
151 | var hrvIconsXPos = App.getApp().getProperty("summaryHrvIconsXPos");
152 | var hrvValueXPos = App.getApp().getProperty("summaryHrvValueXPos");
153 | details.setAllIconsXPos(hrvIconsXPos);
154 | details.setAllValuesXPos(hrvValueXPos);
155 | details.setAllLinesYOffset(me.mSummaryLinesYOffset);
156 |
157 | return details;
158 | }
159 |
160 | private function createDetailsPageHrvSdrr() {
161 | var details = new ScreenPicker.DetailsModel();
162 | details.color = Gfx.COLOR_BLACK;
163 | details.backgroundColor = Gfx.COLOR_WHITE;
164 | details.title = "Summary\n HRV SDRR";
165 | details.titleColor = Gfx.COLOR_BLACK;
166 |
167 | var hrvIcon = new ScreenPicker.HrvIcon({});
168 | details.detailLines[3].icon = hrvIcon;
169 | details.detailLines[3].value.color = Gfx.COLOR_BLACK;
170 | details.detailLines[3].value.text = Lang.format("$1$ ms", [me.mSummaryModel.hrvSdrr]);
171 |
172 | var hrvIconsXPos = App.getApp().getProperty("summaryHrvIconsXPos");
173 | var hrvValueXPos = App.getApp().getProperty("summaryHrvValueXPos");
174 | details.setAllIconsXPos(hrvIconsXPos);
175 | details.setAllValuesXPos(hrvValueXPos);
176 | details.setAllLinesYOffset(me.mSummaryLinesYOffset);
177 |
178 | return details;
179 | }
180 | }
--------------------------------------------------------------------------------