Coder Social home page Coder Social logo

A timer module for PebbleJS about pebblejs HOT 10 OPEN

pebble avatar pebble commented on July 23, 2024
A timer module for PebbleJS

from pebblejs.

Comments (10)

Meiguro avatar Meiguro commented on July 23, 2024

Great writeup!

I wouldn't say it's completely impossible however. For app timers, you would use setTimeout and for the tick timer service, you would use setInterval. You would have to align the interval to the minute or hour yourself with a setTimeout, but they should otherwise work.

Besides that, I think there are two things here.

Watchside Timer Display

It's true, you can't display the elapsed time with millisecond accuracy in pebble.js. A workaround is to display the end time and current time with UI.TimeText, but I agree that is suboptimal.

Implementing a regular stopwatch in pebble.js at the moment is non-trivial. I'll continue to think about this use-case. C code space is very limited, so I have to be careful when addition features that require changes in C. UI.TimeText uses strftime to handle all the time formatting, so that powerful textfield actually came almost for free. This new addition will need a slight bit of code space to handle its own format. This also reminds me that I need to supply the watch time of when a button was pressed to the button events.

Adding a UI.TimerText to make a stopwatch possible might be inevitable, though the name is pretty close to UI.TimeText. UI.TimerValue does solve the name clash, but maintaining consistency with prefixes and suffixes is a good idea. The proposed element would display time based on a formatted string, so it'll be suffixed with Text similar to UI.TimeText. The name is not the major issue though; I point it out in case someone thinks of a better one.

JavaScript Timer Library

In the cases where there is no timer display, is the timer module proposed as a much more convenient interface to setTimeout and setInterval? I agree they can be inconvenient when setting up for absolute time references, but they should be fine for relative time events.

Thinking about it again, it should be as painless as possible to use pebble.js aside from the technical limitations, so it's actually a great idea to provide a convenience timer library. The only issue is the confusion it could cause when there are multiple ways to accomplish the same thing.

In addition, to implement the WakeUp API #7 seamlessly, I'll have to provide some sort of time interface. This may necessitate time related auxiliary functions, so I'll keep this in mind when doing so.

from pebblejs.

samuelmr avatar samuelmr commented on July 23, 2024

Thanks for the reply.

My suggestion was related to the timer display, creating stopwatches and possibly other watchfaces that would display something else than strftime. Long time ago, when I tried to create some kind of timers using simply.js, invoking setInterval with 1000 ms interval didn't really work. (The "seconds" were quite wobbly.) Many watchfaces don't really require millisecond accuracy, but it would be nice if a second wouldn't sometimes take 2 or 3 seconds to pass. I have to admit that PebbleJS feels much more responsive than Simply.js then. I'll check how setInterval(doSomething, 1000) works nowadays.

I think setTimeout and setInterval are fine if you don't have to change the watch display every second. There should be ample copy-pasteable code examples around the net for absolute or relative time references. Nothing extra really needed here. But if I was to set a timer to do something after 30 minutes or more (and not change the watch display in the mean time), I'd definitely want to combine the timer with wakeup API. Perhaps your wakeup implementation will be a handy substitute for setTimeout.

from pebblejs.

samuelmr avatar samuelmr commented on July 23, 2024

I did a quick test. I created a new PebbleJS project in CloudPebble and added two lines:

var counter = 0;
setInterval(function() {main.body('Counted to ' + (++counter));}, 1000);

The result isn't quite satisfactory. It sometimes takes 4 seconds for a counter to increment. I understand that the reason is the bluetooth messaging between the phone and the watch. I thought you could send a message that would subscribe to tick timer service. I agree that it would mean quite a lot of custom code for the C side of things.

For simple counters, it would be nice to even be able to use any time for TimeText, not just the current time on the watch.

from pebblejs.

samuelmr avatar samuelmr commented on July 23, 2024

Just want to add that many watchfaces and watchapps change graphics or vibe on certain intervals. And setInterval isn't smooth enough for frequent updates. That's why I didn't suggest UI.TimerText but a separate module instead.

from pebblejs.

Meiguro avatar Meiguro commented on July 23, 2024

Ah, sorry, UI.TimerText was in reference to UI.TimerValue in your first example. I had mistakenly thought that was part of your request. I think it makes sense to suggest both this new element for stopwatches and a separate timer module however since a stopwatch redraws at at least once per second. You're right, I could also extend the existing UI.TimeText, aside from a few gotchas for greater than one day resolution. At the very least I can add an offset to UI.TimeText which would be useful for displaying different time zones by hand too.

Overall, I apologize for my answers. I think if I explain what I'm thinking in full, it'll be more clear. I believe I understand the feature request now; it is not a Timer module per se, but a method to synchronize actions to occur at exact times. The issues here are very similar to those expressed in #18. I'm going to structure my answer in two parts so that I don't tangle different ideas. The first part will explain why pebble.js is currently limited in this area and marred with latency. The second part will introduce a possible solution.

