Skip to content

Commit

Permalink
5.0.0 - Implemented respiration rate chart
Browse files Browse the repository at this point in the history
  • Loading branch information
dliedke committed Feb 1, 2023
1 parent cfe7edb commit 200cc99
Show file tree
Hide file tree
Showing 17 changed files with 227 additions and 48 deletions.
2 changes: 2 additions & 0 deletions ConnectIQStore/Meditate - Garmin Connect IQ.url
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://apps.garmin.com/en-US/apps/c5fc5ea5-7d12-4fb9-be9c-701663a39db7
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Totally free application developed by vtrifonov and compiled/enhanced for newer

*** Please read all guide below to avoid issues with the application in your device ***

A meditation/yoga app that tracks as an activity the heart rate, stress, respiration rate, heart rate variability (HRV), provides vibration or sound alerts and heart rate chart.
A meditation/yoga app that tracks as an activity the heart rate, stress, respiration rate, heart rate variability (HRV), provides vibration or sound alerts, heart rate chart and respiration rate chart.

Touch and hold the screen (Touch devices) or hold the left middle button to edit the meditation sessions, properties and global settings.

Expand All @@ -18,7 +18,7 @@ Set custom activity name using watch connected to PC via USB with Garmin Express

Use "Yoga" type activity to correctly capture Respiration rate metrics due to bug in Breath work activity in some devices.

Avoid moving your wrist during the session in order to better capture HRV and Respiration rate metrics.
Avoid moving your wrist during the session in order to capture HRV and Respiration rate metrics.

Note: Connect IQ API used for application development only returns the 5min average for the stress metric. That's why the numbers don't match exactly with Garmin Connect, because in GC it shows the real time stress metric when activity started and stopped.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Aplicativo totalmente gratuito desenvolvido por vtrifonov e compilado/aprimorado

*** Leia todo o guia abaixo para evitar problemas com o aplicativo em seu dispositivo ***

Um aplicativo de meditação/Yoga que rastreia atividade com frequência cardíaca, estresse, frequência respiratória, variabilidade da frequência cardíaca (VFC), fornece alertas de vibração ou som e gráfico da frequência cardíaca.
Um aplicativo de meditação/Yoga que rastreia atividade com frequência cardíaca, estresse, frequência respiratória, variabilidade da frequência cardíaca (VFC), fornece alertas de vibração ou som, gráfico da frequência cardíaca e gráfico da frequência respiratória.

Toque e segure a tela (dispositivos com touch screen) ou segure o botão do meio esquerdo para editar as sessões de meditação, propriedades e configurações globais.

Expand All @@ -18,7 +18,7 @@ Defina o nome da atividade personalizada usando o relógio conectado ao PC via U

Use a atividade do tipo "Yoga" para capturar corretamente as métricas da taxa de respiração devido a um bug na atividade de respiração em alguns dispositivos.

Evite mover o pulso durante a sessão para capturar melhor as métricas de VFC e frequência respiratória.
Evite mover o pulso durante a sessão para capturar as métricas de VFC e frequência respiratória.

Observação: a API Connect IQ usada para desenvolvimento de aplicativos retorna apenas a média de 5 minutos para a métrica de estresse. É por isso que os números não correspondem exatamente ao Garmin Connect, porque no GC ele mostra a métrica de estresse em tempo real quando a atividade é iniciada e interrompida.

