We have moved at community.getvera.com

Author Topic: Blink Light Program, is recursion necessary?  (Read 766 times)

Offline marctxk

  • Newbie
  • *
  • Posts: 4
  • Karma: +0/-0
Blink Light Program, is recursion necessary?
« on: September 25, 2018, 05:27:47 pm »
I have been trying out the blinking light lua code that seems to be popular on the forums:

Quote
------  SET THESE VARIABLES to match needs of your system -----
Secs   = 1   ---->>  Secs: Number of seconds between "Blinks"
OffSecs = 1
Device = 8 ---->>  Device: Device#  of Outlet or Light to "BLINK"
-------------------------------------------------------------------
Call ='urn:upnp-org:serviceId:SwitchPower1';Do ='SetTarget';Nil =""


function BlinkOn()
   luup.call_action(Call,Do,{newTargetValue=1},Device)  -->BlinksOn
   if (Run == 1) then
      luup.call_timer("BlinkOff",1,Secs,Nil,Nil)
   end
end


function BlinkOff()
   luup.call_action(Call,Do,{newTargetValue=0},Device)  -->BlinksOff
   if (Run == 1) then
      luup.call_timer("BlinkOn",1,OffSecs,Nil,Nil)
   end
end


if (Run == 1) then
   Run = 0
else
   Run = 1
   luup.call_timer("BlinkOn",1,OffSecs,Nil,1)
end


I have rewritten it and ended up like this:
Quote
------  SET THESE VARIABLES to match needs of your system -----
Secs   = 1   ---->>  Secs: Number of seconds between "Blinks"
OffSecs = 1
Device = 8 ---->>  Device: Device#  of Outlet or Light to "BLINK"
-------------------------------------------------------------------
Call ='urn:upnp-org:serviceId:SwitchPower1';Do ='SetTarget';Nil =""


function TurnOn()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=1},Device)
   return 0
end


function TurnOff()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=0},Device)
   return 0
end



function BlinkOnce()
   if BlinkRun == 1 then
      TurnOn()
      posix.sleep(Secs)
      TurnOff()
      posix.sleep(OffSecs)
   end
   return 0
end

function Blinking()
while BlinkRun==1 do
   BlinkOnce()
   end
   return 0
end


require("posix")
if (BlinkRun == 1) then
   BlinkRun = 0
   
else
   BlinkRun = 1
   luup.call_timer("Blinking",1,1,Nil,Nil)
end
return 0

Now the latter code is tidier to my mind but still more convoluted than I'd like, it has ended up like that in a desperate attempt to make it work in a scene.  The light blinks nicely, but when I attempt to run the scene again, it doesn't immediately stop.  The recursive code stops immediately.  I don't really understand the Vera / luup architecture.  The documentation for the call_timer function (and call_delay) implies that a new worker thread is created to handle the function.

This means that the recursive code will be spinning up threads like a good'un doesn't it?  (Which is one reason I'm keen to find an alternative structure). When I ran top with the recursive version I could see a slight upward trend in memory used but I have no idea of the GC mechanisms in the lua engine so that might not be an issue.  I don't really know even whether the call_timer thread calls the BlinkOn and BlinkOff functions or creates a new thread and dies.  I'm also assuming that all non-local variables have global scope and visibility across the lua engine, otherwise none of this would work, am I correct?

Apols for the rambling nature of this but I hope that somebody will be able to answer clarify things for me.  Thanks for reading.

Offline aa6vh

  • Hero Member
  • *****
  • Posts: 642
  • Karma: +15/-0
Re: Blink Light Program, is recursion necessary?
« Reply #1 on: September 26, 2018, 10:19:35 am »
The reason why it was written that way originally is because you really do not want to use the sleep() command. It can cause lockups, reboots, and such if the sleep is too long. (Yes, one could say it is poorly implemented.)

I would not worry about the constant creation of a new "thread" each time the light switches on or off, its not like the processor has anything better to do.

Offline marctxk

  • Newbie
  • *
  • Posts: 4
  • Karma: +0/-0
Re: Blink Light Program, is recursion necessary?
« Reply #2 on: September 26, 2018, 04:48:06 pm »
I've got rid of sleep and done the below, using call_timer with a null function to hopefully provide 1 second gaps but the result is really horrible.  The timer goes nuts blinking very rapidly but with a lack of regularity, and is equally unable to be interrupted at will.  It's not making any sense to me.
Quote
Device = 8 ---->>  Device: Device#  of Outlet or Light to "BLINK"

Call ='urn:upnp-org:serviceId:SwitchPower1';Do ='SetTarget';Nil =""

function TurnOn()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=1},Device)
   return 0