The short of it is that it's possible to make a Timer module that feels responsive by calling the callback earlier than timed and scheduling the changeset in the future on the watch. If this sounds terrible, read ahead for food for thought!

Pebble.js and Bluetooth Latency

First, thanks for testing setInterval! About subscribing to the C tick timer service; the C tick timer service can't directly be subscribed to because the result would have double the latency of using setInterval by itself. The tick timer service on the watch would send a packet over bluetooth to JavaScript to process it, then the JavaScript would send back the effects over bluetooth. Having timing in only JavaScript cuts out the first latency inducing step. This is why I recommend purely JavaScript timer functionality at the moment. Your testing however reveals that even cutting this latency in half is not enough. A TickTimerService would therefore need to synchronize somehow with the watch in a better way than just described.

You might have already known about this latency; therefore your suggestion might have actually been to hook up the C tick timer service to C actions. This is definitely a solution, and it is the solution that UI.TimeText uses. It is responsive because it automatically registers the tick timer service for you and has its redrawing logic implemented directly in C. But as you already know, I caution about the C code space similar features could use up. In order to have graphical changes or vibes performed immediately when the C tick timer service fires, the corresponding graphic or vibe logic needs to be in C as well. A vibe is simple, but there are numerous different kinds of graphical changes possible. Each kind, such as animating a rect or alternating the background color, would require its own C logic. This is the case if I were to go with the direct approach. When using the direct approach, the solution is often not flexible, hence why UI.TimeText is lacking. But making exceptions for common use cases is important!

On a related note, I'm happy to hear that pebble.js feels faster! It's because of a trick it uses: it combines packets that happen at the same time into a single App Message / Bluetooth packet. When actions occur at different times, pebble.js can then become as slow as Simply.js. For some reason, a delay is induced for each App Message packet; this is something I want to investigate in the future. I believe this is not limited to pebble.js.

In any case, if the latency is caused by bluetooth, a timer module wouldn't actually be able to solve the problem... unless it does something a little extra!

Working Around the Latency

Earlier I mentioned that implementing (tick timer service -> X action) would require more code for each action X. There are a few ways to shortcut this. One way is to leverage the pebble.js packet protocol it speaks over bluetooth. The protocol already knows how to modify everything on screen; I would just need to write a generic (tick timer service -> process Y packet). I could perhaps schedule it to re-process these packets every tick. There are potential issues with this. Consider this snippet:

var elem = new UI.Rect({ backgroundColor: 'black' });
TickTimerService.on('minute', function(e) {
  elem.backgroundColor(elem.backgroundColor() === 'black' ? 'white' : 'black');
});
  1. Using this trick, this callback would actually be called only once. The instruction packet will then only ever ask elem to turn white, but at the very least this would happen every minute without delay.
  2. When elem is removed from the current window, TickTimerService might need to know in order to not accidentally perform an invalid operation. I think it makes sense that if the user adds elem back in, the color would still change every minute until TickTimerService.off is invoked. I would just need to make sure the elem can be removed and added at will without any adverse effects.

This hypothetical TickTimerService works around the latency problem by sending over the changeset that must take place to the watch well before the change needs to happen. We could implement a timer module using the same strategy. At the expense of surprising the user, I could implement a hypothetical timer module that calls all its callbacks at least 5 seconds earlier than necessary. This would give at least 5 seconds for bluetooth to send over the changeset. The watch would synchronize by using an included timestamp of when the change should actually take place.

To solve the first problem with TickTimerService, I could instead utilize the solution of the hypothetical timer module for every minute, and the backgroundColor would then correctly alternative. The callback would also be called every minute as well, just as expected, so it could potentially be used for fetching live server data.

There still remain some gotchas:

  1. It could be confusing to use as it adds an extra dimension to asynchronous operations. Users might be surprised to find out their callback is called moments earlier. Also, if one uses ajax in one of these callbacks for example, they may need to increase how early the callback is called above 5 seconds because of the extra latency the remote server may add.
  2. The second hypothetical version of TickTimerService would incur bluetooth communication every minute throughout the day. While this would enable awesome functionality, I would need to characterize the battery usage of this before promoting this.
  3. You may have already realized this solution may not solve the every second case since it is still sending data over bluetooth. Every second also compounds the power usage problem. Using bluetooth drains both the phone and watch battery. It's primarily why pebble.js is currently recommended for internet related apps, but I'm keen on pushing the boundaries.

Thanks for bring this issue up, it has gotten me to think of this particular solution, battery power aside. If this was actually your suggestion all along, you're way ahead of me, and sorry for the long response! There still remain other possible solutions if this one is lacking. And, of course, this issue is still open for discussion.

from pebblejs.

samuelmr avatar samuelmr commented on July 23, 2024

First of all, I find your responses very clear and thoughtful. Absolutely nothing to apologize for.

Obviously, sending stuff beforehand isn't really a solution for e.g. a stopwatch. ("Press the SELECT button 5 seconds before the race starts and again 5 seconds before the winner finishes.") But then again, timing races is not a good use case, because of the messaging lag.

