Watch face too high power consumption

Hi,

I have three issues with my Watch Face, all related to power consumption. The Watch Face I programmed consumes way too much power: I think its battery is drained in two to three days.

1/ The watch rarely goes in low power mode.
I programmed a counter. When the watch face launches, it initially has a good % of low power time (like 60 to 80%) and I witness the watch being on low power mode. Over time, this percentage decreases to about 5% and I witness the watch being in normal power mode (as opposed to low power mode) most of the time.


2/ Low power mode onEnterSleep() function never runs.

//! The user has just looked at their watch. Timers and animations may be started here.
function onExitSleep() {
awake = true;
}


//! Terminate any active timers and prepare for slow updates.
function onEnterSleep() {
awake = false;
Ui.requestUpdate();

}


The rest of the program is designed to remove the second hand display (else it would be stuck at some random position) when awake is false.

I assume the onEnterSleep function will run once when low power mode becomes active.
On my watch, this function never triggers. I've made some tests, the onEnterSleep() function is just never triggered, and instead I have a second hand that is stuck where it was last updated.

This did not happen on the simulator. Simulator worked well, with second hand being removed when entering low power mode.


3/ Redraw all vs. selective
When programming the Watch face, one can (1) redraw everything each second (clear screen, then draw all), or (2) just redraw incrementally (if the step number changed, erase the just existing step number and redraw it).

On my watch, the execution time for (1) is 250ms, for (2) it's 60ms.
To me, that explains why the battery is drained so fast.

I tried to do incremental redraws. It works.
But once you have a notification and return from it, the screen should be refreshed but it's not, and so you have a weird superimposition of the previous notification with the newly selectively updated data. Not acceptable for a watch face.
Also, when coming back from widgets, the screen is simply white and not refreshed.
Same happens when exploring the settings menu if I recall correctly.

When returning from choosing an activity (Swim, Run, etc..) however, the screen is refreshed (presumably because the watch face is relaunched).

The problem is that there is no trigger when the watch comes back from notification or widget or menu, so I can't ask the screen to refresh/redraw everything.

Question/

Do you have experience with this? Same issue or just me? Any idea to solve these issues?

