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

JShed, a scheduler with json syntax


jgab

Question

Hi,
I have been tinkering with my own

Please login or register to see this link.

for some time now. I know that there are many good examples out there like GEA but I like to write my own code - easier to debug that way ;-). The reason I post here is that I would like to share my experience and code for anyone that wants to play with it. Maybe it can inspire someone to create something more useful for the community...? I'm continuously being inspired and learning things from this community, thanks!

 

Current version: 

Please login or register to see this attachment.

Warning1: Using this code is on own risk, it is not easy and there are millions of ways to make mistakes. Just getting the json syntax correct after a glass of wine is a challenge :-)
If you want an "automated home" instead of a "programmable home" I really recommend purpose built scenes from Sankotronic, Jompa etc...  To use this some coding skills (way of thinking) is required - I've written the code and I still make mistakes defining rules 

 

I couple of goals with this project;

  1. to implemented the rules in json, just because that gives an easy text representation of rules and I had some ideas about composing and pushing rules from an external client. Maybe an easy table in Excel with days and hours and actions that's pushed to the HC2 remote, or actions defined in google calendar.. Haven't implemented that but have grown fond of json anyway...
  2. to be expressive enough so that I could fold all my other scenes into one scene and set of rules. That I have done now.
  3. to both have timer based rules (classic scheduler) and immediate trigger rules (e.g. device events) and to run them in the same scene instance allowing them to share local scene data. I know GEA allows for both but I'm not sure one can share local data (possible?). HC2 spawns a new scene instance for each event so some trickery is needed. I think I solved that. This means that scenes that typically needed a global variable to synchronize between events or states now can just use local variables in the scene.
  4. it can be tested and

    Please login or register to see this link.

     (
    Thanks to

    Please login or register to see this link.

    ). From ZeroBrane, triggers do not work but can be simulated by queuing up events or from the console. There isa limited complete offline mode. See variables in the beginning of the scene (_remote and _offline)
  5. and an engine to play with use cases. My goal is to find some way to abstract templates for typical use cases that could be compiled to run as rules.... 

 

It is still under development (templates, better debugging, state-machine format? etc.) but the reason I post now is that it currently runs what I did in all my old scenes. I will continue to update it as it progress. Good ideas are welcome!

 

Disclamer2: I'm not sure I have tested all parts of my code as I mostly run my own rules, e.g. there are bugs for sure. It may lack some obvious commands (RGB) etc. because I don't have these devices. However, it is easy to add, see below. 

 

The format for a rule is a json array on toplevel

Please login or register to see this code.


each <expr> is evaluated from left to right and stops if an <expr> return false. Typically the leftmost expressions tests if some conditions are true and the rightmost carry out the actions that should happen in that case.
A generic <expr> is a json value/pair of format:

Please login or register to see this code.


 <expr1> is the implicit first parameter/argument to the command. Ex.

Please login or register to see this code.


Arguments can be json arrays for many commands. A json string that starts with '$ is interpreted as a variable. Variables are defined outside the rules with the lua function:
defvar(name,value,index)
Ex.

Please login or register to see this code.


the value can be arbitrary deep ex. 'house.floor.room.lamp'. A common root like with homeTable/JTable also works well.
the 'index' parameter makes a reverse map so given the value the variable can be found, and allows the debugging to show the variable name instead of just the value.  '$phone.tom' is a 'macro' that expands to {'var':'phone.tom'}, an expression returning the variables value
Similar '@var' expands to {'glob':'var'}, which accesses a global variable e.g. fibaro:getGlobal('var')

Variables are auto created at first use in an json expr if not already defined with defvar.

 

Many commands are tests and return true/false
Ex.

Please login or register to see this code.


This tests if the lamp in room1 has been on for 3 hours , can also be expressed as

Please login or register to see this code.


The 'id command accepts a single device or an array of devices and a optional number of tests 

 

The example also introduces '!macros, The are some json string on the format '!TEXT' that expands to expressions,
which is more convenient than writing the full json expression.
'!$test.x=$test.x+1' expands to

Please login or register to see this code.


'[email protected][email protected]+5' expands to

Please login or register to see this code.


