How to define a timer in Indigo 2

Posted on
Sun Jan 14, 2007 10:33 am
macpro offline
User avatar
Posts: 765
Joined: Dec 29, 2005
Location: Third byte on the right

How to define a timer in Indigo 2

Here's a story about how I solved a problem with delays and action groups. It starts with a boring explanation on the problem itself and then introduces the concept of timers, followed by the code needed to implement them.

Boring explanation
I'm using a couple of motion sensors to switch lights on when it's dark. Switching lights off happens when the motion sensor says there is no motion anymore.
Sounds simple, but it isn't.
I use a W800RF32 which gives dozens of "undefined byte from w800rf interface" per day. And sometimes these messages appear instead of the message from a motion sensor saying there is no motion. So Indigo doesn't get this info and the lights will not go off.
Besides that, I don't let the motion sensor switch off the light. I let it execute an action group instead. This action group switches off the light and does some other stuff.
The problem is that I want to be flexible in the timing before lights go out. So I used the delay option for the Action Group to increase the time it actually fires. (Yes, I could change the timing in the motion detector, but that is way too much work.)
Adding a delay to an action group is a nice feature, but unfortunately, it is not possible to remove delayed actions for an action group. That part only works for devices and triggers.
So once the delayed action group is set, it will go off. No matter what I do. This means lights will go out, even if new motion has been detected.

What can be done to solve this?
Then I started thinking about a solution. What I really need is an independent "something" that gets started when there is motion and stops when there hasn't been motion for a certain period of time. Whenever there is new motion, the "something" has to start again. And only when the motion really has stopped, it should execute an action group.

This "something" I need is actually a timer. When motion is detected, it gets set at a desired timeframe (in seconds). A background process decreases the timer and when it reaches 0, the action group is fired. When the motion sensor detects motion again, the timer is reset to it's start value.

This solution solves two problems:
1. I don't need the motion sensor to send a "no motion signal" anymore, so the W800RF32 errors won't interfere with my system.
2. I'm capable of changing the timer length without manually changing the timer setting in the motion sensor itself.

What is needed to implement a Timer?
To get a timer working in Indigo, you need this:
1. A variable "Timer<name>" that will hold the current timer value.
2. A trigger that sets the variable "Timer<name>" to it's start value whenever motion is detected.
3. A trigger that fires an action group when the variable "Timer<name>" reaches 0
4. A background task that decreases the value of "Timer<name>"
5. A trigger that starts this background task when the Indigo Server starts.

Steps 1, 2 and 3 are basic Indigo stuff, so I won't go into detail on them.
Step 4 is the fun part: the background task.

It's a piece of AppleScript which simply does the following:

Get all variables from Indigo whose names begin with "Timer".
If the value of such a variable is bigger than 0, decrease it by 15 seconds.
After all variables have been decreased, wait 15 seconds.

Repeat these steps forever.

So here's the code for the timer script.
To use it, copy it into the ScriptEditor and save it in Indigo's backgroud task folder.
(/Library/Application Support/Perceptive Automation/Indigo 2/Scripts/Background Tasks)

Code: Select all
--
-- Retrieve Timer variables from Indigo and count them down.
--
-- Paul Roomberg
--
-- Version  Date               Info
-- -------    ---------------   --------------------------------------------------------------------
-- 1.0        15-12-2006   First version
-- ------------------------------------------------------------------------------------------------
--
property timerRefreshRate : 15 -- refresh Timer variables every N seconds
property timerTimeOut : 5 -- Time Out value
property theHour : -1 -- Save the current hour

