Jump to content

Welcome to Smart Home Forum by FIBARO

Dear Guest,

 

as you can notice parts of Smart Home Forum by FIBARO is not available for you. You have to register in order to view all content and post in our community. Don't worry! Registration is a simple free process that requires minimal information for you to sign up. Become a part of of Smart Home Forum by FIBARO by creating an account.

 

As a member you can:

  •     Start new topics and reply to others
  •     Follow topics and users to get email updates
  •     Get your own profile page and make new friends
  •     Send personal messages
  •     ... and learn a lot about our system!

 

Regards,

Smart Home Forum by FIBARO Team


Recommended Posts

  • Topic Author
  • 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

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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...

    Please login or register to see this code.

    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

    Share this post


    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

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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()

    Please login or register to see this code.

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

    Share this post


    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 :)

    Share this post


    Link to post
    Share on other sites

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

     

    Share this post


    Link to post
    Share on other sites

    In your wiki you have following example:

    Please login or register to see this code.

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

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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:

    Please login or register to see this code.

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

    The syntax for "script functions" are

    Please login or register to see this code.

    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.

    Please login or register to see this code.

    It means one can call some useful HC2 functions directly

    Please login or register to see this code.

     

    Edited by jgab

    Share this post


    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

    Share this post


    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

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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

    Please login or register to see this code.

    then in the scene you do

    Please login or register to see this code.

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

    Please login or register to see this code.

    ...or something similar..

    You can also do

    Please login or register to see this code.

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

    Please login or register to see this code.

     

    Edited by jgab

    Share this post


    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

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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

    Share this post


    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

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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

    Please login or register to see this code.

    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

    Please login or register to see this code.

    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.

    Please login or register to see this code.

    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 '|| >>'

    Share this post


    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?


     

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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.

    Share this post


    Link to post
    Share on other sites
  • Topic Author
  • 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')

     

    Please login or register to see this code.

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

    Please login or register to see this code.

    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.

    Please login or register to see this code.

    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.

    Please login or register to see this code.

    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)

    Please login or register to see this code.

     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. 

    Please login or register to see this code.

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

    Please login or register to see this code.

    So you can write your handler like this

    Please login or register to see this code.

     

    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.

    Please login or register to see this code.

    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:

    Please login or register to see this code.

     

    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. 

    Please login or register to see this code.

    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

     

    Please login or register to see this code.

    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.

    Please login or register to see this code.

     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.

     

    Please login or register to see this code.

    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

    Please login or register to see this code.

     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...

    Please login or register to see this code.

     

    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.

    Please login or register to see this code.

    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:

    Please login or register to see this code.

     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

    Please login or register to see this code.

    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.

    Please login or register to see this code.

    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.

    Please login or register to see this code.

     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

    Please login or register to see this code.

     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

    Please login or register to see this code.

    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,

    Please login or register to see this code.

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

    Please login or register to see this code.

    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.

    Please login or register to see this code.

    is translated to

    Please login or register to see this code.

    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.

    Please login or register to see this code.

    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.

    Please login or register to see this code.

     ...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

    Please login or register to see this code.

    is translated to

    Please login or register to see this code.

    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".

    Please login or register to see this code.

    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

    Please login or register to see this code.

    would translate to almost exactly the code above...

     

    To be continued....

     

     

     

    Edited by jgab

    Share this post


    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...