'==' is used to test equality '!@timeofday==Night' expands to {'==':[{'glob':'timeofday'},'Night']}
'![10:00]' expands to {'time':36000}, that returns true if it is 36000/60 minutes since midnight, e.g if it is 10 o'clock.
'![07:50,11:33]' expands to a similar {'time':[...,...]} testing if time is between 7:50 and 11:33..
there is also a "bracketed" time test, '![05:00,$Sunrise,07:00]' that test for the middle time but not sooner than the leftmost and not later than the rightmost. Of course you can use expressions/variables etc that evaluates to seconds of the day. In fact !'!10:00' is just a macro expanding to seconds

 

Standard rules that are run on specific intervals are declared:

Please login or register to see this code.


Ex.

Please login or register to see this code.


This decalares a rule that runs with the default 60s interval. It first checks if the lamp is on and has been on for 600s, if not it stops. Else it checks if the movement sensor is off and has been off for 600s. If that is true it continues to run the commands that turn off the lamp and log a statement to the fibaro console.

A rule to be run every 5 seconds is declared; event(5,"[... ]"). Many rules that run on different intervals can co-exist.

 

There are also trigger rules.

Please login or register to see this code.


Ex.  

Please login or register to see this code.

 
Assuming that 331 is the id of the toilet sensor. So the trigger syntax is "{'value':331}". Predefined $vars are allowed too, ex. "{'value':'$room1.lamp'}"
To invoke this rule, '331 value' must be declared in the scene header (as normal is done) for the scene to receive the event.

 

Implementation detail: The first instance of the scene runs the scheduler of standard rules and normally never terminates. Additional instances of the scene starts when it triggers on incoming events declared in the scene header. For now it supports  sourcetriggers that are values, globals, CentralSceneEvents, and SceneEvents. It then uses a shared global (that is autocreated) to hand the event over to the first scene instance so that the declared trigger rule can be run in that context sharing local variables. It uses a simple read/write flag model to handle syncronization and I have stress tested it and have seen no race conditions. However, it is best to set a high number of allowed instances for the scene to be on the safe side. The method introduces a small delay (average 250ms) on events. Many events triggered at the same time may also cause additional small delays. However, to have these two programming models (polling and triggering) converge in one scene instance is worth it... I will implement more types of events in the future. Planning to buy a Fibaro keyfob to play with that new event format...
  
