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

Event based programming...


jgab

Question

This framework has been superseded by the

Please login or register to see this link.

. The basic principle is the same but features and code has evolved.'

 

So this is for Lua coders out there...

Part of the reason for me owning a HC2 is to play around with programming, to some frustration for the other inhabitants :)

 

Anyway, I have some understanding why Fibaro have chosen the model they have. However, I have been trying a "programming model" of funneling all fibaro events into the same running scene instance (

Please login or register to see this link.

).
Normally there is a scene instance started up when the scene autostarts and/or additional sene instances started when events declared in the scene header is received. This implies the need to use fibaro's global variables to sync between scenes instances etc... an issue when combining long running scenes like schedulers with triggers of incoming events.

 

With the model in this example the "autostart" instance is the "main" scene and additional scenes started by triggered events "post" their source trigger back to the "main" scene/instance and exits. (Simplified logic below. In reality a bit more elaborate to avoid race conditions etc)

Please login or register to see this attachment.

(

Please login or register to see this link.

)


I'm currently using it in a json based scheduler but it can be quite interesting to use as a general model for coding scenes.
In particular this lends itself to a model based on matching and posting events. Two main functions are provided in this example code;

Please login or register to see this code.

Event:event(pattern,function) declares a handler for 'incoming' events.
'pattern' is any Lua key/value table matched against incoming events. The event needs at least a 'type' key, something that all Fibaro events (source triggers) have. 'function' is the handler called if the event matches.
Ex. 

Please login or register to see this code.

This prints "Hello!" when a value property event for device id 310 is recieved (given that it is declared in the scene header)
Patterns can be a bit more elaborate and may contain variables to be matched against.

Ex.

Please login or register to see this code.

Variables start with a '$' char and matched variables are available in the env.p parameter sent to the handler.
For incoming events, keys that are not part of the standard (Fibaro) event can be matched against anyway,  given that the key name corresponds to a property of that device id (it's a hack but quite useful).

Ex.

Please login or register to see this code.

(The key 'last' is also allowed and returns the last modification time - works for globals and devices)

So this allows you to handle events, so what? Well always running in the same instance makes stuff like this work

Please login or register to see this code.

'a' is just a local Lua variable keeping its value between, in this case 3, event invocations.... 
'env.last' is the time in seconds since the event was last invoked.

 

Recieving events is all good, but being able to post your own events makes things even more interestings, and actually turns it into a programming model...

Please login or register to see this code.

'event' is a Lua key/value table (with at least the mandatory 'type' key). Time is a either an absolute time, number, seconds since 1970 e.g.  'os.time()' or a string;

"10:00" representing 10.00 today

"+10:00" representing 10 hours from now.

"n10:00" representing the next 10.00 o'clock. Called after 10.00, it becomes 10.00 the next day.

"Sunset" represents sunset hour. "Sunset+10" and "Sunset-10" allowed, likewise for "Sunrise"


Specifying delays like this allows for scheduling events to happen in the future, the base for 'loops' and schedulers...
If time is omitted the event is posted immediatly.

Ex. an event that does something every hour.

Please login or register to see this code.

To run the above every hour we just need to start it with

Please login or register to see this code.

The event matched is available in 'env.event' and is just reposted at the current time + 1 hour.
So, in the same "programming model' incoming fibaro events can be mixed with own events and 'loops'.

A lot of nice to have functions can be built using this model. A more general "scheduler" could look like this.

Please login or register to see this code.

Please login or register to see this code.

This will post an event {type='quarter'} every 15min, that can be acted upon by some other event handler...

Please login or register to see this code.

Will reuse the same event declaration for something that reschedules every 30min..

To be able to schedule Lua functions to be invoked, an 'action' handler can be declared.

Please login or register to see this code.

Then we can do

Please login or register to see this code.

The scheduler wil post the 'action' event every 15min, and the 'action' handler will call the function...

There are some more code in the attached example to showcase other variants of schedulers based on this. 


The code is setup to be able to run offline and simulate time to see the action (see variables in the beginning of file)
It is of course possible to Event:post your own Fibaro events for simulation and debugging.
It is possible to use FibaroSceneAPI. I do most of my development in ZeroBraneStudio on a Mac.
Still better optimisation, error handling needs to be implemented, but it runs as is... :)

 

A setup that is quite convenient for a scene is to have a handler that runs every
midnight and initializes upcoming events for the day, like sunsets/sunrises,
stuff from calendars etc. (More examples in the included code...)