-- Count Down for one particular timer
-- Only if its value is positive, because negative or zero
-- means to not use it.
-- But when a timer counts down below zero, fix it at zero.
on doTimer(theTimer)
   try
      with timeout of timerTimeOut seconds
         tell application "IndigoServer"
            
            set theValue to value of variable theTimer
            if theValue > 0 then
               set theValue to theValue - timerRefreshRate
               if theValue < 0 then
                  set theValue to 0
               end if
               set value of variable theTimer to theValue
            end if
         end tell
      end timeout
   on error number errNum
      if errNum is -1712 then
         tell application "IndigoServer"
            log "Timer '" + theTimer + "' time out occured" using type "error"
         end tell
      else
         tell application "IndigoServer"
            log "Timer '" + theTimer + "' error occured: " & errNum using type "error"
         end tell
      end if
   end try
end doTimer

-- Timer Main Loop
repeat
   try
      -- Timer Loop
      repeat
         try
            with timeout of timerTimeOut seconds
               
               tell application "IndigoServer"
                  
                  -- Show a heartbeat every hour
                  if not (the hours of the (current date)) = theHour then
                     set theHour to the hours of the (current date)
                     log "Timer script heartbeat" using type "info"
                  end if
                  
                  -- Fetch all timer variables and process them
                  repeat with aVar in variables
                     set theTimerName to the name of aVar
                     if theTimerName begins with "Timer" then
                        my doTimer(theTimerName)
                     end if
                  end repeat
               end tell
            end timeout
         on error number errNum
            if errNum is -1712 then
               tell application "IndigoServer"
                  log "Timer loop time out occured" using type "error"
               end tell
            else
               tell application "IndigoServer"
                  log "Timer loop error occured: " & errNum using type "error"
               end tell
            end if
         end try
         
         -- Wait till the next run
         delay timerRefreshRate
      end repeat
      
      -- This is the most important part of this script.
      -- Somehow the inner loop dies on a time out after a
      -- period of time. I have no clue why this happens,
      -- but adding an outer loop with timeout detection
      -- solves this.
   on error number errNum
      if errNum is -1712 then
         tell application "IndigoServer"
            log "Timer main loop time out occured" using type "error"
         end tell
      else
         tell application "IndigoServer"
            log "Timer main loop error occured: " & errNum using type "error"
         end tell
      end if
   end try
   
end repeat


This script needs to be started whenever the Indigo server is started. To get this done, define a new trigger "Indigo Server Startup" with a type "Indigo Server Startup". No condition needed, always fire this trigger.
And the action is to execute AppleScript in a file. Select the Timer script from above that you placed in the Background Tasks folder.
To start the script, you can restart the Indigo server or just execute the trigger.

Testing the script is easy: define a variable "Timer<something>" and give it a value of "40". If everything works well, you'll see the variable go to "25", then "10" and finally "0".

You can define as many timers as you want, as long as the name begins with "Timer". All you have to do to add a new timer is to define a variable "Timer<newname>", a trigger that sets the start value and a trigger that executes when "Timer<newname>" reaches 0.

I've been using timers for about a month by now and it is working ok. No more wondering if the lights will go out. They will. And they will only go out when there has been no more motion for a while.

The only strange problem I had was the timer script stopping without a reason. After debugging I found I needed an extra loop in the AppleScript code, as you can see in the code above. If anyone knows why this is happening, please let me know.

Enjoy!
Last edited by macpro on Sun Aug 05, 2007 9:23 am, edited 1 time in total.

Posted on
Thu Jun 21, 2007 11:15 am
wikner offline
Posts: 181
Joined: Nov 02, 2003

Try built-in timers

Indigo's time-date actions can be used as timers. They can be created or destroyed on the fly. You just have to worry about their names (make sure they are unique).
Code: Select all
on SetTimer()
   local myDelay, a
   tell application "IndigoServer"
      set myDelay to value of variable "My_Delay"
      -- don't need to convert to integer as it is just being poked back into the action, below
      if not (exists time date action "_MyTimer") then
         set a to make new time date action with properties ¬
            {name:"_MyTimer", time trigger type:countdown, countdown delta:myDelay, auto delete:true, enabled:true}
         tell first action step of a
            set action type to executeScript
            set script code to "MyTimeout()"
         end tell
      end if
   end tell