So the normal time testing commands test against minutes. Ex. event("['![10:00]',...]")
and because that example runs at the default interval every 60s, it works very well. However, if the rules run more often i.e. event(30,"['![10:00]',...]") the rule will trigger twice the first minute 10'oclock. To avoid this there is a 'once' command, {'once':'![10:00]'} (shortened in macros to '!^[10:00]') that only returns true if the value has changed since last time. 
This is also useful for rules like event("['!^@Timeofday==Day',...]"). If not 'once' was used it would be true every minute when the rule was run and not just the first time 'Timeofday' was set to 'Day'. Now '@Timeofday==Day' need to be false before it will return true again. There is also a {'truefor':<expression>, 'val':<seconds>} that returns true only if the expression has been true for a certain time (I haven't found a great use for this myself yet. Maybe if you want to send a notify every x minute if a door is open...)
Anyway, beacuse rules are invoked at predefined intervals, some thinking is needed to get rules correct. Watch up with movement sensors reset times, if people turn on and off switches in shorther intervals than your rules are run etc. The 'last' property gives the number of seconds since the device changed value is very useful to get around this...

 

Oh, there is also the 'cron' like time/date test to make more flexible time conditions:
'!{<min> <hour> <day> <month> <weekday>}'  and expands to {'interval':'<min> <hour> <day> <month> <weekday>'}.
It uses the standard UNIX cron format also used and explanied in my

Please login or register to see this link.

Ex. '!{0/15 9,13,17 * jun-aug sat-tue}' Is true every 15 minutes on the hour 9,13, and 17 during June to August at Saturday,Sunday,Mondat, and Tuesday...  '!{0 0/2 * * sat,wed}' is true once every even hour on Saturdays and Wednesdays.

 

Ok, there are many more features. There is a '!!' macro that turns on logging of a rule. That is convenient because logging can be turned on after some tests have returned true to not litter the logg to much. i.e. event("['![10:00]','!!',...]") only logg if it is 10... ('!!' macro expands to {'startdebug':true})
Logging can also be turned on per rule;  event("['![10:00]',...]").debug(true)
Debugging can also be turned on for all rules by setting the local lua variable 'debugAllRules' to true. There is also a 'debugLevel' that controls output and a 'debugProps' that if true debugs every call to fibaro:getValue and fibaro:getGlobal.
Per default a statistics is shown in the log  every night at 1:00, showing how many rules and triggers have been invoked.
$variables can be functions. defvar('test',function() return 'Hello' end) will make '$test' return 'Hello'. '$Sunset','$Sunrise','$Now' is predefned variables using that approach.


New primitive commands are quite easy to add to the "engine" with the defun function.

Ex. I have the Sonos VD to play sound and a playSonosSound(file,volume,duration) lua function. To add a {'playfile':<filename>, 'volume':<expr>} for that ;

Please login or register to see this code.

 

Functions can also be defined in json. Ex.

Please login or register to see this code.

 

If the scene is run remote in ZeroBrane using FibaroSceneAPI, live triggers does not work. However there is a hack. If you suspend the program and go to the Local Console window you can type lua expression for evaluation. Three commands are available to queue up events. 
 

Please login or register to see this code.

qVal(331,'value,60,1) while queue a "331 value" event to  be received in 60s and the value of 331 will be 1. See examples in function initOffline() in the src code. 

If I figure out how to do non-blocking read from the ZeroBrane console I could provide a more convenient command line function....

One anomaly with the FibaroSceneAPI is that it calls os.exit() if something goes wrong, i.e trying to access a non-existing deviceID on the HC2 and I can't catch that error and the scene terminates.

 

The engine is pretty efficient, commands try to optimize themeselves when they are invoked the first time, and I'm trying to avoid stressing the GC too much by being restrictive on temporary datastructures created while running. I see very little CPU load with 40+ rules and 10+ triggers.

 

The commands available now are;

Please login or register to see this code.

Second release: , Supports CentralSceneEvent. '$_src' is set to fibaro:getSourceTrigger() which can be used in the trigger to decide keys etc.

Ex. Keyfob with id 362.

Please login or register to see this code.

To check a key sequence this works:

Please login or register to see this code.

Release 03/14: , Fixed subtle trigger bug

Release 03/14:  , Now supports functions. A small step towards templates... $variables are now auto created at first use if not previous defined with defvar(...).

Release 03/16: 

Please login or register to see this attachment.

, New functionality; init(<expr>), quoting of args, generic fibaro:call, etc

Release 03/18: , 

Please login or register to see this attachment.

,Bugfixes, better error handling, better offline support. "out-of-the-box" it runs the simulation offline. 

Release 04/03: 

Please login or register to see this attachment.

, New syntax and better simulation support

Macros: 

...to be documented - for now see text above. 

 

Edited by jgab
Link to comment
Share on other sites

1 answer to this question

Recommended Posts

  • 0
  • Inquirer
  • Follow up,

    I have extracted the "framework" in "JSched" that allows me to get triggers and startup to run in the same instance. Would be happy if someone could poke a hole in the logic. As I said in the previous post I have stress tested it by sending many events at the same time and no race conditions so far. The only limit is how many parallel instances of the scene that is allowed (but thats the normal case anyway). Each new scene instance that is started due to a new event being recieved is doing a "busy wait" until the mailbox to the first instance (startup) is free to read it.. 

    Anyway, the structure of the "main" then looks like this - allowing shared locals variables between "invocation" of scene instances.... and if you do a loop/polling in your code you need to use 'setTimeout' to give time to the other processes (no fibaro:sleep).

    Please login or register to see this code.

    So, I have abstracted the framework and attached the code here (also contains stuff to run it remote from ZeroBrane): 

    Please login or register to see this attachment.

    Note. the SFrame framework is obsolete now. Have a look at the

    Please login or register to see this link.

    or the simplified 

    Please login or register to see this link.

    instead

    Edited by jgab
    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...