end

function TurnOff()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=0},Device)
   return 0
end

function BlinkNull()
   return 0
end

function BlinkOnce()
   if BlinkRun == 1 then
      TurnOn()
      luup.call_timer("BlinkNull",1,1,Nil,Nil)
      TurnOff()
      luup.call_timer("BlinkNull",1,1,Nil,Nil)
   end
   return 0
end

function Blinking()
while BlinkRun==1 do
   BlinkOnce()
   end
   return 0
end

if (BlinkRun == 1) then
   BlinkRun = 0
else
   BlinkRun = 1
   luup.call_timer("Blinking",1,1,Nil,Nil)
end
return 0



So then I have tried a modification where I used call_timer to call TurnOff to turn the light off and then with a null function to cause the next timer.  The light switches on but never goes off again:
Quote
Device = 8 ---->>  Device: Device#  of Outlet or Light to "BLINK"

Call ='urn:upnp-org:serviceId:SwitchPower1';Do ='SetTarget';Nil =""

function TurnOn()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=1},Device)
   return 0
end

function TurnOff()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=0},Device)
   return 0
end

function BlinkNull()
   return 0
end


function BlinkOnce()
   if BlinkRun == 1 then
      TurnOn()
      luup.call_timer("TurnOff",1,1,Nil,Nil)
      luup.call_timer("BlinkNull",1,1,Nil,Nil)
   end
   return 0
end

function Blinking()
while BlinkRun==1 do
   BlinkOnce()
   end
   return 0
end

require("posix")
if (BlinkRun == 1) then
   BlinkRun = 0
else
   BlinkRun = 1
   luup.call_timer("Blinking",1,1,Nil,Nil)
end
return 0

I really can't figure out what's going on now.  I fundementally don't understand what conditions must be true to re-run the scene to stop the blinking.
« Last Edit: September 26, 2018, 05:20:23 pm by marctxk »

Offline rigpapa

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1121
  • Karma: +187/-3
Re: Blink Light Program, is recursion necessary?
« Reply #3 on: September 26, 2018, 05:12:24 pm »
I've got rid of sleep and done the below:
Quote
Device = 8 ---->>  Device: Device#  of Outlet or Light to "BLINK"

Call ='urn:upnp-org:serviceId:SwitchPower1';Do ='SetTarget';Nil =""


function TurnOn()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=1},Device)
   return 0
end


function TurnOff()
   luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue=0},Device)
   return 0
end


function BlinkNull()
   return 0
end



function BlinkOnce()
   if BlinkRun == 1 then
      TurnOn()
      luup.call_timer("BlinkNull",1,1,Nil,Nil)
      TurnOff()
      luup.call_timer("BlinkNull",1,1,Nil,Nil)
   end
   return 0
end

function Blinking()
while BlinkRun==1 do
   BlinkOnce()
   end
   return 0
end


require("posix")
if (BlinkRun == 1) then
   BlinkRun = 0
   
else
   BlinkRun = 1
   luup.call_timer("Blinking",1,1,Nil,Nil)
end
return 0

The result is really horrible.  The timer goes nuts blinking very rapidly but with a lack of regularity, and is equally unable to be interrupted at will.  It's not making any sense to me.

Not surprised. What you're doing makes no sense. It looks like you are assuming that the call_timer() function is somehow going to delay in-line. It does not. With what you've coded, it will call the BlinkNull function one second after call_timer() is called, but the TurnOff() statement after call_timer() will execute immediately. What you really want to do is something more along the lines of:

Code: [Select]
function BlinkOnce()
    TurnOn()
    luup.call_timer( "TurnOff", 1, 1 )
end

This will immediately turn on the light when called, and return, but after one second, the TurnOff() function will be called, and the light will go out. Just remember that you cannot safely put a delay in execution into your code; what you have to do is tell Vera to run something later, which is the function of call_delay() and call_timer().