end SetTimer


Then elsewhere you can have...
Code: Select all
on MyTimeout()
-- do whatever you need on timeout
end MyTimeout

Posted on
Thu Jun 21, 2007 2:43 pm
macpro offline
User avatar
Posts: 765
Joined: Dec 29, 2005
Location: Third byte on the right

Re: Try built-in timers

Good to get some feedback on this topic.
The main difference between your approach and mine is that I have provided a generic solution that needs no coding to add new timers.
Do you think you can change your solution so it is reusable for multiple timers?

wikner wrote:
Indigo's time-date actions can be used as timers. They can be created or destroyed on the fly. You just have to worry about their names (make sure they are unique).

But what if you need to restart an already running timer?
How would you do that in your solution?

wikner wrote:
Then elsewhere you can have...
Code: Select all
on MyTimeout()
-- do whatever you need on timeout
end MyTimeout

The drawback of this approach is that you need to write your action in AppleScript. And you need a piece of code for every timer you define.
By using a variable, I can define a trigger in Indigo that acts upon the change of value of that variable and use standard Indigo features to react.

(By the way: this is not ment as criticism to your solution. Just curious how you would enhance it so it meets the requirements that I wrote in my "boring explanation".)

Posted on
Wed Aug 01, 2007 11:27 am
macpro offline
User avatar
Posts: 765
Joined: Dec 29, 2005
Location: Third byte on the right

(No subject)

Kevin asked if I could give more details on the first steps for setting up timers. So here's an example for setting up a timer in Indigo, after you have configured your Indigo environment for timers as described above.

Let's say the lights and jacuzi in the garden should stay on as long as there is some movement in the garden. 15 minutes after the last movement, an action group should fire that switches of the lights and jacuzi.

Step 1.
Define a variable in Indigo. Let's call it TimerGarden and give it a value of 0.

Image
Defining a variable with a name that starts with "Timer" is all you need to do to create a new timer.

Step 2.
Define a trigger that's fires whenever the motion detector MDGarden is activated.
This trigger sets the value of TimerGarden to 900. That's 15 minutes specified in seconds.

Image
Image
Image
Don't worry about conditions for this trigger. Just let it fire every time motion is detected and it wil reset TimerGarden back to 15 minutes.

Step 3.
Define a trigger that fires when the variable TimerGarden reaches 0. As soon as that happens, there hasn't been any motion in the garden for 15 minutes. And then it's time to execute the action group that will turn of the lights and jacuzi.

Image
Image
Image
I've left the details for the action group out of this example. You can imagine that this action group would switch at least two devices off.

That's all you need to do to get a new timer working.
The process of decreasing the variable TimerGarden is done in the background. So you don't have to worry about that.

Posted on
Wed Aug 01, 2007 7:59 pm
matt (support) offline
Site Admin
User avatar
Posts: 21411
Joined: Jan 27, 2003
Location: Texas

(No subject)

Great. Step-by-step tutorial complete with screen shots. Thanks!

Matt

Posted on
Fri Aug 03, 2007 6:51 am
Terry offline
Posts: 44
Joined: Apr 24, 2005
Location: Essex Junction, Vermont

(No subject)

Edit: I posted a stupid question without reading the whole thread.

Terry

I started off with nothing...I still have most of it left.

Posted on
Fri Aug 03, 2007 5:20 pm
Cosmuk offline
User avatar
Posts: 115
Joined: Jan 16, 2007
Location: Chicago

(No subject)

If I have a series of time date actions for select times,
I can enable or disable them from the client,
Thru a control page button that will run an applescript to enable or disable the particular times to run or not run the selected TDA.

The question I have is:

How can I post on the control page that it is enabled or disabled?
Say, as a variable image dot?

Skype : cosmuk

