Thursday, November 19, 2020

My Toothbrush Timer: Adding Initial Functionality

I've made progress on this app over the past few weeks, despite only being able to work on it now and then. As usual, my progress in building the app is outpacing my blogging about its construction. So, this blog post is at least the first of two in an effort to catch up. Here I give a report of the design of the app as of about two weeks ago.

Here is a screenshot of the app while running:

One obvious thing I need to work on is showing the time elapsed in the format of minutes:seconds. Using the example of brushing my teeth for 2 minutes, these intervals alert me half-way through (to switch from lower to upper teeth), then a warning 50 seconds later that I only have 10 seconds left, followed by the 2 minute bell. Really, this last alert is not necessary because the toothbrush I use just turns off automatically at 2 minutes. If I wanted to switch between lower and upper every 30 seconds, then just one interval setting of 30 would be needed. In the example of giving myself alerts while making a 12-minute conference presentation - a typical time limit - I would likely add alerts at intervals of 6 minutes (half-way point), 4 minutes (the 10-minute mark), 1 minute (just one minute remaining), and 45 seconds (to give myself 15 seconds to thank everyone and get off the stage). Of course, that would require four intervals, not three, so I would need to add one more interval to the app. (Hint hint: That's a clue about what's coming up in next blog post.)

Telling the Time in LiveCode with the Seconds

Having an accurate timer is fundamental to many applications. The best way to develop a timer in LiveCode is with the function the Seconds. My timer app starts by checking to see what the time is now in seconds. As I write this, it is 7:48:11 am on Wednesday, November 18, 2020. The idea is to make that the starting time when the Start button is pressed. Then, all timers are checking to see how much time has passed since then. So, if the first timer is set to go off 30 seconds later, the time will need to be 7:48:41. But if you think about it, this standard format for telling time is based on a past starting point using an arbitrary system we call the Julian calendar. Today's date and time uses as its starting point the likewise artibrary big "changover" from Before the Common eEa (BCE) to the Common Era (CE), which is just the secular version of BC and AD. The key word there is "arbitrary." Really, any starting point would do as long as we all recognize and accept it as the starting point. In LiveCode, the seconds is based on a similar starting point called the eon, defined in the LiveCode dictionary as follows: 
LiveCode uses midnight, January 1, 1970 as the start of the eon. Date and time functions are computed from that date.
So, the function the seconds gives you the current time in seconds since since very arbitrary starting point. As I write this sentence, the current time, in seconds, according to LiveCode is 1605704460. But now, as I write this sentence the time is 1605704492. So, if you subtract the first from the second you get 32 seconds. That interval is the basis for how my app works.

Doing Two (or More) Things at Once

The important thing to note is that while the timer is running, the app is constantly checking to see if the user presses any buttons. In contrast, many apps work by waiting until the user presses a button to perform a task. The app does the task, then waits for the user to press another button. If the task takes a long time to complete, the user has to wait until the task is completed before any other buttons can be pressed. Doing one thing at a time versus doing multiple things at the same time may sound like a subtle difference, but it requires a special programming technique to pull this off.

Here is the code for the app's engine called MainEventLoop, a procedure I wrote to run everything in the background while the user chooses timing intervals and clicks buttons to stop and stop the app's timer:

1:  on MainEventLoop  
2:    if varRunMainLoop is true then  
3:     lock screen  
4:     put the seconds into varSecondsEnd  
5:     LloydStopWatch  
6:     unlock screen  
7:    end if  
8:    put the pendingMessages into tMessages   
9:    repeat for each line aLine in tMessages   
10:     if item 3 of aLine is "mainEventLoop" then cancel item 1 of aLine   
11:    end repeat    
12:    send "mainEventLoop" to me in 50 milliseconds  
13:  end MainEventLoop  
15:  on LloydStopWatch    
      The magical code goes here
16:  end LloydStopWatch  

