Aller au contenu

jang

Membres confirmés
  • Compteur de contenus

    206
  • Inscription

  • Dernière visite

  • Jours gagnés

    41

Tout ce qui a été posté par jang

  1. If you do fibaro.call(self.id,"test") the QA will crash if function QuickApp:test() ...end throws an error. The pcall will not protect it as the fibaro.call itself doesn't return an error. It's the test function that crashes and kills the QA (and that function is in the same QA as the fibaro.call code). However "test" runs in another thread so pcall doesn't catch it. If you call another QA the fibaro.call always return true unless the id is non-existent. On the other hand the other QA may crash if it has an error. To make things more confusing. Setting __fibaroUseAsyncHandler(false) it will hang for a while and still crashes fibaro.call(self.id,"test") but doesn't crash the whole QA....
  2. Add to this that the QuickApp: setVariable (name, value) looks something like this: (Similar search for QuickApp: getVariable) So, the quickAppVariables are stored in a linear list and the time to retrieve / store a variable depends on where in the list it is I don't understand why they didn't use a hash table (!) I hope that fibaro global variables are stored / retrieved more efficiently.
  3. Yes, but your example is only globally declared functions. We were talking about "forward declaring" local functions. This don't work function QuickApp:onInit() f1("toto"); end function f1(a) return f2(a); end -- f1 local function f2(a) print(a); end This do function QuickApp:onInit() f1("toto"); end local f2 function f1(a) return f2(a); end -- f1 function f2(a) print(a); end
  4. Yes, for QuickAppChildren you first need a male QA and a female QA...
  5. local n = 10000000 -- loop 10 million times... local a = 1 -- our local variable b = 1 -- our global variable local t0 = os.clock() for i=1,n do a=a+1 end local t1 = os.clock()-t0 local t2 = os.clock() for i=1,n do b=b+1 end local t3 = os.clock()-t2 print("Local assignment ",n," times = ",t1,"s") print("Global assignment ",n," times = ",t3,"s") print("You saved ",t3-t1," seconds of your life") [25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: Local assignment 10000000 times = 0.579324 s [25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: Global assignment 10000000 times = 2.69236 s [25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: You saved 2.113036 seconds of your life The HC3 is (or rather Lua is) quite fast.
  6. QuickApp:onInit() is run after the whole file is loaded so it can be anywhere. Fibaro does something like... loadFile("QA file") -- Everything in the file is run - all functions and globals are defined (also locals on "top level"). local self = QuickApp() -- Now all methods are defined, create an instance if self.onInit() then self:onInit() end -- If the user declared an :onInit call it quickApp = qa -- Set the globaö 'quickApp' to the current QuickApp self. So :onInit() is called when everything is defined. Also note that Fibaro sets the global 'quickApp' to self after :onInit() is done. This means that we can use quickApp instead of passing around self to local functions if we are careful. It may not work if we deal with QuickAppChild instances as we then have many instances of "QuickApp" that we pass around... And yes, we need to "forward" declare local variables sometimes. This doesn't work local function bar(x) if x > 0 then foo(x-1) else print('done') end local function foo(x) bar(x-1) end foo(5) This work local foo local function bar(x) if x > 0 then foo(x-1) else print('done') end function foo(x) bar(x-1) end foo(5) The reason is that when bar is defined/compiled, 'foo' is assumed to be a global variable as it has not been mentioned/seen earlier. In the second example 'foo' is recognized as a local variable (even if it at the moment doesn't have a value)
  7. You could make the button toggle intervalRunner --to initialize the QA function QuickApp:onInit() self:debug("onInit") ref={} -- Start running self:button3() end --to toggle running the QA function QuickApp:button3() if ref[1] then -- Running stopIntervalRunner(ref) self:updateView("<button3 id>","text","Halted") else ref = intervalRunner(2, function() return mainCode(self) end) self:updateView("<button3 id>","text","Running") end end "<button3 id>" should be the id of the button.
  8. function intervalRunner(seconds,fun,...) local args,nxt,ref={...},nil,{} local function loop() if fun(table.unpack(args))==':EXIT' or ref[1]==nil then return end nxt=nxt+seconds ref[1] = setTimeout(loop,1000*(nxt-os.time())) end local t = ((os.time()-3600) // seconds) * seconds nxt=t+seconds+3600 ref[1] = setTimeout(loop,1000*(nxt-os.time()-(seconds > 3600 and 3600 or 0))) return ref end function stopIntervalRunner(ref) if ref[1] then clearTimeout(ref[1]) ref[1]=nil end end function QuickApp:onInit() local ref,i=nil,0 ref = intervalRunner(2,function() i=i+1 print(i) if i == 5 then stopIntervalRunner(ref) end end) end
  9. We could do intervalRunner a little better function intervalRunner(seconds,fun,...) local args,nxt,ref={...},nil,{} local function loop() if fun(table.unpack(args))==':EXIT' or ref[1]==nil then return end nxt=nxt+seconds ref[1] = setTimeout(loop,1000*(nxt-os.time())) end local t = ((os.time()-3600) // seconds) * seconds nxt=t+seconds+3600 ref[1] = setTimeout(loop,1000*(nxt-os.time()-(seconds > 3600 and 3600 or 0))) return ref end Then we can do intervalRunner (10, checkVariables, self)
  10. Sorry, my bad. Solution is intervalRunner (10, function () return checkVariables (self) end)
  11. No no no, the solution is to do this in your code intervalRunner (10, function () checkVariables (self) end) intervalRunner shouldn't care what function it is given or if it needs self or not. No need to change intervalRunner.
  12. Yes, because the checkVariables needs 'self' that's not available when called from intervalRunner - so it crashes (silently) Solution: intervalRunner ( 10 , function() checkVariables(self) end) That setTimeout crashes silently when the function crash is really a problem and makes many errors go undetected. You can add my fibaroExtra.lua files to your QA and you will see that you get an error when the checkVariables try to call self:getVariable (it also protects success/error handlers net.HTTPClient() )
  13. Now you are starting to put pressure on me Well it turns out that print(os.date("%c",0)) >Thu Jan 1 01:00:00 1970 So it starts to count at 1 o'clock Jan 1 1970, not at 0 o'clock (midnight). This means that my alignment calculation becomes 1 hour off when we align for time >= 1 hour. My "hack" to subtract of 1 hour makes it work for time of 0-3600 seconds but not for 2 hours. After some thinking this is more correct and allow it to align up to better for hours. function intervalRunner(seconds,fun) local nxt,ref=nil,{} local function loop() if fun()==':EXIT' or ref[1]==nil then return end nxt=nxt+seconds ref[1] = setTimeout(loop,1000*(nxt-os.time())) end local t = ((os.time() - 3600) // seconds) * seconds -- remove 1 hour from epoch 0 for align calc nxt=t+seconds+3600 -- add back to start time ref[1] = setTimeout(loop,1000*(nxt-os.time())) return ref end However, alignment is a tricky concept for time - when we have intervals of hours we maybe would like to align it from midnight. If we do intervalRunner(2*3600,fun) it won't align on even hours which may be what we had expected. It can become 3,5,7,... (reason being, I guess, leap years that don't make hours always align from midnight) We can also skip the whole alignment and simplify the code function intervalRunner(seconds,fun) local nxt,ref=os.time(),{} local function loop() if fun()==':EXIT' or ref[1]==nil then return end nxt=nxt+seconds ref[1] = setTimeout(loop,1000*(nxt-os.time())) return ref end return loop() end ..it will still be drift free.
  14. setTimeout() returns a reference that can be used to cancel the timer with clearTimeout(ref) The reference happens to look like an incrementing number but it's internal to setTimeout. intervalRunner needs to return a reference so that we can stop it with stopIntervalRunner(ref). We can't return setInterval's reference directly as we generate a new reference each loop when we call setTimeout. Instead we return a table with one position where we store the last reference from setTimeout. Each loop in intervalRunner updates the table with the new reference from setTimeout. If (and when) we call stopIntervalRunner(ref), ref will be the table and it will contain the last setTimeout reference. That reference we can then use to cancel the ongoing setTimeout call with clearTimeout(ref[1]) It's so that the time interval should start on even intervals - 60 it starts on even minutes, 15 it starts on even 15s (00:15,00:30 etc), 3600 it starts on even hours. The drift is taken care of with the statement setTimeout ( loop , 1000 * ( nxt - os . time ())) Yes, but it's an expression so it returns a value. if-then-else is a statement and doesn't return a value. We can't do local b = 100 * (if a == 1 then return 9 else return 10 end) but it would have been nice
  15. "There are only two hard things in Computer Science: cache invalidation, naming things and off-by-one errors.
  16. Ok, you seems to have it covered. Fingers crossed Btw, you could add some counters for cache hits and misses - nice stats to see.
  17. function intervalRunner(seconds,fun) local nxt,ref=nil,{} local function loop() if fun()==':EXIT' or ref[1]==nil then return end nxt=nxt+seconds ref[1] = setTimeout(loop,1000*(nxt-os.time())) end local t = (os.time() // seconds) * seconds nxt=t+seconds ref[1] = setTimeout(loop,1000*(nxt-os.time()-(seconds > 3600 and 3600 or 0))) return ref end function stopIntervalRunner(ref) if ref[1] then clearTimeout(ref[1]) ref[1]=nil end end local function test() print("ping") end intervalRunner(60,test) will run test every minute, on the minute. 00:05:00, 00:06:00, 00:07:00 etc. intervalRunner(30,test) will run test every 30s, on the even 30s. 00:00:30, 00:01:00, 00:01:30 etc. intervalRunner(3600,test) will run test every hour, on the hour. 03:00:00, 04:00:00, 05:00:00, etc. It's nice because it syncs to even periods and it doesn't drift. Ex. local function checkGlobals() .... end function QuickApp:onInit() intervalRunner(60,checkGlobals) end If the function returns the string ':EXIT', the loop will stop. You can also stop it with calling stopIntervalRunner(ref) with the ref that intervalRunner returns.
  18. Sorry, long text in english but I don't trust Google translate :-) A cache is a really good idea, but can be tricky also. In my emulator I cache fibaro calls as it can run faster than realtime and calling the HC3 then becomes a bottle-neck. But I only cache when running faster than realtime. It's not just a read cache as I update it when setting properties or calling actions too. The reason is that fibaro.call(x,'turnOn'); if fibaro.getValue(x,'value') then ... end wouldn't work as getValue would retrieve the cached value unless I update the property when doing the turnOn. This is not a fool-proof method as I can't guess how all actions will affect properties. If someone does fibaro.call(MY_QA,"test") if fibaro.getValue(x,"value") then ... end and MY_QA test function updates device x's value property I miss it. That's why I only use it when speeding... In my EventRunner rule engine, I don't cache values at all due to the issue above. I probably could but it requires quite a lot of code to be fool-proof, because rules can have code similar to the above. I was thinking if this is also a problem for GEA? I guess you could say that all rules in GEA run in "parallel" and should see the same state. Then it works to keep a cache for an invocation and clear it before the next. In my system the rules are run sequentially, top to bottom, and some combinations of rules depend on that. E.g. if two rules are triggered, the first rule could change a value that the second rule could use. Then I don't know if you can change a value in a single rule and expect to use that changed value later in the rules action part? What I have done instead is that I focus on keeping down the number of rules being called by triggers. If I have a rule that looks like: "IF ID1:isOn and ID2:power < 5 THEN ...END" My "rule compiler" look at the test part between IF and THEN and see that 2 device properties are involved in the test. ...and thus only 2 events can be a reason to trigger that rule {type='device', id=ID1, property='value'} {type='device', id=ID2, property='power'} With an "intricate" hash schema I associate the rule with these 2 events so if they come into the system I can quickly lookup what rule(s) to run. This allows the system's cpu usage to not be directly dependent on the number of rules as only relevant rules are triggered by incoming events and I don't have to run all of them.
  19. jang

    Quick App - Evénements

    I usually don't promote code I write, but I have a QA library file (fibaroExtra) with some of the missing fibaro functions... including a simple to use refreshStates function (you can also get them more "refined" as sourceTriggers) https://forum.fibaro.com/topic/54538-fibaroextra/ local function handler(event) print("Incoming event:",json.encode(event)) end fibaro.getRefreshStates(handler) -- install handler I'm slowly migrating my own QAs to use this code.
  20. child:setVisible(false) works for me, but I need to refresh the web GUI. I haven't tried the app.
  21. It's not a. 'property' if not api.get("/devices/"..self.id).enabled then for _,child in pairs(self.childDevices) do child:setEnabled(false) end end or (both ways): local enabled = api.get("/devices/"..self.id).enabled for _,child in pairs(self.childDevices) do child:setEnabled(enabled) end self:setEnabled(bool) is the same as api.post("/devices/"..self.id,{enabled=bool==true}) 'enabled' doesn't stop the QA from running, you have to enforce that yourself (ex. in QuickApp:onInit() ) Ex. function QuickApp:onInit() if not __fibaro_get_device(self.id).enabled then self:debug("QA ",self.name," disabled") return end : : end
  22. This is a helper QA that can send triggers to other QAs that subscribe for them. Ok, if you don't need sub seconds responses. https://forum.fibaro.com/topic/49113-hc3-quickapps-coding-tips-and-tricks/?do=findComment&amp;comment=228539
  23. jang

    HC3 Alarme

    Here is a simple QA that sends a customEvent when an alarm partition is "enabled". The customEvent is named "AlarmEnabled1" for partition 1, "AlarmEnabled2" for partition 2 etc. You need to set an exit delay so the QA have time to discover that the partition is enabled. The customEvent can be picked up in a block or Lua scene. (or in a QA with some work) AlarmNotifier.fqa Here is the code local armedPs = {} local ces = {} local function watch() local function loop() for _,p in ipairs(api.get("/alarms/v1/partitions") or {}) do local ce = "AlarmEnabled"..p.id if not (ces[ce] or api.get("/customEvents/"..ce)) then -- if you remove partition, restart QA api.post("/customEvents",{name=ce,userDescription="Alarm enabled"}) -- Create event end ces[ce]=true if p.secondsToArm and not armedPs[p.id] then api.post("/customEvents/"..ce,{}) end if p.secondsToArm then print("partition",p.id,p.secondsToArm,"seconds") end armedPs[p.id] = p.secondsToArm end quickApp:updateProperty("value",next(armedPs)~=nil) setTimeout(loop,1000) end setTimeout(loop,0) end function QuickApp:onInit() self:debug(self.name, self.id) watch() end
  24. jang

    UN petit coup de main SVP

    The Fibaro forum 'HC3 QuickApps coding - tips and tricks" have ~1200 posts starting February 2020. I wouldn't use any code before August 2020 - we have learnt a lot the last 6 months...
  25. jang

    UN petit coup de main SVP

    @ Lazer is right. I didn't notice the setInterval. Yes, the response time can vary depending if there are events available. ... and your requests "run over" each other.
×
×
  • Créer...