Posted on
Sat Aug 04, 2007 10:10 am
matt (support) offline
Site Admin
User avatar
Posts: 21411
Joined: Jan 27, 2003
Location: Texas

(No subject)

Cosmuk wrote:
How can I post on the control page that it is enabled or disabled? Say, as a variable image dot?

I would create a Time/Date Action that executes every 0.5 minutes (or so), select the "Suppress logging" checkbox, and have it run an AppleScript that pushes the Trigger or Time/Date Action's enable state into a variable. The AppleScript would look something like this (untested!):

Code: Select all
set value of variable "fooEnableState" to enabled of time date action "My Foo Time Date Action"

Regards,
Matt

Posted on
Sun Aug 05, 2007 9:04 am
sgljungholm offline
Posts: 41
Joined: May 17, 2007

Lot of trouble for something simple

Maybe I am reading this post wrong.

Why not simply have a rule that says
if motion from A1 turn on lights, auto off after 5 minutes.

If there is motion within the 5 minutes the timer is reset by indigo again and again.

No motion for 5 minutes and the lights are off.

Posted on
Sun Aug 05, 2007 9:16 am
macpro offline
User avatar
Posts: 765
Joined: Dec 29, 2005
Location: Third byte on the right

(No subject)

If you just want to switch off lights, that approach works.
But if you want to execute action groups when there is no motion after a certain period of time, you'll have to use something else.
That is why I came to this solution.

Posted on
Wed Aug 08, 2007 2:37 pm
macpro offline
User avatar
Posts: 765
Joined: Dec 29, 2005
Location: Third byte on the right

(No subject)

In this topic is a good example where this timer solution is implemented: X10 with Applescript & Indigo

The subject of the topic is to automatically switch off lights after a certain period of time. But with the ability to increase the timer interval via buttons and an automatic decrease of the timer interval after a couple of days.

Posted on
Wed Sep 26, 2007 2:38 pm
rolinster offline
Posts: 20
Joined: Jan 26, 2007
Location: St Augustine, FL

Re: Try built-in timers

wikner wrote:
Indigo's time-date actions can be used as timers. They can be created or destroyed on the fly. You just have to worry about their names (make sure they are unique).
Code: Select all
on SetTimer()
   local myDelay, a
   tell application "IndigoServer"
      set myDelay to value of variable "My_Delay"
      -- don't need to convert to integer as it is just being poked back into the action, below
      if not (exists time date action "_MyTimer") then
         set a to make new time date action with properties ¬
            {name:"_MyTimer", time trigger type:countdown, countdown delta:myDelay, auto delete:true, enabled:true}
         tell first action step of a
            set action type to executeScript
            set script code to "MyTimeout()"
         end tell
      end if
   end tell
end SetTimer


Then elsewhere you can have...
Code: Select all
on MyTimeout()
-- do whatever you need on timeout
end MyTimeout


I have a question about the solution proposed by Wikner. When the time date action is created the "set script code to "MyTimeOut()" appears to requires an applescript string containing code. How do you past in a reference to a function/subroutine? I've attempted to implement your solution and could not get it to work by specifying a function in the "set script code" statement. I had to put the entire code which would have in the MyTimeOut() routine inline.

Thanks,
Rolin

Posted on
Wed Sep 26, 2007 2:47 pm
matt (support) offline
Site Admin
User avatar
Posts: 21411
Joined: Jan 27, 2003
Location: Texas

Re: Try built-in timers

Hi Rolin,

Save a separate script file that has your "on MyTimeout()" function inside the attachments folder:

/Library/Application Support/Perceptive Automation/Indigo 2/Scripts/Attachments/

Then, choose the Scripts->Reload Attachments menu item. Your function, MyTimeout(), will then be loaded into Indigo's main script context and you will be able to call it from a dynamically created Time/Date Action.

Regards,
Matt

Posted on
Sat Oct 04, 2008 11:46 am
ricks offline
Posts: 122
Joined: Nov 11, 2006
Location: Reno, NV

