Jump to content

Recommended Posts

12 hours ago, petrkl12 said:

I have tried your scene it looks that it's still doesn't work fully (restart scene).

I have stoped manualy one of watch scene and I can see this message in that scene every minute:  "Aborting: Server not started yet" and scene is not starting ...

Hi, I have been able to fully debug the ping/pong logic and made another change the original post (needed to wait between doing another 'watchScene')

what I noticed is that when I cut and paste from the forum to the HC2 I get strange characters in the code that the HC2 doesn't like and I just chased a bug where the HC2 Lua compiler ignored a large part of a table initialiser without saying anything because of some strange invisible characters there...

Anyway. Yes because the the scene is not running it logs  "Aborting: Server not started yet" when it gets a ping, and doesn't send a pong. However, after a while when the client doesn't get a pong back it will restart the scene.

Because I just recently started to urlencode/urldecode the payload in fibaro:startScene (to cope with foreign chars), the log statement for startScene was not so nice, so I fixed that to url decode the payload when loging. That's the change in the version I just pushed for iOSLocator and EventRunner.

 

Edited by jgab
Link to post
Share on other sites
  • Replies 2.6k
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular Posts

Note. The first ~2000 posts of this thread is mainly about EventRunner3 that is for the HC2. EventRunner3 is not developed further, but bugs are fixed as they are reported. For HC3, the version i

I've been playing with the HC3 a bit  (I don't own a HC3 but a friend has allowed me to remotely login to do testing - I'm very grateful for that). ...and I have made some progress with EventRunn

Working with event runner rules it can be useful to understand a bit how they work behind the "hood". Rules are always on the form   <test> => <statments> Ex 88:breached

Posted Images

3 hours ago, petrkl12 said:

Another question:

 

I have a lot of rules that use timers:

 rule("@@hh:mm") 

it means that after restart all that rules run in one time at the beginning - impact is big load i.e. during Fibaro restart or scene restart

 

Is there possibility to add some parameter that first run will be after end of first interval?

 

Ok, a bit of a hack but...

Rule.eval("@@-00:10 => log('HUPP')")

i.e. a negative time will run the interval after the first time. In this case after 10 min, and then run it every 10 minute. Just pushed the new version.

 

Edited by jgab
Link to post
Share on other sites

Thanks for adding.

 

 

55 minutes ago, jgab said:

Hi, I have been able to fully debug the ping/pong logic and made another change the original post (needed to wait between doing another 'watchScene')

what I noticed is that when I cut and paste from the forum to the HC2 I get strange characters in the code that the HC2 doesn't like and I just chased a bug where the HC2 Lua compiler ignored a large part of a table initialiser without saying anything because of some strange invisible characters there...

Anyway. Yes because the the scene is not running it logs  "Aborting: Server not started yet" when it gets a ping, and doesn't send a pong. However, after a while when the client doesn't get a pong back it will restart the scene.

Because I just recently started to urlencode/urldecode the payload in fibaro:startScene (to cope with foreign chars), the log statement for startScene was not so nice, so I fixed that to url decode the payload when loging. That's the change in the version I just pushed for iOSLocator and EventRunner.

 

 

Special characters are OK. I use PSPad

 

But in new version there is still problem with watchRefs that is not defined

Edited by petrkl12
Link to post
Share on other sites
8 minutes ago, petrkl12 said:

Thanks for adding.

 

 

 

Special characters are OK. I use PSPad

 

But in new version there is still problem with watchRefs that is not defined

That code is cursed! :-) Ok, fixed it.

Talking about the the delayed '@@', Another way to do it is to do this inside main()

  for _,rule in ipairs({
      "@@00:10 => log('A')",
      "@@00:10 => log('B')"
      }) do Event.post(function() Rule.eval(rule) end, osTime()+math.random(10,60)) end

This will run the Rule.eval(<script>) randomly 10-60 seconds later, just not to run them all at the same time at startup

Link to post
Share on other sites

Testing scene is working now. Thanks!

 

For solving problem with @@ I prefer first solution. It' s more simple for adding :)

Link to post
Share on other sites

In your wiki you have following example:

define('test',function test(a,b) return a+b end)
rule("2+test(4,5)*2")

it doesn't work ... I need to define some function inside rule

