├── .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 | ![TestHrvDemo](TestHrvDemo.gif) 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**: ![TestHrvActivity](./Screenshots/TestHrvOverviewActivity.png) 30 | 31 | The summary data is saved under Stats, section Connect IQ: 32 | 33 | ![SummaryStats](./Screenshots/SummaryStats.png) 34 | 35 | The extra graphs are shown next to the built-in Garmin graphs: 36 | ![Graphs](./Screenshots/Graphs.png) 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 | } --------------------------------------------------------------------------------