jgab 898 Author Share Posted October 19, 2018 1 hour ago, jompa68 said: Get some error and it is perhaps my fault because i setup triggers wrong... -- Larm, kameror Rule.eval("kameror={camera.synologyVD,camera.synologyVD2,camera.synologyVD3}") Rule.eval("arm_sensors={834,822,825,828,831,837}") Rule.eval("$UHASSleepState =='Sover' | $UHASPresentState =='Borta' => arm_sensors:armed=1;kameror:btn=3;log('Larmar på huset, startar kameror')") Rule.eval("$UHASSleepState =='Vaken' | $UHASPresentState =='Hemma' => arm:sensors:armed=0;kameror:btn=4;log('Larmar av huset, stänger av kameror')") When i did go to sleep last night an error did occur in debug and this morning the 4th Rule.eval did not run. [DEBUG] 22:14:25: fibaro:call(834,"setArmed",1) [DEBUG] 22:14:26: fibaro:call(822,"setArmed",1) [DEBUG] 22:14:26: fibaro:call(825,"setArmed",1) [DEBUG] 22:14:26: fibaro:call(kallare.dorr,"setArmed",1) [DEBUG] 22:14:26: fibaro:call(831,"setArmed",1) [DEBUG] 22:14:26: fibaro:call(837,"setArmed",1) [DEBUG] 22:14:26: fibaro:call(camera.synologyVD,"pressButton",3) [DEBUG] 22:14:26: fibaro:call(camera.synologyVD2,"pressButton",3) [DEBUG] 22:14:26: fibaro:call(camera.synologyVD3,"pressButton",3) [DEBUG] 22:14:26: Larmar på huset, startar kameror [DEBUG] 22:14:26: Error in 'table: 0x875a688': bad deviceID 'nil' for 'prop' [DEBUG] 22:14:26: fibaro:call(gastrum.fonster,"turnOff") [DEBUG] 22:14:26: fibaro:call(sovrum.fonster,"turnOff") [DEBUG] 22:14:27: fibaro:call(vrum.bokhylla,"pressButton",2) [DEBUG] 22:14:27: Släcker bokhyllan [DEBUG] 22:14:27: fibaro:call(vrum.kruka,"turnOff") [DEBUG] 22:14:27: Dax att sova, godnatt! [DEBUG] 22:14:28: fibaro:call(Sonoff.koksbanken,"pressButton",2) [DEBUG] 22:14:28: Släcker lampan under köksbänken [DEBUG] 22:17:51: fibaro:call(kallare.taklampa,"turnOff") [DEBUG] 22:22:09: fibaro:call(wc.flakt,"turnOff") [DEBUG] 01:29:13: fibaro:call(wc.flakt,"turnOn") [DEBUG] 01:30:37: fibaro:call(wc.flakt,"turnOn") [DEBUG] 01:40:55: fibaro:call(wc.flakt,"turnOff") [DEBUG] 05:00:08: fibaro:call(vrum.bokhylla,"pressButton",1) [DEBUG] 05:00:08: Tänder bokhyllan [DEBUG] 05:00:08: fibaro:call(Sonoff.koksbanken,"pressButton",1) [DEBUG] 05:00:08: Tänder lampan under köksbänken UPDATE Found why the 4th Rule.eval did not run. Should be this, perhaps the error from last night is gone also now. Rule.eval("$UHASSleepState =='Vaken' | $UHASPresentState =='Hemma' => arm_sensors:armed=0;kameror:btn=4;log('Larmar av huset, stänger av kameror')") Yes, the previous rule would give that kind of error. I will see if I can implement more helpful error messages. Quote Link to post Share on other sites
jgab 898 Author Share Posted October 20, 2018 "Cross posting" dimmer use case. Dimming up and down a light when holding in a key on the fibaro keyfob Quote Link to post Share on other sites
jompa68 121 Share Posted October 21, 2018 I really like the possibility to test all rules with ZeroBraneStudio before place them in a live scene Quote Link to post Share on other sites
jgab 898 Author Share Posted October 21, 2018 (edited) 4 hours ago, jompa68 said: I really like the possibility to test all rules with ZeroBraneStudio before place them in a live scene Thanks, I just pushed a new version with some bug fixes and enhancements.... introduced a bug in the last version that prevented some rule to trigger when running off-line. Yes, it is *very* convenient to test rules off-line. I especially like to be able to post "fake" events to see how my rules reacts. Ex. a simple turn on light when sensor is breached sensor = 66 lamp = 77 Rule.eval("sensor:breached => lamp:on") -- turn on lamp if sensor is breached Rule.eval("for(00:05,sensor:safe) => lamp:off") -- turn off lamp if sensor is safe for 5min ---- test logic of rule by posting fake sensor events --- Rule.eval("wait(00:10); sensor:on; wait(00:00:30); sensor:off") -- wait 10min and breach sensor, wait another 30s and make it safe Edited October 21, 2018 by jgab Quote Link to post Share on other sites
jgab 898 Author Share Posted October 21, 2018 (edited) .If I have many sensors I want to test the logic for I make an helper function that automatically makes the sensor safe after a specified time sensor = 66 lamp = 77 Rule.eval("sensor:breached => lamp:on") Rule.eval("for(00:05,sensor:safe) => lamp:off") ------- Test logic of rule ------- function makeSensor(sensor,timeout) local self,ref = {} function self.breach() Event.cancel(ref) -- cancel eventual timer already set fibaro:call(sensor,"setValue",1) -- breach sensor ref = Event.post(function() fibaro:call(sensor,"setValue",0) end, timeout) -- make safe after timeout end return self end s1 = makeSensor(sensor,"+/00:00:30") Rule.eval([[ wait(00:10); s1.breach(); wait(00:00:15); s1.breach() ]]) Here I can breach s1 and it will automatically become safe after 30s as specified when created with makeSensor. It will behave like a real motion sensors and extend the 30s window if breached again before. Edited October 23, 2018 by jgab Quote Link to post Share on other sites
jgab 898 Author Share Posted October 21, 2018 (edited) A reason that I developed the script language was that I want some kind of library of global functions. See thread here about issues with global functions... The EventScript language I put together is almost a complete language (almost a Lua in Lua) but developing more advanced scripts in it still requires insights into programming in general and can be quite complex. The idea has been more that because scripts are Lua strings I can store them outside the EventRunner scene, lika a Fibaro global or another scene and read them into the EventRunner at startup - something we are not allowed to do with Lua code. More general, a library with known working script templates can be developed for common automation tasks and easily be instantiated by people that are not so proficient in coding. Example below are two templates, one that turns on/off a lamp using a sensor, and another template that dims a light up and down when a key is pressed on a keyfob. This is close to real sharable code, on a home automation tasks level... or like scenes in scenes... --- Script template library, just a Lua table with strings and can be stored in a fibaro global or another scene... local scripts = { lightOff={[[ <sensor>:breached & <light>:isOn => <light>:on for(<timeout>,<sensor>:safe) => <light>:off ]],{"light","sensor","timeout"}}, keyDimmer={[[ <keyfob>:central.keyId == <key> => post(#dim{key=<keyfob>:central.keyId,attr=<keyfob>:central.keyAttribute}) #dim{attr='Released',key=<key>} => cancel(ref<rnd>) #dim{attr='HeldDown',key=<key>} => || <light>:value < 99 & up<rnd> >> <light>:value=min(<light>:value+<step>,99) || up<rnd> >> up=false || <light>:value > 0 >> <light>:value=max(<light>:value-<step>,0) || true >> <light>:value=<light>:value+<step>; up<rnd>=true;; ref<rnd> = post(env.event,time(<delay>)) ]],{"keyfob","key","light","delay","step"}} } -- script instantiating function function script(name,...) local s = scripts[name] local params1,params2,args,rules = s[2],{},{...},s[1] params1[#params1+1]="rnd" -- some scenes needs unique variables args[#args+1]=tostring(math.random(100,100000)) for i,p in ipairs(params1) do params2[p]=i end rules = rules:gsub("<%a+>",function(str) -- substitute in arguments for parameters return args[params2[str:sub(2,-2)]] end) Rule.load(rules) end --- Scene using templates function main() lamp1 = 77; sensor1=66 lamp2 = 22; sensor2=99 lamp3 = 81; sensor3=12 script("lightOff",lamp1,sensor1,"00:05") script("lightOff",lamp2,sensor2,"00:04") script("lightOff",lamp3,sensor3,"00:05") keyfob=5 delay = "00:00:02" step = 10 script("keyDimmer",keyfob,"2",lamp1,delay,step) script("keyDimmer",keyfob,"3",lamp2,delay,step) script("keyDimmer",keyfob,"4",lamp3,delay,step) end This is still a proof of concept and need some more error checks and storing script templates elsewhere... Edited November 8, 2018 by jgab Quote Link to post Share on other sites
jgab 898 Author Share Posted October 21, 2018 (edited) Here is an attempt to describe syntax and commands available in the EventScript language. Is not complete but will be updated over time. There are 2 previous posts on how to write schedulers and trigger rules with EventScript. Grammar <symbol> ::= (a-zA-Z) (a-zA-Z_0-9) <global> ::= $<symbol> <var> ::= <symbol> <event> ::= #<symbol>{<symbol>=<expr>,...,<symbol>=<expr>} <constant> ::= 'now' | 'midnight' | 'sunrise' | 'sunset' | 'wnum' | 'true' | 'false' | 'nil' | '{}' <time> ::= HH:MM | HH:MM:SS | YYYY/mm/DD/HH:MM | YYYY/mm/DD/HH:MM:SS <timeExpr> ::= <time> | +/<time> | n/<time> | t/<time> <oper> ::= + | - | * | / | % | "|" | & | > | < | >= | <= | == | = | =~ | = | .. | .| : | ! | @ <expr> ::= <table> | <call> | <event> | <num> | <string> | <constant> | <var> | <global> | <timeExpr> | <addr> <expr> ::= <expr> <oper> <expr> | - <expr> | ( <expr> ) <expr> ::= fn(<var>,...,<var>) <statements> end <expr> ::= <expr> ? <expr> : <expr> -- not implemented yet... <call> ::= <fun>(<expr>, ..., <expr>) | <var>(<expr>, ..., <expr>) | (<expr>)(<expr>, ..., <expr>) <statements> ::= statement [ ; <statements> ] <statement> ::= <expr> <statement> ::= || <expr> >> <statements> {|| <expr> >> <statements>} [;;] <rule> ::= @<time> [& <expr>] => <statements> | @{<time>, ..., <time>} [& <expr>} => <statements> <rule> ::= @@<time> [& <expr>] => <statements> <rule> ::= <expr> => <statements> <rule> ::= <event> => <statements> Rules Script rules comes in four flavours, and always contain a '=>' Rule.eval("@(<time>)&<expression> => <expression>") -- daily schedules Rule.eval("@@(<time>)&<expression> => <expression>") -- interval schedules Rule.eval("<trigger expression> => <expression>") -- trigger rules Rule.eval("#<event>&<expression> => <expression>") -- event rules Rules are registered and invoked when matching conditions are met. Expression given to Rule.eval without an '=>' is evaluated immediatly. Rule.eval("5+6") @time -- always part of a rule. Run action at time of day Rule.eval("@10:00 => lamp:on") -- turn on lamp at 10:00 every day @{t1,...tn} -- always part of a rule. Run action at times of day Rule.eval("@{sunset+00:15, sunrise-00:20} => lamp:on") -- turn on lamp at sunrise minus 15min and sunset plus 15min every day @{catch,t1,...} -- if the first argument in the time parameter list is the constant 'catch' the framework tries to catchup actions that have already passed when the framework starts up Rule.eval("@{catch,10:00} -- If it's after 10:00 when starting up run the action anyway and schedule the next for next day.. @@intervall -- always part of a rule. repeat action at specified interval Rule.eval("@@00:10 => lamp:on") -- turn on lamp every 10min Rule.eval("@@rnd(00:10,00:40) => lamp:on") -- turn on lamp random interval between 10min and 40min #event -- event triggering rule. '#foo' is a short for {type='foo'} '#foo{bar=9}' is a short for {type='foo', bar=9} Rule.eval("#property{deviceID=9,value='$>0'} => log('Device 9 turned on')") -- same as Rule.eval("9:isOn => log('Device ...)" Matching with constraints are allowed in event values and start with '$..'. Variables bound in matching expression available as local vars in rest of rule. Rule.eval("#global{name='Foo',value='$a'} => log('Fibaro global Foo's value set to %s',a)") Rule.eval("#property{deviceID=78,value='$a>0'} => log('Dimmer 78 turned on with value %s',a)") 'env' -- script variable that is bound the the environment the script is executing in. Specifically env.event is the event (sourceTrigger) that triggered the rule. Rule.eval("#foo => log('inifinte loop'); post(env.event)") Rule.eval("#property{deviceID=66} => log('deviceID 66 value is %s',env.event.value)") Mathematical and logical operators '!','&','|' - logical operators Rule.eval("6 > 5 & !(7 > 8) | true) '+','-','*','/','%' - matematical operators. '%' is the 'reminder'/'modulus' operators Rule.eval("-7 + 8/2 == -3") '>','<','>=','<=','~=','==' - comparision operators. works with numbers and strings Rule.eval("6 > 4 & 'A' ~= 'B'") '+=','-=','*=' - increment and assign. works with variables only Rule.eval("a = 7; a += 10; a == 17") Rule.eval("a = 7; a -= 10; a == -3") Rule.eval("a = 7; a *= 10; a == 70") 'rnd([<low>,]<high>)' -- random number between <low> and <high> including, <low> defaults to 1 Rule.eval("rnd(3,10)") -- return random number between 3 and 10, including. Rule.eval("rnd(10)") -- return random number between 1 and 10, including. 'round(<num>)' -- round a number. round(<num>) == math.floor(<num>+0.5) Rule.eval("round(5.45) == 5") Rule.eval("round(5.55) == 6") 'sum' -- return sum of table with numbers Rule.eval("sum({1,2,3}) == 6") 'min(<e1>,...<en>)' or 'min(<table>)' -- min of arguments or table of arguments Rule.eval("min(4,3,2,5) == 2") Rule.eval("min({4,3,2,5}) == 2") 'max(<e1>,...<en>)' or 'max(<table>)' -- max of arguments or table of arguments Rule.eval("max(4,3,2,5) == 5") Rule.eval("max({4,3,2,5}) == 5") Assignment Rule.eval("a = 7") -- assign script variable 'a' b -- global Lua variable Rule.eval("b = 7") -- assign global Lua scene variable 'b'. Will not work with local Lua variables. Rule.eval("$c = 7") -- assign Fibaro global variable 'c', i.e. fibaro:setGlobal("c","7") Rule.eval("a = {}; a[1] = 42; a['x'] = 43; a.y = 44") -- table assignment Rule.eval("label(55,'Label1') = 'Hello'") -- assign value to VD 55's label 'Label1' Rule.eval("slider(55,'Slider1') = 55") -- assign value to VD 55's slider 'Slider1' Device properties 'ID:isOn' - :isOn returns true if device is on, i.e. 'fibaro:getValue(ID,'value')>0'. {ID1,...,IDn}:isOn is true if any of the devices are on. The rationale is that if any lamp is on in a room, the room is considered lit. Rule.eval("lamp=55") Rule.eval("lamp:isOn") -- returns true if deviceId 55 is on Rule.eval("lamps={55,66}") Rule.eval("lamps:isOn}") -- returns true if deviceId 55 or deviceID 66 is on 'ID:isAllOn' - If there is a need to test if all IDs are on, e.g. {ID1,...,IDn}:isAllOn Rule.eval("lamp=55") Rule.eval("lamp:isAllOn") -- returns true if deviceId 55 is on, same as ':isOn' Rule.eval("lamps={55,66}") Rule.eval("lamps:isOn") -- returns true if both deviceId 55 and deviceID 66 is on 'ID:isOff' - :isOff returns true if device is off, i.e. 'fibaro:getValue(ID,'value')==0'. {ID1,...,IDn}:isOff is true if all the devices are of. The rationale is that if any lamp is on in a room, the room is considered lit. Rule.eval("lamp=55") Rule.eval("lamp:isOff") -- returns true if deviceId 55 is off Rule.eval("lamps={55,66}") Rule.eval("lamps:isOff") -- returns true if deviceId 55 and deviceID 66 is on 'ID:isAnyOff'- If there is a need to test if any ID is off, e.g. {ID1,...,IDn}:isAnyOff Rule.eval("lamp=55") Rule.eval("lamp:isAnyOff") -- returns true if deviceId 55 is off, same as ':isOff' Rule.eval("lamps={55,66}") Rule.eval("lamps:isAnyOff") -- returns true if deviceId 55 or deviceID 66 is off 'ID::value'- ID:value i.e. 'fibaro:getValue(ID,'value')' '{ID1,...IDn}:value returns a table of values for the IDs listed Rule.eval("lamp:55") Rule:eval("lamp:value") -- i.e fibaro:getValue(lamp,'value') Rule.eval("lamps:{55,56}") Rule:eval("savedStates=lamps:value") -- i.e savedStates = {fibaro:getValue(55,'value'),fibaro:getValue(66,'value')} -- random toggle device 55 and 56... Rule:eval("lamps:value=savedStates") -- i.e fibaro:setValue(55,savedStates[1]),fibaro:setValue(66,savedStates[2]) 'ID:last' - :last returns seconds since the device was last changed. Only supported by device that have last modified/lastbreached properties. 'ID:scene' - :scene returns sceneActivation value (button clicked value) of the device Rule.eval("switch=76") Rule.eval("switch:scene == S2.click => lamp:on") -- scene activation S2, single click Rule.eval("switch:scene == S2.double => lamp:on") -- scene activation S2, double click 'ID:central' - CentralSceneEvent - returns the 'data' portion of the event Rule.eval("55:central.keyId=='2' => log('Key 2 pressed')") 'ID:access' - AccessControlEvent- returns the 'data' portion of the event Rule.eval("55:access.status=='Lock' => log('Door locked')") Rule.eval("55:access.status=='Unlock' => log('Door unlocked')") 'ID:safe' - :safe returns true if the device is safe, i.e. 'fibaro:getValue(ID,'value')==0' Rule.eval("houseSensors={12,34,65,76,87,23,54,76,98}") Rule.eval("houseSensors:safe & once(00:00..00:00) => log('Midnight and everything is calm!')") 'ID:breached' - :breached returns true if the device is breached, i.e. 'fibaro:getValue(ID,'value')>0' Rule.eval("houseSensors={12,34,65,76,87,23,54,76,98}") Rule.eval("houseSensors:breached & $HomeStatus=='away' => log('Someone moved and we are away!')") 'ID:lux' - :lux returns the lux value for a light sensor, i.e. 'fibaro:getValue(ID,'value')' Rule.eval("luxsensors={66,77,88}; lamp=99") Rule.eval("sum(luxsensors:value)/size(luxsensors) < 200 => log('Average lux value in room < 200, turning on lamp!'); lamp:on") Rule.eval("max(luxsensors:value) < 200 => log('Max lux value in room < 200, turning on lamp!'); lamp:on") Rule.eval("sort(luxsensors:value)[round(size(luxsensors)/2)] < 200 => log('Median lux value in room < 200, turning on lamp!'); lamp:on") 'ID:temp' - :temp returns temperature for a temp sensor, i.e. 'fibaro:getValue(ID,'value')' Rule.eval("houseTemps={12,34,65,76,87,23,54,76,98}") Rule.eval("sum(houseTemps:temp)/size(houseTemps) > 30 => log('Average temp over 30!')") 'ID:manual' - :manual returns seconds since last manually changed, or -1 if changed by the script. Rule.eval("sensor:breached => lamp:manual>10*60 & lamp:on") --turn on the lamp if the sensor is breached, but only if the last manual change was at least 10min ago. Rule.eval("lamp1:isOn & lamp1:manual>=0 => lamp2:on") --turns on lamp2 if lamp1 was turned on manually 'ID:bat' - :battery returns battery property. i.e.'fibaro:getValue(ID,'batteryLevel') 'ID:armed' - :armed returns if device is armed. i.e. 'fibaro:getValue(ID,'armed') 'ID:R',':G',':B: - gets RGB values from a RGB device. 'ID:name' - name of device ID 'ID:roomName' - name of room for device ID If a device doesn't support a property there will be an runtime error. Some ':' properties acts as commands. 'ID:on' - :on turn on device ID. i.e. 'fibaro:call(ID,'turnOn')' Rule.eval("sensor=88; kitchenLamps={77,55,33}") Rule.eval("sensor:breached => kitchenLamps:on") 'ID:off' - :off turn off device ID. i.e. 'fibaro:call(ID,'turnOff')' 'ID:value=<expr>' - calls 'setValue' on device ID. i.e. 'fibaro:call(ID,'setValue',<expr>) Rule.eval("@23:00 & wday('mon-fri') => lamp:value=50") -- dim lamp to 50% on weekdays at 23:00 'ID:toggle' - :toggle turn on device if off, otherwise turn off. Rule.eval("lamps={66,88,77}") Rule.eval("@@rnd(00:10,00:30) & $Simulate='true' => lamps[rnd(size(lamps)]:toggle") 'ID:armed=<0/1>' - arms/disarms a device Rule:eval("houseSensors = {33,44,66}") Rule:eval("$HouseState=='sleep' => houseSensors:armed=1") Rule:eval("$HouseState=='wake' => houseSensors:armed=0") 'ID:start' - :start starts scene with ID. i.e. 'fibaro:startScene(ID,)' Rule.eval("wakeupScene=123") Rule.eval("$HouseState=='awake' => wakeupScene:start") 'ID:start=args' - :start starts scene with ID and args. i.e. 'fibaro:startScene(ID,args)'. However, if args is an event '{type=...}' then it translates to 'args._from=__fibaroSceneId; fibaro:startScene(ID,{json.encode(args)})' Rule.eval("wakeupScene=123; eventRunnerScene=125") Rule.eval("$HouseState=='awake' => wakeupScene:start={play='godmorning.wav'}") -- send {play='godmorning.wav'} to scene Rule.eval("eventRunnerScene:start=#test{arg1=8}") -- send {json.encode({type='test',arg=8,_from=<sceneID>})} to scene 'ID:stop' - :start stops scene with ID. i.e. 'fibaro:killScene(ID)' 'ID:msg="text"' -- :msg="text" sends "text" to phone device with id ID. i.e. 'fibaro:call(ID,'push',"text")' Rule.eval("phoneIDs={101,102}; sensors={66,77,88}") Rule.eval("sensors:breached & $HomeState=='away' => phoneIDs:msg='House breached!'") 'ID:btn=BtnID' - :btn=BtnID, press button with id BtnID on virtual device ID, i.e. 'fibaro:call(ID,'pressButton',BtnID)' Rule.eval("sensor:breached & once(06:00..08:00) => sonosID:btn=2") -- start Sonos first time someone enter the kitchen in the morning 'ID:R=<expr>','ID:G=<expr>','ID:B=<expr>' - sets RGB values for a RGB device. 'ID:color={<expr>,<expr>,<expr>}' - sets RGB values for a RGB device. Date and time 'HH:MM[:SS]' - time constant translating to seconds 60*(MM+60*HH)+SS Rule.eval("05:10 == 18600") Rule.eval("05:10:03 == 18603") Rule.eval("05:10 + 03:40 == 08:50") '+/HH:MM[:SS]' - same as os.time()+HH:MM:SS Rule.eval("post(#foo,+/05:00)") -- Post event {type='foo'} in five hours from now. 't/HH:MM[:SS]' - epoc today, e.g. epoc(last midnight)+HH:MM:SS Rule.eval("ostime() > t/10:00 & log('It is after 10 o'clock today')") 'n/HH:MM[:SS]' - epoc today if current time before or tomorrow if current time has passed Rule.eval("post(#foo,n/10:00)") -- Post event {type='foo'} at 10:00 today if before 10:00, else post 10:00 tomorrow 'ostime()' - returns epoc, seconds since january 1st, 1970 Rule.eval("log('Days since January 1st are %s',round(ostime() / 24*3600)") 'osdate([frmt]) - Works as Lua os.date. Rule.eval("log('Time is %s',osdate('%X'))") 'midnight' - constant returns epoc seconds to last midnight 'sunset' - constant returns seconds since midnight to sunset Rule.eval("@sunset-00:15 => log('It is 15min before sunset')") 'sunrise' - constant returns seconds since midnight to sunrise Rule.eval(@sunrise+00:15 => log('It's 15min after sunrise')") 'now' - constant that return seconds since midnight Rule.eval("@sunrise+00:30 & now >= 06:45 => 66:on; log('Turn on deviceID 66 at sunset+30min but only if it is after 06:45')") 'wnum' - constant that returns the current week number (important in Sweden) Rule.eval("@12:00 & wnum % 2 == 1 => log('12 oclock on a odd week number')") '<expr>..<expr>' -- test if time is between interval, including. Rule.eval("$Home=='away' & 10:00..15:00 => log('Away and between 10:00 and 15:00')") 'day(<str>)' - returns true if day number expressin is true Rule.eval(@12:00 & day('1') => log('12 o`clock on the first day of the month')" Rule.eval(@12:00 & day('last') => log('12 o`clock on the last day of the month')" Rule.eval(@12:00 & day('lastw') => log('12 o`clock on the first day of the last week of the month')" Rule.eval(@12:00 & day('lasw-last') => log('12 o`clock in the last week of the month')" 'month(<str>)' - Rule.eval(@12:00 & month('dec-feb') => log('12 o`clock on a winter month')" 'wday(<str>)' - Rule.eval(@12:00 & wday('mon-wed,sun') => log('12 o`clock on a Monday,Tuesday,Wednesdaty or Sunday')" 'date(<str>)' - TBD Tables sensor1=55 sensor2=65 sensor1=67 Rule.eval("a = {}") Rule.eval("a = {sensor1,sensor2,sensor3}; a == {55,65,67}") Rule.eval("a[2] == 65") Rule.eval("b = {key=7+1,name=fmt('Name is %s','Jan')}; b == {key=8,name='Name is Jan'}") Rule.eval("b['key'] == 8") Rule.eval("b.key == 8") Rule.eval("b.key = {val=8}") Rule.eval("b.key.val == 8") Rule.eval("add(a,68) == {55,76,67,68}") -- add item last in table Rule.eval("a[size(a)+1] = 69") -- add item last in table (more expensive) --Setup fibaro global HomeTable Rule.eval("$HomeTable=tjson({room1={lamp=88,sensor=77},room2={lamp=99,sensor=101}})") -- Read in HomeTable, convert from json to Lua table. Define as scriptvars. Map var names for debugging Rule.eval("jT = fjson($HomeTable); defvars(jT); mapvars(jT)") Rule.eval("room2:lamp:on") -- Use defined variable from HomeTable 'sort(<table>)' -- sort a table Rule.eval("a = sort({4,3,2,5}); a[1] == 2") Rule.eval("a={4,3,2,5}; sort(a)[round(size(a)/2)] == 3") -- compute median 'size(<table>)' -- return size of table Rule.eval("size({1,2,3}) == 3") Rule.eval("a={1,2,3}; sum(a)/size(a) == 3") -- compute average Logging and format functions 'osdate([fmt]) -- see date and time section 'fmt(str[,...])' -- string format like Lua string.format Rule.eval("a=fmt('5+6=%s',5+6)") 'log(str[,...])' -- loggs string to console and also return the string. arguments like string.format Rule.eval("phoneId:msg=log('5+6=%s',5+6)") -- log to console and send push to phone 'tjson(<expr>)' calls json.encode on expression Rule.eval("tjson({1,2,3} == '[1,2,3]'") 'fjson(<expr>)' calls json.decode on expression Rule.eval("tjson('[33,34,35]')[2] == 34") 'trace(<bool>)' -- starts tracing the byte code, only for the fearless Event functions 'post(<event>[,<time>])' 'cancel(<post ref>)' Delays and rule state 'once(<expr>)' - onceis a special function that keeps state so that it only returns true if it's argument is true but was false before. This is a convenient way to stop an event from triggering multiple times. Rule.eval("downstair.sensor:isOn & once(06:00..08:00) => speakWithSONOS('Good morning!')") This rule triggers whenever the sensor triggers and then checks if it is between 6-8. If that is true the test 06:00..08:00needs to turn false before it will be able to turn true again, effectively only greeting people once between 6-8 when the sensor is breached. 'trueFor(<time>,<expr>)'- forallows for checking if a condition is true for a specified duration. Rule.eval("trueFor(00:10,downstair.sensor:isOff) & downstair.lamp:isOn => downstair.lamp:isOff") The forexpression needs in this case to be true for 10min before it will return true. First time the expression is true a timer is started that checks in 10min if the expression is still true and then returns true. If the expression turns false during that period, the timer is canceled. 'again([<num>])' - Sometimes when the trueFor expression becomes true, one wants to reset the timer to get a new trigger the time again. Typical case is if a door is open for 10min send a notification, and continue to send an alarm for every 10min until the door is closed. So, there is a need to 'tell' the forcommand to re-enable the timer. ¨repeat' does that. Rule.eval("trueFor(00:10,downstair.door:breached) => phone:msg='Door is open'; again()") This will log a message every 10min as long as the door is open. again returns the number of repetition it has currently done, and it can take an argument how many repeats it should do. Rule.eval("trueFor(00:10,downstair.door:breached) => phone:msg=log('Door is open for %s min',again(3)*10)") In this case it will only repeat 3 times, and the for-timer is not reset. However, the next time the for expression becomes true it starts over again. 'wait(<time>)' - waits specified time before continuing executing expression. 'fibaro:sleep' is not allowed as it stops anything going on in the scene while sleeping. Rule.eval("55:isOn => wait(00:10); || 55:last>=00:10 >> 55:isOff") If device 55 is turned on it waits 10min, then checks if 55 hasn't changed state in the last 10min and if so turns off the light. waitis also useful for writing expressions to test rules. Rule.eval("wait(t/10:00); 55:on") Rule.eval("wait(t/10:05); 55:off") Rule.eval("wait(t/10:07); 55:on") This turns on the lamp at 10:00, off again at 10:05, and then on again at 10:07. VD functions 'label(<ID>,<name>)' 'slider(<ID>,<name>)' Edited October 12, 2019 by jgab Quote Link to post Share on other sites
jompa68 121 Share Posted October 21, 2018 WOAW!! This together with your Wiki is a great to start learning how EventRunner works. Quote Link to post Share on other sites
petrkl12 66 Share Posted October 21, 2018 @jgab maybe you have in your post some typo: 1. Only one } Rule.eval("lamp=55}) Rule.eval("lamp:isOff}) -- returns true if deviceId 55 is off 2. I think there should be room2.lamp:on Rule.eval("room2:lamp:on") -- Use defined variable from HomeTable Quote Link to post Share on other sites
jgab 898 Author Share Posted October 21, 2018 (edited) 9 minutes ago, petrkl12 said: @jgab maybe you have in your post some typo: 1. Only one } Rule.eval("lamp=55}) Rule.eval("lamp:isOff}) -- returns true if deviceId 55 is off 2. I think there should be room2.lamp:on Rule.eval("room2:lamp:on") -- Use defined variable from HomeTable Thanks! there were a milli000000n typos... Edited October 21, 2018 by jgab Quote Link to post Share on other sites
jompa68 121 Share Posted October 22, 2018 On 10/17/2018 at 9:38 AM, jgab said: I have a scene continuously calling Apple iCloud Find friends and posting events when family members come and go (I will post that scene later) Interested in this scene. Quote Link to post Share on other sites
jompa68 121 Share Posted October 22, 2018 @jgab after latest update i get this error Quote Link to post Share on other sites
jgab 898 Author Share Posted October 22, 2018 (edited) 22 hours ago, jompa68 said: @jgab after latest update i get this error There is a new version pushed with some better error messages. If running offline the line number of where the offending script rule is defined should be shown. Edited October 23, 2018 by jgab Quote Link to post Share on other sites
jgab 898 Author Share Posted October 22, 2018 (edited) So, in the Github repository an 'iOSLocator' scene has been uploaded. The idea is to think about scenes as services, like a service oriented architecture. Scenes can send and receive events from each other (Ah, shows my age, guess it should be microservices these days). Anyway, the iOSLocator scene is based on the EventRunnerLite framework and polls Apple's Find friends service for family members position, and if they changed location it will send events to whatever EvenRunner scene we have told it to send events to. Because it's based on the EventRunner framework the scene can be tested offline like in ZeroBrane to verify that it works. iOSLocator depends on data in the 'HomeTable' to specify people, their Apple accounts, and places of interests. In the beginning of the 'iOSLocator' file there is an example of the structure required. HomeTable = [[ {"scenes":{ "iOSLocator":{"id":__fibaroSceneId,"send":["iOSClient"]}, "iOSClient":{"id":9,"send":{}}, }, "places":[ {"longitude":17.9876023512517,"dist":0.6,"latitude":60.787947773698,"name":"Home"}, {"longitude":17.955049,"dist":0.8,"latitude":59.405818,"name":"Ericsson"}, {"longitude":18.080638,"dist":0.8,"latitude":59.52869,"name":"Vallentuna"}, {"longitude":17.648488,"dist":0.8,"latitude":59.840704,"name":"Polacksbacken"}, {"longitude":17.5951,"dist":0.8,"latitude":59.850153,"name":"Flogsta"}, {"longitude":18.120588,"dist":0.5,"latitude":59.303781,"name":"Rytmus"} ], "users":{ "daniela":{"phone":777,"icloud":{"pwd":"XXXX","user":"[email protected]"},"name":"Daniela"}, "jan":{"phone":411,"icloud":{"pwd":"XXXX","user":"[email protected]"},"name":"Jan"}, "tim":{"phone":888,"icloud":{"pwd":"XXXXX","user":"[email protected]"},"name":"Tim"}, "max":{"phone":888,"icloud":{"pwd":"XXXXX","user":"[email protected]"},"name":"Max"} }, } ]] The first 'place' in the HomeTable struct should be the 'home' or base, as it is used to calculate how far away people are. Then all other places of interest. Use, e.g. google maps to get lat/long for places that are important for you. The 'list' parameter is the radius from the lat/long that defines the place. Note, I changed the coordinates for my home so you can't come after me if your HC2 crashes Then in the users list we need to list the people's iCloud credentials so we can log in. In the 'scenes' part of the structure, we list what scenes this service should send events to. It knows that it's name is iOSLocator so it will send events to the scene iOSClient (that has id 9 in this case). Every minute it will ask iCloud for the next persons location, which means that if you have 4 persons in the family it takes 4 minutes to update all members. If the iCloud server is polled too often it seems it will start to block requests. If a person moves outside a place an event will be sent that the person is at place "away". So, if we have configured the 'iOSLocator' and start it up we can put event handlers in our iOSClient scene (or whatever scene you want to receive the events to). Nothing special has to be configured in the client as the events arrive just like any other event. function main() Rule.eval("#location{user='$user',place='$place'} => log('User:%s place:%s',user, place)") --Rule.eval("#location{user='$user',place='$place'} => phones:msg=log('%s is at %s',user, place)") end and we can start to add logic to send notifications or detect if everyone is away or if someone has come home.... function main() locations = {} numberOfPeople = 4 homeFlag = false Rule.eval("#presence{state='home',who='$who'} => phonesToNotify:msg=log('%s at home',who)") Rule.eval("#presence{state='allaway'} => phonesToNotify:msg=log('House empty')") -- Event from iOSLocation service Rule.eval([[#location{user='$user', place='$place'} => locations[user]=place; phoneToNotify:msg=log('%s at %s',user,place); post(#checkLocations)]]) Event.event({type='checkLocations'}, function(event) local home = false local who = {} for w,p in ipairs(locations) do if p == 'Home' then home=true; who[#who+1]=w end end if home ~= homeFlag then homeFlag = true Event.post({type='presence', state='home', who=table.concat(who,',')}) -- one at home enough for house to not be empty elseif #locations == numberOfPeople then -- can only be sure that all are away after we seen everyone if homeFlag ~= false then homeFlag = false Event.post({type='presence', state='allaway'}) end end end) end -- main If there is some complicated calculations to do it is easier define a Lua event handler, like in the example above. EventScript rules should be used for writing short concise rules and Lua event handlers for stuff that need to trigger on events . In fact, if there is more advanced calculations like in this example it works as well to break it out into a normal Lua function as they can be called from EventScripts. locations = {} numberOfPeople = 4 homeFlag = false Rule.eval("#presence{state='home',who='$who'} => phonesToNotify:msg=log('%s at home',who)") Rule.eval("#presence{state='allaway'} => phonesToNotify:msg=log('House empty')") function checkLocation() local home = false local who = {} for w,p in ipairs(locations) do if p == 'Home' then home=true; who[#who+1]=w end end if home ~= homeFlag then homeFlag = true Event.post({type='presence', state='home', who=table.concat(who,',')}) elseif #locations == numberOfPeople then if homeFlag ~= false then homeFlag = false Event.post({type='presence', state='allaway'}) end end end Rule.eval([[#location{user='$user', place='$place'} => locations[user]=place; phoneToNotify:msg=log('%s at %s',user,place); checkLocations()]]) Here checkLocation is just an ordinary Lua function helping us to keep track if someone is home or all away. A real presence algorithm would also check sensors and other possible means of detecting people inside the house. This can also be used to send notifications when children come home or arrive at school. Edited October 22, 2018 by jgab forgot homeFlag... Nicer 'who'... corrected homeFlag Quote Link to post Share on other sites
jompa68 121 Share Posted October 22, 2018 So if i change this to false it should use my variable instead? Did try to comment out HomeTable part from scene but then scene stops local _test = true -- use local HomeTable variable instead of fibaro global Quote Link to post Share on other sites
jgab 898 Author Share Posted October 22, 2018 1 minute ago, jompa68 said: So if i change this to false it should use my variable instead? Did try to comment out HomeTable part from scene but then scene stops local _test = true -- use local HomeTable variable instead of fibaro global Yes, that was the idea. if _test is false it should call fibaro:getGlobalValue(_deviceTable), and _deviceTable should be set to the name of the fibre global. Quote Link to post Share on other sites
jompa68 121 Share Posted October 22, 2018 [DEBUG] 20:23:04: Starting iOS Locator service [DEBUG] 20:23:04: 2018-10-22 20:23:04.974203 [ error] timer handler failed with error: /usr/share/lua/5.2/json/decode.lua:74: bad argument #1 to 'match' (string expected, got nil) [DEBUG] 20:23:04: 2018-10-22 20:23:04.975074 [ error] timer handler failed with error: /opt/fibaro/scenes/456.lua:210: attempt to get length of global 'iUsers' (a nil value) Quote Link to post Share on other sites
jgab 898 Author Share Posted October 22, 2018 1 hour ago, jompa68 said: [DEBUG] 20:23:04: Starting iOS Locator service [DEBUG] 20:23:04: 2018-10-22 20:23:04.974203 [ error] timer handler failed with error: /usr/share/lua/5.2/json/decode.lua:74: bad argument #1 to 'match' (string expected, got nil) [DEBUG] 20:23:04: 2018-10-22 20:23:04.975074 [ error] timer handler failed with error: /opt/fibaro/scenes/456.lua:210: attempt to get length of global 'iUsers' (a nil value) So it doesn't manage to read in the HomeTable global. Have you set _deviceTable at line 20 to the name of the fibaro global containing the HomeTable? I've pushed a new version of iOSLocator that checks the value a bit better and quits if it doesn't get a HomeTable config data. If you set _REMOTE to true and add the HC2 credentials you can run the iOSLocator scene in ZeroBrane and it will do fibaro:getGlobalValue to the HC2 and you can debug what value it gets. Set a breakpoint in iOSLocator on line 108 (iUsers = {}), and step through to see what happens. if event.type=='global' and event.name == _deviceTable then iUsers = {} local gname = _test and HomeTable or fibaro:getGlobalValue(_deviceTable) Quote Link to post Share on other sites
jgab 898 Author Share Posted October 22, 2018 (edited) On 10/13/2018 at 11:00 PM, petrkl12 said: Hi, how to start scene with parametres in your script e.g. fibaro:startScene(jT.scene.HueCollorEffect,{"1", "192.168.1.245", jT.hue.HueUserZahrada, "colorloop", 254, 2}) I have found in your wiki: :start. Start scene. Ex. 66:start = fibaro:startScene(66) but I don't know where I should place parametres {a,b,c} Thanks My script - but it doesn't work: Rule.eval("everyHourRun = {00:00,01:00,02:00,03:00,04:00,05:00,06:00,07:00,08:00,09:00,10:00,11:00,12:00,13:00,14:00,15:00,16:00,17:00,18:00,19:00,20:00,21:00,22:00,23:00})") Rule.eval("@everyHourRun & label(Zahrada.Osvetlenizahrady,'sldBrightness')~=0 => log('Run Garden Light Effect');fibaro:startScene(scene.HueCollorEffect,{'1', '192.168.1.245', hue.HueUserZahrada, 'colorloop', 254, 2})") There is another way to achieve things being scheduled at specific intervals. Rule.eval("@@00:01 & date('0 10-16/2') => log('T')") I just changed the interval command '@@' to be drift free. That means that if it starts at the second 15 past the minute it will continue on on that second for every minute and not start to drift. That means that one can use the date() command that is almost a crontab format (it compiles to a code that test the condition very effective). Anyway, date('0 10-16/2') means that the test will return true on the '0' minute every second hour from 10 to 16. To make something on the hour the string is simply date('0 *') - * stands for any hour, the same as date('0 0-23'). Much simpler than having to list all hours with the daily '@' command. Crontab is very flexible, date('15,45 7-19/3 mon-fri dec,jan') means 15min and 45min past every third hour between 7 and 17 on Monday to Friday in January and December. The '@@00:01' will run the test every minute (exactly like crontab does) and if the date() test is true it will run the action. This is very flexible. The day(),wday(),month() functions are implemented with date(). Have a look at any crontab documentation on the net and you will get the gist of it. Edited October 22, 2018 by jgab Quote Link to post Share on other sites
jompa68 121 Share Posted October 23, 2018 7 hours ago, jgab said: Set a breakpoint in iOSLocator on line 108 (iUsers = {}), and step through to see what happens. All sorted now. In my HomeTable i define scene not scenes that are setup with iOSLocator. Added scenes and moved iOSLocator parts to that and all working as we want when we set local _test = false Thanks again. Now time for 1000m swimming before go to work, have a nice morning. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.