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
Search the Community
Showing results for tags 'example code'.
-
Tiny QuickApp Emulator (TQAE) started out as a simple modular QuickApp emulator but have evolved into a pretty good development environment. (Great intro to TQAE <here> from @Joep - thanks!) If you want to use vscode, I recommend this project that I'm currently focusing my development efforts on. /TQAE.lua v0.60 -- Main emulator, loads other modules /modules/sync.lua -- timers with fake asynchronous IO /modules/async.lua -- timers with asynchronous IO based on copas /modules/copas.lua -- copas.* functions /modules/net.lua -- net.* functions /modules/api.lua -- api.* and web api /modules/json.lua -- json library /modules/fibaro.lua -- fibaro.* functions /modules/class.lua -- class function /modules/files.lua -- support for loading files, *.lua and *.fqa files /modules/QuickApp.lua -- QuickApp classes /modules/devices.json -- device templates /modules/webserver.lua -- web server for remote calls (ex. from proxy) /modules/proxy.lua -- creates QA proxy on the HC3 for funnelling back onAction and UIevents /modules/ui.lua -- Local Web UI for QA /modules/time.lua -- Time manipulation - changing start time... /modules/refreshStates.lua -- /refreshStates support /modules/Scene.lua -- Initial support /modules/offline.lua -- Support for local shadowing resources. Currently /globalVariables /modules/stdQA.lua -- Standard (simulated) devices /web/main.html -- Web - Main emulator page /web/qa.html -- Web - QuickApp info /web/ui.html -- Web - QuickApp UI /web/timers.html -- Web - list of active timers /web/settings.html -- Web - editor for configuration file examples/MyQuickApp.lua -- Simple QuickApp examples/Hue.lua -- Hue QuickApp with QuickAppChildren examples/Scheduler.lua -- Simple minute based scheduler examples/Ping.lua -- Ping QuickApp that loads Pong QuickApp and exchanges fibaro.calls... examples/Pong.lua examples/TestScene.lua -- Test scene examples/TestSceneCron.lua -- Test scene with Date condition examples/Backup.lua -- Non-QA code that backs up QuickApps from the HC3 to the local filesystem as *.fqa files examples/SamsunWebSocket.lua -- WebSocket example controlling my Samsung TV examples/TestTCPSocket.lua -- TCPSocket example with emulated responses setup/TQAEplugin.lua -- ZBS plugin to give editor help and fibaro command completion. TQAE.gz -- Every file in one archive Installation First time download the archive, TQAE.tar.gz and unpack it in a suitable directory on your PC/Mac/Linux. On Linux: >tar xvf TQAE-main.tar On PC/Mac use suitable archive program to unpack the archive. ../TQAE/TQAE.lua ../TQAE/TQAE_QA.lua ../TQAE/modules/* ../TQAE/web/* ../TQAE/examples/* ../TQAE/... Alternatively, and preferred, clone the GitHub repository >git clone https://github.com/jangabrielsson/TQAE The setup procedure would be EMB-31KYQ6LT:~ erajgab$ mkdir development # Create new directory to develop in EMB-31KYQ6LT:~ erajgab$ cd development/ EMB-31KYQ6LT:development erajgab$ git clone https://github.com/jangabrielsson/TQAE # Clone repository Cloning into 'TQAE'... remote: Enumerating objects: 1790, done. remote: Counting objects: 100% (192/192), done. remote: Compressing objects: 100% (135/135), done. remote: Total 1790 (delta 118), reused 122 (delta 57), pack-reused 1598 Receiving objects: 100% (1790/1790), 1.75 MiB | 7.28 MiB/s, done. Resolving deltas: 100% (1198/1198), done. EMB-31KYQ6LT:development erajgab$ ls # We got the code... TQAE EMB-31KYQ6LT:development erajgab$ cd TQAE/ EMB-31KYQ6LT:TQAE erajgab$ ls LICENSE TQAE.tar.gz examples modules README.md TQAE_QA.lua jgabs_QAs setup TQAE.lua TQAEconfigs.example lib web EMB-31KYQ6LT:TQAE erajgab$ mkdir test # Create 2 directories to do your own development in EMB-31KYQ6LT:TQAE erajgab$ mkdir dev So both directories dev and test are in .gitignore, so you can update the TQAE code with the command (while standing in the TQAE directory) EMB-31KYQ6LT:TQAE erajgab$ git fetch This will update the TQAE code (it's usually updated quite often) but will leave your test and dev directories untouched. You can also link dev to some other directory where you do your development. I will do my best to keep the repository clean. This way it's easy to keep up with updates of the code. Download ZeroBrane studio <link> Open ZBS and open the TQAE_QA.lua file Set project directory in ZBS to the current file, TQAE_QA.lua (Project->Project Directory->Set From Current File) Also set Lua interpreter to Lua 5.3 (Project->Lua Interpreter->Lua 5.3) Now, run TQAE_QA.lua (F5 in ZBS). The output will look something like: ---------------- Tiny QuickAppEmulator (TQAE) v0.30 ------------- [11.10.2021] [08:33:23] | SYS|: No connection to HC3 [11.10.2021] [08:33:23] | SYS|: Created WebAPI at 192.168.1.18:8976 [11.10.2021] [08:33:23] | SYS|: sunrise 07:43, sunset 19:50 Note first that there is no connection to the HC3 - we are missing user, password, and IP for the HC3. Secondly, note the WebAPI address. 192.168.1.18 is my machine, your IP address may be different. The port is 8976. While TQAE_QA.lua is running open http://192.168.1.18:8976/web/main in your browser. (the Web UI only works while the emulator is running) Goto [Settings] in the web page menu (upper right). Fill in User ID, Password, and IP address for the HC3. Click "Save" Hopefully there is now a TQAEconfigs.lua file with the HC3 credentials that the emulator can use. Go back to ZBS and stop the program (Shift-F5) and run it again: ---------------- Tiny QuickAppEmulator (TQAE) v0.30 ------------- [11.10.2021] [09:13:43] | SYS|: Using config file TQAEconfigs.lua [11.10.2021] [09:13:43] | SYS|: Created WebAPI at 192.168.1.18:8976 [11.10.2021] [09:13:43] | SYS|: sunrise 07:14, sunset 17:52 It loads the config file and doesn't complain that there is no connection to the HC3 anymore. If you run ZBS I strongly encourage you to download the copy the TQAE/setup/TQAEplugin.lua file to your home directory ~/.zbstudio/packages/TQAEplugin.lua Also create directory ~/.zbstudio/tqae and move the files TQAE/setup/codeTemplates.lua and TQAE/setup/fileDownloads.lua to that directory. It will give you tooltips for the fibaro lua functions and some shortcuts to download updates to TQAE. Restart ZBS after installing the files. Great we are up and running! Supported functions (v0.33) fibaro.debug(tag,str) fibaro.warning(tag,str) fibaro.trace(tag,str) fibaro.error(tag,str) fibaro.call(deviceID, actionName, ...) fibaro.getType(deviceID) fibaro.getValue(deviceID, propertyName) fibaro.getName(deviceID) fibaro.get(deviceID,propertyName) fibaro.getGlobalVariable(varName) fibaro.setGlobalVariable(varName ,value) fibaro.getRoomName(roomID) fibaro.getRoomID(deviceID) fibaro.getRoomNameByDeviceID(deviceID) fibaro.getSectionID(deviceID) fibaro.getIds(devices) fibaro.getAllDeviceIds() fibaro.getDevicesID(filter) fibaro.scene(action, sceneIDs) fibaro.profile(profile_id, action) fibaro.callGroupAction(action,args) fibaro.alert(alert_type, user_ids, notification_content) fibaro.alarm(partition_id, action) fibaro.setTimeout(ms, func) fibaro.clearTimeout(ref) fibaro.setInterval(ms, func) fibaro.clearInterval(ref) fibaro.emitCustomEvent(name) fibaro.wakeUpDeadDevice(deviceID) fibaro.sleep(ms) net.HTTPClient() net.TCPSocket() net.UDPSocket() net.WebSocketClient() net.WebSocketClientTLS() mqtt.Client.connect(uri, options) <mqttclient>:addEventListener(message,handler) <mqttclient>:subscribe(topic, options) <mqttclient>:unsubscribe(topics, options) <mqttclient>:publish(topic, payload, options) <mqttclient>::disconnect(options) api.get(call) api.put(call <, data>) api.post(call <, data>) api.delete(call <, data>) setTimeout(func, ms) clearTimeout(ref) setInterval(func, ms) clearInterval(ref) json.encode(expr) json.decode(string) plugin.mainDeviceId plugin.deleteDevice(deviceId) plugin.restart(deviceId) plugin.getProperty(id,prop) plugin.getChildDevices(id) plugin.createChildDevice(prop) class QuickAppBase class QuickApp class QuickAppChild class <name> property(get,set) QuickApp:onInit() -- called at startup if defined QuickApp - self:setVariable(name,value) QuickApp - self:getVariable(name) QuickApp - self:debug(...) QuickApp - self:trace(...) QuickApp - self:warning(...) QuickApp - self:error(...) QuickApp - self:updateView(elm,type,value) QuickApp - self:updateProperty(name,value) QuickApp - self:createChildDevice(props,device) QuickApp - self:initChildDevices(table) The idea and reason that I implement an offline emulator for the HC3 is that it is generally speaking a pain to debug a moderately complex QuickApp on the HC3. You are left to using debug statements and it slow you down as you can not set breakpoint and step through the code to understand what logic it is following. With an offline emulator running on a PC/Mac you can use a modern IDE like ZeroBrane or VisualStudio to develop your QA. You can quickly put together 99.9% of your QA and the speed/productivity tripples. When it works in the emulator you can move the code to the HC3 and it will work. No need to develop on the HC3 and accidentally crash the whole box and make your family upset. Here is a video showing a typical workflow. (Note that the Web UI now has a button "Upload" that uploads the QuickApp when it's ready to the HC3) Writing HC3 (HC2) emulators seems to be a hobby for me - but what better thing to do on a vacation? So far I have HC2 Scene emulator - quite ok and feature complete, not actively developed anymore but still works well (not much has happened to the HC2 scene framework lately) HC3 emulator (fibaroapiHC3.lua) - gazillion of features, 9.5k lines - haven't come across any QA it can't run. Also some basic Scene support HC3 micro emulator - ~70 lines of Lua that can run QAs in parallel but have a very limited function support. Developed to prove that we could enhance the call model of QAs without much work Well, the micro emulator got my interest and I have evolved it to a more full featured emulator - Tiny QuickApp Emulator (TQAE). Runs multiple QAs in parallel - can test fibaro.calls between them Supports net.HTTPClient, setTimeout, clearTimeout, setInterval, clearInterval, json.*, class, property Supports QuickApp methods - self:setVariable, self.getVariable, self:updateProperty, self:updateView Supports fibaro.* functions Supports QuickAppChild devices Supports simple web UI to test QuickApp buttons/sliders Supports proxy QA on the HC3 to make to emulated QA interact with QAs running on the HC3 - Real HC3 QAs can call the QA you are running in the emulator. (and you get a real UI too) The main idea is that the emulator could be used as a light weight playground for testing out QAs The main logic is still under 1000 lines with current modules but stuff like json support and the whole fibaro.* sdk requires quite some code so it tends to swell - in any case it's modular. Dependencies on luasocket. (I recommend the free ZeroBrane Studio as it comes with the necessary libraries and is a specialised development environment for Lua) local http = require("socket.http") local socket = require("socket") local ltn12 = require("ltn12") It has an easy structure (I think). There are very few differences between TQAE and fibaroapiHC3.lua so I mainly work with TQAE these days. Advantages compared to fibaroapiHC3.lua. Easier to debug as code is not as asynchronous as it is in the fibaroapiHC3 scheduler (possibility to use a sync scheduler). Uses a vanilla fibaro.* implementation only leveraging api.* and __fibaro_* calls, so it should be easy to upgrade if we get new fibaro.* calls. Module structure makes it easy to add new libraries etc. It's modular. Include it in your QA code _=loadfile and loadfile("TQAE.lua"){ user="admin", pwd="admin", host="192.168.1.57" } --%%name='Test' --%%id=99 --%%quickVars={x=88,y=99} function QuickApp:onInit() self:debug(self.name,self.id) self:debug("y=",self:getVariable("y")) local n = 5 setInterval(function() n=n-1 if n <= 0 then os.exit() end self:debug("PING") end,1000) end The loadfile at the top loads in the emulator and run your QA. The { ... } table after loadfile("TQAE.lua") is a table with configuration options to the emulator. The emulator needs ip, user and password to access the HC3. If you don't want to type that you can store a config file for TQAE with you standard settings. The default name for the file is "TQAEconfigs.lua" and it can look like return { user="admin", pwd="admin", host="192.168.1.57", verbose=false, modPath = "TQAEmodules/", temp = "temp/" --localModules = {"myModule.lua"} --globalModules = {"UDP.lua"} } However, you still need to call loadfile("TQAE.lua"){} with and empty table. If you would like to have another name of the file you can specify that loadfile("TQAE.lua"){ config = "myTQAEconfigfile.lua" } The config is parameters for the emulator. Then you can also set "parameters" for the individual QAs that you run using --%% directives The --%% directives in your QA code are collected into a Lua table. In the case above { name='Test', id=99, quickVars = { x=88, y=99 } } and if present will be used to add fields to the QA. This is the way to tell what name, id and type you want your QA to have. A special field is quickVars that will be inserted as quickAppVariables of the QA at startup. Note that each field can only be one line. It's easy to startup another QA from within your code _=loadfile and loadfile("TQAE.lua"){ user="admin", pwd="admin", host="192.168.1.57" } --%%name='Test' --%%id=99 hc3_emulator.installQA{id=444,file='MyOtherQA.lua'} -- Load in another QA and set id to 444 function QuickApp:onInit() self:debug(self.name,self.id) fibaro.call(444,"test",5,6) -- call other QA end If the other file is open in the IDE you can set breakpoints in it and jump between the QAs. In fact, hc3_emulator.installQA{id=444,file='MyThirdQA.fqa'} will load in a .fqa file directly. In that case temporary files are stored for each of the files in the .fqa. This means we can do trick like this, downloading a QA from the HC3 and run it in the emulator with one line of code _=loadfile and loadfile("TQAE.lua"){ user="admin", pwd="admin", host="192.168.1.57" } hc3_emulator.installQA{id=700,code=api.get("/quickApp/export/700")} -- Load in QA 700 from HC3 and run it function QuickApp:onInit() self:debug(self.name,self.id) fibaro.call(700,"test",5,6) -- call QA 700 end Another useful directive is --FILE:<filename>,<name>; that allow us to include extra files in our QA. A QA can consist of several files but it must have a 'main' file. The QA code you run in the emulator will always be the main, and then you can include extra files that will be added to the QA as "files". Ex. _=loadfile and loadfile("TQAE.lua"){ user="admin", pwd="admin", host="192.168.1.57" } --FILE:myUtilities.lua,utilities; function QuickApp:onInit() self:debug(self.name,self.id) LOG("This is a test") -- Using global LOG function defined in myUtilities.lua end Running and logs When running there will be output of two types. Standard logging that the QA does with fibaro.debug, self:debug etc,. System debugs, that are the emulators way to inform on what is ongoing. How much the system logs depends on the configuration parameter .logLevel. ---------------- Tiny QuickAppEmulator (TQAE) v0.5 ------------- [29.07.2021] [11:17:34] |SYS |: Loading QA:TestQA1 - ID:1001 Start [29.07.2021] [11:17:34] |SYS |: Starting QA:TestQA1 - ID:1001 [29.07.2021] [11:17:34] [DEBUG] [QUICKAPP1001]: TestQA1 - 1001 Here we se the system log (|SYS |) that the QA is loading and then the log that it's running. The first is when the QA code is loaded and all functions are defined. Also if you do something on top-level, outside functions it will run here. In the example the QA does a print("Start") on top-level of the QA so that is executed when loading. Then, if the QA is successfully loaded, it will be "started, meaning that the function QuickApp:onInit() will be called if it's defined. That's the second SYS log. It's good to pay notice to this. If you get an error before Loading it typically means that you have a syntactic error in the QA code - non-valid Lua code. If you get an error after Loading but before Starting it's something on top-level that is run, outside of function QuickApp:onInit() If you get an error after Starting, well, then it's just something wrong with your code when it runs. A run-time error will look like: [29.07.2021] [12:27:47] [ERROR] [QUICKAPP1002]: [string "temp/main_16546.lua"]:5: attempt to call a nil value (global 'foo') This tells us that the error was in the QA with id 1002 (unless you have changed __TAG) The QA file is 'main'. A QA can consist of many files and this gives us the clue in what file the error was. If you only have one file, its name is 'main'. Then it tells us that is was line 5 where the error occurred ([string "temp/main_16546.lua"]:5:) and then that we tried to call a global variable 'foo' that didn't have a value (a function) So, in the main file look for a foo() call on line 5 - that's the error... Turning up logLevel will also log modules loaded etc. It also sets a Lua global in the QA, LOGLEVEL to the level, so that variable can be used by you to also allow your code to log more or less. Other features The Web UI allows the file to be saved as a *.fqa file that can be uploaded manually to the HC3. It you have included multiple files with the --FILE: directive they will also be included. A simple way to code and create multi-file QAs. The Web UI can also upload the QA directly to the HC3. The directive 'interfaces' Ex. --%%interfaces={"power"} will add the interfaces as the initialInterfaces property of the .fqa. An easy way to include and extra interface in the ready .fqa. Emulator options: (set in the header _=loadfile and loadfile("TQAE.lua"){...} ) user=<user> Account used to interact with the HC3 via REST api pwd=<Password> Password for account used to interact with the HC3 via REST api host=<IP address> IP address of HC3 configFile = <filename> File used to load in emulator options instead of specifying them in the QA file. Great place to keep credentials instead of listing them in the QA code, and forget to remove them when uploading codeto forums... Default "TQAEconfigs.lua" debug={ traceFibaro=<boolean> -- default false QA=<boolean>, --default true module=<boolean>, --defaul false module2=<boolean>, --defaul false lock=<boolean>, --default false child=<boolean>, --default true device=<boolean>, --default true refreshStates=<boolean> -- default false } modPath = <path>, Path to TQAE modules. Default "TQAEmodules/" temp = <path> Path to temp directory. Default "temp/" startTime=<time string> Start date for the emulator. Ex. "12/24/2024-07:00" to start emulator at X-mas morning 07:00 2024. Default, current local time. htmlDebug=<boolean>. If false will strip html formatting from the log output. Default true colorDebug=<boolean>. If true will log in ZBS console with color. Default true copas=<boolean> If true will use the copas scheduler. Default true. noweb=<boolean> If true will not start up local web interface. Default false lateTimers=<seconds> If set to a value will be used to notify if timers are late to execute. Default false timerVerbose=<boolean> If true prints timer reference with extended information (expiration time etc) QuickApp options: (set with --%% directive n file) --%%name=<name> --%%id=<number> --%%type=<com.fibaro.XYZ> --%%properties={<table of initial properties>} --%%interfaces={<array of interfaces>} --%%quickVars={<table of initial quickAppVariables>} --%%proxy=<boolean> --%%save=<boolean> Documentation Emulator parameters and QA parameters Web UI - and QA UI Some implementation notes Notes and ToDos Mostly developed as a playground for my own coding and it fits my workflow. I may add features/fix bugs if others are using it - but no more than 500 lines of code
-
Hi, I have been tinkering with my own schedulers 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: JSched_0_98.lua 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; 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... 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. 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. it can be tested and run from ZeroBrane studio with FibaroSceneAPI (Thanks to petergebruers for fixing the bugs). 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) 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 [<expr1>, <expr2>, <expr3>, ..., <expr_n>] 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: {COMMAND:<expr1>, &PARAMETER2:<expr2>, &PARAMETER3:<expr3>, ...} <expr1> is the implicit first parameter/argument to the command. Ex. {'push':['$phone.tom','$phone.mary'], '&msg':'Hello'} 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. defvar("phone",{tom='5784582358', mary='565767788'}, true) defvar("room1",{lamp=42}, true) defvar("toilet",{lamp=55, sensor=66}, true) defvar("test",{x = 0}, true) 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. {'and':[{'on?':'$room1.lamp'},{'>',[{'last':'$room1.lamp'},{'*':[60,60,3]}]}]} This tests if the lamp in room1 has been on for 3 hours , can also be expressed as {'id':'$room1.lamp', '&value>':0, '&last>':'!60*60*3'} 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 {'set':{'var':'test.x}, '&val':{'+':[{'var':'test.x'},1]}} '[email protected][email protected]+5' expands to {'set':{'glob':'test.x}, '&val':{'+':[{'glob':'test.x'},5]}} '==' 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: rule(<optional interval,><json array with expressons><,optional substitution table>) Ex. rule("[{'id':'$toilet.lamp', '&value>':0, '&last>':600},{'id':'$toilet.sensor', '&value<':1, '&last>':600}, {'off':'$toilet.lamp'}, {'log':'Turning toilet lamp off'}]") 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. trigger("{ID:PROPERTY}",<json rule><,optional substitution table>) Ex. trigger("{'value':331}","[{'on?':'$toilet.sensor'}, {'off?':'$toilet.lamp'}, {'on':'$toilet.lamp'}, {'log':'Turning on toilet lamp'}]") 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 previous scheduler. 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 ; -- {'playfile':file,'volume':val} defunLua('playfile','file', function(cmd, env) local file = env.eval(cmd.file) local volume = env.eval(cmd.volume) local duration = env.eval(cmd.duration) playSonosSound(file,volume,duration) return true end ) Functions can also be defined in json. Ex. defvar("myfun","{'fun':['x','y'], '&body':{'+':['$x','$y']}}") rule("[{'log':' 44 + 66 = %s', '&args':[{'call':'$myfun' ,'&args':[44,66]}]}]") 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. qVal(id,prop,time,val) qGlob(name,time,val) qEvent(id,key,attr,time) 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; <time> -> "HH:MM" <time> -> "$Sunset" | "$Sunrise" | "$Now" (predefined user variables) <expr> -> {'time':<time1>} -- true if current time is time1 {'time':[<time1>,<time2>]} -- true if current time is between time1 and time2 (incl) {'time':[<time1>,<time2>,<time3>]} -- true if current time is time2 bracketed by time1 and time3 {'interval':'<cron format>'} -- see explanation earlier in the in doc {'>':[<expr1>,<expr2>]} -- true if expr1 > expr2 (>,<,==,>=, <=) {'+':[<expr1>,<expr2>]} -- returns expr1 + expr2 (+,-,*,/). '-' allows for {'-':<expr>} <prop> -> 'value', 'last', 'battery','power','lux','scene' <test> -> '>','<','==','>=','<=' <proptest> -> <prop><test> <id> -> any expression that returns an integer (or string representing an integer) that matches a device ID {'id':<id or array of ids> &<proptest1>:<expr1>, &<proptest2>:<expr2>,, ...} -- true if all tests applied to ids are true {<prop>:<id>} -- returns fibaro:get(<id>,<prop>) with 'lux' -> 'value' and 'last' being treated specially {<prop>:[<id1>,<id2>,...]} -- returns array [fibaro:get(<id1>,<prop1>),fibaro:get(<id2>,<prop2>),...] {'min':[<expr1>,<expr2>,...]} -- returns smallest of expressions {'max':[<expr1>,<expr2>,...]} -- returns largest of expressions {'mean':[<expr1>,<expr2>,...]} -- returns the average of expressions {'and':[<expr1>,<expr2>,...]} -- return true if all expressions are true {'or':[<expr1>,<expr2>,...]} -- return first value of first <expr> that is true {'not':<expr>} -- returns true if <expr> is false {'if':<expr1>, 'then':<expr2>, 'else':<expr3>} -- if-then-else test, else is optional {'on':<id or array of ids>} -- turns on all <ids> given as arguments, {'on?':<id or array of ids>} -- true if all <ids> have value>0 {'off':<id or array of ids>} -- turns off all <ids> given as arguments {'off?':<id or array of ids>} -- true if all <ids> have value<1 {'dim':<id or array of ids>,'&val':val} {'push':<mobile id>,'&msg':<string>,'&args':...} -- push message to phones, MobileID can be array of ids. Optional args are for formatting the message. See 'log' {'press':<id>,'&btn':<expr>} -- press button on VD {'start':<id or array of ids>} -- start scene(s) with ID (can be array) {'stop':<id or array of ids>}-- stops scene(s) with ID (can be array) {'glob':<expr>} -- fibaro:getGlobal(NAME) {'var':<expr>} -- getUserVariable(NAME) {'set':{'glob':<expr1>},'&val':<expr2>} -- fibaro:setGlobal(<expr1>,<expr2>), expr1 should evaluate to string {'set':{'var':<expr1>},'&val':<expr2>} -- setUserVariable(<expr1>,<expr2>), expr1 should evaluate to string {'enable':rule/table} -- enables the rule {'disable':rule/table} - disable the rule {'log':<expr>,'&args':...} -- log to the fibaro console, 'args' optional ex {'log':'Time global is ",'args':['@Time']} {'date':<expr1>,'&val':<expr2>} -- date form (e.g. os.date()) {'date':'%H:%M','val':'$Now'} -> i.e "10:00", useful for logging/messages {'once':<expr or array of expr>} -- returns false if EXPR hasn't changes, otherwise return EXPR {'truefor':<expr1>, '&val':<expr2>} -- returns true if EXPR has been true for SECONDS, otherwise false {'setTimer':<string>, '&val':<expr>} -- sets a timer with name that becomes true after <expr> seconds {'timer':<string>} -- returns true if the timer has expired {'clearTimer':<string>} -- cancel a timer that is running. {'label':<id>,'&val':<expr>} -- return the value of an VD label 'ui.<expr>.label' {'setLabel':<id>,'&val':<expr1>, '&expr':<expr2>} -- sets VD label 'ui.<expr1>.label'to <expr2> {'format':<string>, '&args':<array of expr>} -- makes a string.format {'fun':[parameters], '&body':<expr or array of expr>} -- defines a function, ex. {'fun':['x','y'], 'body':{'+':['$x','$y']}} {'call':<expr>, '&args':<array of expr>} -- makes a a call to a json defined fun {'decode':<string>} -- returns json.decode on the string {'encode':<expr>} -- returns json.encode on the expr {'devices':<expr>} -- calls fibaro:getDevicesId(expr). Note!, need to quote a constant json expr {'%':<expr>} -- quote.. returns expr without evaluating it {'fcall':<id or array of ids>, 'args':[<expr1>,<expr2>,......]} -- Makes a fibaro:call(<id>, <expr1>,<expr2>,...) 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. trigger("{'event':362}","[{'log':'Key %s %s', '&args':['$_src.event.data.keyId','$_src.event.data.keyAttribute']}]") To check a key sequence this works: defvar("key",{k1=0,k2=0}) trigger("{'event':362}","['!$_src.event.data.keyId==1','!$key.k1=$Now']") trigger("{'event':362}","['!$_src.event.data.keyId==2','!$Now<=$key.k1+1','!$key.k2=$Now']") trigger("{'event':362}","['!$_src.event.data.keyId==3','!$Now<=$key.k2+1',{'log':'Sequence 123'}]") 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: JSched_0_95.lua, New functionality; init(<expr>), quoting of args, generic fibaro:call, etc Release 03/18: , JSched_0_96r3.lua,Bugfixes, better error handling, better offline support. "out-of-the-box" it runs the simulation offline. Release 04/03: JSched_0_98.lua, New syntax and better simulation support Macros: ...to be documented - for now see text above.