Link to post
Share on other sites
17 hours ago, petrkl12 said:

Into testing scene I need to add some of waiting time after Fibaro reboot. How to do it? Thanks

 

You could just do a fibaro:sleep(...) the first thing inside main().

Is it because you want your scenes to start in a specific order? I was thinking of having a supervisor scene that starts up the other scenes (and could also do the ping keep.alive logic)

 

9 hours ago, petrkl12 said:

In your wiki you have following example:

define('test',function test(a,b) return a+b end)
rule("2+test(4,5)*2")

it doesn't work ... I need to define some function inside rule

The syntax for "script functions" are

  Rule.eval("a=fn(a,b) return(a+b) end")
  Rule.eval("a(7,9)",true)

However, I'm not sure how useful the construct is. Normally I just use Lua function. If a Lua function is global (no "local function...") it can be used inside a script expression.

Ex.

function main()
   function foo(a,b) return a+b end
   Rule.eval("log('a+b=%s',foo(3,4))",true)
end

It means one can call some useful HC2 functions directly

Rule.eval("@sunrise => w=api.get('/weather'); phone:msg=log('Godmorning! It is %s and %s degrees',w.WeatherCondition,w.Temperature)")

 

Edited by jgab
Link to post
Share on other sites
1 hour ago, jgab said:

You could just do a fibaro:sleep(...) the first thing inside main().

Is it because you want your scenes to start in a specific order? I was thinking of having a supervisor scene that starts up the other scenes (and could also do the ping keep.alive logic)

OK, I have created simple supervisor scene that waching of other EventRunner scenes

 

Functions - OK Thanks

 

Another question because I'm rebuilding all my scenes into EventRunner framework :) :

 Is it possible to create some rule that will be possible to call from other rules?

ie. I will have separate rules that switch off lights in ground floor, 1st floor and in garden (different types VD for Hue, Fibaro etc. - complicated) and I would like to use those rules in main rule that will switch off all lights in my house

 

Edited by petrkl12
Link to post
Share on other sites

