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


  • 0

EventProxy


Question

Posted (edited)

-There is always this issue with device IDs needed to be declared in the header. Then a device is reincluded and gets a new id and code breaks.

-Then there is the issue of an integer being a bad way to refer to devices when coding and thus we build hometables or declare variables.

 

This code is an example of trying to solve both issues. It works but is more of a concept for the moment as some more checks and more type of triggers need to be supported. (It supports value, power,sceneActivity and simple CentralSceneEvent for now)

 

Anyway, we have a scene that builds a "hometable", lets call it the "HomeTableBuilder". It builds the table from meta data stored in the 'Device description' field for devices (not for virtual devices, they don't have a description field and they don't trigger scenes anyway).

 

The format is very flexible to specify where in the home table 'tree' the deviceId should be inserted..

Please login or register to see this attachment.

'Device description' = ["dev.%room.tableLamp.%id"]

will set HomeTable.dev.kitchen.tableLamp=417, if the device is in room 'kitchen' and has id 417

'Device description' = ["dev.%section.%room.tableLamp.%id"]

will set HomeTable.dev.upstairs.kitchen.tableLamp=417, if the device is in section 'upstairs' and room 'kitchen' and has id 417

'Device description' = ["dev.remote.%id"]

will set HomeTable.dev.remote=362, if the device is a fibaro remote with id 362

The code in the HomeTableBuilder that needs to be setup is:

Please login or register to see this code.

So this take care of creating a hometable (HT in the code) from meta data stored in the devices themselves. We can then add more static configuration data to the hometable etc.

After that we declare which of our other scenes should receive events. (Test1 has id 313, and Test2 has id 316 in this example). sceneId(string) is a convenience function to get the ID from a scene name.

Please login or register to see this code.

This says that we want to receive property.value events from "tablelamp" and send it to the scenes named "Test1" and scene named "Test2"

We declare the same for "power"

And our fibaro remote should send its events to "Test1" and "Test2" too.

 