Another approach

I'm building an Applescript that essentially does for Airfoil what the applescript attachment "iTunes sync.scpt" does for iTunes -- namely control Airfoil and sync Indigo variables with Airfoil.

In the process, I needed a timer. I decided to follow wikner's approach above, but with a couple of twists. What I do is pass three variables to SetTimer, a name (which has "_MyTimer" appended), the time to delay, in seconds, and Applescript code that gets inserted into the time date action. So, an example to call the code below:
Code: Select all
SetTimer("dakar", 3600, "AF_SpeakersOFF('dakar')")

In this case, AF_SpeakersOFF is a subroutine that will be called in one hour that is designed to turn off the speaker named dakar. Note my subroutine below converts the single quote to double quote that Applescript will understand.

By individually naming each timer (in this case "dakar_MyTimer") you should be able to easily have multiple timers and be able to access their properties (to display the time left for a counter, for example). If you don't want to pass the Applescript directly, you could implement the MyTimeout subroutine as shown by winkner, but pass the variable name into it, and have if/then statements look for the variable name. For my purposes, passing the applescript was more direct and less complicated.

The code:

Code: Select all
      on SetTimer(delayname, delaytime, ASscriptCode)
         --Use single quotes, will convert to proper token
         set delayname to FindandReplace(" ", "_", delayname) --replace spaces
         set ASscriptCode to FindandReplace("'", "\"", ASscriptCode) --replace single quotes with double quotes
         local a
         tell application "IndigoServer"
            if not (exists time date action (delayname & "_MyTimer")) then
               set a to make new time date action with properties ¬
                  {name:delayname & "_MyTimer", time trigger type:countdown, countdown delta:delaytime, auto delete:true, enabled:true}
               tell first action step of a
                  set action type to executeScript
                  set script code to ASscriptCode
               end tell
            else
               set countdown delta of time date action (delayname & "_MyTimer") to delaytime
            end if
         end tell
      end SetTimer


And the FindReplace Code:

Code: Select all
      on FindandReplace(find, replace, searchin)
         set prevTIDs to text item delimiters of AppleScript
         set text item delimiters of AppleScript to find
         set searchin to text items of searchin
         
         set text item delimiters of AppleScript to replace
         set searchin to "" & searchin
         set text item delimiters of AppleScript to prevTIDs
         return searchin
      end FindandReplace


Oh, and I forgot to mention, if the code is called again while the timer is running, it'll automatically reset the timer. One could modify this to add a set time to the current time, rather than resetting it.

Enjoy!

Posted on
Sun Feb 22, 2009 2:11 pm
bob offline
User avatar
Posts: 500
Joined: Jun 14, 2006

Timer won't reset when motion detected

When using the Timer by wikner;

Code: Select all
on SetTimer()
   local myDelay, a
   tell application "IndigoServer"
      set myDelay to value of variable "My_Delay"
      -- don't need to convert to integer as it is just being poked back into the action, below
      if not (exists time date action "_MyTimer") then
         set a to make new time date action with properties ¬
            {name:"_MyTimer", time trigger type:countdown, countdown delta:myDelay, auto delete:true, enabled:true}
         tell first action step of a
            set action type to executeScript
            set script code to "MyTimeout()"
         end tell
      end if
   end tell
end SetTimer

on MyTimeout()
-- do whatever you need on timeout
end MyTimeout


it will not reset itself to the value of variable "My_Delay" when the timer is triggered again while it is running. From looking at the "next action indicator" in Indigo's main window the timer appears to run out to zero then start again. If motion is detected while the Timer is running the "next action indicator" in Indigo's main window resets to the time remaining on the present timing action. How can I have the Timer reset to to the value of variable "My_Delay" every time motion is detected?

Can the variable "My_Delay" be made to show the countdown in the Variables window?

thanks,

bob

Who is online

Users browsing this forum: No registered users and 2 guests