Timerd is a daemon that connects to Autobus, much like speakd does. In fact, timerd uses speakd to perform timer announcements. Timerd can still function without a working instance of speakd, however, and in this mode it will show notifications across all computers connected to it but it won't send any notifications to speakd. (If speakd were to connect to the bus later on, timerd would immediately start sending notifications to it again.)

In contrast to VocalClock, which is being rewritten as timerd, speakd, and saytime (and Autobus, although I'm considering that a separate project), the timer list is dynamic, meaning that timers can be added and removed as needed.

Each timer has a name, which can be changed as needed. Each timer has a number of seconds associated with it, and it's up to the client application to figure out the corresponding number of minutes and hours (although timerd provides utility functions for doing this). Each timer also has a current state, which is 1, 2, or 3, for counting up, counting down, and stopped, respectively.

Timers also have a value n such that the timer's current value will be announced when the timer's minute field modulo n is equal to zero. Timerd also provides a function that will instruct a timer to announce itself when the function is called.

Timerd makes available an object containing the state of all timers. This object is updated only when a timer changes state (which includes when a timer goes off; such a timer transitions from counting up or counting down to stopped) or value or other information. Another object is made available that updates once every second and contains the number of seconds since timerd started up (which is kept in sync with the system clock, so it might skip every now and then due to delays with the object being updated). Timer durations present in the first object are specified relative to the startup time of timerd, which allows all of the timer's current values to be calculated without having to rebroadcast the state of every timer every single second.

Timer values are modified via two different functions: set_time and update_time. The former sets the time, in seconds, on a particular timer to an absolute value. The latter adds a particular value to the timer's current value, truncating the value at 0 if the resulting value ends up being negative. The latter function is generally preferred for mutator applications such as up and down buttons on a client, as multiple clients clicking the up or down button repeatedly will be treated correctly and have the corresponding number of seconds added or subtracted correctly. If a value is set to a negative number via either function, it will be truncated at zero and appropriate action for the timer taken (for example, if the timer is counting down, it will be set to stopped and an announcement made over speakd if announce-on-trigger is enabled for that timer).

On rare occasion, due to update intervals between the object containing the information for all of the timers and the object containing the number of seconds since timerd started up, timer values may appear to briefly go negative to a client. Such values should be treated (and displayed) as if they were actually zero.

A timer counting down has its zero time set to be the number of seconds that timerd will have been running when the timer should reach zero. A timer counting up has its zero time set to be the number of seconds that timerd would have been running when the timer started off at zero, which may be negative if the timer has been manually set to have a number of seconds greater than the number of seconds since timerd started up. Clients should handle this accordingly.

Two objects, timers and startup, are present on the timer interface, which are the list of timers (as a list of maps) and the seconds since timerd startup, respectively.

There are two functions, set_time and update_time, as discussed above, that are used for setting time. create is used to create a new timer. It returns the number of the timer just created. delete is used to delete a timer with a certain number. Newly-created timers are always assigned the lowest number greater than or equal to 1 not already being used by another timer.

The following additional attributes are available on timers. These can be set by calling the set function, which takes a timer number as its first argument and a map as its second argument, and sets all attributes with keys in the map onto the timer. The attributes are:

	state: The state of the timer, as discussed above. This is 1, 2, or 3 for counting up, counting down, and stopped, respectively.
	
	announce_interval: A number n such that the timer's current time will be announced when the equation (s%3600/60)%n=0 becomes true, where s is equal to the time remaining on the timer, in seconds. In other words, when the number of minutes that a user would see were they to view the timer's remaining time as hours, minutes, and seconds becomes a multiple of n, the timer's current time will be announced. If this is zero, the timer will not be announced at regular intervals.
	
	name: A name for the timer. This is purely informative; multiple timers could theoretically share the same name.
	
	announce_count: The number of times "Timer n is beeping" should be said when the timer's value reaches zero. If this is 0, the timer will not be announced. A call to the dismiss function will preemptively stop a timer from announcing itself this many times. TODO: figure out when to add the events to speakd so that other things can be said at the same time, too.
	
	anounce_on_state_change: True to announce the timer's current time and state whenever the timer transitions state. This will not cause any announcement to be made if the state transition is as a result of the timer reaching zero.
	
Note that changes to the state attribute are processed after changes to all other attributes. As a result, setting announce_on_state_change to true when it was previously false and changing the timer's state in the same function call will cause the state change to be announced.

I can't remember if I previously mentioned this or not, but when a timer that is counting down reaches zero, the timer's state is set to stopped and the actions mentioned in the attribute list above are taken. In the future, the timer system will tie in with the to-be-written action framework, which will allow actions to be fired when a timer reaches zero or when the timer would be announced as per its announce_interval (and there may very well end up being multiple announce_intervals, if requested, and each one could trigger a different action). This still needs some thought.

Now, to implementation details. I'm thinking most, if not all, of the timerd functions should run in separate threads but should all synchronize on a common object.

So, I just thought of a problem. Storing timers as offsets from timerd's startup is great for timers that are counting up or counting down, but it doesn't work for timers that are stopped. I'm thinking that the time will be stored as an offset for timers that are counting up or counting down and as an absolute value in seconds for timers that are stopped. That should solve the problem.

So, timers are stored internally in the same fashion. The number of seconds that have elapsed since timerd's startup is stored as a global var. So is the precise time (as a datetime object) that timerd started up, and the time since startup is calculated from this once per second. This may cause all timers to jump two seconds at a time on occasion; I may fix that later by having timerd only sleep precisely the number of microseconds needed to advance to the next second.

The day part of the interval resulting from the calculation of the startup time is multiplied by 86400 and then added to the number of seconds. This will cause all of the timers to skip by one second when a day with a leap second occurs, but I don't consider one second out of every few months (if not longer) to be worth correcting at the moment.

Once this thread has calculated the new time, it scans through all the timers and finds those that have a (calculated) absolute value of zero or less that are counting down. These timers have their state transitioned to stopped, their absolute value set to zero, and appropriate actions taken. Then, the thread checks for any timers with an absolute value that is negative. These are set to have a value of zero. Then the thread checks to see if any of the timers have their (calculated) second fields equal to zero and their minute fields satisfying their announcement equation; if this is true, those timers are put on a list to announce. Once all of this has taken place, timerd then publishes the changes to the global timer object if it changed. It then sends all phrases to announce to speakd, if speakd is available. (If it isn't, timerd relies on the Autobus server throwing an exception on function call to detect this and will silently ignore the exception.)

When a method call comes in that directly sets the state to a different value than it's currently at on a timer set to announce state changes, timerd immediately forwards that over to speakd. Indirectly changing the state (which, right now, can only happen by manually setting a timer's value to zero) does not cause such an action; the cycle thread (the thread that runs once per second) will be the one that eventually causes the state change.

Incoming values that change a timer's absolute value to be negative are accepted and stored as perfectly valid. The next thread cycle will cause the timer's value to be set to zero, as mentioned above. It's up to clients to determine if the apparent absolute value is less than zero and translate it to zero before displaying it to the user.

All of the functions that cause changes to the timer object cause the object to be immediately and synchronously broadcast. Because of this, it's generally a wise idea to invoke such functions asynchronously on the client side to avoid deadlock if any object listeners try to acquire locks that code invoking these functions runs on. This is notoriously easy to do when calling these functions from the Java Swing event dispatch thread; having an object listener that synchronously updates the UI with the new timer values on the EDT and having an ActionListener on a button that calls a timer-changing function synchronously will cause the EDT to deadlock when the button is clicked, which will cause the application's UI to freeze up until it is forcibly killed.
























 