1hz Watch Faces - Q&A

I'm not sure if many folks have been trying the new "always active" watch faces in 2.3.x, but the 2.3.0.beta1 has been out for a month or so now. It's probably getting close to the 2.3.1 SDK release, and thought I'd start a Q&A for the feature, and start with some of what I've picked up in adapting a number of watch faces to use 1hz.

I'll be calling this feature "1hz", as that's what it really is - being able to change the screen on a watch face every second.
The most common thing will likely being the ability to display seconds all the time. One thing I'll note right off, is while you can update the screen every second, the underlying data still changes at the rate it did before, so if you use 1hz for the HR, you won't see the HR change every second.

A few basics.

  • 1hz is only available on some watches. The f5, f5s, f5x, 935, and Q5, but if you support a bunch of different watches, you can easily have it on some and not others.
  • 1hz happens by way of a new onPartialUpdate() for watch faces. onUpdate() is just like it was in the past, and when you are allowed to do a 1hz update, onPartialUpdated() is called. You don't have to change a thing in your code if you don't want to use 1hz.
  • onPartialUpdate() is VERY strict as to how long it can run. You have a "power budget", and the rule is that each minute, the average run time each second can't be greater than 30 milliseconds. This is the tricky part! :)



I started with the Analog Sample in the SDK, which does show what to do, but has some things you don't need to do for digital watch faces - there's no second hand passing over a bunch of different places on the screen with a digital one. The time is in a fixed spot on the screen in most cases.



Anyway's, the first thing I did was cut back to the basics of what was needed for a digital WF. I'll attached a project with a very basic 1hz watch face, but first a few notes about it.

  • vs1hzapp.mc shows how to use the delegate for the power exceeded deligate on watches that have 1hz, and still run on watches that don't do 1hz.
  • vs1hzview.mc I tried to comment as to where to do things, where not to do things, but took a simple approach where I don't just update the seconds at 1hz, I actually do hh:mm:ss. In real code, you likely have a separate space for seconds, and your setClip() will be adjusted for that. I just wanted to get the ball rolling here :). Also, note onEnterSleep() and the code there for 1hz...
  • In onPartialUpdate(), I have a comment about a simple change to make the watch face exceed the power budget, so you can see how that works



Ask questions, suggest other ideas, whatever. That's what this thread is for!

So here's the project! (it might be a bit different than the original one that was lost in the forum update) :

VS1hz-old.zip



NOTE: I should have mentioned this before, but you want to use a 2.3.x SDK for this project!

  • One of the things here, is there could be some things found that you simply can't/don't want to do in onPartialUpdate(). So note those things here.

    Something I heard (I think it was at the Summit), was you don't want to load any resources. Anything you need should be loaded prior to onPartialUpdate()
  • I have another project for very simple watch face with a very simple background process. If there is interest, I'll post that, but maybe in a new thread.

    The watch face itself displays the current time (no 1hz or 12/24), as well as the background data it has seen. And the background process simply returns a string with HH:MM when it's run (a temporal event every few minutes). It's got a number of Sys.println() calls to see when things happen in the sim. But if you sideload it and have a <myapp>.txt log, it will show if something is running in the foreground process or the background one was the one did that Sys.println().
  • I think the "Always Active" 1hz feature is a great addition!
    Jim's code has been very useful as a guide.

    Recently, I've been busy experimenting with a "marching invader" watchface.. a different take on displaying the seconds! Here's what it looks like...

    Vaders! at high power
    https://gfycat.com/FluidJointEel

    Vaders! at 1hz
    https://gfycat.com/FrighteningHonorableCoypu

    I can tell you now — the game logic plus juggling multiple dynamic clip frames was tricky to shoe-horn into a 50ms budget.
    What I've found, is that the power budget is very easy to exceed if you need to render anything "big" on the screen in any form.

    So, using too large a clipping size, or using too many clipping frames will exceed the budget. My example uses about 5 clipping frames at most.
    Also accessing a dictionary, iterating though an array, or accessing multiple objects can cause a process to exceed the power budget.

    Cheers..
  • looks nice! a bit slowish but fluid animations
  • Franco. It's even harder when the max avg is 30ms and not 50ms :)

    I forced exceeding the budget and here's what I see from the delegate:

    Average execution time: 53.728001
    Allowed execution time: 30.000000


    I forced this by simply skipping the dc.setClip() with onPartialUpdate().
  • Thanks for great example Jim!

    I have been fiddling around with this and managed to get always active seconds in watch models which support it. I created separate space for seconds, and my setClip() is adjusted just for seconds field.

    However now minutes are not updated anymore, those are left below onUpdate() where they use to be. I am calling onPartialUpdate() only in case partial updates are allowed:
    partialUpdatesAllowed = ( Toybox.WatchUi.WatchFace has :onPartialUpdate );
    and if watch is in sleep mode. I can see from console Sys.println's that onPartialUpdate() is called, but how an earth is minutes not updated under onUpdate() anymore? I can see from Sys.println's that onUpdate is "run" every minute and onPartialUpdate() every second :confused:

    On watch models that do not support "always active" seconds minutes are working as they used to be.
  • Thanks for great example Jim!

    I have been fiddling around with this and managed to get always active seconds in watch models which support it. I created separate space for seconds, and my setClip() is adjusted just for seconds field.

    However now minutes are not updated anymore, those are left below onUpdate() where they use to be. I am calling onPartialUpdate() only in case partial updates are allowed:
    partialUpdatesAllowed = ( Toybox.WatchUi.WatchFace has :onPartialUpdate );
    and if watch is in sleep mode. I can see from console Sys.println's that onPartialUpdate() is called, but how an earth is minutes not updated under onUpdate() anymore? I can see from Sys.println's that onUpdate is "run" every minute and onPartialUpdate() every second :confused:

    On watch models that do not support "always active" seconds minutes are working as they used to be.


    Do you clear the clip again in onUpdate()?

    Not sure, hard to say without code where things go wrong... I was messing around a lot only to find out I had reversed two lines of code by accident causing some coordinates to be out of sync.
  • My guess is that you aren't doing a clearClip() or not doing it at the right time. Without the clearClip() working properly, the only area of the screen that updates is where your last setClip() defines, so all that will update on the screen will be the seconds even from onUpdate().

    In the code I posted, notice where I do the clearClip(). I have it commented out at the end of onPartialUpdate() with a comment noting that doing it there didn't see to work right (at least as of last time I tried which was the 2.3.0.beta1), and I do the clearClip at the start of onUpdate() (with the proper checks) instead.