A procedure called MainEventLoop just runs and runs by looping forever while the app is running. This works by having it "call itself" in line 12 in a very short amount of time (i.e. 50 milliseconds). I know, that's a very weird idea. It's usually very hard for new programmers to grasp. I know it was for me. In that short amount of time, it completes any assigned task while "watching" to see if the user does something, such as clicking a button. There is nothing special about 50 ms. It could be called in one second, but 50 ms is plenty of time for the app to do its work.

Another procedure, LloydStopWatch, has all of the code that actually computes how many seconds have transpired since starting the timer. This code also keeps track of the intervals and knows when play a chime at the end of each interval. Note that line 5 is where the LloydStopWatch procedure is triggered, but only if the variable varRunMainLoop is true. This variable controls whether the timer is active. If the user clicks the Start button it becomes true. If the user clicks the Stop button, it switches to false.

So, how does one get MainEventLoop to start running. The answer is in the code for the button "Start:"

1:  on mouseup  
2:    put 0 into varTimeCurrentLoop  
3:    put 0 into varIntervalTotal  
4:    put 1 into varIntervalTurn  
5:    put false into varRestartInterval  
6:    put empty into field "notes"  
7:    put empty into field "Interval Totals"  
8:    put empty into varTimeLog  
9:    put field "Iminutes1" into varIminutes1  
10:    put field "Iseconds1" into varIseconds1  
11:    put field "Iminutes2" into varIminutes2  
12:    put field "Iseconds2" into varIseconds2  
13:    put field "Iminutes3" into varIminutes3  
14:    put field "Iseconds3" into varIseconds3  
15:    put true into varRunMainLoop  
16:    put the seconds into varSecondsBegin  
17:    put varSecondsBegin into varSecondsBeginInterval  
18:    MainEventLoop  
19:  end mouseup  

Lines 2-18 initiate all of the main fields and variables, essentially getting them ready for the start of a new timing session. Line 15 sets the variable varRunMainLoop to true and line 18 initiates the MainEventLoop procedure.

Lines 9-14 takes whatever interval values the user had entered and passes them on to unique variables for each for use within the LloydStopWatch procedure.

Line 16 essentially checks the current time and becomes the moment in time from which the time elapsed is figured. This value is held by the variable varSecondsBegin. Line 4 in the MainEventLoop procedure continually checks the current time, puts it in a variable named varSecondsEnd, then calls the LloydStopWatch procedure. The time elapsed in seconds is simply the difference between these two values. This simple subtraction problem is happening every 50 ms.

Interestingly, I first put the line "put the seconds into varSecondsEnd" into the LloydStopWatch procedure. But, I experienced some weirdness in the operation of the app. After the app had been running for about 10 minutes, the app started skipping a bunch of seconds which, of course, ruined the reliability of the app. I really couldn't figure out why. I tried all sorts of programming hacks, but none worked. So, in a bit of desperation, I moved this line to the MainEventLoop procedure and it solved the problem. All I can say I had a hunch it would work. I think the problem had something to do with the recursive nature of this MainEventLoop procedure. I've experienced weirdness in this coding technique in previous apps. I actually asked for help in one of the LiveCode forums a couple of years ago about this. Lines 8-11 and the lock/unlock screen code were suggested to me by an expert LiveCode user which solved the problem for me back then. So, I've continued to use this code anytime I use this looping technique. In short, you start building up a backlog of calls for the MainEventLoop procedure and eventually all hell breaks loose. Lines 8-11 clear out all previous calls for the procedure.

Finally, the timer stops when the user presses the "Stop" button - here is the code for the Stop button:

1:  on mouseup  
2:    put false into varRunMainLoop  
3:  end mouseup  

By changing the value of varRunMainLoop to false, it stops the loop within MainEventLoop that had been triggering the LloydStopWatch procedure.

Short List of Things to Do

Actually, the list is quite long, but here are some of the obvious revisions and updates that are needed:

  • Add a fourth timer interval
  • Change the elapsed time format
  • Allow the user to pause the timer
  • Add a choice of alert sounds
  • Add a mobile interface design, including spinner wheels to select time intervals and a vibrate option for alerts

No comments:

Post a Comment