Lua timers in SHAC/NAC

Discussion in 'C-Bus Automation Controllers' started by Pie Boy, Aug 15, 2018.

  1. Pie Boy

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    Hi all,
    I created a small lua library of functions for timing on/off a group address
    which means the function can be called from any script, event, resident etc

    1- create a library called Vibe, paste in the code below then save

    time_t={time_triged = {}, net= {}, app = {}, group = {}, level = {}, rrate= {}, delay_time = {}, tag = {}, index = {}, level_after_timeout = {}}


    function Time_func(net_f, app_f, group_f, level_f, rrate_f, delay_time_f, level_after_timeout_f)

    SetCBusLevel(net_f, app_f, group_f, level_f, rrate_f)
    time_t.time_triged[group_f] = os.time()
    time_t.net[group_f] = net_f
    time_t.app[group_f] = app_f
    time_t.group[group_f] = group_f
    time_t.level[group_f] = level_f
    time_t.rrate[group_f] = rrate_f
    time_t.delay_time[group_f] = delay_time_f
    time_t.tag[group_f] = GetCBusGroupTag(group, app, net)
    time_t.index[group_f] = group_f
    time_t.level_after_timeout[group_f] = level_after_timeout_f
    storage.set('time_t', time_t)

    end


    function Timer_Stop(group)

    for i = 0, 254 do
    yt = storage.get('time_t')
    if yt.group == group then
    yt.group = nil
    storage.set('time_t', yt)
    end
    end

    end


    function Timer_Chk()

    xt = storage.get('time_t')

    for ii = 0,254 do
    if xt.group[ii] then
    if os.time() - xt.time_triged[ii] >= xt.delay_time[ii] then
    SetCBusLevel(xt.net[ii], xt.app[ii], xt.group[ii], xt.level_after_timeout[ii], xt.rrate[ii])
    xt.group[ii] = nil
    storage.set('time_t', xt)
    end
    end
    end

    end


    2 - create resident script with delay of 0 to call the Time_Chk function
    require ("user.Vibe")
    Timer_Chk()

    3- call the function from what ever script you want eg in event script

    require('user.Vibe')
    -- Time_func(net, app, group(number), level(0-255), rrate, delay_time(in sec), level after timeout(0-255)
    Time_func(0, 56, 22, 255, 0, 5, 0)

    if you want to cancel/stop timer call the Timer_Stop(group) function with group being the same group number that was included in the Time_func() as above.

    I will modify the group parameter to also accept ga tag name, when i have a spare 10 min...
    I am keen for some feedback on this one as far as i can tell it is accurate to 1sec
     
    Pie Boy, Aug 15, 2018
    #1
  2. Pie Boy

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    Thanks for posting, Pie Boy.

    I found a use for this by modifying your code to incorporate an 'optional' group turn-on (level to '-1' if no turn-on change desired), and also made some optimisations that you might find useful.

    Your previous code was limited to managing up to 255 timers, while the below is unlimited in number, and also able to manage a set of timers extending across multiple networks and applications without group number collision. I've tried to improve memory management of the LUA tables, too (still a bit of an LUA noob, so there's probably more improvements to be had).

    Cheers,
    S.

    Code:
    time_t = {time_started = {}, ramp_rate = {}, delay_time = {}, level_after_timeout = {}}
    
    
    function Timer_Group(net_f, app_f, group_f, level_f, rrate_f, delay_time_f, level_after_timeout_f)
    
      if (level_f > -1) then
        SetCBusLevel(net_f, app_f, group_f, level_f, rrate_f)
      end
    
      local knet, kapp, kgrp
     
      if ( type(net_f)=='string' ) then
        knet = GetCBusNetworkAddress(net_f)
      else
        knet = net_f
      end
      if ( type(net_f)=='string' ) then
          kapp = GetCBusApplicationAddress(knet, app_f)
      else
        kapp = app_f
      end
        if ( type(net_f)=='string' ) then
          kgrp = GetCBusGroupAddress(knet, kapp, group_f)
      else
        kgrp = grp_f
      end
      local key = tostring(knet) ..":" .. tostring(kapp) .. ":" .. tostring(kgrp)
      time_t.time_started[key] = os.time()
      time_t.ramp_rate[key] = rrate_f
      time_t.delay_time[key] = delay_time_f
      time_t.level_after_timeout[key] = level_after_timeout_f
    
      storage.set('time_t', time_t)
    
    end
    
    
    function Timer_Stop(net_f, app_f, group_f)
    
      time_t = storage.get('time_t')
    
      local knet, kapp, kgrp
     
      if ( type(net_f)=='string' ) then
        knet = GetCBusNetworkAddress(net_f)
      else
        knet = net_f
      end
      if ( type(net_f)=='string' ) then
          kapp = GetCBusApplicationAddress(knet, app_f)
      else
        kapp = app_f
      end
        if ( type(net_f)=='string' ) then
          kgrp = GetCBusGroupAddress(knet, kapp, group_f)
      else
        kgrp = grp_f
      end
      local key = tostring(knet) ..":" .. tostring(kapp) .. ":" .. tostring(kgrp)
    
      if (time_t ~= nil) then
        time_t.time_started[key] = nil
        time_t.ramp_rate[key] = nil
        time_t.delay_time[key] = nil
        time_t.level_after_timeout[key] = nil
      end
     
      storage.set('time_t', time_t)
    
    end
    
    
    function Timer_Chk()
    
      local key, time_started
      time_t = storage.get('time_t')
    
      if (time_t ~= nil) then
        for key,time_started in pairs(time_t.time_started) do
          if os.time() - time_t.time_started[key] >= time_t.delay_time[key] then
            local knet = tonumber(string.match(key, "(%d+):"))
            local kapp = tonumber(string.match(key, ":(%d+):"))
            local kgrp = tonumber(string.match(key, ":(%d+)$"))
    
            SetCBusLevel(knet, kapp, kgrp, time_t.level_after_timeout[key], time_t.ramp_rate[key])
    
            time_t.time_started[key] = nil
            time_t.ramp_rate[key] = nil
            time_t.delay_time[key] = nil
            time_t.level_after_timeout[key] = nil
    
            storage.set('time_t', time_t)
          end
        end
      end
    
    end 
     
    ssaunders, Feb 15, 2019
    #2
  3. Pie Boy

    Ashley

    Joined:
    Dec 1, 2005
    Messages:
    1,524
    Likes Received:
    173
    Location:
    Adelaide, Australia
    What's wrong with the PulseCBusLevel command?
     
    Ashley, Feb 15, 2019
    #3
  4. Pie Boy

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    Absolutely nothing at all. I used Pie Boy's code as a concept using lighting groups emulating the same thing as PulseCBusLevel, with the intent of timing things other than simple groups. Ultimately I ditched the lot in favour of using groups / PulseCBusLevel / a bunch of event-based scripts for each group, but thought the code might be useful to someone one day.
     
    ssaunders, Feb 15, 2019
    #4
  5. Pie Boy

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    Nice Work, Ssaunders,

    Yeah i have on a few different sites L5504AUX auxiliary input units with various beam sensors, flow valves, pressure switches connected etc so i use these timers, for a different Ga to be activated (Than the one that triggered the timer) after the timeout period.

    Many Thanks.
     
    Pie Boy, Jun 9, 2019
    #5
  6. Pie Boy

    paddyb

    Joined:
    Aug 9, 2020
    Messages:
    7
    Likes Received:
    1
    Location:
    Ireland
    Hey @ssaunders , Thanks for posting that enhanced Timer script. I think the forum engine might have garbled some of the syntax, could you help me to figure it the correct code? There are a few calls to store a time that was simple to spot and fix (storage.set('time_time_t') had the closing single quote missing. Unfortunately, I'm new to Lua and my fault-finding abilities end there!

    Line 40: (not sure how to correct this ?)
    Code:
    time_t = storage.get('time_t  local knet, kapp, kgrp


    Line 74: (not sure how to correct this ?)
    Code:
    time_t = storage.get('time_t  if (time_t ~= nil) then
    Would appreciate any help or guidance you can give.

    I am hoping to use your code snippets, as I have had trouble with the standard 'PulseCBusLevel' command, which I have found successive requests made by it seem to cancel the previous command, and set the group to 0...(as reported elsewhere on the forum)
     
    paddyb, Feb 1, 2021
    #6
  7. Pie Boy

    Timbo

    Joined:
    Aug 3, 2004
    Messages:
    22
    Likes Received:
    4
    Location:
    Perth, Australia
    Hi @paddyb,

    Found a couple of small errors with the above code (mostly setting time_t had invalid vars).
    Try this out:

    Code:
    time_t = {time_started = {}, ramp_rate = {}, delay_time = {}, level_after_timeout = {}}
    
    
    function Timer_Group(net_f, app_f, group_f, level_f, rrate_f, delay_time_f, level_after_timeout_f)
    
      if (level_f > -1) then
        SetCBusLevel(net_f, app_f, group_f, level_f, rrate_f)
      end
    
      local knet, kapp, kgrp
     
      if ( type(net_f)=='string' ) then
        knet = GetCBusNetworkAddress(net_f)
      else
        knet = net_f
      end
      if ( type(app_f)=='string' ) then
          kapp = GetCBusApplicationAddress(knet, app_f)
      else
        kapp = app_f
      end
        if ( type(group_f)=='string' ) then
          kgrp = GetCBusGroupAddress(knet, kapp, group_f)
      else
        kgrp = group_f
      end
      local key = tostring(knet) ..":" .. tostring(kapp) .. ":" .. tostring(kgrp)
      time_t.time_started[key] = os.time()
      time_t.ramp_rate[key] = rrate_f
      time_t.delay_time[key] = delay_time_f
      time_t.level_after_timeout[key] = level_after_timeout_f
    
      storage.set('time_t', time_t)
    
    end
    
    
    function Timer_Stop(net_f, app_f, group_f)
    
      time_t = storage.get('time_t')
    
      local knet, kapp, kgrp
     
      if ( type(net_f)=='string' ) then
        knet = GetCBusNetworkAddress(net_f)
      else
        knet = net_f
      end
      if ( type(net_f)=='string' ) then
          kapp = GetCBusApplicationAddress(knet, app_f)
      else
        kapp = app_f
      end
        if ( type(net_f)=='string' ) then
          kgrp = GetCBusGroupAddress(knet, kapp, group_f)
      else
        kgrp = group_f
      end
      local key = tostring(knet) ..":" .. tostring(kapp) .. ":" .. tostring(kgrp)
    
      if (time_t ~= nil) then
        time_t.time_started[key] = nil
        time_t.ramp_rate[key] = nil
        time_t.delay_time[key] = nil
        time_t.level_after_timeout[key] = nil
      end
     
      storage.set('time_t', time_t)
    
    end
    
    
    function Timer_Chk()
    
      local key, time_started
      time_t = storage.get('time_t')
    
      if (time_t ~= nil) then
        for key,time_started in pairs(time_t.time_started) do
          if os.time() - time_t.time_started[key] >= time_t.delay_time[key] then
            local knet = tonumber(string.match(key, "(%d+):"))
            local kapp = tonumber(string.match(key, ":(%d+):"))
            local kgrp = tonumber(string.match(key, ":(%d+)$"))
    
            SetCBusLevel(knet, kapp, kgrp, time_t.level_after_timeout[key], time_t.ramp_rate[key])
    
            time_t.time_started[key] = nil
            time_t.ramp_rate[key] = nil
            time_t.delay_time[key] = nil
            time_t.level_after_timeout[key] = nil
    
            storage.set('time_t', time_t)
          end
        end
      end
    
    end 
    Cheers,

    Tim
     
    Timbo, May 9, 2021
    #7
  8. Pie Boy

    paddyb

    Joined:
    Aug 9, 2020
    Messages:
    7
    Likes Received:
    1
    Location:
    Ireland
    Hi Tim,
    Thanks a mill for trying to help, but it seems even the example you posted above is somehow altered by the forum engine. It seems to be parsing the code in an effort to render it safely, but unfortunately, it's rendering it in a non-functional way. It just throws errors when pasted and trying to save it in the shac editor. Even if you paste your code from your post above into a plain text editor, on line 33 for example it is missing a closing single quote on the "storage.set" command. Somethings, like this particular line, is easy to spot and fix for a novice, but other syntax errors are also introduced by the forum engine that is are not as easy for me to spot and fix, I can't tell if it's missing a closing bracket, a closing quote mark etc. ....Again, totally nothing to do with you, but if it's possible to attach a text file with the raw code, that would be great, and if not thanks for trying anyway, I appreciate your effort so far.
     
    paddyb, May 9, 2021
    #8
  9. Pie Boy

    Timbo

    Joined:
    Aug 3, 2004
    Messages:
    22
    Likes Received:
    4
    Location:
    Perth, Australia
    Morning,

    Not a problem - I didn't do any of the heavy lifting you can thank the others for that ^^.
    Just tried copy and paste into VS Code and it looks fine - I also copied into the editor and it didn't seem to complain.

    Try this:
    https://pastebin.com/CUkU9HZD

    Cheers,

    Tim

     
    Timbo, May 10, 2021
    #9
  10. Pie Boy

    paddyb

    Joined:
    Aug 9, 2020
    Messages:
    7
    Likes Received:
    1
    Location:
    Ireland
    Cheers @Timbo ! The Pastebin code works perfectly, thanks.
    The forum seems to have issues with some closing single quotes and some closing brackets...
    It also removed a few 'new lines' or carriage returns, which I see now is what was throwing me...
    Makes much more sense now!
    Thanks again
     
    paddyb, May 10, 2021
    #10
  11. Pie Boy

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    I am SO sorry I missed your message @paddyb. (And thanks for helping out @Timbo.)

    @Ashley I came across a rather weird scenario where PulseCBusLevel would just not work for me in an LUA script. In short: Kids bedrooms have a PIR that is used to detect occupancy. The kids aren't there? Turn off the bloody light!

    I tried with Pulse, but it failed miserably. On getting a PIR trigger in an event script, pulse the GA to the level that it is at now, with a timeout of ten minutes to 'off'. If the PIR triggers again then the pulse occurs again.

    It didn't work, no matter how much I messed with it. The lights would turn on by pressing the switch, but would turn themselves off again about two seconds later! (Turning on the switch is in the field of view of the PIR.)

    Don't know whether it was some weird timing thing, but I dusted off this rusty old bit of script and got things to work perfectly in about two minutes, and moved on.

    One advantage of the script approch over pulse is that you don't have to specify an initial level of the GA, just the final timeout level, which is great for this kids-leave-lights-on scenario.

    I did note in the release notes for 1.10.0 that there was a database issue with pulse fixed. Sounded unrelated, but for the record I only tried the kids' light timeout on 1.6.0, have since upgraded, but not tried pulse again so no idea whether that would have helped.
     
    ssaunders, Jan 12, 2022
    #11
Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.