Why this rule doesn't work?

 

  rule([[#property{deviceID=Pracovna.Vypinacustolu} => d=env.event.deviceID
     || label(d,'lblButtonID')=='1002' >> log('%s - %s - Short Button ON id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='1003' >> log('%s - %s - Long Button ON id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='2002' >> log('%s - %s - Short Button DIM UP id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='2003' >> log('%s - %s - Long Button DIM UP id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='3002' >> log('%s - %s - Short Button DIM DOWN id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='3003' >> log('%s - %s - Long Button DIM DOWN id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='4002' >> log('%s - %s - Short Button OFF id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='4003' >> log('%s - %s - Long Button OFF id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     ]]) 

 

I think issue will be with d=env.event.deviceID following II but I don't know how to solve it ....

 

and also I would like to use somethink like 

rule([[#property{deviceID in ListOfSwitches}  

to have optimized and effective structure :)

Edited by petrkl12
Link to post
Share on other sites
2 hours ago, petrkl12 said:

Why this rule doesn't work?

 

  rule([[#property{deviceID=Pracovna.Vypinacustolu} => d=env.event.deviceID
     || label(d,'lblButtonID')=='1002' >> log('%s - %s - Short Button ON id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='1003' >> log('%s - %s - Long Button ON id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='2002' >> log('%s - %s - Short Button DIM UP id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='2003' >> log('%s - %s - Long Button DIM UP id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='3002' >> log('%s - %s - Short Button DIM DOWN id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='3003' >> log('%s - %s - Long Button DIM DOWN id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='4002' >> log('%s - %s - Short Button OFF id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     || label(d,'lblButtonID')=='4003' >> log('%s - %s - Long Button OFF id=%s lblLastAction=%s',d:roomName,d:name,d,label(d,'lblLastAction'))
     ]]) 

 

I think issue will be with d=env.event.deviceID following II but I don't know how to solve it ....

 

and also I would like to use somethink like 

rule([[#property{deviceID in ListOfSwitches}  

to have optimized and effective structure :)

Ok, as far as I understand it, buttons in VDs don't create scene triggers (Labels and Sliders do) - so you never get a trigger.

The way around it is to add some Lua code to each button in the VD that do

event = {type='btnClicked', value='<id of this button>'}
fibaro:startScene(scene,{urlencode(json.encode(event))})

then in the scene you do

Rule.eval("#btnClicked{value='$btn'} => log('Button %s was clicked',btn)")

Well, if you have a list of switches you can do

Rule.eval("switches={33,44,55,66,77}")
Rule.eval("switches:isOn => ...)

...or something similar..

You can also do

  Rule.eval("#property{deviceID={66,77}} => log('device is %s',env.event.deviceID)")

This is a special case that only works for {type='property', deviceID=...} and that expands to 

 Rule.eval("#property{deviceID=66} => log('device is %s',env.event.deviceID)")
 Rule.eval("#property{deviceID=77} => log('device is %s',env.event.deviceID)")
 

 

Edited by jgab
Link to post
Share on other sites

Thanks but this doesn't work:


 rule("#property{deviceID={66,77}} => log('device is %s',env.event.deviceID)")

 

Error: table index is nil

Link to post
Share on other sites
12 minutes ago, petrkl12 said:

Thanks but this doesn't work:


 rule("#property{deviceID={66,77}} => log('device is %s',env.event.deviceID)")

 

Error: table index is nil

Ops, should work in the new version I just pushed. It has worked using Event.event({type='property', deviceID={88,99}}, function(env) ...end) for a while now but I had some old code in the script compiler hat barfed on that. (i'm working on a new version of the EventRunner framework were this already works, so I missed that the old version didn't support this).

Edited by jgab
Link to post
Share on other sites

Thanks, your freamewrok is number ONE!

 

but

In respository there is only testing lines ...

 

some other syntax problem:

this doesn't work:  rule("#property{deviceID=66} => d=env.event.deviceID || label(d,'test')=='aha' >> log('aha')")
this is OK:  rule("#property{deviceID=66} => || label(env.event.deviceID,'test')=='aha' >> log('aha')")
 

so I can't do d=env.event.deviceID before ||  ?

 

Edited by petrkl12
Link to post
Share on other sites

Ops again, I pushed the old version. Now it should work.

The "|| <expr> >> <statements>" is a statement in itself, and statements are separated by ,;, so it should be

rule("#property{deviceID=66} => d=env.event.deviceID; || label(d,'test')=='aha' >> log('aha')")

A semicolon after the assignment to 'd'. It should be an syntax error reported here so I will check on that.

Because the it is "|| <expr> >> <statements>", where we can have a list of statements after the '>>' like

Rule.eval("|| test() >> log('Hello'); log('world!')")

Both 'Hello' and 'Worl'd will be logged if test() is true. If we want to terminate the '|| >>' before the last 'log' we use a double semicolon.

Rule.eval("|| test() >> log('Hello') ;; log('world!')")

In this case, 'Hello' will be logged if test() is true, but 'World!' will always be logged as it belongs to another statement, outside the '|| >>'

Link to post
Share on other sites

For testing purpose I want to use:

  rule("wait(t/19:00); dev:power=30")
  rule("wait(t/19:10); dev:power=3")
  rule("wait(t/19:15); dev:power=0")

 

but it doesn't work....

How should I setup power?


 

Link to post
Share on other sites

Ok, power isn't supported to set... because it's impossible. However, I pushed a new version that compiles '66:power=30 to fibaro:call(66,'setPower',30) and do the right thing if you run offline (i.e. remember the value and send an event that power has changed).

If you run on the HC2 you get an error as setPower isn't supported,

New version of EventRunner and EventRunnerDebug pushed.

Link to post
Share on other sites

This post is about the Event model that the EventRunner framework is based on, and how to program using that model.

The EventRunner framework also supports an EventScript syntax for writing rules that is a concise way to express common home automation tasks. However, sometimes more complex computations need to be invoked when an event happens, and then it is easier to write the complete rule in Lua (and then we don't call them rules, we call them 'event handlers').

In fact, EventScript rules are "compiled" to Lua expressions using the Event model, and EventScript rules and Lua event handlers can coexists, share local variables, and trigger each other by posting events.

 

Another advantage of Lua event handlers are that they are a bit easier to debug. EventScripts compiles to an internal representation that in general is hard to debug (well, we can have some simple trace of which rules are invoked and what they return). Lua handlers can be debugged like any other Lua code and break-points can be set inside the event’s handler function, if we run offline that is.

 

So, let’s see how we can write event handlers.

 

Assume that we want to turn on the light with deviceID 88 if the motion sensor with ID 66 is breached (i.e. its value is set to '1')

 

--[[
%% properties
66 value
%% events
%% globals
HomeStatus
%% autostart
--]]
 
local trigger = fibaro:getSourceTrigger()
if trigger.type == 'property' and trigger.deviceID==66 then
  if fibaro:getValue(66,'value') > 0 then fibaro:call(88,'turnOn') end
end

 This is the normal way to code it in a Lua scene. However, in the EventRunner framework we code it like this:

--[[
%% properties
66 value
%% events
%% globals
HomeStatus
%% autostart
--]]
 
function main()
 
  -- Declare event handler - Event.event(<source trigger table>,<Lua function to call>)
  Event.event({type='property', deviceID=66},
      function(env) 
         if fibaro:getValue(66,'value') > 0 then fibaro:call(88,'turnOn') end
         end)
         
end

The Event.event(<table>,<function>) registers an "event handler" that will match incoming sourceTriggers against the first argument, the <table>. If there is a match the second argument, the <function>, will be called. SourceTriggers are always Lua tables with a 'type' field {type='...', ...}. In the EventRunner framework we call that data structure an 'event', i.e. any table structure with a 'type' field.

 

So, what do we win by using this? Not much in this case. 

However, if you receive an event from the HC2 with 'property' and 'deviceID', it always comes with a 'propertyName'. E.g.

{type='property', deviceID=66, propertyName='value'}

and the EventRunner framework is clever enough to see that and realize that you probably want to know the value of this device too and thus fetches the property value and add it to the event before sending it to your defined event handler.

{type='property', deviceID=66, propertyName='value', value='1'}

so, if it is a sensor you get a value field of '0' or '1' and if it is a dimmer you get something between '0' and '99'

This means that we can write the rule above as (we don't include the header declarations needed in the scene for triggers)

function main()
 
  -- Declare event handler - Event.event(<source trigger table>,<Lua function to call>)
  Event.event({type='property', deviceID=66, value='1'},
      function(env) fibaro:call(88,'turnOn')
         end)
         
end

 The "event handler" will only match against an incoming event where the value field is '1', so we are safe to turn on the light every time it matches. Look, it's almost getting simpler than the original Lua scene! (besides the 1000 lines of extra code you need to add to the end of the scene, however that is code you shouldn't need to bother with)

 

There is another event that the framework fills in for you. 

{type='global', name='HomeStatus'}

If you get this event because a global have changed its value it's completed with the global’s current value 

{type='global', name='HomeStatus', value='Away'}

So you can write your handler like this

function main()
 
  Event.event({type='global', name='HomeStatus', value='Away'},
      function(env) fibaro:call(100,'setArmed',1) end)
         
  Event.event({type='global', name='HomeStatus', value='Home'},
      function(env) fibaro:call(100,'setArmed',0) end)
         
end

 

So, in the previous example we turned on a light when the sensor is breached. How about turning off the light if the sensor is safe for 5 minutes?

That is a little more involved but quite simple.

function main()
 
  local timer = nil
  
  -- Turn on light if someone moves...
  Event.event({type='property', deviceID=66, value='1'},
      function(env) 
          fibaro:call(88,'turnOn')
          timer=Event.cancel(timer)  -- if we have a timer cancel the timer
         end)
  
  -- Ok, sensor safe, start timer and turn off light if still safe after 5 minutes.
  Event.event({type='property', deviceID=66, value='0'},
      function(env) 
          timer=Event.post(function() fibaro:call(88,'turnOff') end, "+/00:05") -- post a 'timer' that turns off the light on 5 minutes
         end)
         
end

We introduce some new concepts here. Let's start with the second handler. 

 

It matches when the sensor 66 becomes safe (value == '0').

When the sensor becomes safe, we post a timer (a Lua function in this example) that should be called in 5 minutes from now ("+/00:05").

The function we use is Event.post(<function> or <event>, <time>). It is a little bit like the 'setTimeout' function available in scenes as it calls a function or posts an event at a specified time in the future.

Here we tell it to execute the function 'function() fibaro:call(88,'turnOff') end' in 5 minutes from now.

Event.post returns a reference to this future 'post', in our example we store it in the variable 'timer'. That reference we can use to cancel this future post (if it has not already happened, then it is too late).

 

In the first event handler, we besides turning on the light when the sensor is breached, we also cancel the timer. It could be that the sensor became safe and we have started the timer, but the sensor was then breached, and we thus need to cancel the timer. 

 

A side note; To call Event.cancel on nil or a timer reference that doesn't exist is no problem, and that is why we always call cancel when the sensor is breached. It is a little more efficient to call cancel on nil then on a nonexistent reference, in the first case cancel returns immediately, in the second case it has to look through all existing timer references. Event.cancel(timer) always returns nil, and thus we usually code 'timer=Event.cancel(timer)' to also set the variable to nil.

 

Friend of order would argue that this is easy to code without the EventRunner framework, like this:

local trigger = fibaro:getSourceTrigger()
if trigger.type == 'property' and trigger.deviceID==66 then
  if fibaro:getValue(66,'value') > '0' then  -- sensor breached
      fibaro:call(88,'turnOn')               -- turn on light
      local time = 0
      while (true) do
        fibaro:sleep(1000*5) -- sleep 5 seconds     -- sleep 5 seconds
        if fibaro:getValue(66,'value') == '0' then  -- if still safe, add 5s to time
          time=time+5
        else                                        -- if breached, zero time
          time=0
        end
        if time > 5*60*time then                    -- if we have 5 minutes of safe timer
          fibaro:call(88,'turnOff')                 -- turn off light and end scene.
          fibaro:abort()
        end
        end
      end
  end
end

 

This code also solves the task, but now we are starting to see the advantage with the framework. First, I would argue that the two event handlers in the previous example make it easier to understand what is going on, but I'm biased. Secondly, we have to poll the timer in a loop - here we do it every 5s so worst case we turn off the lamp 5min and 5s after the sensor became safe - not a disaster, but there are other cases where we need more direct reactions. The EventRunner framework runs immediate on incoming even/triggers so that is an advantage. Thirdly, how do we do this if we want many sensors looking after many lamps? We probably need to poll both on sensor being breached and becoming safe... then if we want extra conditions when lamps should be turned on etc... soon it becomes messy.

 

With EventRunner framework we can easily duplicate the rule to handle many sensors and lamps, without the rules clashing with each other. 

function main()
 
  local timer1, timer2 = nil,nil
  
  -- Turn on light if someone moves...
  Event.event({type='property', deviceID=66, value='1'},
      function(env) 
          fibaro:call(88,'turnOn')
          timer1=Event.cancel(timer1)  -- if we have a timer cancel the timer
         end)
  
  -- Ok, sensor safe, start timer and turn off light if still safe after 5 minutes.
  Event.event({type='property', deviceID=66, value='0'},
      function(env) 
          timer1=Event.post(function() fibaro:call(88,'turnOff') end, "+/00:05") -- post a 'timer' that turns off the light on 5 minutes
         end)
  
  -- Turn on light if someone moves...
  Event.event({type='property', deviceID=67, value='1'},
      function(env) 
          fibaro:call(89,'turnOn')
          timer2=Event.cancel(timer2)  -- if we have a timer cancel the timer
         end)
  
  -- Ok, sensor safe, start timer and turn off light if still safe after 4 minutes.
  Event.event({type='property', deviceID=67, value='0'},
      function(env) 
          timer2=Event.post(function() fibaro:call(89,'turnOff') end, "+/00:04") -- post a 'timer' that turns off the light on 4 minutes
         end)
 
end

If this is something we do a lot we can easily define a Lua function that define these handlers (rules) for us for any combination of sensor and lamp

 

function main()
 
  function watchLight(light,sensor,timeout)
    local timer = nil
    Event.event({type='property', deviceID=sensor, value='1'},
      function(env) 
          fibaro:call(light,'turnOn')
          timer=Event.cancel(timer)  -- if we have a timer cancel the timer
         end)
    Event.event({type='property', deviceID=sensor, value='0'},
      function(env) 
          timer=Event.post(function() fibaro:call(light,'turnOff') end, timeout) 
         end)
  end
  
  watchLight(88,66,"+/00:05")
  watchLight(89,67,"+/00:04")
  watchLight(91,92,"+/00:07")
  watchLight(101,102,"+/00:10")
 
end

These four calls to watchLight will create eight (4x2) event handlers that looks after the lights and sensors specified. Having many event handlers does not slow down the framework as handlers are looked up efficiently with a hash function based on deviceID and property or other fields in the event. 

 

Let's get back to the Event.post function. In the example above, we posted a Lua function to turn off the light in the future (and cancelled that post if the sensor was breached again). We can also post our own events.

Event.event({type='myEvent', value='Hello'},
       function(env) fibaro:debug("Hello") end)
    
Event.post({type='myEvent', value='Hello'},"+/00:10")

 Here we define an event handler for the event {type='myEvent', value='Hello'}, something that we would never get from the HC2 as a response to a device.

However, we can still define it and we can post such an event ourselves. In the example above above we post it 10 minutes into the future. The event will then match the handler we have defined, its function will be called and "Hello" will be printed.

 

Event.event({type='event',event={type='CentralSceneEvent',data={keyAttribute='Pressed'}}},
    function(env) Event.post({type='keyPressed', deviceID=env.event.event.data.deviceId,key=env.event.event.data.keyId}) end)

Here we have a keyfob from Fibaro with keys that send CentralSceneEvents when we press buttons. If we are only interested when buttons are pressed, we can write an event handler that just transforms the rather complex CentralSceneEvents to something easier to read.

 

Our handler matches against any CentralSceneEvent that has a keyAttribute of 'Pressed'. The event handler function gets a parameter (env) when called, which stands for 'environment'. That is the context when the event handler is called. One important part of the context is the complete event that triggered the handler and is available in 'env.event'. In our case, even if we just matched against a part of the event, 'env.event' has the complete event structure

{type="event",event={data={icon={path="fibaro\/icons\/com.fibaro.FGKF601\/com.fibaro.FGKF601-2Pressed.png",source="HC"},keyAttribute="Pressed",keyId=2,deviceId=5},type="CentralSceneEvent"}}

 What we do is that we pick out the deviceId and keyId field and post it as a new event {type='keyPessed', deviceID=<ID>, key=<keyID>}

 

Then we can do a simpler handler that handles key press events...

  Event.rule({type='keyPressed', deviceID=5},
       function(env) 
           if env.event.key==1 then ... do whatever
           elseif env.event.key==2 then ... do whatever
           elseif env.event.key==3 then ... do whatever
          end
        end)
        
  Event.rule({type='keyPressed', deviceID=5, key=6},
       function(env) fibaro:debug("Button 6 was pressed on keyfob with id 5") end)

 

So, this is a way that we can make our code clearer - we can react on obscure sourceTriggers events and repost them as events that makes sense to us.

  Event.event({type='property', deviceID=door, value='1'},
     function(env) Event.post({type='doorOpened'}) end)
     
  Event.event({type='doorOpened'}, 
      function(env) startAlarm() end)

Another advantage is that if we want to test out the logic we can just post an {type='doorOpened'} event, and see what happens, instead of having to run off to the door and open it...

 

Back to the previous 'MyEvent' example. If we modify it like this:

Event.event({type='myEvent', value='Hello'},
       function(env)
           fibaro:debug("Hello")
           Event.post({type='myEvent', value='Hello'},"+/00:10")
       end)
    
Event.post({type='myEvent', value='Hello'},"+/00:10")

 The event will be posted the first time 10min into the future. The event handler will trigger on the event and print "Hello" and then post the event again 10min into the future... then the event handler will get the event again and it will repeat itself. We have created an infinite loop that runs every 10 minutes. This is not very different from doing a "loop" with setTimeout where the function calls setTimeout again at the end. 

However, it's a bit more powerful/reusable

Event.event({type='myEvent'},
       function(env)
           fibaro:debug(env.event.value)
           Event.post(env.event,env.event.time)
       end)
    
Event.post({type='myEvent', value='Hello', time='+/00:10'})
Event.post({type='myEvent', value='World', time="+/00:15"})

Two tricks here. We let the event handler print out the 'value' field of the event, so we can print more than "Hello". Secondly, we let the handler repost the whole event (available in env.event) at the time specified in the 'time' field of the event.

The advantage is that we can post two events with different 'value' and with different 'time' values. In effect creating two loops that run on 10min and 15min intervals respectively. The 'myEvent' handler is in reality a generic loop handler.

Event.event({type='loop'},
       function(env)
           env.event.fun()
           Event.post(env.event,env.event.time)
       end)
    
Event.post({type='loop', fun=function() fibaro:debug('Hello') end, time='+/00:10'})
Event.post({type='loop', fun=function() fibaro:debug('World') end, time="+/00:15"})
Event.post({type='loop', fun=function() fibaro:debug('Again') end, time='+/00:07'})
Event.post({type='loop', fun=function() fibaro:debug('Always') end, time="+/02:20"})
Event.post({type='loop', fun=function() fibaro:debug('Sometimes') end, time='+/01:10'})
Event.post({type='loop', fun=function() fibaro:debug('Goodbye') end, time="+/00:27"})

Here we easily create 6 loops with different time intervals printing different messages. In a home automation system this is an easy way to spawn tasks that should be done periodically. It is also a way to structure the code to make it easier to read and understand (when you got the hang of thinking in events). A made-up example could look like this, where our loop construct re-post a specified event at a specified interval.

Event.event({type='loop'},
       function(env)
           Event.post(env.event.ev)
           Event.post(env.event,env.event.time)
       end)
    
Event.post({type='loop', ev={type='checkWater'}, time='+/00:05'})
Event.post({type='loop', ev={type='checkWindows'}, time="+/00:15"})
Event.post({type='loop', ev={type='checkTemperature}, time='+/01:00'})
 
Event.event({type='checkWater}, -- happens every 5min
        function(env) 
            ... whatever needs to be done to check the water...
        end)
        
Event.event({type='checkWindows},  -- happens every 15min
        function(env)
            ... whatever needs to be done to check the windows
        end)
 
Event.event({type='checkTemperature}, -- happens every hour
        function(env) 
           ... whatever needs to be done to check house temperature
        end)

 We have so far specified the time to Event.post as, e.g. "+/00:15", which should be read as "plus 15 min from now".

There is also another time specification we can use "n/10:00", and it should be read as "the next 10 o'clock, either today or if we passed it, 10 o'clock tomorrow"

That turns out to be a useful construct because we can reuse the previously defined loop handler and easily check the water at 15:00 every day by doing

Event.post({type='loop', ev={type='checkWater'}, time='n/15:00'},'n/15:00')

 This will post the first water check for 15:00, and the loop handler will repost it for the next 15:00, i.e. the next day at 15:00.

If we had done

Event.post({type='loop', ev={type='checkWater'}, time='n/15:00'})

The loop handler would have got called immediately and water would have been check immediately (which sometimes are ok), but then it would have start to be called at 15:00 every day by the loop. 

 

There is yet another time format, "t/HH:MM" that creates a time (t)oday. 

 

And finally, there is a format that can specify sunset and sunrise with an offset in minutes; "sunset+10" and "sunrise-10" gives the number of seconds from midnight to sunset plus 10min and sunrise minus 10 minutes, respectively.

However, to give the value to Event.post we need to prefix it with "t/" that produces an epoc time that Event.post requires.

If we do an Event.post({type='test'],"t/10:00") it will post it at 10 o'clock today or if we already passed 10:00, it will not be posted at all (which is the difference to "n/10:00")

Why bring this up? Well, if we want to schedule something for sunrise tomorrow, "n/sunset" doesn't work as it will compute the sunset today and add 24 hour to that, which is close but not the sunset tomorrow. The only way we can guarantee to get the right value is if we compute the sunset the same day as the sunset in question. One way to do that is to schedule all activities for the day at midnight,

dailyEvents = {}
  
  Event.event({type='Midnight'},
    function(env)
        for _,e in ipairs(dailyEvents) do Event.post(e.action,e.time) end -- schedule registered events
        Event.post(env.event,"n/00:00") -- do it again next midnight
      end)
  Event.post({type='Midnight'},"n/00:00") -- Start midnight loop
  
  function daily(time,action)
    if toTime(time) > osTime() then Event.post(action,time) end -- Later today?, post it
    dailyEvents[#dailyEvents+1] = {action=action, time=time} -- register it to re-schedule at midnight
  end
  
  daily("t/sunrise-10",function() Log(LOG.LOG,"Hello") end)
  daily("t/16:00",function() Log(LOG.LOG,"16!") end)
  daily("t/20:00",function() Log(LOG.LOG,"20!") end)
  daily("t/sunset-15",function() Log(LOG.LOG,"Goodbye") end)

Here we have the start for a simple scheduler. Maybe we want to filter for some specific days too:

dailyEvents = {}
  
  Event.event({type='Midnight'},
    function(env)
        for _,e in ipairs(dailyEvents) do Event.post(e.action,e.time) end -- schedule registered events
        Event.post(env.event,"n/00:00") -- do it again next midnight
      end)
  Event.post({type='Midnight'},"n/00:00") -- Start midnight loop
  
  function daily(args)
    local time,action1,days = args.time,args.action,args.days or "Mon,Tue,Wed,Thu,Fri,Sat,Sun"
  	local action2 = function() if days:match(osDate("%a")) then action1() end end
    if toTime(time) > osTime() then Event.post(action2,time) end -- Later today?, post it
    dailyEvents[#dailyEvents+1] = {action=action2, time=time} -- register it to re-schedule at midnight
  end
  
  daily{time="t/sunrise-10",days="Mon,Wed",action=function() Log(LOG.LOG,"Hello") end}
  daily{time="t/16:00",days="Fri",action=function() Log(LOG.LOG,"16!") end}
  daily{time="t/20:00",days="Sat,Sun",action=function() Log(LOG.LOG,"20!") end}
  daily{time="t/sunset-15",action=function() Log(LOG.LOG,"Goodbye") end}

It's easy to build on this to create a scheduler tailored for a specific use-case.

 

Event.event also allows some special syntax to deal with multiple events.

Event.event({type='property', deviceID={ID1,ID2,...IDn}}, function(env) ... end)

is translated to

local fun = function(env) ... end
Event.event({type='property', deviceID=ID1},fun)
Event.event({type='property', deviceID=ID2},fun)
   :
Event.event({type='property', deviceID=IDn},fun)

This only works with type 'property' and deviceID being a list of deviceIDs. The advantage is that we can easily trigger on something happening to a set of devices.

Event.event({type='property', deviceID={66,77,88}, value='1'},
      function(env) fibaro:debug("A sensor upstairs was breached!"); startAlarm() end)

This will react on any of the devices being breached. If we want to react on all devices having a specific state, we need to check it ourselves.

Event.event({type='property', deviceID={66,77,88}, value='0'},
      function(env)
          for _,id in ipairs({66,77,88}) do if fibaro:getValue(id,"value") > "0" then return end end
          fibaro:debug("All sensors are safe, house empty"); enableAlarm() 
      end)

 ...but we only do the check whenever one of the devices turns safe.

 

Another special syntax with Event.event is that we can give a list of events it should trigger on

Event.event({Event1,Event2,...Eventn}, function(env) ... end)

is translated to

local fun = function(env) ... end
Event.event(Event1,fun)
Event.event(Event2,fun)
   :
Event.event(Eventn,fun)

Assume, you want to start the alarm when a sensor is breached and the fibaro global 'HomeStatus' is equal to "Away".

It's not enough to trigger only on the sensor being breached. The sensor becomes breached, but the 'HomeStatus' is not set to 'Away', no reaction. However, a few seconds later the HomeStatus is set to 'Away', the sensor still being breached but we don't trigger on HomeStatus and thus don't react. The solution is of course to trigger both on sensor being breached and HomeStatus being changed to "Away".

Event,event({{type='property', deviceID=sensor, value='1'},{type='global', name='HomeStatus', value='Away'}},
    function(env) if fibaro:getValue(sensor,"value") == "1" and fibaro:getGlobalValue("HomeStatus")=="Away" then startAlarm() end end)

We could of course break it down to 2 event handlers, but this makes it easier to see what is going on.

If you are familiar with EventScript, the rule

Rule.eval("sensor:breached & $HomeStatus=='Away' => startAlarm()")

would translate to almost exactly the code above...

 

To be continued....

 

 

 

Edited by jgab
Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...