Expand Down
Binary file added ConnectIQStore/MobileStoreIcon_24bit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectIQStore/MobileStoreIcon_64_colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectIQStore/preparationTime.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectIQStore/sessionSummaryDetailedDemo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ConnectIQStore/sessionSummaryHRChart.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion HrvAlgorithms/sources/activity/RrActivity.mc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ module HrvAlgorithms {
private var totalRespirationSamples;
private var totalRespirationRateSum;
private var rrSummary;

private var mRRHistory = [];

// Method to be used without class instance
function isRespirationRateSupported(){
if (ActivityMonitor.getInfo() has :respirationRate) {
Expand Down Expand Up @@ -54,7 +55,13 @@ module HrvAlgorithms {
updateSummary(respirationRate);
}

// Update respiration rate history for chart
if (respirationRate!=null) {
mRRHistory.add(respirationRate);
}

return respirationRate;

} else {
return -1;
}
Expand All @@ -81,6 +88,8 @@ module HrvAlgorithms {
}

function getSummary() {

rrSummary.rrHistory = me.mRRHistory;
return rrSummary;
}
}
Expand Down
1 change: 1 addition & 0 deletions HrvAlgorithms/sources/activity/RrSummary.mc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ module HrvAlgorithms {
var maxRr;
var averageRr;
var minRr;
var rrHistory;
}
}
2 changes: 1 addition & 1 deletion Meditate/manifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<!-- This is a generated file. It is highly recommended that you DO NOT edit this file. -->
<iq:manifest xmlns:iq="http://www.garmin.com/xml/connectiq" version="3">
<iq:application entry="MeditateApp" id="9abb375dcf7c4ace87ff66f4f774f6c8" launcherIcon="@Drawables.launcherIcon" minSdkVersion="3.0.0" name="@Strings.AppName" type="watch-app" version="4.7.0">
<iq:application entry="MeditateApp" id="9abb375dcf7c4ace87ff66f4f774f6c8" launcherIcon="@Drawables.launcherIcon" minSdkVersion="3.0.0" name="@Strings.AppName" type="watch-app" version="5.0.0">
<iq:products>
<iq:product id="approachs62"/>
<iq:product id="d2air"/>
Expand Down
2 changes: 1 addition & 1 deletion Meditate/resources-por/strings/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<string id="SummaryStress">Sumário\n Stress</string>
<string id="SummaryStressStart">Stress no Início: $1$</string>
<string id="SummaryStressEnd">Stress no Fim: $1$</string>
<string id="SummaryRespiration">Sumário\n Respiração</string>
<string id="SummaryRespiration">Respiração</string>
<string id="SummaryRespirationMin">Min: </string>
<string id="SummaryRespirationAvg">Med: </string>
<string id="SummaryRespirationMax">Max: </string>
Expand Down
2 changes: 1 addition & 1 deletion Meditate/resources/strings/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<string id="SummaryStress">Summary\n Stress</string>
<string id="SummaryStressStart">Stress Start: $1$</string>
<string id="SummaryStressEnd">Stress End: $1$</string>
<string id="SummaryRespiration">Summary\n Respiration</string>
<string id="SummaryRespiration">Respiration</string>
<string id="SummaryRespirationMin">Min:</string>
<string id="SummaryRespirationAvg">Avg:</string>
<string id="SummaryRespirationMax">Max:</string>
Expand Down
17 changes: 12 additions & 5 deletions Meditate/source/activity/MeditateView.mc
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ class MeditateView extends Ui.View {
}
}

var lastElapsedTime = -1;

