Aller au contenu

jang

Membres confirmés
  • Compteur de contenus

    209
  • Inscription

  • Dernière visite

  • Jours gagnés

    42

Tout ce qui a été posté par jang

  1. Sorry for this long post in English in a French forum. However, I don't trust Google to translate my non-native English into French ;-) Anyway, it's the 'beginner's questions' and we are discussing how to structure our programs. Week-end reading for everyone interested in writing their own rule system – or just structure programs in a more “declarative” way. condition_1 => action_1 condition_2 => action_2 : condition_n => action_n If you have this way of programming, you typically need to test your conditions in a loop that runs every x second local function loop() if condition_1 then action_1 end if condition_2 then action_2 end : if condition_n then action_n end setTimeout(loop,100*30) -- check every 30s end loop() Ex. In this case checking every 30s. This is the way standard GEA rules behaves. However, GEA has another model with the -1 trigger, that reacts directly on external events, like devices changing states etc., and then run the loop checking the rules. That way rules can respond immediately to ex. sensors being breached. We can add that immediate check too local function loop() if fibaro.getValue(88,'value')==true then fibaro.call(99,'turnOff') end if condition_2 then action_2 end : if condition_n then action_n end setTimeout(loop,100*30) -- check every 30s end loop() local function check_sensor() if fibaro.getValue(88,'value')==true then loop() end setTimeout(check_sensor,1000*1) -- check every second end check_sensor() We have a loop that runs every 30s and checks all our "rules". We have another function, check_sensor, that runs every second and if the sensor is breached it calls the main loop that checks all rules. The ‘loop’ function still carries out the action when the light is on. This gives us the flexibility to check everything every 30s but also to react immediately when a sensor is breached. We can improve this. Now we check the sensor in both loops (fibaro.getValue). We have already detected that the sensor was breached so we can tell the main loop what has happened. local function loop(event) if event=='sensor_88_breached' then fibaro.call(99,'turnOff') end if condition_2 then action_2 end : if condition_n then action_n end setTimeout(loop,100*30) -- check every 30s end loop() local function check_sensor() if fibaro.getValue(88,'value')==true then loop('sensor_88_breached') end setTimeout(check_sensor,1000*1) -- check every second end check_sensor() Let's make the check_sensor code be more generic and check more devices local function rule_checker(event) if event.type=='device' and event.id==88 and event.value==true then fibaro.call(99,'turnOff') end if condition_2 then action_2 end : if condition_n then action_n end setTimeout(function() rule_checker({type='loop'}) end,100*30) -- check every 30s end rule_checker({type='loop') local myDevices = { 88, 99, 101, 120, 444, 55, 6 } -- ID of devices to check local myDeviceValues = { } -- Starts empty but will store last values of our devices local function check_devices() for _,id in ipairs(myDevices) do local value = fibaro.getValue(id,'value') -- Fetch device value if myDeviceValues[id]~=value then -- Has the value changed? myDeviceValues[id]=value -- Remember new value rule_checker({type='device', id=id, value=value}) -- Call our rule checker with new value end end end setInterval(check_devices,1000*1) -- check devices every second First our 'loop' function is renamed to 'rule_checker' and takes one argument 'event' 'event' is a Lua table with at least a key that is 'type'. When rule_checker calls itself every 30s it sends the argument {type='loop'} to itself. This is to make sure that there is always an argument 'event' that we can check against. Our check_sensor has become check_devices, and checks multiple devices if they have changed state and then calls our check rules with an argument (event) that is of type 'device' with information what deviceId it was and what new value it has: {type='device', id=<id of device>, value=<new device value>} Now our rule checks if the type of event was 'device' and the id was 88, and in that case turns off device 99. Note that the rule will not act every 30s as it now requires the event type is 'device' - not 'loop' We could keep both rules if that would make sense. if event.type=='device' and event.id==88 and event.value==true then fibaro.call(99,'turnOff') end if event.type=='loop' and fibaro.getValue(88,'value')==true then fibaro.call(99,'turnOff') end The observant reader will have discovered a bug. Every time check_devices calls 'rule_checker' it will start a new loop as rule_checker ends with a call to itself (the 'setTimeout'). Well,that will be fixed in the next example. Now let’s make one more abstraction of this code. Sending the event to our rule_checker should be a function - 'post' local post -- Forward declaration... local function rule_checker(event) if event.type=='device' and event.id==88 and event.value==true then fibaro.call(99,'turnOff') end if condition_2 then action_2 end : if condition_n then action_n end end local myDevices = { 88, 99, 101, 120, 444, 55, 6 } -- ID of devices to check local myDeviceValues = { } -- Starts empty but will store last values of our devices local function check_devices() for _,id in ipairs(myDevices) do local value = fibaro.getValue(id,'value') -- Fetch device value if myDeviceValues[id]~=value then -- Has the value changed? myDeviceValues[id]=value -- Remember new value post({type='device', id=id, value=value}) -- Post event end end end function post(event) rule_checker(event) end -- Posting an event means calling check rules with event setInterval(check_devices,1000*1) -- Check devices every second setInterval(function() post({type='loop'}) end,1000*30) -- Post 'loop' event every 30s Instead of the rule_checker calling itself with setTimeout, we have a separate setInterval, "posting" the 'loop' event every 30s to the rule_checker function. The abstraction is a bit better, and we integrate periodic and immediate rule checks. The next steps is to abstract the rule_checker function. it's just checking a number of rules in sequential order. Let’s break that apart in 2 steps. local rules = { device = function(even) if event.id==88 and event.value==true then fibaro.call(99,'turnOff') end end, <type_2> = function(event) action_2 end, <type_n> = function(event) action_n end, end We make it a Lua table with the event type as key, associated to a function carrying out the action. Our 'post' function then becomes function post(event) if rules[event.type] then -- Do we have a rule for this type ? local action = rules[event.type] -- Then get the action -- action(event) -- ..and call it setTimeout(function() action(event) end,0) -- Lets call the action with setTimeout instead of calling it directly... end end We can also add rules to the table like this local rules = {} local function addRule(eventType,action) rules[eventType]=action end addRule('device', function(even) if event.id==88 and event.value==true then fibaro.call(99,'turnOff') end end) addRule('loop',function(event) action_2 end) There is a big problem with the approach to store rules using the event type as key. We can only have one rule for each key, because keys are unique in a Lua table (or we overwrite the old value) Instead of using the type as key we can just store them in an array and let post search for matching keys local function addRule(eventType,action) table.insert(rules,{type=eventType,action=action}) end function post(event) for _,rule in ipairs(rules) do if event.type==rule.type then setTimeout(function() rule.action(event) end,0) end end end This means that we can define rules like this addRule('device', function(even) if event.id==88 and event.value==true then fibaro.call(99,'turnOff') end end) addRule('device', function(even) if event.id==101 and event.value==true then fibaro.call(99,'turnOff') end end) addRule('loop',function(event) action_2 end) addRule('loop',function(event) action_n end) If we have a 'device' event, both rules will run, checking if it was 88 or 101 that was breached. A drawback with this is that we run both device rules even though we know that if one match the other will not. And we have to do the if event.id == .. test in both. We can do better. Our post functions now only look at the 'type' key of the event. Instead it could look at all fields in the event. local function addRule(event,action) table.insert(rules,{event=event,action=action}) end local function match(event1,event2) for key,value in ipairs(event1) if event2[key]~=value then return false end -- If a key in event1 is not the same as in event2, no match - return false end return true -- All keys matched, return true end function post(event) for _,rule in ipairs(rules) do if match(rule.event,event.type) then setTimeout(function() rule.action(event) end,0) end end end Now when we call addRule to add a rule we provide the whole event it should match for the action to be called. addRule({type='device',id=88, value=true}, function(even) fibaro.call(99,'turnOff') end) addRule({type='device',id=101, value=true}, function(even) fibaro.call(99,'turnOff') end) addRule({type='loop'},function(event) action_2 end) addRule({type='loop'},function(event) action_n end) The 'post' function may not be the most efficient as it needs to look through all the rules and see if they match. Don't worry about that now (we can make it much more efficient). Instead enjoy the abstraction :-) Let’s improve the 'post' function with one more feature. local function postEvent(event) for _,rule in ipairs(rules) do if match(rule.event,event.type) then rule.action(event) end end end function post(event,delay) return setTimeout(function() postEvent(event) end,1000*(delay or 0)) end 'post' can now delay the invocation of the matching rules. (If we don't specify a delay it becomes zero and is invoked immediately) Why is that a nice feature? local rules = {} local function addRule(event,action) table.insert(rules,{event=event,action=action}) end local function match(event1,event2) for key,value in ipairs(event1) if event2[key]~=value then return false end -- If a key in event1 is not the same as in event2, no match - return false end return true -- All keys matched end local function postEvent(event) for _,rule in ipairs(rules) do if match(rule.event,event.type) then rule.action(event) end end end function post(event,delay) return setTimeout(function() postEvent(event) end,1000*(delay or 0)) end addRule({type='device',id=88, value=true}, function(even) fibaro.call(99,'turnOff') end) addRule({type='device',id=101, value=true}, function(even) fibaro.call(99,'turnOff') end) addRule({type='start'},function(event) post({type='check_devices', interval=event.check_interval}) -- Start checking devices every second post({type='loop', interval=event.loop_interval}) -- Start period 'loop' end) addRule({type='loop'},function(event) -- 'loop' event respost then'loop' event to get a periodic loop post(event,event.interval) -- and we delay it with the interval value specified in the event end) addRule({type='loop'},function(event) -- Do something every interval print("Periodic check") end) local myDevices = { 88, 99, 101, 120, 444, 55, 6 } -- ID of devices to check local myDeviceValues = { } -- Starts empty but will store last values of our devices addRule({type='check_devices'},function(event) for _,id in ipairs(myDevices) do local value = fibaro.getValue(id,'value') -- Fetch device value if myDeviceValues[id]~=value then -- Has the value changed? myDeviceValues[id]=value -- Remember new value post({type='device', id=id, value=value}) -- Post event end end post(event,event.interval) -- Loop and check devices end) post({type='start', check_interval=1, loop_interval=30}) - Post 'start' event to get things going... (This is the "whole" system) It's cool, because we have a 'start' rule that invokes another rules ('loop' and 'check_devices') by posting events. The 'loop' rule then just reposts the event after a delay to create a periodic loop. Then we can have other rules that trigger on the 'loop' event and carries out some actions. We have abstracted away all setInterval's as "looping" is done with rules that repost events they trigger on (check_devices and loop). We also see, that events can carry “arguments”, like the interval value that the loops use to decide how much to delay their posts to themselves. So, this becomes a powerful abstraction model where our logic is expressed in rules and events, and where posting events connects our rules into a logic flow. Another classic example. Assume that we want a rule that turns on a light (99) if a sensor (88) is breached and then turns it off if the sensor has been safe for 60s. For this case we need one more function, ‘cancel’, that allow us to cancel an event posted in the future - in case we change our mind… local function cancel(timer) if timer~=nil then clearTimeout(timer) end -- cancel event, e.g. the setTimeout that will post the event return nil end addRule({type='device', id=88, value=true},function(event) timer=cancel(timer) -- Cancel timer, if there were a timer running fibaro.call(99,'turnOn') -- Turn on light end) addRule({type='device', id=88, value=false},function(event) timer = post({type='turnOff', id=99},60) -- Post event in 60s that will tuen off light end) addRule({type='turnOff'},function(event) timer=nil fibaro.call(event.id,'turnOn') -- Turn off light end) This takes care of the resting of the interval if the sensor is breached while the timer is counting down. ...This is my all-time favourite model how to structure my programs.... especially when they becomes large and the logic complex and there are several parallell things going on. (and it integrates well with other asynchronous functions like net.HTTPClient requests.)
  2. I guess the reason is that as you define more and more conditions and actions it will stabilise over time and you will only focus on the rules e.g. local rules = { {condition=condition1,action=action1}, {condition=condition2,action=action2}, {condition=condition3,action=action3} } combining existing conditions and actions to new rules. You could have conditions that combine other conditions (AND). You could have conditions that watch if another conditions is true for a certain time. You could improve the "rule engine" to stop evaluating rules if an action returns the "break. You could add automatic logging etc. Of course you will end up with something like GEA at the end.... The point is that the abstraction allow you to focus on the problem - home automation rules - like, ex. GEA does. When the number of rules/conditions/actions are as small as in the example above the overhead may not be justified, but when you sit there with 100+ rules, you would like an abstraction that allows reuse of logic and an easy way to add features... So, I don't write all my code in this style - but dealing with large complex test logic is tempting to write a rule engine - done it many times. My other favourite is "event style" coding. Defining event handlers and posting events to drive execution between the handlers. It's really suitable in asynchronous logic (like home automation tends to be) and it integrates well my app logic with both the asynchronous net.HTTPClient:request(...) we have as well as the triggers from /refreshStates etc. Your code becomes like a state-machine, but a bit more flexible. Here is an old post from the HC2 days https://forum.fibaro.com/topic/25214-event-based-programming/ ...and it has improved a lot since then.
  3. jang

    setTimeout seule solution?

    Updated TriggerQA to v1.21 Supported triggers {type='alarm', property='armed', id=<id>, value=<value>} {type='alarm', property='breached', id=<id>, value=<value>} {type='alarm', property='homeArmed', value=<value>} {type='alarm', property='homeBreached', value=<value>} {type='alarm', property='activated', id=<id>, seconds=<seconds>} {type='weather', property=<prop>, value=<value>, old=<value>} {type='global-variable', property=<name>, value=<value>, old=<value>} {type='quickvar', id=<id>, name=<name>, value=<value>; old=<old>} {type='device', id=<id>, property=<property>, value=<value>, old=<value>} {type='device', id=<id>, property='centralSceneEvent', value={keyId=<value>, keyAttribute=<value>}} {type='device', id=<id>, property='accessControlEvent', value=<value>} {type='profile', property='activeProfile', value=<value>, old=<value>} {type='custom-event', name=<name>} {type='deviceEvent', id=<id>, value='removed'} {type='deviceEvent', id=<id>, value='changedRoom'} {type='deviceEvent', id=<id>, value='created'} {type='deviceEvent', id=<id>, value='modified'} {type='sceneEvent', id=<id>, value='started'} {type='sceneEvent', id=<id>, value='finished'} {type='sceneEvent', id=<id<, value='instance', instance=<number>} {type='sceneEvent', id=<id>, value='removed'} {type='sceneEvent', id=<id>, value='modified'} {type='sceneEvent', id=<id>, value='created'} {type='onlineEvent', value=<boolean>} {type='room', id=<id>, value='created'} {type='room', id=<id>, value='removed'} {type='room', id=<id>, value='modified'} {type='section', id=<id>, value='created'} {type='section', id=<id>, value='removede'} {type='section', id=<id>, value='modified'} {type='location',id=<userid>,property=<locationid>,value=<geofenceaction>,timestamp=<number>} {type='ClimateZone',...} {type='ClimateZoneSetpoint',...} It also supports time/cron events Time subscription: {type='cron', time=<cronString>, tag=<string>} cron string format: "<min> <hour> <day> <month> <wday> <year>" min: 0-59 hour: 0-23 day: 1-31 month: 1-12 wday: 1-7 1=sunday year: YYYY Ex. "0 * * * * *" Every hour "0/15 * * * * *" Every 15 minutes "0,20 * * * * *" At even hour and 20min past "0 * * 1-3 * *" Every hour, January to March "0 7 lastw-last * 1 *" 7:00, every sunday in the last week of the month "sunset -10 lastw-last * 1 *" 10min before sunset every sunday in the last week of the month Ex. self:subscribeTrigger({type='cron', time="0/15 * * * * *", tag="Quarter"}) will send back a source trigger ever 15min in the format {type='cron', time="0/15 * * * * *", tag="Quarter"} 'tag' can be useful to match timers to actions... This gives more or less the same or more functionality than what you get from Scene conditions.
  4. jang

    setTimeout seule solution?

    Try to install this QA TriggerQA.fqa v1.21 and then create another QA: function QuickApp:subscribeTrigger(event) local s = self:getVariable('TRIGGER_SUB') if s == nil or s == "" then s = {} end s[#s+1]=event self:setVariable('TRIGGER_SUB',s) end function QuickApp:sourceTrigger(tr) self:debug(json.encode(tr)) end function QuickApp:onInit() self:debug("onInit") self:subscribeTrigger({type='device'}) end This will call QuickApp: sourceTrigger with the trigger as soon as it happens. You can be more specific and do self:subscribeTrigger({type='device', id=88}) self:subscribeTrigger({type='device', id=99}) to only get triggers from deviceId 88 and 99 etc. Inside QuickApp: sourceTrigger you need to look at the trigger to see what trigger it is if you subscribe to different triggers. If you don't need super optimized trigger handling (then you run your own / refreshStates loop) then the TriggerQA is a simple helper that makes it easy for other QAs to receive sourceTrigger with very little overhead - similar to how scenes gets them - and it scales very well.
  5. jang

    setTimeout seule solution?

    It's not the setTimeout - it's what you do inside the setTimeouts... Here I start 1 million setTimeouts in parallell. local t = os.time() for i=1,1000000 do -- Start a million setTimeouts fibaro.setTimeout(1,function() end) end fibaro.setTimeout(30,function() print("OK2") print(os.time()-t) end) The last setTimeout with 30ms timer will not be allowed to run until all the 1 million have completed (that's how we time it). It takes 41s on the HC3 [27.05.2021] [12:07:13] [DEBUG] [SCENE24]: OK2 [27.05.2021] [12:07:13] [DEBUG] [SCENE24]: 41 That's actually quite ok. The trick with polling for state changes is not to do it with a setTimeout loop doing fibaro.getValue. It's to do http:requests to the /refreshStates api as the http request will hang if there are no events and not consume any cpu during that time. When an event is available the http request will return immediately. Granted is that you will get all kinds of events and need an efficient way to filter for events that you look for (devices changing state or globals changing value) - but that is easy to do with a table lookup. I have a ready made library 'fibaroExtra.lua' that implements an event/sourceTrigger callback function that is as efficient as it gets.
  6. So the example becomes local function condition1(self) return true end local function condition2(self) return math.random(1,3) > 1 end local function condition3(self) return false end local function action1(self) fibaro.call(...) end local function action2(self) fibaro.call(...) end local rules = { {condition=condition1,action=action1}, {condition=condition2,action=action2}, {condition=condition3,action=action2} } function mainCode(self) for _,r in ipairs(rules) do -- Test conditions and run actions if true if r.condition(self) then r.action(self) end end end with the "passing around self' style.
  7. Well, all QuickApp functions declared are callable from other QAs or through the web interface as has been discussed in other posts previously. Functions that are local to the QA I believe should be declared as just local functions. The problem is that you don't have access to 'self' in local functions, for calling ex. self:updateProperty() self:debug() etc. However, there is a global Lua variable that fibaro sets for us 'quickApp' that gets the value of 'self' - or the QuickApp object. The problem with 'quickApp' is that it's not set until we exit the QuickApp:onInit() function. So I use something like this. local function foo() quickApp:debug("Foo") end function QuickApp:onInit() quickApp = self foo() end The reason they set 'quickApp' after we exit :onInit() is that the object is not considered initialised until :onInit() has run. So just be careful that you don't access self variables that you initialise in :onInit() from, in this case, 'foo'. A purist would say we shouldn't use global variables like 'quickApp' but pass around the self value - however it tends to be a bit tedious if we only have one QuickApp object. However, there is an exception (or rule). If you have QuickAppChild objects too in your QA, then you in effect have several 'QuickApps' and need to pass around the right self to your local functions. local function foo(self) self:debug("Foo") end class 'Child'(QuickAppChild) function Child:onInit() foo(self) -- Child's self end function QuickApp:onInit() foo(self) -- QuickApp's self end Remember to declare 'local foo' before it's referenced in the Child:onInit() function.
  8. The type of the QA defines a lot of the available key-values of the QA device structure. Changing the type of the QA would require some migration of values. For some types the property.value is a boolean for others a float etc. It would be messy. For children you need to update the mother QAs, as children can't migrate to another mother QA (one could experiment with changing the .parentId but my guess it could create havoc...) I have a Hue QA that I have updated the mother QA for a year now and users have been able to keep their children and more important the children deviceId's. I have my own version of the QuickAppChild class called QuickerAppChild that manages the children and reloads them with the correct class and it works well. I have also an 'UpdaterQA' that allow people to upgrade (and downgrade) my EventRunner and ChildrenOfHue's QAs.
  9. I guess it's something like if (conditions1) then self:actions1() end ? You could make your conditions into self:condition1() too. if self:condition1() then self:action1() end The important thing is that your conditions need to return true/false. Then you could put them into an list function QuickApp:condition1() return true end function QuickApp:condition2() return math.random(1,3) > 1 end function QuickApp:condition3() return false end function QuickApp:action1() fibaro.call(...) end function QuickApp:action2() fibaro.call(...) end local rules = { {condition='condition1',action='action1'}, {condition='condition2',action='action2'}, {condition='condition3',action='action2'} } function mainCode(self) for _,r in ipairs(rules) do -- Test conditions and run actions if true if self[r.condition](self) then self[r.action](self) end end end Then you run the mainCode every 30s and rename your QA to GEA ;-) P.S I probably wouldn't declare conditions/actions as QuickApp methods.
  10. jang

    Récupérer valeur dans un API

    _ is a variable like all others... _ = 42 for _,_ in ipairs(({ 6,5,4,3 })) do print(_) end print(_G['_']) A_B, _A, _ are all valid variable names. However, as @Leisure points out it's a convention to name a temporary (local) variable that you don't need '_' as a signal to other that reads the code. Ex. code checking programs (LuaLint, Luacheck) also don't warn for "unused variables" if the variable is named '_'.
  11. jang

    Slider

    I think your analysis is justified. Fibaro is not always consistent in how they define their APIs....
  12. https://forum.fibaro.com/topic/54915-quickapp-slider-minimum-and-maximum-range/
  13. The general advice to declare your variables in the beginning of the code/functions is usually a very good advice. Actually, the QA is "executed" when the code is loaded. It's just that fibaro helps us by collecting a lot of nice-to-have functions in the QuickApp class and when the code is loaded is creates a QuickApp object and calls its :onInit() function as an "entry point" for our code. If fibaro had not done that we could have achieved the same thing by creating the QuickApp object ourselves at the end of our code. function QuickApp:onInit() self:debug("foo") end quickApp = QuickApp() -- create object from class quickApp:onInit() -- call our "entry point" However, we don't need an :onInit() in our QA. If there is no :onInit(), fibaro will not call it. setInterval(function() fibaro.call(88,"turnOff") end,60*60*1000) is a perfectly valid QA that will turn off device 88 every hour.
  14. is still valid. When Lua loads your file the statements are executed top-to-bottom. Your first example: sets local test to "foo" defines function function QuickApp:onInit(). When onInit() is defined (compiled) the variable 'test' is determined to be a local variable as it has been defined previously (step 1). When the file is loaded no one has called QuickApp:onInit() yet. However after the QA is loaded, fibaro calls your QuickApp:onInit() to "start" your QA. The QA prints "foo". Your second example. defines function function QuickApp:onInit(). When onInit() is defined (compiled) the variable 'test' is determined to be a global variable as it has not been seen previously. sets local test to "foo" fibaro calls your QuickApp:onInit() functions that tries to print the global variable 'test' that has the value nil. In my previous post I talked about "forward" declaring variables. This would solve it. local test function QuickApp : onInit () self : debug ( "onInit" ) print ( test ) end test = "foo" declares local variable test defines function function QuickApp:onInit(). When onInit() is defined (compiled) the variable 'test' is determined to be a local variable as it has been defined previously (step 1). local test is set to "foo" fibaro calls your QuickApp:onInit() functions that prints the global variable 'test' that has the value "foo"
  15. You can also just do local clim = 120 function QuickApp:updateClima() self:updateView("label1", "text", tostring(fibaro.getValue(clim, "power")).."W") end function QuickApp:onInit() self:debug(self.clima) setInterval(function() self:updateClima() end,10*1000) end but you probably would like to have some "smoothing" of the value...
  16. It's not that straight forward.... However, you can leverage a library I have for that. Add fibaroExtra.lua to your QA https://forum.fibaro.com/topic/54538-fibaroextra/ Then in your QA main: local clim = 120 function QuickApp:updateClima() self:updateView("label1", "text", tostring(fibaro.getValue(clim, "power")).."W") end function QuickApp:onInit() self:debug(self.clima) self:updateClima() self:event({type='device', id=clima, property='power'}, function(env) self:updateClima() end ) end
  17. Well, the local variables just shadow each other - it will work with same name - I choose different ones just for clarity. ...and clarity is good! Here you have 3 versions. Parallell - all request run and handled in parallell. Sequential - one request after another, handled in sequence. Parallell_join - The request run in parallell and the response collected in a common result that is sent to the callback when all results are ready. The advantage of sequential with the performance of parallell :-) function httpGet(self, url, callback) self.http:request(url, { success = function(response) if response.status < 400 then callback(true, url, response.data) else callback(false, url, response.status) end end, error = function(err) callback(false, url, err) end }) end local urls2call = { "https://www.apple.com", "https://www.google.com", "https://www.amazon.com", "https://www.facebook.com", } function parallell(self, urls ,handler) -- call all urls in parallell, handle asynchronous for _,url in ipairs(urls) do httpGet(self, url, handler) end end function sequential(self, urls, handler) -- call all urls in sequence local i = 1 local function callback(success, url, result) handler(success, url, result) i=i+1 if i <= #urls then httpGet(self,urls[i], callback) end end httpGet(self, urls[i], callback) end function parallell_join(self, urls, handler) -- call all urls in parallel, call join local result = {n=0,responses={}} for _,url in ipairs(urls) do httpGet(self, url, function(success, url, response) result.responses[#result.responses+1]={url=url,success=success,response=response} result.n = result.n+1 if result.n == #urls then handler(true, nil, result.responses) end end) end end function QuickApp:onInit() self.http = net.HTTPClient() -- parallell(self,urls2call, -- function(success, url, result) self:debug("Success "..tostring(success).." "..url) end -- ) -- sequential(self,urls2call, -- function(success, url, result) self:debug("Success "..tostring(success).." "..url) end -- ) parallell_join(self,urls2call, function(success, url, result) for _,r in ipairs(result) do self:debug("Success "..tostring(r.success).." "..r.url) end end ) end
  18. Ahh, you want to run them in parallell?
  19. ...and you see that currentData(...) becomes a generic http get function... function httpGet(self, url, callback) self.http:request(url, { success = function(response) if response.status == 200 then self:debug("Success: status = "..tostring(response.status)) local jsonTable = json.decode(response.data) callback(true, jsonTable) else self:warning("Error: status = " ..tostring(response.status)) end end, error = function(err) callback(false, err) self:warning("Error: " ..err) end }) end function mainCode(self) httpGet(self,"http://worldtimeapi.org/api/timezone/Europe/Stockholm", function(success1, datajsonTable1) if success1 then self:debug("1",datajsonTable1.datetime) httpGet(self,"http://worldtimeapi.org/api/timezone/America/New_York", function(success2, datajsonTable2) if success2 then self:debug("2",datajsonTable2.datetime) else self:error("ça ne s'est pas bien passé :", datajsonTable2) end end) else self:error("ça ne s'est pas bien passé :", datajsonTable1) end end ) end function QuickApp:onInit() self.http = net.HTTPClient() mainCode(self) end
  20. function currentData(self, callback) self.http:request("http://worldtimeapi.org/api/timezone/Europe/Stockholm", { success = function(response) if response.status == 200 then self:debug("Success: status = "..tostring(response.status)) local jsonTable = json.decode(response.data) callback(true, jsonTable) else self:warning("Error: status = " ..tostring(response.status)) end end, error = function(err) callback(false, err) self:warning("Error: " ..err) end }) end function mainCode(self) currentData(self, function(success1, datajsonTable1) if success1 then self:debug("1",datajsonTable1.datetime) currentData(self,function(success2, datajsonTable2) if success2 then self:debug("2",datajsonTable2.datetime) else self:error("ça ne s'est pas bien passé :", datajsonTable2) end end) else self:error("ça ne s'est pas bien passé :", datajsonTable1) end end ) end function QuickApp:onInit() self.http = net.HTTPClient() mainCode(self) end
  21. setView("Humidity is %s%%",50) %% -> %
  22. In the first version I missed some % directives. It should be fixed in the last edit of the post. Yes
  23. In Lua, all strings created, in code and that are read in, are "interned" which means that there are only one version of the same string stored. This makes comparing if two strings are equal as fast as comparing if 2 integers are equal. The drawback is that it takes a little longer to create strings as they need to be interned (usually put in an efficient hash table and references replaced by the hash value). Java, Lisp and many other languages use the same concept. This is the reason why it's much more efficient to add strings to a table and then do table.concat, or often better to do a string.format than multiple ... concats that creates intermediate strings. It's also a reason why Lua's table lookup is so fast, all strings have a pre-computed hash-value already associated with them.
  24. There was some missing fields in the setView call, fixed. What's missing is to check the user provided quickAppVariables that they are valid and warn if not.
  25. ...and the reason it's pretty quick to run your original QA, step through it, understand what it's doing and then modifying it, is that it runs very well in my new little offline emulator.
×
×
  • Créer...