The HomeTableBuilder then dynamically creates another scene in the HC2 named "EventProxy"..softwareVersion. (it's always created hidden for me!?, do "show hidden")

with the following code:

Please login or register to see this code.

This "EventProxy" scene receive triggers for the devices declared and send them to the scene we declared having interest in these events, Test1 and Test2 in this case.

The code in a receiving scene (like Test1) then looks like below. (No declared triggers in the header, it gets all triggers from startScene from the EventProxy)

Please login or register to see this code.

Scenes should still declare globals, weather etc... triggers that are not connected to devices IDs.

 

So, what have we achieved? 

-We don't have to deal with numerical device IDs in our code

-We can add meta data to devices that tells where in the HomeTable they should be inserted

-We don't have to declare numerical IDs in headers

 

Drawback: A small performance penalty to let the EventProxy trigger other scenes instead of they being triggered directly. (Seems to be very small penalty though, logs are always on the same second in EventProxy and receiving scenes for me). One could generate different EventProxy scenes for different triggers to distribute the load a bit and ensure that the EventProxy doesn't hit the maximum 10 active instances... However, an activated EventProxy instance is very quick to dispatch the event and then terminate.

 

Improvement: Let scenes dynamically register which events they want when they start up. Only issue is how often the EventProxy need to be rebuilt...

 

Code for the complete HomeTableBuilder:

Please login or register to see this code.

 

Edited by jgab
  • Like 1
  • Thanks 1

Recommended Posts

  • 0
  • Inquirer
  • Posted (edited)

    So, I was a bit sweeping in claiming that this is "the way" to synchronise, and the read/write was kind of "atomic"....

    If this was generic code in a threaded/multiprocessor context

    Please login or register to see this code.

    it would have a potential race condition (possible event/msg overwrite). However, the Lua instances spawned with new scene invocations seem to  have some quasi threaded behaviour over them...? at least it seems so when I'm testing things.

    If I from another scene do repeated burst of triggers (writing a fibaro global here) this code seems to work (all messages are received)

    Please login or register to see this code.

    I can't prove it for sure - but that's how it behaves for me...

    In fact, what used to stumble me was to get a unique ticket/lock that would differ between scene invocations (if you don't have that, msg overwrite happens at least 1 on 20). Using random don't work because it always start at the same value. Even os.time() and os.clock() would be the same for rapid instances... What I resorted to was to take the "address" of the sourceTrigger table (tostring) (+ the event), which seems to vary enough...  

    A lot of IF's and empirical assumptions here but stress testing it for long series of burst of 5-8 simultaneous  triggers have not resulted in one missed event.. (1000+ triggers).

    I would be happy if someone could prove me wrong (without exceeding max instances). I've been running this for 6+ month now and if there has been a missed event I haven't noticed... and maybe I don't want to know... :-) 

     

    Edited by jgab
    • 0
    Posted

    @jgab Thank you for your elaborate testing and long report. Max instances and race bugs were (and are) indeed the points I worried about. I will revisit your concept when I rewrite one of my most difficult scenes. Unfortunately, this probably won't happen in the next 1 or even 2 months due to other priorities...

    • 0
  • Inquirer
  • Posted (edited)

    So, this is a nicer version of a scene that "translates" incoming fibaro triggers to call-backs in separate threads in the same scene instance. The advantage is that local Lua variables can be shared between call-backs so the need for Fibaro global variables in some complex scenes can be avoided.

    Ex.

    Please login or register to see this code.

    This would have been impossible with a normal scene because the HC2 starts a new instance with a new Lua environment for every trigger/event invocation, e.g. the counter would always be 1. This means that we have a local "state" and it also means that the scene is always running.

    Compared to the code in the previous thread this version have some more convenience code for debugging and a more elaborate format for the 'post' function and is more future proof for some more advanced functions to come - more on that in the next post.

    If run offline (PC/Max/Linux) it brings in a  file with some missing fibaro functions (

    Please login or register to see this attachment.

    ). This allows for testing and debugging scenes using this framework on a PC and not stress the HC2 too much...

    Please login or register to see this link.

    .

     

    The main() example here turns on a light if a sensor is triggered - doesn't make much sense to have this model for this simple case but I will add some more interesting examples in the next post to avoid this one to be too long...

    Please login or register to see this code.

     

    --- Forum merged my posts... ----

     

    So, main() is called whenever the scene receives a device/light/sensor event because of a state change. You still have to declare them in the header of the scene as usual, but you get call-backs into the same Lua environment/instance.

    In the code, the 'post' function can be used to send your own defined events to yourselves so they also "come in" via the main() function. However, post can delay the event being sent at a specified time in the future (see 'toTime' function in the code).

    Please login or register to see this code.

     

    So, when the scene starts up the first time it gets an 'start' event. There it posts 2 events to happen during the day - at 12.00 and at sunset-10min. Those events are then checked for in the code below that does whatever should be done at that time. However, it ends by posting a new 'start' event to itself at 5min past midnight. That means that this code will be called again and setup the daily events again for the next day, and repeat that for every day. Look, a simple scheduler(!).

    The code becomes relatively clean within main() and it is trivial to mix and match fibaro events with user defined events to drive the scene logic.

    Edited by jgab
    • 0
  • Inquirer
  • Posted (edited)

    With your own sense of time (the code uses a defined osTime() instead of os.time) it is quite easy to adjust the drifting clock on the HC2 - mine is 417 secs behind at this moment. Hopefully Fibaro will fix this - but meanwhile code like this makes sure that the scheduled events are triggered on time. I use a call to timezonedb.com to get my local time. (I have no affiliations with timezonedb). The code also allows me to run the clock faster for debugging purpose, when running offline.

    Please login or register to see this code.

     

    Edited by jgab
    • Like 1
    • 0
  • Inquirer
  • Posted (edited)

    Another example, a recent user had this

    Please login or register to see this link.

    . The normal way is to check that the time is between the interval(7.30 and 9.30) and set a Fibaro global to a flag that the action is carried out, and maybe a busy wait. Its doable...

    With the event model it can be coded like below, and we don't need to use any Fibaro global or fibaro:sleep(), and a pretty clean logic...

    Please login or register to see this code.

    ..and it is easy to add additional tests for other sensors and other time intervals in the same main() without them clashing with each other - something that is difficult with the fibaro:sleep approach...

    Edited by jgab
    • 0
  • Inquirer
  • Posted (edited)

    ..and to save one line of code from the above example, and to show how to schedule Lua functions

    Please login or register to see this code.

     

    Edited by jgab
    • 0
    Guest FredrikKarlsson
    Posted

    Hi @jgab

     

    Thank you for your great effort in trying to improve  HC2. After having just briefly glanced at what you are doing here, I can't say that I understand it entirely. But two questions arose by this quick glance

    1) What would you say is the relationship between your effort and GEA? 

    2) How well would you say that the system scales? The screen dump you posted did indicate some elevated CPU activity, at least compared to my "vanilla" system, with 5-6 scenes being generated within your system. What would the load be for say 25-35 scenes? Have you perhaps run some simulations on this?

     

     

     

     

     

     

    • 0
  • Inquirer
  • Posted (edited)

    1) I think GEA could use this model to integrate rules  and instant triggers smoother (e.g. they could share local Lua variables).. and it could try to be be smarter/more dynamic in when to run rules instead of on a fixed interval (30s)... which could actually lower the load and make it more responsive.

    In GEA, the example in my previous post would either be solved with an “instant trigger” declaration that saved the state in a Fibaro global, or a rule checking every x (default 30) sec if sensor breached which would mean some random delay. The event model reacts instantly and don’t need a Fibaro global. It’s more verbose than GEA, but it’s possible to replace the event rules with some table script language similar to GEA to make it more compact and provide some often used patterns as pre made functions.

    2) The elevated CPU (~30%) is there no matter what - think it is GC or other housekeeping tasks. I have run 1 and 6 scenes and didn't see much difference. The other thing is that the model kind of allow you to merge scenes into one as the events run independent and don't clash with each other. It tends to runs smoother as the rules run on their own time (thread) schedule and are not invoked on particular intervals, like GEA tend to do. At least if I compare to my old scheduler that worked on intervals it was much more "spiky".

    Today I have a Lightning scene (also takes care of power rules), an Alarm scene, an iCloud scene, and a Control scene, and a Notifier scene, and a Sonos scene on this framework. In theory they could all be merged into one scene because they are all type of event rules... The reason I factor them into separate scenes now is the fear of "too many instances" - but that is a problem for any scene model.

    The 'consumer' part of the scene wakes up every 250ms in the example code and checks a global and then goes back to sleep (setTimeout thread). That's extremely quick and one can shorten or extend that time to change the responsiveness... If I time the "consumer' loop doing 1000 iterations it takes 252s, which implies that it takes 2ms to check if there is and event and then it sleeps for 250ms...

    Edited by jgab
    • 0
  • Inquirer
  • Posted (edited)

    So, I don't think the "framework" is a memory and/or cpu hog. Some of the scenes are heavy - the lightning scene has a *lot* of rules and many of the other scenes call out to external other system and parses a lot of json etc. so there is where the work is done. A lot of sleeps and busy wait loops do tax the system and this is one way to get away from it. Another aspect is that because the scene is always running  means that I can afford to do a lot of heavy initialization when the scene starts (like parsing hometables, "compiling and setting up rules", or other stuff...) instead of doing it every time the scene is triggered. Many load and parse their hometable and do other expensive stuff for every device trigger coming in...

     

    Edited by jgab
    • 0
  • Inquirer
  • Posted (edited)

    So, I develop most of my scenes in

    Please login or register to see this link.

     and I have support to run the event model framework offline and make it behave as close as possible to what it would be on the HC2. My own 'setTimeout', HTTPClient etc.

    Scenes can do remote fibaro calls to the HC2 using the

    Please login or register to see this link.

    and that works great, which means that I can debug scenes that turn on and off lights for real etc.

    However, it has been impossible the other way around, to get triggers from the HC2. The framework allows for simulating incoming triggers by posting events in the code. That allows for replaying event sequences and that has been really useful for debugging logic of schedulers and others scenes, but it's not the "real thing".

     

    What I have tried with some success recently  is to make a simple listener running in ZeroBraneStudio in the "framework", that listen to  'sourceTriggers' posted from the HC2 and 'post' them locally to make it look like I receive the events in my code. I haven't stress tested it that much yet and I probably violate some of the HTTP protocol ...but it ticks along... I can turn on/off my lights and breach sensors and my offline scene running in ZeroBraneStudio gets the source trigger.

    Maybe I can forward all triggers on the HC2 and run all my scenes offline...  ;)

    Example code on the HC2 with 3 property triggers:

    Please login or register to see this code.

    Code running on ZeroBraneStudio in the "framework" using 'post' and 'setTimeout' (needs to be cleaned up a bit)

    Please login or register to see this code.

     

    Edited by jgab
    • 0
    Posted (edited)
    10 hours ago, jgab said:

    Maybe I can forward all triggers on the HC2 and run all my scenes offline...  ;)

     

    one can emulate 'CentralSceneEvent' event trigger (but there is more, e.g. 'SceneActivationEvent', 'accessControlEvent', 'VideoGateIncomingCallEvent' ... but not yet implemented in that script below.)

     

    Please login or register to see this code.

     

     

    Edited by tinman
    • Like 1
    • 0
  • Inquirer
  • Posted (edited)

    Cool!, seems like something is installed on my HC2 but I doesn't seem to do anything (centralSceneEvent returns nil but 'property' return a 'not registered..."). This is useful to emulate events on the HC2 (my example was kind of doing the opposite, sending the events to a PC where I can debug my scenes, kind of live...)

    However, with the '/plugins/publishEvent' one could easily do a VD for a ‘soft’ keyfob... that would be nice. A more generalised publishEvent would allow for replaying any sequence of events to debug scripts and logic... or a 'presence' script simulating that someone is home... and allowing for user defined 'events' would be an alternative to startScene(args)... could probably come up with some more applications. :)

    Thanks for letting me know what you are up to.

    Edited by jgab
    • 0
  • Inquirer
  • Posted (edited)

    So, this is an evolved version of the event model framework. Previously  the main(event) function was called for all incoming events. That is ok, but it also meant that inside the main() function one had to test what kind of event it was and take appropriate actions. Lots of if-then-else...

    The new version changes the semantic and main() is only called once at startup and inside main one instead register event handlers, or 'rules', or 'call-backs'.

    Rules are defined by what event they matches (an event pattern) and what corresponding function should be called if matched.

    Please login or register to see this code.

    This register a rule to catch an incoming sourcetrigger for a 'property' trigger with value 1 for device 56. If there is a match it calls the function to print the message that the light is on.

    More specifically

    The {type='property', deviceID=56} pattern matches {type='property', deviceID=56, propertyName='value', value=99}

    but the {type='property', deviceID=56, propertyName='value', value=99} pattern doesn't match {type='property', deviceID=56} because there is no 'propertyName' and no 'value' in the latter.

    A pattern can also contain 'constraints' in the pattern

    The {type='property', deviceID=56, value='$>0'} pattern matches {type='property', deviceID=56, propertyName='value', value=99}

    A constraint is a string that starts with a '$' sign and an comparison operator and a value e.g. '$>0' in the pattern above. This allows for more flexibility when matching events patterns.

    It's not a big difference, one of the previous examples would be recoded like this:

    Please login or register to see this code.

    Quite straight forward. The advantage that will be appearant in the next post is that the handlers (rules) now are functions and can 'keep' local values.

    Here is the code for the new event model framework. A bit more code for event handling and matching but it is worth the convenience it gives us...

    Please login or register to see this code.

     

    ------------ Forum merged my posts again ----------------

     

    So, I have been asked for how this relates/compares to GEA. GEA polls rules on specific intervals to see if the conditions are true (or have been true for a 'duration' of time) and then adds some 'options' that apply additional constraints (like time of day) and carries out actions if all conditions are true.

    GEA also have 'instant triggers' that fire directly when an event happens, but one have to declare the triggers in the scene header as normal. Also, because instant triggers spawn new scene instances they can't share lua locals with the rules that run in a loop in the startup scene instance. This is a trade-off and works very well. I recommend GEA to anyone that are not deep into lua coding and wants a flexible way to express typical home automation rules.

    I've no intention to reimplement GEA but just want to show that the basic execution model of GEA is possible to implement with the event model. However, the difference is is there is no interval polling to check conditions, everything acts on 'instant triggers'. The drawback is that all devices and globals in the conditions need to be declared in the scene header (GEA doesn't need that because it polls the devices/globals regularly instead). However, the advantage is no unnecessary polling... and the possibility to allow rules and trigger to set and share local lua variables, e.g if something has been triggered, without having to resort to global fibaro variables.

     

    I've just implemented the rudimentary GEA support (the execution logic) to be able to handle rules like below

    Please login or register to see this code.

    But of course, GEA has a lot of other conditions and options...

    The other thing that this code exemplifies is that it is easy to write functions that write rules. The GEA.add function below creates rules (2*conditions+1) to handle events from the devices/triggers and carry out the actions when all conditions are true, while taking care of 'durations', repeat' and 'inverse'...

    Please login or register to see this code.

     

    Edited by jgab
    typo in GEA code...
    • 0
  • Inquirer
  • Posted (edited)

    Some spare time during Xmas and some improvement to the EventRunner code. The syntax has changed for defining event handlers/rules, using the 'table' or 'named' parameter syntax. This makes it simpler to add optional arguments.

    Please login or register to see this code.

    The 'between' argument makes it easy to limit rules for certain time intervals, which is a common requirement. More complicated (time) constraints could benefit from explicit handlers that enable/disable rules. e.g.;

    Please login or register to see this code.

    ...or one could of course put additional tests inside the 'action' handler.

    The functions available now are:

    Please login or register to see this code.

    My attempt in the previous post trying to emulate GEA was not very elegant and not really thought through (it did work with the test cases though). A better approach is the below new 'Event:rule' constructor.

    Please login or register to see this code.

    This allows for writing "GEA" style rules like this

    Please login or register to see this code.

    The advantage with the "GEA" model is of course that it is easy to express rules that fire when something is true or false for a certain amount of time. It was achievable before but required some timer manipulation... ..that is hidden now in the Event:rule construct.

    Running this offline on a Mac in ZeroBrane gives the output:

    Please login or register to see this code.

    Tests and actions are still Lua functions but could easily be replaced with some 'table' script sugar to make the rules more condensed.

    The other thing that differs from a test and execute model like GEA is that one have to declare what 'events' are involved in the test, the 'event={...}' part. The advantage is that the rule don't need to be run every x seconds to see if the test is true. Instead the test is only run when an event is triggered (e.g. a sensor or light changing state and thus the test could possibly give another result). It is also easy to integrate other events, like results from external web service calls... Integration of this into my own scenes will simplify some logic.

    And of course, it integrates well with the "event programming model" that makes it easy to mix and match scheduling with triggers with loops etc... and its easy to debug off-line...

    Please login or register to see this attachment.

    Please login or register to see this attachment.

     

    Edited by jgab
    • 0
  • Inquirer
  • Posted (edited)

    So... I'm not sure why this thread became a question in the new forum...

    Anyway, I kept the basic EventRunner framework with the Event.event and Event.post primitives and it have been working great for my home scripts so far.

    However, I've added a scripting language to make the rules more "expressive" and I have been running that for a week now without any bugs (or at least working with the rules I have).

    The new scripting language (arguments to 'Rule.eval') allows me to write stuff like this:

    Please login or register to see this code.

    Statements of type '<conditions> => <actions>' are defining rules, otherwise the expressions are just evaluated.

    I'm developing in my

    Please login or register to see this link.

    and there is the code and (eventually more) documentation available.

    I compile the 'string' expressions to a simple byte-code that is interpreted. It gives my the advantage of Lua's loadstring and co-routines and it is quite fast (don't see any strange cpu loads).

    The rule that triggers on fibaro triggers, only trigger when they change state so no need to poll every rule every 30s or so like other engines do.

    E.g.

    Please login or register to see this code.

    Is only run when 'dev.kitchen.lamp' changes state, then the conditions are checked and if true the log written out.

    When a rule is defined (with '=>') the left hand <conditions> part is searched through for device IDs and/or fibaro globals, which will trigger the rule when they change state.

    The left hand can also contain 'daily(<time>)' statements to run a rule at a specific time every day. Combined with other date tests that turn out to be a very powerful scheduler.

    E.g.

    Please login or register to see this code.

     

    The ':' operator access fibaro device properties or runs actions. Left operand should be something that evaluates to an integer and the right hand side is an property/action. 

    '66:on' turns off light for device with id 66. '66:isOn' return true if device id 66 is on. '66:value=99' is the same as fibaro:call(66,'setValue','99') etc. Left hand side can also be an array of device IDs. e.g. '{66,55}:on' will turn on both 66 and 55...

     

    The 'for' construct in the first example takes 2 arguments, a time and an expression. The expression needs to be true for the given time, and then the action is carried out.

    When 'dev.kitchen.lamp' changes state the 'for' construct is run and checks if expression is true. The 'repeat' construct in the action tells the 'for' construct to reset the timer and wait another 10min in this case. 'repeat' also takes an number argument being the number of times it should repeat (and 'repeat' return how many times it has repeated so far). The example both logs and sends the message to Jan's phone every 10min for 5 times (50min) or stops if the door is closed before.

     

    Defining the script language allows for some funky syntax. Time constants are written just HH:MM[:SS] that internally is translated to seconds, which allows for arithmetic expressions like '10:00+00:05' or 'sunset+00:10'. Intervall checks can use the operator '..' that checks if the time is between the left and the right operand, and there are constants for 'sunrise', 'sunset', 'now', 'midnight' etc.

    E.g.

    Please login or register to see this code.

    '|| <expr> >> <expr>' is like a chainable if-then, and fibaro globals are accessed with a '$' prefix. 

    '#abc{a=6}' evaluates to '{type='abc', a=6}' which is a convenient way to write event data structs. '#abc' becomes just '{type='abc'}'

    It's a quite compact format and the Earth hour example can be written like:

    Please login or register to see this code.

    'Rule.load' evaluates many statements at once...

    Will be back with more examples...

     

    Edited by jgab
    • Thanks 1
    • 0
  • Inquirer
  • Posted (edited)

    With the above compact script language a scheduler can be coded as below. The last part of the scene triggers events to test/verify the scene logic. Only works if running off-line in Zerobrane etc.

    Please login or register to see this code.

     

    Edited by jgab
    • 0
  • Inquirer
  • Posted (edited)

    ...a little more explanation on the syntax.

    So, script rules comes in 3 flavors. Daily rules, Trigger rules, and Event pattern rules. Common is that they all are on the form

    Please login or register to see this code.

    e.g. a rule contains the '=>' keyword. Rule.eval called on expressions/statements without a '=>' are just executed and returning the result. Useful for setting up values and/or give immediate commands to devices.  
    Let's start with some expressions.


    Expressions

    Please login or register to see this code.

    From now on `Rule.eval` are sometimes excluded in the examples to ease the typing :-) . 
    Assume that there is a light switch/dimmer with ID 55.

    Please login or register to see this code.

    turns on the light with device ID 55. ':' is an operator that on the left hand side takes an ID (number), 55 in this case, or a table of IDs, and on the right hand side takes a 'function' that retrieves or sets a device ID property.

    Please login or register to see this code.

    turns on the light for device 55 and 66. Other functions available are:

    • '55:isOn', returns true if device 55 is turned on. '{55,66}:isOn' returns true if any of 55 or 66 is turned on. The logic is that if you have a room with many lights and at least one light is on in the room there is light in the room.
    • '{55,66}:isAllOn', like the above but all lamps have to be on. On a single device it behaves like ':isOn'
    • '55:off`, '55:isOff'. '55:isAnyOff', is the corresponding functions for setting/testing if devices are turned off.
    • '55:value', return the device ID's value property. Calls 'fibaro:get(55,'value')'. '{55,66}:value' returns a table with the IDs values. Ex. '{55,66}:value => {'0','1'}'
    • '55:lux', '55:temp', are identical to '55:value' but more descriptive if lux or temperature values are accessed. '55:safe', '55:breached' are the same as '55:off' and '55:isOn' respective but more descriptive when dealing with sensors.
    • '55:toggle`, toggle the light, if it's turned on it's turned off etc.
    • '55:scene'', return the 'sceneActivation' property of a device. 
    • '55:last', return seconds since the device last changed state.
    • '101:start', and '101:stop', start resp. stop the scene with ID 101, works with table of IDs also.  

    all of the above works with tables of IDs.  
    Properties X that are not recognized are sent to `fibaro:get(ID,X)`. Ex. `55:batteryLevel`works too. 
    Some 'properties' can be set also.   

    • '55:value=1', sets the property 'value' to of device 55 to '1'. E.g. 'fibaro:call(55,'setValue','1')
    • ':R', ':G', `:B', ':color', ':armed', ':W', ':time', are other properties with corresponding 'fibaro:call(55,'setX',Y)' actions.
    • '200:msg="Hello"', push the message "Hello" to the registered phone with ID 200.
    • '87:btn=2', presses button 2 on VD with ID 87. 

    These also works with tables of IDs.  Ex. ''{200,201}:msg="Hello"'

    One way to play with this is to include the following as the only rule in 'main()'

    Please login or register to see this code.

    This uses the primitive Event.event/Event.post functions to define a rule that every 10min stops and reads in a script expression from the console and executes it. It needs to be tested on an IDE and not on the HC2!. If run with '_speedtime' set there is no 10min wait, but it gives time to other rules to execute. If run in realtime I would advise to set the ' post' interval to '+/00:00:02', e.g. 2sec. Even more daring is to set the '_REMOTE' flag to true in 'EventRunnerDebug.lua' to actually carry out the commands on devices on the HC2, kind of remote controlling lamps from a shell... In the example below '_REMOTE' is set to false and the fibaro commands are just logged to the console.

    Please login or register to see this code.

    Last example sets a variable, `myLamp` to 55 and use that in the following expressions. Variables are declared when they are first used. They can also be declared outside the script in Lua to setup variables. Useful for bringing in a home table etc.

    Please login or register to see this code.

    Fibaro's global variables are specified with a `$`prefix. Ex. `$Presence` is equal to `fibaro:getGlobalValue('Presence')`

    Please login or register to see this code.


    Daily rules
    Rule with a 'daily(<time>)' or 'daily({<time1>,...})' (or shorthand '@<time>') are treated as daily rules and is run every day at the time(s) specified. Ex.

    Please login or register to see this code.

    The first rule is run at 10:00 every day. The second at sunset-15min, and the last will be run both at sunrise+10min and sunset-10min. They all call the function `log`that also can take arguments and format like Lua's `string.format`. Ex. 'log('%a+%b=&c',4,5,4+5)' 
    Left hand side or the right hand side can contain more conditions to further specify if the action 8right hand side should be carried out).

    Please login or register to see this code.

    Right hand side of rules are <statements>. A statement can be an <expr> or some other specific commands, separated with a semi-colon ';'. This is only allowed on the right hand side of a rule, or in 'non-rules'. Ex. 'Rule.eval("a=77; a:on")'

     

    Trigger rules
    Rules that don't contain 'daily' have their left-hand side scanned for uses of deviceIDs or fibaro globals. All IDs and globals found are used as triggers to run the rule, i.e. any state change the IDs or globals. Ex.

    Please login or register to see this code.

    This will identify deviceID 66 and global 'Presence' and whenever they change state the rule will be called. The whole rule will be run so if 66 is not on or 'Presence' is not greater than 0, the right hand statement will not be executed.   
    For this to work the ID 66 and global 'Presence' need to be declared in the header of the scene like normal scenes, or the EventRunner framework will not be notified about the state changes...

     

    The reason for using intervals as triggers are that an expression like this:

    Please login or register to see this code.

    would not trigger if the lamp is turned on and presence set to >0 before 11:00, as the rule would not be run if there were no further state changes for the lamp or the global after 11:00. With 11:00 and 12:00:01 as trigger the rule is checked when we enter and leave the time interval too, making the intention of the rule to work.


    If the left hand contains 'daily', test like above will just be treated as tests and not triggers. Ex.

    Please login or register to see this code.

    This will only be run at 10:00 every day and not when 66 or 'Presence' change state. However, the tests need to be true for the statement to be executed. If need to condition a trigger with a time interval, use `..` operator.

    Please login or register to see this code.

    Because '..' is an operator, left hand and right hand are just expressions. Ex.

    Please login or register to see this code.

    If no deviceID or global is found (or 'daily') in a rule, the framework will complain. It is important that deviceIDs used are declared before the rule is declared as the scanning is only done when the rule is declared. Ex. if variables are used for device IDs they need to be setup before. Ex.

    Please login or register to see this code.


    Event rules
    Standard fibaro events from devices and globals are easiest handled with trigger rules as in the previous section. However, for 'user defined events', handlers for them can also be declared. Ex.

    Please login or register to see this code.

    Event rules can only have a single event on the left hand side, no other test/expressions. Additional test/conditions need to be carried out on the right hand side.

    The first rule in the example sets up a handler that matches events of type `{type='test', val=42}`. 
    In the second rule, 'val' can case match any value but we have specified that it should bind the variable 'x' to whatever actual value 'val' has. ('$' is used here to mark a constraint in matching, and has nothing to do with fibaro globals).
    In the third rule, 'x' is bound to the value of 'val' but we have an additional constraint that val has to be > 52.
    When we in the last expression post the event '#test{val=42}' (at 10:00) the first and the second handler will be triggered. 'x' can conveniently be used as a local variable in the rule. This allows the script expressions to be easily used together with the Event.event/Event.post primitives in the base framework.

    Assume something needs to be done every 15min on some specific days

    Please login or register to see this code.

    First rule triggers on an incoming {type='check'} event, tests if it is the right day, if so calls the checkWater function and then posts the event again in 15min. This will trigger the rule again and we have a loop checking the water every 15min. The second statement just makes the initial post to get things going. (I may probably extend the script language with a primitive for 'repeats')

    User defined functions like 'checkWater' above is just local variables bound to Lua functions. Ex.

    Please login or register to see this code.

     

    To be continued...

    Edited by jgab
    • Like 1
    • 0
    Posted

    @jgab hi! where sources? :)

    • 0
  • Inquirer
  • Posted (edited)
    19 hours ago, 10der said:

    @jgab hi! where sources? :)

    In the mentioned 

    Please login or register to see this link.

    Edited by jgab
    • Thanks 1
    • 0
  • Inquirer
  • Posted (edited)

    ...continued

     

    More functions
    There is also a rudimentary script function syntax (that may change in the future)

    Please login or register to see this code.

    If-then...

    Please login or register to see this code.

    '|| <expr> >> <statements>' should be read like an 'if-then' and many can be chained after each other. The snag is that the ';' can not be used to end the 'if-then' statement as the 'then' part can be a list of statements separated by ';'. To overcome that there is a 'double-stop', ';;' that can be used. Ex.

    Please login or register to see this code.

    This will only check the water if it is between 21-09, but will always do the 'log'. With a single ';' the log would have belonged to the statements conditioned by '21:00..09:00'.
    The reason for this syntax is that I like to write rules like this:

    Please login or register to see this code.

    Another useful function is 'wait'. 'fibaro:sleep' is not so nice as it stops anything going on in the scene while sleeping. To overcome that one can use 'setTimeout'. 'wait' looks like a sleep but uses 'setTimeout' under the hood so other rules can run meanwhile.

    Please login or register to see this code.

    If device 55 is turned on it waits 10min, then checks if 55 hasn't changed state in the last 10min and if so turns off the light. 'wait' is also useful for writing expressions to test rules.

    Please login or register to see this code.

    This turns on the lamp at 10:00, off again at 10:05, and then on again at 10:07.  

    `once`is a special function that keeps state so that it only returns true if it's argument is true but was false before. This is a convenient way to stop an event from triggering multiple times. Ex.

    Please login or register to see this code.

    This rule triggers whenever the sensor triggers and then checks if it is between 6-8. If that is true the test '06:00..08:00' needs to turn false before it will be able to turn true again, effectively only greeting people once between 6-8 when the sensor is breached.

    'for' allows for checking if a condition is true for a specified duration. It's a bit like GEA's rules that checks if something is true for a period of time. The difference is that we don't run the rule every 30s but only when one of the device IDs or globals in the expression changes state.

    Please login or register to see this code.

    The 'for' expression needs in this case to be true for 10min before it will return true. First time the expression is true a timer is started that checks in 10min if the expression is still true and then returns true. If the expression turns false during that period, the timer is canceled.
    Sometimes when the 'for' expression becomes true, one wants to reset the timer to get a new trigger the time again. Typical case is if a door is open for 10min send a notification, and continue to send an alarm for every 10min until the door is closed. So, there is a need to 'tell' the `for`command to re-enable the timer. ¨repeat' does that.

    Please login or register to see this code.

    This will log a message every 10min as long as the door is open. `repeat` returns the number of repetition it has currently done, and it takes an argument how many repeats it should do. This allows us to write expressions like below ('log' writes to the console but also return the string, that we send to the phone).

    Please login or register to see this code.

     

    Edited by jgab

    Join the conversation

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

    Guest
    Answer this question...

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