There are use cases where it would work. My original thinking was to minimize the messaging between the phone and the watch. (The examples like displaying the TimerValue and doing a vibe wouldn't require anything from the phone, right?) And yes, this would mean (C code) logic on the watch.

Originally I thought that the things you could do with t.on() would be limited to simple instructions (like e.g. vibe). Things that you could just tell the watch once. Not complex graphics and certainly nothing more than things that are already possible with PebbjeJS. I wasn't thinking too much about the actual implementation, but the basic idea was to instruct the watch once: "start doing this every second" or "after 5 seconds, do this". I kind of thought that the callbacks would be already called beforehand on the phone (when t.start() was called), the results would be "pre-packaged" and sent to the watch to be executed/displayed at a certain time. (Like your first trick above with the background change.)

In retrospect, the t.on() callback functionality in my proposal seems quite silly. The idea was to minimize messaging between the phone and the watch. I first thought of displaying a simple counter counting upwards or downwards, which would be quite powerful for many use cases that PebbleJS doesn't solve at the moment. When I was thinking about subscribing to the TickTimerService, I got the idea that a timer could be a lot more than a simple counter. What if a (silly) requirement was to alternate between the shown words "left" and "right" once a second for 15 seconds and then between "up" and "down" once a second for another 15 seconds?

Calling the callbacks early could be a solution for callbacks, but I'm not sure it would offer enough additional value over setInterval and setTimeout. And it would be a lot more confusing. And it wouldn't really solve the once-a-second case.

Sorry if I'm just making you more confused. I think you have already grasped the idea quite well. I'm confident that something useful will eventually grow from the seeds sown. My suggestion would be to start with a simple text field that could display a counter counting up from zero or down from a specified value. But you're more than welcome to plan more generic and ambitious solutions.

from pebblejs.

Meiguro avatar Meiguro commented on July 23, 2024

I apologized because I realized my first answer didn't completely explain the problem, having forced you to test setInterval, but now it looks like we're on the same page!

For a counting text field, I believe the need vs code space ratio justifies a new element. Maybe the name can go against the grain of suffixes and be UI.TimeCounter. It'll start off with a small set of format codes, something along the lines of %h,m,s.

About the timer callbacks solution earlier, I agree, calling callbacks early would be confusing. Perhaps just the future scheduling can be factored out of that idea, yielding a function such as setFuture(timestamp, callback) where the callback is called immediately and the resulting pebble.js packet is scheduled to execute exactly at the given timestamp with time guarantees only if the timestamp given is far enough in the future to compensate for delay. This should be a less confusing building block for making synchronized minute resolution events.

The tricky part is supporting watchfaces that change every second, such as the very simple blinking colon. I think I have to unfortunately compromise and come up with an idea that's limited in some way. Here's one, a static time cycle:

var cycle = new TimeCycle();
cycle
  .at(1000, 'static', function() { elem.backgroundColor('black'); })
  .at(2000, 'static', function() { elem.backgroundColor('white'); })
  .begin();

// This sets up the following time cycle:
// [ . . . . . . | . . . . . . |] repeat
// 0             1s            2s
//                \ turn black  \ turn white

As soon as .begin() is called, all keyframe callbacks are called, generating the pebble.js packets to be wrapped in a time cycle. The entire cycle is sent to the watch, and the watch would execute the cycle indefinitely without any further bluetooth communication. If all specified times are a factor of a second, minute, or hour (1000, 60000, 360000), then it'll utilize the C tick timer service with the smallest factor to minimize power usage since app timers incur additional overhead. In this example, the background alternates every second. The limitation of course is that it's static; the resulting cycle cannot be a function of a changing input.

If a user truly requires a dynamic cycle, they can set a certain keyframe to be dynamic. Dynamic keyframes would be called only when it's their turn in the cycle, not immediately when .begin() is called. This comes with all the nasty latency issues, essentially turning it into a fancy setInterval, but it's there just in case someone needs that power tradeoff. If they combine it with setFuture, then the latency can be hidden, but obviously only for minute resolution or higher.

I can think of the built-in watchface 'Text Watch,' as an example of a complicated time cycle. If that time cycle would result in under 4KB instructions, then this solution could work. That time cycle in particular would be programmatically generated.

from pebblejs.

samuelmr avatar samuelmr commented on July 23, 2024

👍

from pebblejs.

samuelmr avatar samuelmr commented on July 23, 2024

Do you have to assume it's a cycle?

If I just want a vibe every second for five seconds starting at 55 seconds from now?

Perhaps you could have a .repeat() method? (Optionally specifying how many times the cycle should repeat?)

from pebblejs.

Meiguro avatar Meiguro commented on July 23, 2024

I was thinking the same thing, except that I didn't come up with a good method name. I will have to think about the overall class name, how you would specify repeating or not, how many repeats, and whether that method would also immediately begin it. .repeat([count]) is a good suggestion for the current format. There will also be .end() which aborts it immediately no matter what stage it's at.

from pebblejs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.