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!

  • With clip regions, the number of rows has more impact than the number of columns, as the way I understand it, when the display updates, it does it by row. So in an analog WF, the impact to the powerBudget is lower when the hand is at 9 or 3 than when it's as 12 or 6.

    I'm not sure having a reasonable clipping region at the top and the bottom of the screen is a huge problem.

    Another thing to consider is if you have update something based on onPartialUpdate. Consider updating "steps". They only time you really need to update it in onPartialUpdate is if it changed from what's already on the screen. If it's the same, do nothing. If the steps aren't changing every second, you can avoid a lot of updating to the screen, and the budget is an average.
  • Thanks jim_m_58 for clarification related to rows and columns.



    Made "steps" as per your suggestion to onPartialUpdate. I am having quite tall clipping region for Steps, HR and seconds (see picture attached):


    I changed code also as per your suggestion, that update is only when it is changed from what is already on screen. Unfortunately I am still going over budget slightly in simulation at least:
    Average execution time: 32.281712
    Allowed execution time: 30.000000

    When update is needed for HR it takes 56 ms and without it (steps and seconds) : 15 ms.

    So it seems I need to change layout so that there is less rows and thus also "bouncing box" is smaller. If I replace batterybar with HR info it will fit into budget without updating "steps" on PartialUpdate.


  • Do you use the "only if changed" for steps and HR? As far as steps, you are driving it with simulation>ActivityMonitor, and maybe simulated data for the HR?

    If you have it set for 300 steps/min, it might be that you are changing more often than in real life, and on the real watch, you likely won't see the step count increase every second in a WF. With simulated data, the HR might be changing more than will happen in the real world, so maybe play back a .fit of your own recorded with 1hz
  • Yes I use the "only if changed" for steps and HR. I am driving it Simulation, ActivityMonitor 300 steps/min and for HR data Simulation,Fit Data, Simulate data. Good points that maybe my simulation is heavier than normal usage.

    EDIT: I guess smaller used average total time means smaller energy consumption? From that perspective it maybe reasonable to drive`used time as low as possible?
  • I don't know many people that just walk around at 300 steps a minute. :) And in some of my testings, while steps can change every second in the sim, I've seen where on a real watch in a watchface, they only actually change a few time a minute. You can also add some logic and not only update the screen when there's a change, but only check every other time onPartialUpdate is called. This alone would reduce the times you do a screen update from a max of 60 times a minute to a max of 30 times, and maybe something to consider for a complex situation.

    even something like each time onPartialUpdate is called, do steps or HR, but not both. Mix that in with "only if changed", and there could be times that onPartuialUpdate does neither.
  • ^ Heh ;), I checked that top race walkers have a step rate in the range of 185 to 200, some walkers (most notably the Chinese) takes as many as 230 steps per minute. So no need to test above 230 atleast. Not racers are doing on the average about 94-105 steps per minute.

    I added logic as per your suggestion: HR is updated even seconds and steps odd seconds. Now I am not tripping 30 ms budget anymore. Let's hope that will be the case in real device as well.

    Thanks for help!
  • That's the thing as the powerBudget is an average. At time, you can exceed 30ms, and still keep the avg below 30ms. In Simple Vaders, most of the time, I have 2 clip regions. One row of vaders and the missile. Maybe 2-3 times a minute, 3 clip regions, because I move the base, and when the board clears, I'll do 5-6 (once every 5-7 minutes).

    If you're doing more than just updating seconds, you might have to be a bit creative in how you use the budget.
  • For all the 1hz fans, I'm building a Tetris clone called "Watchtris", that drops a shape at the passing of each second.

    Here's what it looks like running at 1hz;
    https://gfycat.com/ClutteredWellwornAfricanaugurbuzzard

    From an "onPartialUpdate" perspective, it was really difficult to shoehorn all the rendering into 30ms (shape drawing, collision detection, time rendering). Using BufferedBitmaps helped quite a bit in speeding up the code. Like with my Nyan Cat, the partialUpdates are managed with a dynamic clipping window.

    Here's what the dynamic clipping window looks like;
    https://gfycat.com/TanWeightyIguanodon

    At the "end" of the game, I need to clear the entire screen. Doing this during 1hz would exceed the budget. You'll notice near the end of that video, I do this by clearing one row at a time.

    Interested to hear your thoughts.

  • What would be the best way to totally suppress a seconds hand assuming there is a settings toggle? This is assuming the device supports onPartialUpdate.

    Would it be best to use the toggle to stop the code from entering onPartialUpdate(), or allow the code to enter it but simply not do anything after evaluating the toggle within onPartialUpdate()?