Please login or register to see this code.

Please login or register to see this code.

In the attached code there is a more 'full' example of a 'startup handler.

 

Will play around with this a bit more and post some more use cases...

ZEvent_030.lua - First version

ZEvent_031.lua - Fixed better time representation

ZEvent_032.lua - Small fixes and a simple emulation of net.HTTPClient to use when running offline

ZEvent_033.lua - More efficient pattern matching with variables, and the beginning of CEP...

Please login or register to see this attachment.

 - Fixed bug (lesson, never redefine json.encode). Support for inter-scene events

Please login or register to see this attachment.

 - Lots of new functions, see later post

Please login or register to see this attachment.

 - Bug fix, copy&paste miss in 035

Please login or register to see this attachment.

 - Better performance, better error handling, less bugs... and the unavoidable intro of a script representation... :-/

Please login or register to see this attachment.

 - Refactored code. Separate Event_utils file with function to run/emulate offline. Bug fixes. Event:ping/pong

Please login or register to see this attachment.

 - Utilities for running offline 

Edited by jgab
  • Thanks 1
Link to comment
Share on other sites

18 answers to this question

Recommended Posts

  • 0
  • Inquirer
  • Some use cases;

    A simple way to schedule events from a table, assuming we have the night shedule of a 'daily_init' event from the previous post

    Please login or register to see this code.

    Handlers need to be defined for each event, 'wakeup' etc.

     

    A more elaborate scheduler with day and month options could be defined like this;

    Please login or register to see this code.

    Example of usage would be

    Please login or register to see this code.

    The use case of turning off the power for the coffe machine after x minutes can also be handled. Assuming that it is on when the power exceeds
    100 and off when it is below 10 (every coffee machine is different).

    Need to declare power value trigger in the scene header for the coffe machine id...

    Please login or register to see this code.

    Event:post() returns a reference that can be used with Event:cancel(ref) to cancel the event if it has not been posted yet (uses clearTimeout).

    Matching against variables ($) can use simple constraint tests like <,>,==, etc. ('$_' is an anonymous var)

     

    To test the logic for the coffe case one can post events simulating power changes, like;

    Please login or register to see this code.

     

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Another example.

    One way of thinking about an event based model is that the scene logic becomes like a state machine, where transitions are triggered by external or internal events. Scenes are by nature asynchronous and modelling the logic as a state machine can sometimes help with structuring the code in more complex scenes.

    There is also the net:HTTPClient() that is asynchronous, making it difficult to decide when a a http request has completed (unless chaining the whole program through succeess handlers...). Ex, creating a variable through the Fibaro HTTP API sometimes haven't completed before you need to access it... One approach is to have the http  success handler post an event that is picked up by an event handler that carries on with the scene logic, knowing that the variable has been sucessfully created... kind of the first steps in a 'startup' state machine...

    Ex.

    Please login or register to see this code.

     

    Link to comment
    Share on other sites

    • 0

    I can certainly see the power in this and does seem to suit the HC2 use case well. A bit over my head for a quick skim read during lunch, but kudos for the write up, it certainly offers new and more powerful ideas to those that have the time and inclination to put the effort into making the more complex automations. Hopefully one day that will include me, but for now I don't have the time to dedicate to it. 

     

    Are you a coder by profession? 

    Link to comment
    Share on other sites

    • 0

    Hi @jgab

     

    Nice write up... Thanks for taking the time to share. 

     

    Have you seen @Sankotronic main scene for time based events?  I use this for a my scheduled and repeated tasks ....

     

    -f

     

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • 2 hours ago, AutoFrank said:

    Hi @jgab

     

    Nice write up... Thanks for taking the time to share. 

     

    Have you seen @Sankotronic main scene for time based events?  I use this for a my scheduled and repeated tasks ....

     

    -f

     

    Hi thanks,

    Yes, and it is great to see all the nice scenes contributed by people, and also their dedication in supporting the community  to learn how to use them.

    I have gone through my fair share of schedulers and have written I couple myself. The latest is a json scripted scheduler that runs my home at the moment.

    In general though, there are always some strange use case, integration with some other scenes, some new events to handle, that hits the limit of pre-made scene. Don't misunderstand me,

    it is just these well decided limits that makes the code useful and understandable by most people, and it is an art in itself to draw that line. However, people requests features, you add them in and after a while it becomes very complex . I have been there :-)

    So this is a bit back to basic, just having a simple model and most actions in lua.

    Want to add the events from GCalendar from @

    Please login or register to see this link.

    , well a few line to add..

    Please login or register to see this code.

    This gives a lot of freedom to combine and mix scheduler (continuous running loops) with handling fibaro events from devices to do asynchronous http requests.

    However, I see it mainly as a programmers toolbox.  I publish this code here because I'm playing with this model to become the engine in my next scheduler.

    I'm also aiming for a more expressive event model type CEP (correlate events with each other) or why not record triggers and actions and train a neural network so the lamp knows to turn on just before you press the button :-) 

    Anyway, I will continue to use this thread to post more code snippets related to this model as it progresses..

     

    P.S I really want to push for the ability to develop/run/debug code offline on a PC/Mac using something like ZeroBraneStudio ( @riemers 

    Please login or register to see this link.

    ) - in my code example I have code that allows me to run the same scene both on the HC2 and offline, use FibaroSceneAPI to remote trigger devices on the HC2 or just simulate the fibaro calls. Just changing some variables in the header of the code. it makes all the difference and is worthwhile to invest in for novice programmers too - the HC2 is too fragile for buggy code ;-) 

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • The trick with all kind of schedulers is to find a good and flexible representation of times and intervals.
    In a previous post in this thread I gave the example;

    Please login or register to see this code.

    Pretty basic, but note that the scheduler schedules event for exact these and only these times and is thus rather efficient.
    However, a scheduler is not only depending on time to decide if an action should be carried out. Often there are other conditions depending on globals or state of devices, or combinations of things happening witin a time intervall. Therefore some schedulers have generalized rules like;

    <condition><actions>

    Here , the scheduler often resorts to testing the rules with regular intervals (like GEA defaulting to testing rules every 30s).
    This is actually quite reasonable and allows for very complex schedulers/rule systems. The drawback is that it is impossible to predict when the next action will be invoked. Just test at every interval and if condition is true call action.

    With the event model it is easy to make 'schedulers' that do stuff at certain intervals and with a test/filter we can achive a more generalized scheduler.

    Please login or register to see this code.

     This posts a 'ding' event every hour during Sundays. But it runs the test every hour 7 days a week. However, these tests are so efficient so they will not put any load on the HC2 - and the Event:() implementation also use setTimout() to be less CPU hungry than a fibaro:sleep() approach. It is also noteworthy that  this approach is not prone to time drifting as the handler quickly post the asynchronous user event and then reposts its own event. The user event does not add time to the scheduler loop and the short time it takes for the loop is not measurable with the clock. It could add up eventually if you use the "+HH:MM" format, but is quite easy to compensate for that...


    A flexible, tried and true, syntax for expressing time intervals is the

    Please login or register to see this link.

    t. A bit tricky at first glance but every sysadmin knows how to setup tasks to run at specific times and intervals using that format. Crontab runs every minute and consists of time patterns and a tasks to be carried out when a time pattern is true. 
      
    <minute><hour><day><month><weekday><year> <task>

    <minute> -> 0 .. 59
    <hour> -> 0 .. 23
    <day> -> 1 .. 31
    <month> -> 1 .. 12
    <weekday> -> 1 .. 7 , Sunday=1 and Saturday=7
     
    letter lowercase abbreviation allowed for days and months. Ex ‘sun,mon,tue,..’ ‘jan,feb,mar,…’
     
     *         -> matches any value for that field. Ex. ‘*’ in minute field matches 0 .. 59
     x,y,z   -> matches the listed values for that field. Ex. ‘1,20’ in month field matches the 1st and the 20th day of month
     x-y     -> matches values in interval for that field. Ex. ‘sun-sat’ in week day field matches 1,2,3,4,5,6,7
     x/y     -> matches  values starting at x with y increments. Ex. ‘0/1’ matches ‘0,1,2,3,4,5,…    ‘0/15’ matches 0,15,30,45   ‘1/2’ matches ‘1,3,5,7…’
     
    With the included Cron:new(<minute><hour><day><month><weekday>) function in my example code it is easy to achieve a similar function. The function returns a time pattern function that is true for times specified;
    Ex.

    Please login or register to see this code.

    Cron:new also allows for the minute field to be replaced with ‘@Sunrise’ or ‘@Sunset’ and hour field with offset in minutes.
    Ex. "@Sunrise -10 * * mon-fri” means sunrise-10min weekdays (Monday to Friday)
          "@Sunset 15 * * sat-sun” means sunset+15min every Saturday and Sunday


    Because the test is called every minute it is easy to combine test

    Please login or register to see this code.

    (If you need to run the tests more often, say every 30s like GEA, the Cron:new function need to be tweaked a bit because it would return true twice every minute)
     

    (Ok, this is a bit different, but the forum merged my posts.)

    So for now this is more of a party trick;

    Please login or register to see this code.

    The idea with CEP (Complex Event Processing) is to combine, filter and correlate events (sometimes known as event stream processing).

    Here Event:event({'and',<Event1>,<Event2>,...,<Eventn>},function() ... end) calls the function when all events have matched. It is possible to code with single events in a state-machine fashion, but this is shorter to code and it is easier to pick up results  from the variable macthes.

    With just Event:event and Event:Post we can combine events and repost as more complex events. I.e. 'door opens' and 'lightswitch is turned on' -> reposted as 'someone entered the room'

    To make this really useful in a home automation context we would need to be able to express event patterns over a window of time;

    i.e. 'someone opens the front door', 'turns on the hall lamp',' trigger sensor in the stairways', 'trigger sensor in bedrom' -> 'someone came home and went to bed' (not a great example).

    However, this would need some more complex handlers/syntax and I'm not really sure I can come up with use cases worth the effort - maybe just throw all events at a neural network and let the house outsmart me ;) 

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Maybe this is a more useful example then the fibonacci case in the previous post.

    Here we call Apple's icloud service to get the ios device location. Response time can differ depending on load and network, and here we combine two requests for two users into one answer with an event 'and' that picks up when both requests have completed.  
     

    Please login or register to see this code.

     

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • In the latest version (0.34) (besides fixing a terrible bug), a small change now takes incoming args from fibaro:args() and repost them as events. That means that we can easily send and receive events between scenes, and thus distribute our event logic across multiple scenes. That could be useful due to performance or script size constraints. In particular it is easy to implement a client/server model for calling and receiving values from subroutines defined in other scenes;

    Please login or register to see this code.

    SceneID is the ID of the scene that the code runs in and is declared in the beginning of the Event example code. (is there any way to get the ID of your own scene like fibaro:getSelfId()?)

    IOSLocator is the ID of the "server scene" in this example.

    The client post a 'sceneMsg' event to the IOSLocator scene, that returns a 'locationResponse' event with the result.

    On the server scene it looks similar

    Please login or register to see this code.

    The server scene picks up to what scene the response should be returned to from the ._from field in the event. This way the server doesn't have to hard code where to send responses and it becomes very much like a subroutine call that can return values, and serve any number of clients.

    It is important that the 'client scenes' and the 'server' scene have unique SceneIDs (preferable the actual scene IDs)

    Here the server scene responds to a 'locate' event, but a lot of different handlers for good to have subroutines could reside in the 'server scene' and it is almost a shared library.... 

     

    Of course, instead of declaring the 'sceneMsg' handler one can define lua functions for sending and returning remote events

    Please login or register to see this code.

    and this is now available in the SDK from 0.35.1

       

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Another piece in the toolbox.

    The previous example of looking up an iPhone's position using the iCloud service is not always reliable - sometimes Apple will not find the phone. It's primary use is to see if someone is at home and can then be combined with an arp lookup service. There are ready made VDs for this but is quite easy to implement to get it into the event model. This will send a {type='arp', device='NAME', status='ON/OFF'} event every time a device becomes visible for arp or hasn't been seen for 120s. The arp behaviour and the 120s delay varies from types of devices but it seems to work for me with four 6S and 7S on the network. Combining these events with what we get from iCloud and it is getting reasonable...

    Please login or register to see this code.

    but then one of the kids forgets the iphone at home.... so the next piece is to combine this with sensor events...

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Having experimented with the model during the last days, implementing common types of scenes, I have extended the model with more 'Event functions' to be more convenient to use;

    Please login or register to see this code.

    The general code structure of a scene using this model then looks like this

    Please login or register to see this code.

    If the latest ZEvent_0.351 example code is run offline, in ZeroBraneStudio or similar, it will give the following example output with the example handlers provided...

    Please login or register to see this code.

    The code starts up, loads the event declarations (calls main() etc)

    Then follows an example of a loop. This one runs 5 times with 3sec intervals. If the number of iterations are left out (5 in this case) it will loop forever, but asynchronous giving time for the other code. It is possible to break out of the loop by having the function return Event.BREAK 'function(env) return Env.BREAK end'

    Please login or register to see this code.

    This loop example gives the following output. Note that it prints the log statement every 3 sec. env.p.i conveniently is set to the loop index.

    Please login or register to see this code.

    If there are many event handlers matching the same incoming event, all of them are called,

    Please login or register to see this code.

    Here we post one event and triggers 2 handler. Which gives the expected output;

    Please login or register to see this code.

    Event handlers are called in the order that they are defined. If a function handler returns Event.STOP then additional event handlers will not be called for the specific incoming event.

    Please login or register to see this code.

    and thus only prints the first Log message

    Please login or register to see this code.

    An event handler can also remove itself. A reference to the event handler is available in env.myself given to the handler.

    Please login or register to see this code.

    The second posted event is reported as an unhandled event because there is no handler for type='test3' events anymore...

    Please login or register to see this code.

    There are some special event forms, where {'not',<events>,<timeout>} is one and calls the handler if the specified events have not been seen for <timeout> time. If an event is seen, the timer is restarted.

    Please login or register to see this code.

    Here we post an event 7 seconds into the future (we post at 17:18 for 17:25). The 'not' handler triggers after 5 seconds (17:18+5) as expected. The test4 event is posted at 17.25 and resets the 'not' timer which means that the next 'not' event is triggered 17:30 and then continues. If a 'not' handler returns 'true' it stops (Note to self: should probably use Event.BREAK here) 

    Please login or register to see this code.

    Another special event form is 'seq', where {'seq',event1, optional-delay1,event2,optional-delay2...,} calls the action handler if the events arrives in the sequence specified with the optional maximum delay in between. This allows us i.e. to write a handler that waits for keypress 1,2,3 with max 2sec between the keys in a quite simple way. (The example also have a handler to rewrite the fibaro event structure to a simpler event structure.) The code then posts 3 keypresses with 2 secs between to simulate  a correct sequence. If delays are increased or key order changed the handler will not trigger. 

    This could be useful to sensors or doors are breached in a specific order.

    Please login or register to see this code.

    Which gives the output.

    Please login or register to see this code.

    A special form is also for fibaro's property event. If the deviceID is an array of IDs, it will trigger for all of them (we actually creates multiple handlers with the same action function)

    Please login or register to see this code.

    In this case the only way to find out what device id triggered is to look into the env.event parameter that contains the matched event

    Please login or register to see this code.

    There is also an 'and' form (used to be called 'join') .  {'and',event1, event2,...,} calls the action handler if all events are matched in any order. The advantage to this form instead of just declaring multiple separate event handlers  is that matched variables can be picked up in one place in the code. In the fibbonachi example we pick up the result from both fib(x-1) and fib(x-2) and can return (post) the sum of them.

    Please login or register to see this code.

    For higher values than 3 this can generate a lot of debug output.... Btw, for events with a key _sh=true will suppress logging.

    Please login or register to see this code.

     

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
    16 minutes ago, jgab said:

    Having experimented with the model during the last days, implementing common types of scenes, I have extended the model with more 'Event functions' to be more convenient to use;

    Please login or register to see this code.

    The general code structure of a scene using this model then looks like this

    Please login or register to see this code.

    If the latest ZEvent_0.35 example code is run offline, in ZeroBraneStudio or similar, it will give the following example output with the example handlers provided...

    Please login or register to see this code.

    The code starts up, loads the event declarations (calls main() etc)

    Then follows an example of a loop. This one runs 5 times with 3sec intervals. If the number of iterations are left out (5 in this case) it will loop forever, but asynchronous giving time for the other code. It is possible to break out of the loop by having the function return Event.BREAK 'function(env) return Env.BREAK end'

    Please login or register to see this code.

    This loop example gives the following output. Note that it prints the log statement every 3 sec.

    Please login or register to see this code.

    If there are many event handlers matching the same incoming event, all of them are called,

    Please login or register to see this code.

    Which gives the expected output;

    Please login or register to see this code.

    Event handlers are called in the order that they are defined. If a function handler returns Event.STOP then additional event handlers will not be called for the specific incoming event.

    Please login or register to see this code.

    and thus only prints the first Log message

    Please login or register to see this code.

    An event handler can also remove itself. A reference to the event handler is available in env.myself given to the handler.

    Please login or register to see this code.

    The second posted event is reported as an unhandled event because there is no handler for type='test3' events anymore...

    Please login or register to see this code.

    There are some special event forms, where {'not',<events>,<timeout>} is one and calls the handler if the specified events have not been seen for <timeout> time. If an event is seen, the timer is restarted.

    Please login or register to see this code.

    Here we post an event 7 seconds into the future (we post at 17:18 for 17:25). The 'not' handler triggers after 5 seconds (17:18+5) as expected. The test4 event is posted at 17.25 and resets the 'not' timer which means that the next 'not' event is triggered 17:30 and then continues. If a 'not' handler returns 'true' it stops (should probably use Event.BREAK here) 

    Please login or register to see this code.

    Another special event form is 'seq', where {'seq',event1, optional-delay1,event2,optional-delay2...,} calls the action handler if the events arrives in the sequence specified with the optional maximum delay in between. This allows us to write a handler that waits for keypress 1,2,3 with max 2sec between the keys quite simple. (The example also have a handler to rewrite the fibaro event structure to a simpler event structure.) The code then posts 3 keypresses with 2 secs between to simulate  a correct sequence. If delays are increased or key order changed the handler will not trigger. 

    This could be useful to sensors or doors are breached in a specific order.

    Please login or register to see this code.

    Please login or register to see this code.

    A special form is also for fibaro's property event. If the deviceID is an array of IDs, it will trigger for all of them (actually creates multiple handlers with the same action function)

    Please login or register to see this code.

    In this case the only way to find out what device id triggered is to look into the env.event parameter that contains the matched event

    Please login or register to see this code.

    There is also an 'and' form (used to be called 'join') .  {'and',event1, event2,...,} calls the action handler if all events are matched in any order. The advantage to this form instead of just declaring multiple separate event handlers  is that matched variables can be picked up in one place in the code. In the fibbonachi example we pick up the result from both fib(x-1) and fib(x-2) and can return (post) the sum of them.

    Please login or register to see this code.

    For higher values than 3 this can generate a lot of debug output....

    Please login or register to see this code.

     

    Hi @jgab

     

    Thanks for sharing the ideas and sample code. I've been reading your posts over the last while and to be honest I understand only some of it.

    It looks like a advanced scheduler...Is your own system setup and running this way ?

    I use the Main Scene developed by @Sankotronic, Have you seen this ?

    What is the main advantage of your approach over something like the approach used in Main Scene referenced above.

     

    -f

     

     

    Thanks

    -f

    Edited by AutoFrank
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • @AutoFrank It's building blocks that allows you to build advanced schedulers to your own preferences (or any other scene logic). In the previous post I have shown some approaches, from scheduling from GCalender, to a UNIX Crontab version. I have in the past used and coded many schedulers (

    Please login or register to see this link.

    ,

    Please login or register to see this link.

    ). Where you end, up as I tried to explain in earlier posts, is that a scheduler doesn't only act on 'time of day'. There are usually a lot of additional conditions that needs to be true if a scheduled actions should be carried out. That typically means that the schedule designer allows the users to add lua functions or some simple script language to specify these extra conditions. However, it is very difficult to cover all cases, especially for conditions depending on other sensors states, events etc. It usually becomes very messy and a lot of global fibaro variables...

     

    As an example, the GEA scheduler is very elegant and with rule expressiveness that can be adopted to most use cases. I was inspired by GEA when doing what became my

    Please login or register to see this link.

    that I have been using for some time now. However, GEA was a traditional 'loop-scheduler' e.g. 'run-rules-every-30-sec-and-fire-when-rule-is-true'. This meant that it was not suitable for actions that needed immediate actions, like turning on a light when a sensor trigger. They later (3.0) added support for triggers (fibaro events declared in the scene header) to cope with that. However, these trigger rules was run in new scene instances and thus needed to use global (panel) variables to communicate with the other loop rules - like a timer to turn off the light in the bathroom. 

     

    This is a general problem for scene coders. If you have a scene that needs to coordinate more than one trigger you have to either use global panel variables for coordination or some hack like polling the values of the sensors/devices in a busy loop. The former is prone to race conditions and the latter doesn't scale and makes a messy code...

     

    So, If I'm allowed to say so myself, the elegance with the example code here is that here all triggers (or events)  end up in the same "mailbox" in the same scene instance. And as a coder you retrieve events from the mailbox by declaring event handler that specify a matching pattern for the events to handle (some languages like Prolog and Erlang has this type of matching of rules). That they run in the same scene instance is the magic. It means that I can write code reasoning about incoming triggers from sensorA, sensorB, and sensorC without having to resort to save some state in a fibaro global because the sensors were all starting new instances of the scene. 

    Having coded with this model I don't think I can go back to the old model anymore... it so much easier to use lua variables for shared states between rules. 

     

    So this is not just building blocks for creating schedulers, it is very useful building blocks for making scenes that need to coordinate many asynchronous triggers and events to do something intelligent, like various auto light scenes, turning on and off devices depending on sensors etc. (and I have some interesting ideas towards the AI direction...)

     

    Ex. the case of turning on the light with a sensor and having a timer turning off the light or being rescheduled if new movements are detected. (a small trick here is to start the timer when the sensor becomes safe again, because continuous movements may keep the sensor breached for a long time, longer than the timer.

    Please login or register to see this code.

    The first handler turns on the lamp if a movement is detected and kills any previous timer. 'timer' is a lua variable that keeps it's value between incoming events. No need to store it or any timer value in a global panel variable. The second handler starts the timer to turn off the light when the sensor has been safe for 1 minute. Pretty clean and this code snippet can be combined with other event handlers in the same scene without clashing or blocking each other...

     

    A more complex example. An alarm function. This scene triggers on incoming events from 2 smoke sensors (fireSensor) and posts a fire event that is picked up by a handler that starts to blink lamps (only 5 times, not easy to find your way out if lamps blink ;) ) and also starts an extra alarm siren. It also sends off a remote event to a notifier scene. Then there is another handler in the same scene that reacts if any key is pressed on a fibaro remote and stops the blinking lights and turns off the extra siren (the smoke sensors must be turned off manually). 

    And then the scene ends with posting 2 fake events to test the alarm scene. I think that is pretty condensed and understandable code - but I wrote it :-) 

    Please login or register to see this code.

    So, I haven't looked that deep into the Main scene by @Sankotronic, but it looks like a traditional scheduler that try to be as flexible as possible when it comes to invoking actions or take other conditions into considerations. It also have a nice UI. He also have many other scenes like smart lights and appliance monitoring. Having separate scenes for these use cases makes them easy to understand and is why they are so greatly appreciated by users (and a nice UI). If it solves your problems hang on to it with your life :-) 

    However, under the surface it is all rules about events. GEA tries to solve all these use cases in one scriptable scene. I try to create some easy to use programming concepts to deal with asynchronous events and time based invocations, because my experience is that it works up to a certain level but  when things get more complicated you need to have an underlying model to help you deal with the asynchronous nature of scenes and triggers. Many 'gremlins' come out of this complexity, and the model that Fibaro chose with spawning new scene instances for triggers does not help the inexperienced developer, and makes it hard for the experienced.

     

    So, I'm recoding most of my scenes with this model now. The approach is not to have a big scene that handles everything but instead have a couple of specialised scenes (scheduler, alarms, presence, event pattern detection, library of  voice/notification functions, etc). Some of them are up and running and are extended versions of some of the earlier posts (my alarm fire alarm scene is similar to the one above). All build on this model and sending and receiving events between them (Event:remote(scene_id, event). It is quite easy to make publish/subscribe contracts between scenes and let scenes restart their handlers when global configuration data changes (like a JTable) - and the the model allows me to focus on the event interaction logic and I don't have to deal with the rest of the challenges with fibaro's asynchronous scene model. I will come back with more posts as this proceeds.

     

    Btw, I was a bit worried about the performance but I see quite modest CPU usage. Earlier schedulers that used to run all rules like every minute had a tendency to create quite spiky CPU usage patterns (even though they never hit the roof)... here things seems to be more smoothed out as each event loop tends to be its own "scheduler" running on its own times and/or triggered by events. 

     

    The other thing is that this framework allows me to code and debug the scenes on a PC/Mac with something like ZeroBraneStudio, with timers and events in real time or speeded up and with some semi communication back to the HC2 with the FibaroSceneAPI. I can have test cases for my scene by posting sequences of events that simulate triggers on the HC2 and verifies the scene logic. That makes all the difference for my coding productivity...

     

    So, the reason I post this is first that it makes my head clearer by writing down my thoughts. Secondly, it would be cool if someone could find any holes in my logic/approach and/or have ideas for improvements. 

    If you are an inexperienced coder you shouldn't jump on this code but wait for ready made scenes to come out. If you are an experienced coder you may see the benefit of this model and you are free to try it out or to copy parts of the concepts presented (ex. have look at how to have a shared global mailbox variable and avoiding race conditions, i.e. the code around _BOXNAME). Beware that the code is still not very stable as I continuously come up with new ideas...

    Coding is fun!

    Edited by jgab
    Link to comment
    Share on other sites

    • 0

    Really looking forward to seeing how this all progresses. Still way above my current understanding (and/or time to sit down and read it properly), but I can read the code as psudeo logic and get most of the meaning of what you are doing. I get the fact that this better suits the underlying logic that Fibaro use in the HC2 and can see how it can remove the gremlins that I have experienced myself trying to manage state of devices/scenes and handle events that are constantly happening (time of day changing, motion detected, light switches being activated). 

     

    Kudos for exploring the possibilities, I'll wait for you to make a bit more progress and hopefully you can post some threads in the future like Sankotronic has with scene examples and explanations so that I might use them in my HC2. 

    Link to comment
    Share on other sites

    • 0

    Interesting approach. I´m kind of surprised you have not received feedback by more of the capable programmers attending the forum. Please update and keep us informed about your progress!

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • So, I have been busy coding my home scenes using this model which has gained me some interesting insights (and hunted down some bugs) and I'm now up to

    Please login or register to see this link.

    .
    Rules are written in Lua instead of the traditional configuration/scripting which means that a typical rule for turning on/off lights may look like this;

    Please login or register to see this code.

    Another handler schedules the 'Evening' and 'Night' events depending on a calendar. F.off,F.on,F.log are convenience wrappers for Fibaro functions.
    Anyway, I like that the rules are expressed in Lua but having many of these kind of rules makes it a bit "verbose" so I introduced a minimal scripting option.
    This means that Event:event(pattern, handler) now also accepts a 'table script' expressions by wrapping the handler in a call to _eval

    Please login or register to see this code.

    This allows the rule above to be written

    Please login or register to see this code.

    which saves some typing. However, still regular lua functions work well too.
      
    The _eval function is quite generic and is provided here if someone needs to add "scriptable" rules to their code and it is also quite easy to extend.

    Please login or register to see this code.

    The code has some dependencies on some functions I use...

    Please login or register to see this code.

    Examples of expressions and examples to run are:

    Please login or register to see this code.

    It is easy to extend the evaluator with additional functions. The 'env' parameter is not used for now but is useful for adding hooks into  the code for "variables" and "script functions" in the future... so the next post will have an extension is to add support for "variables" from the 'HomeTable' or similar structs

    Edited by jgab
    Support for table constructor
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Ok, we are diverging a bit from the Event model now. 

    Assume that we want to add 'variables' to the _eval function in the previous post.

    If we have a 'HomeTable' type of struct like 

    local jTable = {a = {b=8}, b=7}

    If you do a 

    Please login or register to see this code.

    it will work, but if we change jTable.a.b to some other value the rule will still use the old value because when the table is constructed jTable.a.b will return 8 and thats it.

    To make the rule always use the current value of jTable.a.b we need some more code...

    Please login or register to see this code.

    Almost as much code as the _eval code but added  it gives us much more possibilities.

    Assume jTable = {bar=89, bark=88}

    Please login or register to see this code.

    Accessing a variable that doesn't exist gives an error but setting a variable that doesn't exists creates it - like your old Basic language.

     

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Another use case. Event handler that records all light events during the last week. When '@away' event is received (a have another home presence scene) a simulation starts and posts all light events that happened the same day a week ago, and repeats it next day. And continue to do so until a '@home' event is received. 

    Please login or register to see this code.

     

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
    31 minutes ago, jgab said:

    Another use case. Event handler that records all light events during the last week. When '@away' event is received (a have another home presence scene) a simulation starts and posts all light events that happened the same day a week ago, and repeats it next day. And continue to do so until a '@home' event is received. 

    Please login or register to see this code.

     

    Wow that is a really nice feature.

    This event handler is far above my understanding, wondering what you'll come up with next ;)

     

    Link to comment
    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
    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...