Jump to content

Recommended Posts

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.

Link to post
Share on other sites
  • Replies 2.7k
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular Posts

Note. The first ~2000 posts of this thread is mainly about EventRunner3 that is for the HC2. EventRunner3 is not developed further, but bugs are fixed as they are reported. For HC3, the version i

I've been playing with the HC3 a bit  (I don't own a HC3 but a friend has allowed me to remotely login to do testing - I'm very grateful for that). ...and I have made some progress with EventRunn

EventRunner4. v 0.5 fix 32 -  EventRunner4.fqa   I have made an overhaul of the alpha version I released earlier and I'm starting to run ER4 for some of my stuff at home now. I will upload n

Posted Images

"Cross posting" dimmer use case. Dimming up and down a light when holding in a key on the fibaro keyfob

 

Link to post
Share on other sites

I really like the possibility to test all rules with ZeroBraneStudio before place them in a live scene :D :-D

Link to post
Share on other sites
4 hours ago, jompa68 said:

I really like the possibility to test all rules with ZeroBraneStudio before place them in a live scene :D :-D

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 by jgab
Link to post
Share on other sites

.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 by jgab
Link to post
Share on other sites

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 by jgab
Link to post
Share on other sites

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 by jgab
Link to post
Share on other sites

@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
Link to post
Share on other sites
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 by jgab
Link to post
Share on other sites
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. 

Link to post
Share on other sites
22 hours ago, jompa68 said:

@jgab after latest update i get this error

 

20181022_09-41-25.png.b3ea1545bdac0523087e141dcbd44799.png

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 by jgab
Link to post
Share on other sites

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 by jgab
forgot homeFlag... Nicer 'who'... corrected homeFlag
Link to post
Share on other sites

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

 

Link to post
Share on other sites
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.

Link to post
Share on other sites
[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)

 

Link to post
Share on other sites
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)

 

Link to post
Share on other sites
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 by jgab
Link to post
Share on other sites
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.

Link to post
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
Reply to this topic...

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