How do I work out what to redraw in onUpdate()?

Hi. I'm sure this question must have been raised before, but I can't find an answer.

In the pre-OLED days, I think I understood how onUpdate()/onPartialUpdate() were supposed to work. With the Venu 2 (and, I guess, other OLED devices) things seem to be different. When the watch is woken by a button-press or gesture, onUpdate() gets called repeatedly -- perhaps ten(?) times -- at one-second intervals. Then the watch goes back to sleep, and it doesn't get called again until the next minute.

Meanwhile, with the OLED watches, it looks as if onPartialUpdate() is never called at all.

So -- suppose I want to update a small area of the screen when the watch is awake -- perhaps a seconds display. I don't see any alternative to drawing the entire screen every time onUpdate() is called. There doesn't seem to be any way to work out what actually needs to be updated. This seems like a waste of battery, erasing and redrawing the entire screen every second, when only a tiny region has changed.

And what happened to onPartialUpdate()? It is still called on the Vivoactive 3 (maybe others as well), but is never called on the Venu 2.

So, in short -- with the OLED watches, how do I update a small screen area only at 1Hz?

Have I missed something?

  • But I _want_ the difference to be major. I want my WF use as little energy as is reasonably practicable, and I'm happy to reduce the frequency of display updates, and the number of lit pixels, to do that. My sole reason for implementing this WF is specifically to use minimal energy. I care about nothing else, in this application.

    I'm happy to pre-compute everything that can be precomputed, pre-render fonts into memory, etc., etc., to achieve the absolute minimum run-time computation. I want most of the calls that happen once a second to do nothing at all. Better still, I want these calls not to be made at all, but that's a different matter.

    But the ConnectIQ API simply thwarts my attempts to minimize energy usage, by forcing my app to update when there is no need for it to do so.

    One of the repeated user complaints about ConnectIQ watch faces is that they're battery-guzzlers. In some cases that's probably just because of developer carelessness. It galls me that, however careful I'm willing to be, I end up fighting an API that forces me to do work every second when the display has not changed.

    This just seems completely wrong to me. Maybe the API can't be changed without breaking backward compatibility. But I just can't see how the current implementation can be considered optimal.

  • The way around it is not to waste time developing for something that seems so broken, with respect to power management.

    Hard agree

    fighting an API

    Common CIQ dev complaint

  • You can limit the amount of times onUpdate actually does things. I have a basic sample of using a boolean and timer to only redraw every 2 seconds. I don't wear my watch while sleeping and it drops less than 2% battery overnight. hard to say since they won't give us decimal values ;)

    You won't see much savings with this example because it's only 1 thing being drawn, but on my WF with a couple of things (date, hr, body battery, time, seconds, steps, temperature) it makes a noticeable difference in the average in the simulator's profiler. About half, for a short sample period. You could add other bools and checks and draw a black (if that's your bg color) rectangle over the seconds to clear that part of the screen and only redraw the text of that part. It could probably get complicated with a lot of data.

    Sorry for the unformatted code, I tried the other option which told me I'm blocked.

    import Toybox.Application;
    import Toybox.Graphics;
    import Toybox.Lang;
    import Toybox.System;
    import Toybox.WatchUi;
    import Toybox.Time.Gregorian;

    class SimpleWatchFaceView extends WatchUi.WatchFace {
    var _canUpdate = false;
    var _lowPwrMode = false;
    var _blanked = false;
    var _updateInterval = 2000;
    var _updateTimer = new Timer.Timer();

    function initialize() {
    System.println("view initialize");
    WatchFace.initialize();
    }

    function startUpdateTimer() {
    System.println("starting update timer");
    _updateTimer.start(method(:onTimerUpdate), _updateInterval, false);
    }

    function stopUpdateTimer() {
    if (_updateTimer != null) {
    System.println("stopping update timer");
    _updateTimer.stop();
    }
    }

    function onTimerUpdate() as Void {
    System.println("onTimerUpdate");
    _canUpdate = true;
    WatchUi.requestUpdate();
    }

    // Load your resources here
    function onLayout(dc as Dc) as Void {
    System.println("onLayout");
    }

    // Called when this View is brought to the foreground.
    // Restore the state of this View and prepare it to be shown.
    // This includes loading resources into memory.
    function onShow() as Void {
    System.println("onShow");
    _canUpdate = true;
    startUpdateTimer();
    }

    // Updates the view.
    // Called every second in high power mode.
    // Called once a minute in low power mode.
    function onUpdate(dc as Dc) as Void {
    System.print("onUpdate: ");

    if (_lowPwrMode && !_blanked) {
    System.println("low power mode");
    dc.setColor(0, 0);
    dc.clear();
    // for debugging
    /*dc.setColor(Graphics.COLOR_PURPLE, -1);
    dc.drawText(208, 208, Graphics.FONT_SMALL, "Low Power Mode", Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);*/
    // end debugging
    _blanked = true;
    return;
    }

    if (_canUpdate == false) {
    System.println("skipping");
    return;
    }

    System.println("drawing");

    // clear screen
    dc.setColor(0, 0);
    dc.clear();

    // time
    dc.setColor(Graphics.COLOR_DK_RED, -1);
    dc.drawText(208, 208, Graphics.FONT_TINY, System.getClockTime().sec.format("%02d"), Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);

    // Only allow timer tick to update screen.
    //_canUpdate = false;
    //startUpdateTimer();
    }

    // Called when this View is removed from the screen. Save the state of this View here.
    // This includes freeing resources from memory.
    function onHide() as Void {
    System.println("onHide");
    stopUpdateTimer();
    _canUpdate = false;
    }

    // Terminate any active timers and prepare for slow updates (once a minute).
    function onEnterSleep() as Void {
    System.println("onEnterSleep");
    stopUpdateTimer();
    _lowPwrMode = true;
    _blanked = false;
    //WatchUi.requestUpdate();
    }

    // The user has just looked at their watch. Timers and animations may be started here.
    function onExitSleep() as Void {
    System.println("onExitSleep");
    _canUpdate = true;
    _lowPwrMode = false;
    WatchUi.requestUpdate();
    }
    }




  • It's simple.  In onUpdate you need to redraw everything.

    There are things like notifications and toasts that can obscure all or part of the screen and there's no way to know.  Also on some devices, the dc is cleared before onUpdate is called.  You don't see this in the sim.

    There are many threads here about this very topic.

    The only time you can get by with updating only part of the screen is in onPartialUpdate

  • Thanks. I thought of an example now, like the charging notification, although that seems to get preference and draws over the watch face.

  • Yes.  You have no control of that.

    Also, doing something like ignoring every other call to onUpdate could lead to a flashing display on devices where the dc is cleared before onUpdate is called.

  • "on some devices, the dc is cleared before onUpdate is called"

    Sounds like it could save some rendering time. Which devices have this behavior? Is there a way to check for this? Thanks :)

  • Not really, as you'll want the DC set to the background color for your app.  You have no way of knowing if something like a toast has covered part of the screen, etc.

    This has come up numerous times over the last 9 years, and even Garmin says the proper way to handle this is to re-draw everything each time onUpdate is called.  If you want to only update a part, you use onPartialUpdate.

  • I recently bought a FR255 and found that on the device (but not in the sim) it clears the screen without me having to do it in the code. But what you said makes sense and is probably safer and required if you want a different background color.

  • Unless you have a whole bunch of devices to test with, you always just want to do the clear yourself as that's safe in all cases, and for a watchface, most of the time that's only once a minute.

    You don't see the clear/no clear difference in the sim, and never have.