Thanks.
  • Thanks Brian.

    FYI, my D2 Bravo is running 2.30.

    By the way, I got a pushed software update yesterday. I was on 2.30 before, and now I'm still on 2.30. Funny.
    The issue with enterSleep and exitSleep occured both before and after.
  • Thanks Brian.

    FYI, my D2 Bravo is running 2.30.

    By the way, I got a pushed software update yesterday. I was on 2.30 before, and now I'm still on 2.30. Funny.
    The issue with enterSleep and exitSleep occured both before and after.


    There are a few other things that will give you a sw update message, like tz files and region files, so that's probably what you got. They are really quick to install - full FW takes longer.
  • Hey,

    After the D2 Bravo 2.40 update, onEnterSleep and onExitSleep are called correctly.

    I now stumbled upon a few more issues. I made a video to describe them.

    1/ After entering onEnterSleep, the watch still makes updates for another 10 seconds. Also, the update rate is 2Hz.
    Here's how it goes:
    - Watch face initializes
    - during 10 seconds, watch face is "awake" and calls onUpdate at 2Hz (strange!)
    - After these initial 10 seconds, onEnterSleep is called and I flag that
    - Based on flag, I know the watch is asleep, but onUpdate is called at 2Hz for another 10 seconds
    - After these 10 seconds (so 20 seconds total), the watch face finally stops calling onUpdate.

    Reason could be that I call Ui.requestUpdate() in:
    - onShow()
    - onExitSleep()
    - onEnterSleep()
    - onSettingsChanged() (in App).

    2/ When returning from menus or widgets back to the watch face, onShow() is not always called.

    onShow() is called when returning from:
    - Widgets using back button
    - Apps menu

    onShow() is not called when returning from:
    - Widgets using up/down button
    - Light menu
    - Settings menu
    - Notification (top part or full screen)

    It shouldn't be a big deal, but I'm trying to reduce power consumption as much as possible. For this, in onUpdate(), I test variables and only overwrite those that have changed. I don't draw the background at every cycle. Because of that, I need to know (via onShow()) when the watch returns from a menu or widget in each case, so I can redraw background.


    Here's a video to make things clearer with examples.

    www.youtube.com/watch

    Do you guys experience issue 1 and issue 2 as well?
    Thanks!
  • Wild guess here for #1. In your code, do you call Ui.requestUpdate() at all? Could it be in an onUpdate() triggered by CIQ, in some code paths you call Ui.requestUpdate()? That will probably result in an extra call to onUpdate(), and the extra 10 sec worth when going into low power mode.

    on #2, maybe it would help if you said what you're doing in onShow. I don't think you can really count on the order of stuff like this, but there may be ideas to get around this....
  • Noticed on your picture, you do sunrise/sunset stuff it seems. Are are trying to do that as part of on onUpdate/onShow?

    As this is a daily thing, you could only do it once a day the next time after 0:00:00, as well as in initialize()? That would reduce when you do that calculation.
  • Hi Jim,

    Thanks for always taking the time to help.

    I confirm that sun calcs are only done once a day or upon new location.

    Here is the code. I removed all unnecessary stuff to clarify as much as possible. It's still 200 lines, sorry.

    using Toybox.Graphics as Gfx;
    using Toybox.Application as App;
    using Toybox.System as Sys;
    using Toybox.Lang as Lang;
    using Toybox.Math as Math;
    using Toybox.Time as Time;
    using Toybox.Timer as Timer;
    using Toybox.Time.Gregorian as Calendar;
    using Toybox.WatchUi as Ui;
    using Toybox.ActivityMonitor as ActMon;
    using Toybox.Activity as Act;

    var settingsChanged = false;
    var forceUpdate = true;
    var timer1;


    class ClarityApp extends App.AppBase {

    function onStart() {
    loadProperties();
    }

    function onStop() {
    saveProperties();
    }

    function getInitialView() {
    return [ new ClarityView() ];
    }

    function onSettingsChanged() {
    settingsChanged = true;
    Ui.requestUpdate();
    }
    }


    class ClarityView extends Ui.WatchFace {

    var PrevStatus = new PreviousStatus();

    var awake = true;
    var timer10;
    var countLowPowerSecs;
    var status = "not initialized";


    function initialize() {
    status = "initialize";
    forceUpdate = true;
    }

    function onLayout(dc) {
    status = "onLayout";
    forceUpdate = true;
    }

    function onShow() {
    status = "onShow";
    timer10 = Time.now().value();
    forceUpdate = true;
    Ui.requestUpdate();
    }

    function onHide() {
    status = "onHide";
    }

    function onExitSleep() {
    status = "onExitSleep";

    awake = true;
    forceUpdate = true;
    Ui.requestUpdate();
    }

    function onEnterSleep() {
    status = "onEnterSleep";

    awake = false;
    forceUpdate = true;
    Ui.requestUpdate();

    }

    function onUpdate(dc)
    {
    // PART 1 : figuring out whether to do a full screen update, a "variable" one that only updates changed values, or a "initial quick" one when returning from onShow
    timer1 = Sys.getTimer();
    var clockTime = Sys.getClockTime();
    var now = Time.now();

    var initialQuickUpdate = false;

    if (true){ // this is my switch between full update at each cycle (false) or update only when data is changed (true)
    // purpose for the below is that when returning from menu/widget via onShow, I first make a quick initial update with just the hour and date, and I make the longer calculations and displays on the second time. This is to speed up transition back to Watch Face.
    if (status.equals("afterOnShow")){
    forceUpdate = true;
    status = "onUpdate";
    } else {
    if (status.equals("onShow")){
    initialQuickUpdate = true;
    status = "afterOnShow";
    }
    }
    } else {
    forceUpdate = true;
    }


    if (PrevStatus.minute != clockTime.min){
    forceUpdate = true; // if minute changed, the whole onUpdate loop will be executed for a full recalculation and redraw (except for sun data)
    } else {
    // keep exising value of forceUpdate
    }


    // PART 2 : calculate and draw data
    if (forceUpdate){ // background
    dc.setColor(options.colorBackground, options.colorBackground);
    dc.fillRectangle(0,0,width, height);
    }


    if (forceUpdate){
    // display current time
    // Display date
    }

    if (awake and options.displaySecondsText){
    // Display seconds Text
    }


    if (!initialQuickUpdate){ // initialQuickUpdate is only true just after going through onShow. This is to lower time required to display watch face initially

    if (forceUpdate){
    // load astro data from object store? (only if local variable is empty, after watch face initialized)
    // Evaluate if suntime calculation is necessary (it is only necessary once a day or if location changed)
    // If necessary, Calculate or use previous data,
    // Display suntimes
    }

    var stats = Sys.getSystemStats();
    var settings = Sys.getDeviceSettings();
    if (forceUpdate || PrevStatus.battery != stats.battery || PrevStatus.bluetooth != settings.phoneConnected){
    // get Sys stats and display them
    }

    // Display Activity
    var Activity = ActMon.getInfo();
    if (forceUpdate || PrevStatus.moveBar > Activity.moveBarLevel){
    // draw Movebar
    }
    var actInfo = Act.getActivityInfo();
    if(actInfo.altitude != null){
    var altitude = actInfo.altitude;
    if (forceUpdate || PrevStatus.altitude != altitude){
    // Display altitude
    }
    if (actInfo.currentHeading != null && awake){
    // Display heading
    }
    }
    }
    }

    // resetting flag "forceUpdate" for next cycle
    forceUpdate = false;


    // the following is to display energy debug data on the left side of the screen
    if (options.energyDebug){
    // timer to count the low power mode time
    if (now.value() - timer10 > 1.9 or !awake){ // low power mode
    countLowPowerSecs[0] += now.value() - timer10;
    } else { // high power mode
    countLowPowerSecs[1] ++;
    }

    var timer10Str = "lp" + (countLowPowerSecs[0] *100 / (countLowPowerSecs[0] + countLowPowerSecs[1])).toString() + "%";

    dc.setColor(options.colorNormal, options.colorBackground);
    dc.drawText(15,109-24,Gfx.FONT_XTINY, timer10Str, Gfx.TEXT_JUSTIFY_LEFT | Gfx.TEXT_JUSTIFY_VCENTER);
    timer10 = now.value();

    // number of suncalcs
    dc.drawText(13,109-8,Gfx.FONT_XTINY, AstroUpdates.toString() + "sc", Gfx.TEXT_JUSTIFY_LEFT | Gfx.TEXT_JUSTIFY_VCENTER);

    // exec time
    var timer2 = Sys.getTimer();
    var execTimeMs = timer2-timer1;
    var execStr = execTimeMs.toString() + "ms";

    alternate = !alternate;
    if (alternate){
    dc.setColor(Gfx.COLOR_PINK, options.colorBackground);
    } else {
    dc.setColor(Gfx.COLOR_BLUE, options.colorBackground);
    }
    dc.drawText(11,109+8,Gfx.FONT_XTINY, execStr, Gfx.TEXT_JUSTIFY_LEFT | Gfx.TEXT_JUSTIFY_VCENTER);
    }
    }
    }
  • I seem to have found a solution.

    I just had to completely remove all Ui.requestUpdate(). Now it works much better. None was necessary, as it seems onEnterSleep() and onExitSleep() both trigger an update anyways.
    The reason I had put Ui.requestUpdate() initially is that onEnterSleep() and onExitSleep() were not functioning properly on my D2 Bravo but with update 2.40 it was solved.

    My issue is now that when returning from a menu or from widgets, the graceful transition is no longer graceful, due to the 400ms required to render the screen when "awake" (as opposed to "in sleep mode"). There's a big lag in the transition. Could be solved by either removing the transition, or informing the watch face (via onShow() being called) of the transition event, so as to lower the exec time of the first couple cycles.

    Julien
  • Former Member
    Former Member over 9 years ago
    I seem to have found a solution.

    I just had to completely remove all Ui.requestUpdate(). Now it works much better. None was necessary, as it seems onEnterSleep() and onExitSleep() both trigger an update anyways.
    The reason I had put Ui.requestUpdate() initially is that onEnterSleep() and onExitSleep() were not functioning properly on my D2 Bravo but with update 2.40 it was solved.

    My issue is now that when returning from a menu or from widgets, the graceful transition is no longer graceful, due to the 400ms required to render the screen when "awake" (as opposed to "in sleep mode"). There's a big lag in the transition. Could be solved by either removing the transition, or informing the watch face (via onShow() being called) of the transition event, so as to lower the exec time of the first couple cycles.

    Julien


    I have the same issue as you with selective updating the screen when returning from a widget. Have you found a solution for this? I can't find an event or something to be notified when the screen has to be cleared completely before drawing the whole watchface again.
  • Hi Stefan,

    I still have the issue. I haven't solved it but will try this weekend.

    My plan is based on the assumption that when going to a widget or menu, you don't update the watch for some amount of time greater than 1 second (I'll actually use 2 seconds to make sure). In the watch face, I'll record the time at each update, and if at the next update, time difference is greater than 2 seconds, then I will make a full update. I'll call that the Return Event.

    I have actually a greater problem, which is that transitions mess everything up. The screen is half updated and the other half displays what's left over from the transition. To solve that I'll make another full update 1 or 2 seconds after the Return Event, i.e. once the transition is finished.

    I also plan on making only a quick update at the Return Event to make transitions snappier, and only make the full update 2 seconds later once transition is completed.

    It's a shame that the framework doesn't yet make things easy on this end (like disable transitions, or don't do updates while transitioning and notify that the transition is finished for a full update).


    I think the good news is, battery life really improves. Going from 450ms to 45ms took battery life from 2 days to 5 days. Which is still really bad. But much better than 2 days. I'm considering removing useless stuff on my watch face (or letting users choose) so that battery life can be further optimized.

    Finally, my last point is that, as mentioned above, sometimes the watch updates twice per second, even after it entered sleep. That lasts another 8 seconds. Removing this problem may extend battery life by one more day.

    Conclusion is that using a complex watch face brings your battery life from 6 weeks to 1 week :(
  • Former Member
    Former Member over 9 years ago
    Hi JULIENVM,

    Thanks for the update! I will give it some thoughts as well. It would be nice if some moderator from Garmin could give us some advice on this subject. The other annoying thing is that partial screen updates seem to work on the simulator just fine.