Watch Face Heart Rate Null to Trigger Blank Screen

I am trying to enable non-AMOLED devices to blank the watch face screen when heart rate is not detected in low power mode for some period of time, e.g., 1 minute, in order to conserve battery. I am also using onPartialUpdate to update heart rate and seconds in low power mode for compatible devices. I got it to work in the simulator, but when I tested it on my VA3M, the screen did not blank.

First I made a function in a separate Util.mc file to determine whether the time limit has been exceeded for null heart rate:

    function getHeartRateNullExceeded() {
		var heartRate = Activity.getActivityInfo().currentHeartRate;

		if (heartRate == null) {
			
			// This doesn't blank the screen in the simulator:
			// var timeLimit = new Time.Duration(1 * 60);
			// var hrHistory = Act.getHeartRateHistory(timeLimit, true);
			// if (hrHistory == null) {
			// 	return true;
			// }
			
			// So I did this instead:
			var currentTime = new Time.Moment(Time.now().value());
			var hrTime = currentTime;
			var hrHistory = Act.getHeartRateHistory(1, true);
			var hrSample = hrHistory.next();

			if ((hrHistory != null) and (hrSample.heartRate != Act.INVALID_HR_SAMPLE)) {
				hrTime = hrSample.when;
			}

			var elapsedTime = currentTime.subtract(hrTime);
			var timeLimit = new Time.Duration(1 * 60);

			if (elapsedTime.greaterThan(timeLimit)) {
				return true;
			}
			else {
				return false;
			}
		}
		else {
			return false;
		}
	}

Then I used this function in onUpdate in View.mc:

    function onUpdate(dc) {

		// Clear clip
		if(gHasPartialUpdate) {
			dc.clearClip();
		}
				
		// Blank screen in sleep mode if HR is not detected
		if (!isAwake and gSettings.hrBlankScreen) {
			var hrNullExceeded = Util.getHeartRateNullExceeded();
			if (hrNullExceeded) {
				dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_BLACK);
				dc.fillRectangle(0, 0, dc.getWidth(), dc.getHeight());
				blankScreen = true;
				return;
			}
		}
		
		// update screen
		blankScreen = false;
    }

And in onPartialUpdate in View.mc:

    function onPartialUpdate(dc) {

		if (gSettings.hrBlankScreen) {
			var hrNullExceeded = Util.getHeartRateNullExceeded();
			if (blankScreen) {
				return;
			}
			else if (!blankScreen and hrNullExceeded) {
				dc.clearClip();
				dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_BLACK);
				dc.fillRectangle(0, 0, dc.getWidth(), dc.getHeight());
				blankScreen = true;
				return;
			}
		}
		
		// Draw seconds with clip

		// Draw heart rate value with another clip
    }

Note: Variables starting with "g" are global, and other non-declared variables are class wide in the View file.

To prevent onPartialUpdate from writing a new clip over an otherwise blank screen, I used the variable blankScreen true/false to tell it to wait until onUpdate refreshed the entire screen again.

In the simulator, this code successfully blanks the screen when HR is turned off in low power mode, although it is immediate. (It does not wait 1 minute.) Then if HR is turned back on or the device exits low power, the entire screen updates as intended. However, on my actual VA3M device, the screen never blanks.

Is there a different way I should be detecting null heart rate so it will work on the actual device?

  • With ActMon.getHeartRateHistory(), invalid is 255, while with SensorHistory.getHeartRateHistory, it's a null

    So if you check for INVALID_HR_SAMPLE or null, it works for either way you call getHeartRateHistory()

    if you use getHeartRateHistory, it can be a couple minutes before you see a 255 or null, but with Activity.getActivityInfo().currentHeartRate; you see it sooner (a null).  That's also how you can display "real time" HR on a watchface.

  • IMHO it can either be null, 0 or 255. All these should mean "invalid"

  •  thanks for your comments. I realized that my actual mistake was something very simple: I had copied and pasted the if statement from my normal heart rate function, checking for != ActivityMonitor.INVALID_HR_SAMPLE, so invalid values were being avoided instead of detected. Once I set this to == instead of != it was fine. I have edited my previous comment.

    After fixing this, I found out that using the current HR by itself without HR history does not make much of a difference for this application because the interval between each recorded measurement could be 1 minute or more, and it can take a few seconds to get a new reading. E.g., the user could wake up the watch, put it on their wrist, and then it goes to low power mode while HR is still null. The screen would blank because the most recent values in the HR history are all still invalid and the current HR is null.

    So I will use current hr == null as the only check, without using the history.

    Shout out to  for posting code that helped me print the HR history for troubleshooting: https://forums.garmin.com/developer/connect-iq/i/bug-reports/venu2-plus---heart-rate-history-has-wrong-when-value