For a longer-term blink, something more like this would make sense, building on the above. Let's remove TurnOn and TurnOff and just incorporate them where needed:

Code: [Select]
function BlinkOn()
    luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue="1"},Device)
    luup.call_timer( "BlinkOff", 1, 1 )
end

function BlinkOff()
    luup.call_action('urn:upnp-org:serviceId:SwitchPower1','SetTarget',{newTargetValue="0"},Device)
    if BlinkRun == 1 then
        luup.call_timer( "BlinkOn", 1, 1 )
    end
end

function StartBlinking()
    BlinkRun =  1
    BlinkOn()
end

function StopBlinking()
    BlinkRun = 0
end

The StartBlinking() function calls the BlinkOn() function to get things started, but once started, BlinkOn() and BlinkOff() launch each other indirectly via the luup.call_timer() function. That continues until StopBlinking() is called (or BlinkRun is otherwise somehow set to 0).

Edit: fixed a missing "end" and did something a little tricky... made it so that blinking will only stop after an "off", so the light is always off when blinking is stopped.
« Last Edit: September 26, 2018, 05:15:35 pm by rigpapa »
Author of Reactor, DelayLight, SiteSensor, Rachio, Deus Ex Machina II, Intesis WMP Gateway, Auto Virtual Thermostat and VirtualSensor plugins. Vera Plus w/100+ Z-wave devices. Vera3, Lite. Hassio, Slapdash.

Offline marctxk

  • Newbie
  • *
  • Posts: 4
  • Karma: +0/-0
Re: Blink Light Program, is recursion necessary?
« Reply #4 on: September 26, 2018, 05:30:25 pm »
@rigpapa, thanks, I was busy editing my last post, but your response is really helpful.  So the call_timer function sets an event and the lua engine spins up a new thread to execute the callback function, but meanwhile the calling thread continues execution which in the recursive case is to terminate.  So there really is no danger of an increasing call stack in the "recursive" case because it's not really recursive.  Is that all correct?

I'm still not getting the variable scope that allows us to toggle the Run variable in the "recursive" case but not in my original case with the sleep going on.  Can anybody explain that to me please?

 

Offline rigpapa

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1121
  • Karma: +187/-3
Re: Blink Light Program, is recursion necessary?
« Reply #5 on: September 26, 2018, 08:24:25 pm »
@rigpapa, thanks, I was busy editing my last post, but your response is really helpful.  So the call_timer function sets an event and the lua engine spins up a new thread to execute the callback function, but meanwhile the calling thread continues execution which in the recursive case is to terminate.  So there really is no danger of an increasing call stack in the "recursive" case because it's not really recursive.  Is that all correct?

Yes, that's right.

Quote
I'm still not getting the variable scope that allows us to toggle the Run variable in the "recursive" case but not in my original case with the sleep going on.  Can anybody explain that to me please?

Basically, a variable that is not declared "local" is global. If it is declared local, it is local to the scope in which it is declared. Example:

Code: [Select]
function test()
    local alpha = 123
    beta = 456
end

test()
print("alpha is", alpha)
print("beta is", beta)

This code, when run, would produce the following output:

Code: [Select]
alpha is nil
beta is 456

The reason for this is that alpha is declared local in the function, so its scope is the function, and it does not exist outside the function, so its value shows nil, because that's the value of a variable that doesn't exist. On the other hand, beta has not been declared local, so it is therefore global, and visible outside the scope of the function. There are lots of additional details here, but this is the generality of it.

I went back and looked at the original code in the first post... pretty much what I gave you. The issue there might be that your Nil isn't actually nil, and that may be confusing call_timer() and causing errors. The keyword and value nil (lowercase n) has a very specific and important value in Lua. Your code creates a new value that isn't nil, it's an empty string, and these are not the same.
« Last Edit: September 26, 2018, 08:32:12 pm by rigpapa »
Author of Reactor, DelayLight, SiteSensor, Rachio, Deus Ex Machina II, Intesis WMP Gateway, Auto Virtual Thermostat and VirtualSensor plugins. Vera Plus w/100+ Z-wave devices. Vera3, Lite. Hassio, Slapdash.