// Update the view
function onUpdate(dc) {
View.onUpdate(dc);
Expand Down Expand Up @@ -226,12 +228,17 @@ class MeditateView extends Ui.View {
me.mHrvText.draw(dc);
}

if (me.mMeditateModel.isRespirationRateOn()) {
var respirationRate = me.mMeditateModel.getRespirationRate();
me.mBreathIcon.draw(dc);
me.mBreathText.setText(me.formatHr(respirationRate));
me.mBreathText.draw(dc);
// Only get respiration rate every second
if (elapsedTime!=lastElapsedTime) {
if (me.mMeditateModel.isRespirationRateOn()) {
var respirationRate = me.mMeditateModel.getRespirationRate();
me.mBreathIcon.draw(dc);
me.mBreathText.setText(me.formatHr(respirationRate));
me.mBreathText.draw(dc);
}
}

lastElapsedTime = elapsedTime;
}

// Called when this View is removed from the screen. Save the
Expand Down
186 changes: 186 additions & 0 deletions Meditate/source/summaryScreen/RespirationRateGraphView.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
using Toybox.Math;
using Toybox.System;
using Toybox.Application as App;
using Toybox.WatchUi as Ui;
using Toybox.Graphics as Gfx;
using Toybox.Activity as Activity;
using Toybox.SensorHistory as SensorHistory;
using Toybox.ActivityMonitor as ActivityMonitor;

class RespirationRateGraphView extends ScreenPicker.ScreenPickerView {

hidden var position_x, position_y;
hidden var graph_width, graph_height;
var centerX;
var centerY;
var summaryModel;

function initialize(summaryModel) {
ScreenPickerView.initialize(Gfx.COLOR_BLACK);
me.summaryModel = summaryModel;
}

// Update the view
function onUpdate(dc) {

// Clear the screen
dc.setColor(Gfx.COLOR_TRANSPARENT, Gfx.COLOR_WHITE);
dc.clear();
ScreenPickerView.onUpdate(dc);

// Calculate center of the screen
centerX = dc.getWidth()/2;
centerY = dc.getHeight()/2;

// Calculate position of the chart
position_x = centerX - (centerX / 1.5) - App.getApp().getProperty("heartRateChartXPos");
position_y = centerY + (centerY / 2) - App.getApp().getProperty("heartRateChartYPos");

graph_height = dc.getHeight() / 3;
graph_width = App.getApp().getProperty("heartRateChartWidth");

if (me.summaryModel.minRr instanceof String) {
me.summaryModel.minRr = "--";
}

if (me.summaryModel.maxRr instanceof String) {
me.summaryModel.maxRr = "--";
}

if (me.summaryModel.avgRr instanceof String) {
me.summaryModel.avgRr = "--";
}

dc.setColor(Gfx.COLOR_BLACK, Graphics.COLOR_TRANSPARENT);

// Draw title text
dc.drawText(centerX,
25,
App.getApp().getProperty("largeFont"),
Ui.loadResource(Rez.Strings.SummaryRespiration),
Graphics.TEXT_JUSTIFY_CENTER);

// Draw MIN RR text
dc.drawText(centerX - centerX / 2 + 10,
centerY - centerY / 2 + 10,
Gfx.FONT_SYSTEM_TINY,
Ui.loadResource(Rez.Strings.SummaryRespirationMin) + me.summaryModel.minRr.toString(),
Graphics.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER);

// Draw AVG RR text
dc.drawText(centerX,
centerY + centerY / 2 + 3,
Gfx.FONT_SYSTEM_TINY,
Ui.loadResource(Rez.Strings.SummaryRespirationAvg) + me.summaryModel.avgRr.toString(),
Graphics.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER);

// Draw MAX RR text
dc.drawText(centerX + centerX / 2 - 10,
centerY - centerY / 2 + 10,
Gfx.FONT_SYSTEM_TINY,
Ui.loadResource(Rez.Strings.SummaryRespirationMax) + me.summaryModel.maxRr.toString(),
Graphics.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER);

// Retrieve saved RR history for this activity
var respirationRateHistory = me.summaryModel.rrHistory;

// Validate if we have respirate rate history
if (respirationRateHistory == null || respirationRateHistory.size() <=0) {
return;
}

//DEBUG
//me.summaryModel.minRr = 6;
//me.summaryModel.maxRr = 14;

// Get min and max RR and check if they are valid
var HistoryMin = me.summaryModel.minRr;
var HistoryMax = me.summaryModel.maxRr;

if (HistoryMin instanceof String) {
return;
}

if (HistoryMax instanceof String) {
return;
}

// Reduce a bit the min respiration rate so it will show in the chart
HistoryMin-=3;

// Calculate different between min and max RR
var minMaxDiff = (HistoryMax - HistoryMin).toFloat();

// Chart as light blue
dc.setPenWidth(1);
dc.setColor(0x27a0c4, Graphics.COLOR_TRANSPARENT);

// Try adapting the chart for the graph width
var skipSize = 1;
var skipSizeFloatPart = 0;

// If chart would be larger than expected graph width
if (respirationRateHistory.size() > graph_width) {

// Calculate with maximum precision the skip
skipSizeFloatPart = respirationRateHistory.size().toFloat() / graph_width.toFloat();
skipSizeFloatPart = skipSizeFloatPart - Math.floor(skipSizeFloatPart);
skipSizeFloatPart = skipSizeFloatPart * 10000000;

// Calculate the basic skip for the for loop
skipSize = Math.floor(respirationRateHistory.size().toFloat() / graph_width.toFloat()).toNumber();
}

// Draw RR chart
var xStep = 1;
for (var i = 0; i < respirationRateHistory.size(); i+=skipSize){

var respirationRate = respirationRateHistory[i];

if (respirationRate!=null && respirationRate > 1) {

var lineHeight = (respirationRate-HistoryMin) * (graph_height.toFloat() / minMaxDiff.toFloat());

dc.drawLine(position_x + xStep,
position_y - lineHeight.toNumber(),
position_x + xStep,
position_y);
}

// Skip to fit the chart in the screen
if (skipSizeFloatPart > 0) {
if ((xStep * 1000000) % (skipSizeFloatPart).toNumber() > 1000000) {
i++;
}
}

xStep++;
}

// Draw lines and labels
dc.setPenWidth(1);
dc.setColor(Gfx.COLOR_BLACK, Graphics.COLOR_TRANSPARENT);

var lineSpacing = graph_height / 4;

for(var i = 0; i <= 4; i++){

// Draw lines over chart
dc.drawLine(position_x + 3,
position_y - (lineSpacing * i),
position_x + graph_width,
position_y - (lineSpacing * i));

if (i!=0) {

// Draw labels for the lines except last one
dc.drawText(position_x + App.getApp().getProperty("heartRateChartXPosLabel"),
position_y - (lineSpacing * i),
Gfx.FONT_SYSTEM_XTINY,
Math.round(HistoryMin + (minMaxDiff / 4) * i).toNumber(),
Graphics.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER);
}
}

}
}
2 changes: 2 additions & 0 deletions Meditate/source/summaryScreen/SummaryModel.mc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SummaryModel {
me.maxRr = me.initializeHeartRate(rrSummary.maxRr);
me.avgRr = me.initializeHeartRate(rrSummary.averageRr);
me.minRr = me.initializeHeartRate(rrSummary.minRr);
me.rrHistory = rrSummary.rrHistory;

if (me.minRr == 9999999) {
me.minRr = me.initializeHeartRate(0);
Expand Down Expand Up @@ -147,6 +148,7 @@ class SummaryModel {
var maxRr;
var avgRr;
var minRr;
var rrHistory;

var stress;
var stressStart;
Expand Down
Loading

0 comments on commit 200cc99

Please sign in to comment.