Timer with increasing frequency

Someone requested a script that checked a database value over a period of time. The more time that passed, the more frequently the script had to check the value. For example, for the first 10 minutes check every 20 seconds, for the next ten minutes check every 10 seconds. The user needed to be alerted the first time the value did not match the desired value and the script needed to time out after a while if the values did not match during the defined period.
Here is my solution:

set timeoutUser to 5 -- minutes until script times out
set intervals to {8, 6, 4, 2} -- seconds
set tiers to {1, 2, 3, 525949} -- minutes
set timeoutScript to (timeoutUser * 60)
set myTrigger to false -- condition matched through database query
set time0 to (current date)
set intervalN to 1

repeat
	set waitTime to (current date) - time0
	
	if myTrigger = true then
		exit repeat
		
		-- If myTrigger is not executed, tell user to wait ONCE	
	else if waitTime < item 1 of intervals then
		display dialog "Please wait for myTrigger to return true" buttons {"Cancel"} default button "Cancel" giving up after 3
		
		-- 	myTrigger never executed, tell user script timed out
	else if waitTime ≥ timeoutScript then
		display dialog "Timed out" buttons {"Cancel"} default button "Cancel"
	end if
	
	-- Delay logic
	if waitTime / 60 > item intervalN of tiers then
		set intervalN to intervalN + 1
	end if
	delay item intervalN of intervals
	display dialog (item intervalN of intervals) buttons {"Cancel"} default button "Cancel" giving up after 1
end repeat

display dialog "MATCH!" buttons {"Cancel", "OK"} default button "OK"

I thought an interesting idea for a script would be:
Given a period and min & max frequency values, have the script increase frequency in proportion to the remaining time. If anyone is interested, I’d like to see some of your ideas.

Hi, adayzdone.

For “watch” situations over prolonged periods of time, you should check out ‘idle’ handlers. (As it happens, I’ve just posted a script containing one here.)

Hi Nigel,

I am not clear about idle handler best practices. If the structure of your script is:

(* 
Do stuff 1 time
"watch" situation over prolonged period of time
Do stuff 1 time
*)

Is a stay open application the best way to proceed? How do you signal exit the handler? Quit the application? Thanks.

Until I implement Nigel’s suggestion, here is a script that increases frequency in proportion to the remaining time.

set timeoutUser to 1 -- minutes until script times out
set {maxInterval, minInterval} to {10, 1}  -- seconds between tests
set timeoutScript to timeoutUser * minutes
set myTrigger to false -- condition matched through database query
set time0 to (current date)

repeat
	set waitTime to (current date) - time0
	
	if myTrigger = true then
		exit repeat
		
		-- If myTrigger is not executed, tell user to wait ONCE    
	else if waitTime < maxInterval then
		display dialog "Please wait for myTrigger to return true" buttons {"Cancel"} default button "Cancel" giving up after 3
		
		--    myTrigger never executed, tell user script timed out
	else if waitTime ≥ timeoutScript then
		display dialog "Timed out" buttons {"Cancel"} default button "Cancel"
	end if
	
	-- Delay logic
	set myDelay to ((timeoutScript - waitTime) / timeoutScript) * maxInterval
	if myDelay < minInterval then set myDelay to minInterval
	
	delay myDelay
	display dialog myDelay buttons {"Cancel"} default button "Cancel" giving up after 1
end repeat

display dialog "MATCH!" buttons {"Cancel", "OK"} default button "OK"

Hi.

It has to be a stay-open application in order to use an ‘idle’ handler. The idea is that instead of having a script (run by some application) looping interminably and executing a ‘delay’ command for much of that time, you have an applet which sits there doing precisely nothing until it receives an ‘idle’ event. It executes its ‘idle’ handler once in response and then goes back to doing nothing. The handler should return a number when it exits. If it was the operating system which sent the event (it usually is), it’ll interpret the number as the number of seconds to wait before sending another such event to the applet.

Any initialisation can be done in the applet’s ‘run’ handler, which of course only responds to the initial ‘run’ event.

on run
	-- Any initial stuff here.
end run

on idle -- Executed whenever the applet receives an 'idle' event.
	-- Look at the situation now.
	-- Act or not as required.	
	-- return a number telling the system how many seconds to wait before sending the applet another 'idle' event.
end idle

Is an idle event any event you declare within an if statement in idle handler or something else?

Would you provide an example of an event not sent by the operating system (meaning return 3 won’t wait 3 seconds?)

How do you work past the condition in the idle handler once it is satisfied to the rest of the script? For example, how would you say 4 after 3 below?

property idleCount : 0
global idleEvent

on run
	-- Do stuff
	set idleEvent to true
	say 4 -- continue rest of the script after condition in idle handler is met
end run

on idle
	if idleEvent = true then -- A condition to test in the middle of the script
		set idleCount to idleCount + 1
		say idleCount
		if idleCount = 3 then set idleEvent to false
		return 1
	end if
end idle


Sorry. I still haven’t made myself clear. They get sent anyway. This from the AppleScript Language Guide:

So when the script application receives an ‘idle’ command, it executes its ‘idle’ handler. You don’t have to do anything about that.

Well another script could theoretically tell the script application to ‘idle’. The ‘idle’ handler would then execute and the other script would receive whatever’s returned. The other script might not be written to respond to this in the same way as the system. (This wouldn’t affect the ‘idle’ commands being sent anyway by the system.)

The idle handler is the rest of the script. Saying “4” would have to be written into it.

A quick’n’dirty adaptation of your script at the top of this thread would be:

global timeoutUser, intervals, tiers, timeoutScript, myTrigger, time0, intervalN, waitTime

on run
	set timeoutUser to 5 -- minutes until script times out
	set intervals to {8, 6, 4, 2} -- seconds
	set tiers to {1, 2, 3, 525949} -- minutes
	set timeoutScript to (timeoutUser * 60)
	set myTrigger to false -- condition matched through database query
	set time0 to (current date)
	set intervalN to 1
end run

on idle
	set waitTime to (current date) - time0
	try
		if (waitTime > 2) then display dialog (item intervalN of intervals) buttons {"Cancel"} default button "Cancel" giving up after 1
		
		if (myTrigger) then
			display dialog "MATCH!" buttons {"Cancel", "OK"} default button "OK"
			error number -128
			
			-- If myTrigger is not executed, tell user to wait ONCE	
		else if waitTime < item 1 of intervals then
			display dialog "Please wait for myTrigger to return true" buttons {"Cancel"} default button "Cancel" giving up after 3
			
			-- 	myTrigger never executed, tell user script timed out
		else if waitTime ≥ timeoutScript then
			display dialog "Timed out" buttons {"Cancel"} default button "Cancel"
		end if
		
		-- Delay logic
		if waitTime / 60 > item intervalN of tiers then
			set intervalN to intervalN + 1
		end if
	on error number -128
		-- A "Cancel" button has been clicked above or this is the deliberate error after the "Match!" dialog.
		quit -- Quit on the next idle.
		return 1 -- Please send the next idle as soon as possible.
	end try
	
	return item intervalN of intervals
end idle

Thank you Nigel for taking the time to explain that. I’m sure it will be useful to many of the other users as well.