This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Staircase script with dimming option
#1
I use the Staircase script frequently. Thanks again!
Now I would like to use it also in rooms where by default a dimmed lamp burns softly (e.g. 30%), for example in the hallway. When the motion detector of the alarm detects someone in the hallway, I would like the lamp to burn brighter for a defined time (e.g. 90% for 60 seconds). And then back to the old dimming value (30%).
If the lamp is turned off in the meantime, it should go out and turn on again the next time in the dimming mode (30%). I've tried all kinds of things, but can't get it to work properly.
Every time it seems to work, but then there is still an error somewhere. And I have solved it with three different scripts. It is complex to explain. Can someone please modify the script in such a way that this does work properly? Hopefully more people will enjoy this enhanced script  Smile .


Thanks in advance!
Reply
#2
Can you post the scripts that you are using now?
Reply
#3
(16.03.2022, 09:25)admin Wrote: Can you post the scripts that you are using now?

Thanks for the question and apologies for not responding sooner. I did not notice your response.

I created two different scripts. The first one responds to the PIR. For that, I modified the beautiful Staircase logic. See my explanation in the script. I added a lot of logs to find for myself why it doesn't work properly.

With the second script I want to achieve that if the lamp is turned off (manually or by a program or by another script), that the running first script then stops, that the lamp goes off, and the next time it comes on again dimmed.

In practice, it works well, until we go to bed. Usually the lamp goes off first and then on again. Also, regularly the lamp goes directly on at the brighter setting. So then the brighter setting is the same as the standard setting.

It would be best if this could work with just 1 script. I would like to use this script in more places in the house (and especially outside).

Here are the scripts:

SCRIPT 1:
Code:
-- Note that at 3/0/7 there is also a script, which should stop this staircase if the lamp is turned on/off.

-- Only react to PIR going on, not going off:
inputvalue = event.getvalue
if (event.getvalue() == false or event.getvalue() == 0) then
  return
end

-- ** Staircase logic with external time object and retriggering on input object Version 3.1 ** --
-- ****************************** Created by Erwin van der Zwart **************************** --
-- ************************************** SET PARAMETERS ************************************ --

-- Set input address
AddressInput = '9/0/1'

-- Set output address
AddressOutput = '1/1/7'

-- Set unique name for staircase timer (to avoid same storage name usage)
StaircaseName = 'PIR_LAMP_HAL'

-- Set external time address (optional)
AddressExternalTime = ''

-- Use time left indication
UseTimeLeft = false -- Set to true if time left indication will be used

-- Set feedback adress of time left indication (optional)
AddressTimeLeft = ''

-- Set time delay (Used when external time is not available)
SetDelay = 15

-- Seconds or Minutes
SetSec = true-- Set to false for Minutes

-- Set factor delay (Multiplies Delay)
SetFac = 1

-- Logic can be turned of by value 0
Off_by_Value_Zero = false


-- Additions for brightening the lamp on motion dd 14-01-2022:

Schemer = grp.getvalue('6/0/6') -- If it is dusk, this is set to 1.

AanStandHal = grp.getvalue('3/0/7') -- This indicates whether lamp is on when the script starts.
DimStandHal = grp.getvalue('3/2/7') -- This is the dimming value at the start of the script.

AdressOpgeslagenBeginStandHal = '1/2/7'-- Dim value field especially for this script to save initial state (I don't manage to keep this in memory)
OpgeslagenBeginStandHal = grp.getvalue(AdressOpgeslagenBeginStandHal)

TijdelijkHarderHal = grp.getvalue('39/1/1') -- This contains the (brighter) dimming value that the lamp should temporarily be at (e.g. 90%)

-- If DimStandHal is set to 0, the lamp is off, and nothing needs to happen. Nothing needs to happen when there is no twilight either.
if (DimStandHal) == 0 or (Schemer) == false then
  --log('return because hall lamp not on or no dusk')
  return
end

-- If DimStandHal == TemporaryHarderHal, then the script has run before. Then nothing should happen. Otherwise save current value.
if DimStandHal == TijdelijkHarderHal then
  --log ('DimStandHal gelijk aan TijdelijkeHarderHal')
  --log ('OpgeslagenBeginStandHal is'..OpgeslagenBeginStandHal)
  --log ('TijdelijkHarderHal is'..TijdelijkHarderHal)
  -- do nothing
  else
  grp.write(AdressOpgeslagenBeginStandHal, DimStandHal)
  OpgeslagenBeginStandHal = grp.getvalue(AdressOpgeslagenBeginStandHal)
  --log ('Nieuwe start script (Else)')
  --log ('OpgeslagenBeginStandHal is'..OpgeslagenBeginStandHal)
  --log ('DimStandHal is'..DimStandHal)
  --log ('TijdelijkHarderHal is'..TijdelijkHarderHal)
end

-- ************************************** END PARAMETERS ************************************ --
-- *************************** DON'T CHANGE ANYTHING UNDER THIS LINE ************************ --

inputvalue = event.getvalue
if Off_by_Value_Zero == false and (event.getvalue() == false or event.getvalue() == 0) then
  -- Exit script
  return
end

ValueInput = grp.getvalue(AddressInput)
ValueOutput = grp.getvalue(AddressOutput)
ValueExternalTime = grp.getvalue(AddressExternalTime)
if SetSec == true then
  SetSeconds = 1
else
  SetSeconds = 60
end
if ValueExternalTime == nil then
ValueExternalTime = 0
end
if ValueExternalTime > 0 then
  StairCaseTime = ValueExternalTime * SetSeconds * SetFac
else
  StairCaseTime = SetDelay * SetSeconds * SetFac
end
if ValueInput == true then
  --check for earlier started scrips
  --check storage for stpid value
  stpid = storage.get(StaircaseName) 
  --check if stpid has a value
  if stpid == nil then
     pid = os.getpid()
     storage.set(StaircaseName, pid)
  else
     -- kill earlier running script   
     os.kill(stpid, signal.SIGKILL)
     -- create new pid for next time to kill
     pid = os.getpid()   
     storage.set(StaircaseName, pid)
  end
  if ValueOutput < TijdelijkHarderHal then
    grp.write(AddressOutput, TijdelijkHarderHal)
    ValueOutput = true
  end
  -- Check time left indication is used
  if UseTimeLeft == true then
    if StairCaseTime > 0 then
        grp.update(AddressTimeLeft, StairCaseTime)
        repeat
          StairCaseTime = StairCaseTime - 1
          grp.update(AddressTimeLeft, StairCaseTime)
          os.sleep(1)
        until StairCaseTime == 0
    end   
  else
    os.sleep(StairCaseTime)
  end
  ValueOutput = grp.getvalue(AddressOutput)
  if ValueOutput == TijdelijkHarderHal then
    ValueInput = grp.getvalue(AddressInput)
    if ValueInput == true then
        grp.write(AddressInput, false)
      ValueInput = false
    end
    if Off_by_Value_Zero == false then
      if ValueOutput == TijdelijkHarderHal then
            grp.write(AddressOutput, OpgeslagenBeginStandHal) -- value here was 0, but to OpgeslagenBeginStandHal to set it back to this position.
        ValueOutput = 0
          end
    else
      -- Do nothing, this will trigger else condition below on next run

      end
  end
else
  --check for earlier started scrips
  --check storage for stpid value
  stpid = storage.get(StaircaseName) 
  --check if stpid has a value
  if stpid == nil then
  else
    -- kill earlier running script   
    os.kill(stpid, signal.SIGKILL)
    grp.update(AddressTimeLeft, 0)
    pid = nil
    storage.set(StaircaseName, pid)
  end
 
  -- Terugschrijven naar beginstand
  if ValueOutput == TijdelijkHarderHal then
    --log ('ValueOutput == TijdelijkHarderHal')
    grp.write(AddressOutput, OpgeslagenBeginStandHal)
  end

end


SCRIPT 2:

Code:
--When switching this lamp, any running script on the hall PIR (9/0/1) is stopped.
--This script is activacted at 3/0/7 and 0/1/1 (group lighting downstairs)

StaircaseName = 'PIR_LAMP_HAL'
stpid = storage.get(StaircaseName)
os.kill(stpid, signal.SIGKILL)
grp.update(AddressTimeLeft, 0)
pid = nil
storage.set(StaircaseName, pid)

-- If script is turned off, while the lamp is louder during the PIR script, then the "old" DimValue must be written over the feedback.
-- The idea is that the lamp then starts up the next time with the harder setting (on which it went out) and then automatically dims back to the old dimming value.

OpgeslagenDimWaarde = grp.getvalue('1/2/7')
grp.write('3/2/7', OpgeslagenDimWaarde)
Reply
#4
This is easier to do via a resident script (0 sleep time).

Single script can be used for multiple timers by adding more entries to the timers table. The script uses current dimmer control value to determine what level to set, only values set by other sources (not the script itself) are used. There are two points - high and low, if the current value is less than low then low value is used, otherwise high value is used. The status value cannot be used here because it's impossible to tell who change the value that caused the status update.

An alternative solution is to create a day/night object to have two different sets of values for movement/no movement.
Code:
if not client then
  timers = {
    {
      input = '1/1/1', -- binary PIR status
      output = '1/1/3', -- dimmer control (0..100%)
      onvaluehigh = 90, -- high output value in %
      onvaluelow = 30, -- low output value in %
      timeout = 60, -- in seconds
    }
  }

  function setinputvalue(timer, event)
    local value = busdatatype.decode(event.datahex, dt.bool)

    if not value then
      return
    end

    if timer.outvalue < timer.onvaluelow then
      value = timer.onvaluelow
    elseif timer.outvalue < timer.onvaluehigh then
      value = timer.onvaluehigh
    else
      value = nil
    end

    if not timer.ticks and value then
      grp.write(timer.output, value, dt.scale)
    end

    timer.ticks = timer.timeout
  end

  function setoutputvalue(timer, event)
    timer.outvalue = busdatatype.decode(event.datahex, dt.scale)
    timer.ticks = nil -- stop timer
  end

  for _, timer in ipairs(timers) do
    timer.outvalue = grp.getvalue(timer.output)
  end

  sender = 'tm'
  grp.sender = sender

  client = require('localbus').new(0.1)
  client:sethandler('groupwrite', function(event)
    if event.sender == sender then
      return
    end

    for _, timer in ipairs(timers) do
      if timer.input == event.dst then
        setinputvalue(timer, event)
      elseif timer.output == event.dst then
        setoutputvalue(timer, event)
      end
    end
  end)
end

client:loop(1)

for _, timer in ipairs(timers) do
  if timer.ticks then
    timer.ticks = timer.ticks - 1

    if timer.ticks == 0 then
      grp.write(timer.output, timer.outvalue, dt.scale)
      timer.ticks = nil
    end
  end
end
Reply
#5
This script is more complex than I can make myself. Very cool to see that you made this one so for me. Thank you, thank you!

I have adopted the script as resident script with 0 sleep time.
I also deleted my own scripts.
I filled in the table as follows:

{
input = '9/0/1', -- binary PIR status
output = '1/1/7', -- dimmer control (0..100%)
onvaluehigh = 90, -- high output value in %
onvaluelow = 10, -- low output value in %
timeout = 5, -- in seconds
}

And I also tried filling in the status object 3/1/7 as output instead of 1/1/7.
After two weeks, I tried one more time of all. Sometimes you then suddenly see what is going wrong. Unfortunately, I am not able to get this script to work.
Can you give any advice...?
Reply
#6
Post your full script listing. Also enable logging for mapped object and see if they change values. Check Error logs as well.
Reply
#7
(09.06.2022, 15:33)admin Wrote: Post your full script listing. Also enable logging for mapped object and see if they change values. Check Error logs as well.

Thanks. Here is my answer:
Sorry for not noticing the error log. There are errors:

---
Resident script:19: attempt to compare nil with number
stack traceback:
Resident script:19: in function 'setinputvalue'
Resident script:54: in function <Resident script:47>
Library localbus: in function ''
Library localbus: in function ''
Library localbus: in function 'loop'
---

'1/1/7' is the object of the dimming value.
I have logged 1/1/7. No logs registered.


Code:
if not client then
  timers = {
    {
      input = '9/0/1', -- binary PIR status
      output = '1/1/7', -- dimmer control (0..100%)
      onvaluehigh = 90, -- high output value in %
      onvaluelow = 10, -- low output value in %
      timeout = 5, -- in seconds
    }
  }

  function setinputvalue(timer, event)
    local value = busdatatype.decode(event.datahex, dt.bool)

    if not value then
      return
    end

    if timer.value < timer.onvaluelow then
      value = timer.onvaluelow
    elseif timer.outvalue < timer.onvaluehigh then
      value = timer.onvaluehigh
    else
      value = nil
    end

    if not timer.ticks and value then
      grp.write(timer.output, value, dt.scale)
    end

    timer.ticks = timer.timeout
  end

  function setoutputvalue(timer, event)
    timer.outvalue = busdatatype.decode(event.datahex, dt.scale)
    timer.ticks = nil -- stop timer
  end

  for _, timer in ipairs(timers) do
    timer.outvalue = grp.getvalue(timer.output)
  end

  sender = 'tm'
  grp.sender = sender

  client = require('localbus').new(0.1)
  client:sethandler('groupwrite', function(event)
    if event.sender == sender then
      return
    end

    for _, timer in ipairs(timers) do
      if timer.input == event.dst then
        setinputvalue(timer, event)
      elseif timer.output == event.dst then
        setoutputvalue(timer, event)
      end
    end
  end)
end

client:loop(1)

for _, timer in ipairs(timers) do
  if timer.ticks then
    timer.ticks = timer.ticks - 1

    if timer.ticks == 0 then
      grp.write(timer.output, timer.outvalue, dt.scale)
      timer.ticks = nil
    end
  end
end
Reply
#8
Line 19 should have timer.outvalue instead of timer.value:
Code:
if timer.outvalue < timer.onvaluelow then
Reply
#9
(13.06.2022, 07:00)admin Wrote: Line 19 should have timer.outvalue instead of timer.value:
Code:
if timer.outvalue < timer.onvaluelow then

Thanks for reaction. I have changed this, but error is the same:

Code:
if not client then
  timers = {
    {
      input = '9/0/1', -- binary PIR status
      output = '1/1/7', -- dimmer control (0..100%)
      onvaluehigh = 90, -- high output value in %
      onvaluelow = 10, -- low output value in %
      timeout = 5, -- in seconds
    }
  }

  function setinputvalue(timer, event)
    local value = busdatatype.decode(event.datahex, dt.bool)

    if not value then
      return
    end

        if timer.outvalue < timer.onvaluelow then
      value = timer.onvaluelow
    elseif timer.outvalue < timer.onvaluehigh then
      value = timer.onvaluehigh
    else
      value = nil
    end

    if not timer.ticks and value then
      grp.write(timer.output, value, dt.scale)
    end

    timer.ticks = timer.timeout
  end

  function setoutputvalue(timer, event)
    timer.outvalue = busdatatype.decode(event.datahex, dt.scale)
    timer.ticks = nil -- stop timer
  end

  for _, timer in ipairs(timers) do
    timer.outvalue = grp.getvalue(timer.output)
  end

  sender = 'tm'
  grp.sender = sender

  client = require('localbus').new(0.1)
  client:sethandler('groupwrite', function(event)
    if event.sender == sender then
      return
    end

    for _, timer in ipairs(timers) do
      if timer.input == event.dst then
        setinputvalue(timer, event)
      elseif timer.output == event.dst then
        setoutputvalue(timer, event)
      end
    end
  end)
end

client:loop(1)

for _, timer in ipairs(timers) do
  if timer.ticks then
    timer.ticks = timer.ticks - 1

    if timer.ticks == 0 then
      grp.write(timer.output, timer.outvalue, dt.scale)
      timer.ticks = nil
    end
  end
end

Resident script:19: attempt to compare nil with number
stack traceback:
 Resident script:19: in function 'setinputvalue'
 Resident script:54: in function <Resident script:47>
 Library localbus: in function ''
 Library localbus: in function ''
Libbrarary localbus: in function 'loop'
Reply
#10
The same script works for me. Does 9/0/1 exist?
I've added some logging, check what you get in the Logs tab:
Code:
if not client then
  timers = {
    {
      input = '9/0/1', -- binary PIR status
      output = '1/1/7', -- dimmer control (0..100%)
      onvaluehigh = 90, -- high output value in %
      onvaluelow = 10, -- low output value in %
      timeout = 5, -- in seconds
    }
  }

  function setinputvalue(timer, event)
    local value = busdatatype.decode(event.datahex, dt.bool)

    if not value then
      return
    end

    log('pir trigger', timer.outvalue)

    if timer.outvalue < timer.onvaluelow then
      value = timer.onvaluelow
    elseif timer.outvalue < timer.onvaluehigh then
      value = timer.onvaluehigh
    else
      value = nil
    end

    if not timer.ticks and value then
      grp.write(timer.output, value, dt.scale)
    end

    timer.ticks = timer.timeout
  end

  function setoutputvalue(timer, event)
    timer.outvalue = busdatatype.decode(event.datahex, dt.scale)
    timer.ticks = nil -- stop timer
    log('output change', timer.outvalue)
  end

  for _, timer in ipairs(timers) do
    timer.outvalue = grp.getvalue(timer.output)
    log('timer init', timer.outvalue)
  end

  sender = 'tm'
  grp.sender = sender

  client = require('localbus').new(0.1)
  client:sethandler('groupwrite', function(event)
    if event.sender == sender then
      return
    end

    for _, timer in ipairs(timers) do
      if timer.input == event.dst then
        setinputvalue(timer, event)
      elseif timer.output == event.dst then
        setoutputvalue(timer, event)
      end
    end
  end)
end

client:loop(1)

for _, timer in ipairs(timers) do
  if timer.ticks then
    timer.ticks = timer.ticks - 1

    if timer.ticks == 0 then
      log('timer stop', timer.outvalue)
      grp.write(timer.output, timer.outvalue, dt.scale)
      timer.ticks = nil
    end
  end
end
Reply
#11
I modified the code and it still did not work. I searched for a while, until I found the ultimate solution: reboot the device. After that, the error messages were immediately gone, and the script works. I can't explain it, but fortunately it's solved. Thank you, thank you!

I'm still running into a problem, and I also have a question about applying this script to other lamps inside and outside the house:

Problem:
If the script is running and has turned the lamp up when there is movement, and then within the set time period the lamp is turned off (for example, because we are going to bed), then the script turns the lamp - after the set time has expired - back on to its original dimming status. Unfortunately, this was not the intention. The lamp should then remain off. Is there a solution for this?

Question:
Is it possible to indicate in the script for each PIR via a separate object whether it is active or not?
And is it possible to include a PIR twice in the timers table. For example, an outdoor lamp in the backyard in winter has a different behaviour than in summer?
Reply
#12
A separate timer on/off object can be added as well as another on/off object to monitor manual lamp operation. What should happen when the lamp is turned on manually? Which level should the script use?
Reply
#13
(17.06.2022, 14:18)admin Wrote: A separate timer on/off object can be added as well as another on/off object to monitor manual lamp operation. What should happen when the lamp is turned on manually? Which level should the script use?

Super that this is possible!
Preferably, the lamp returns to its last initial position. That is, the position the lamp was in the last time, before movement was detected.
E.g. the lamp is on 35%, it is turned to 90% by the script and is then turned off manually or by another script or a scene. The next time the lamp is turned on manually (or by a scene), it should start at 35%.
But if that results in a very complex script, then a fixed value, e.g. 30%, is also good.

Thanks.
Reply
#14
Updated version with an optional timer on/off object and status monitoring. For this to work the dimmer must have a status output active and should report the status as soon as possible (some dimmers have an option to report status when only after a transition). When the script writes its output value it will ignore the status report for 1 second. This can be lowered (line 53) to prevent possible issues when a manual control is performed at the same moment as the scripted control.

Code:
if not client then
  timers = {
    {
      input = '1/1/1', -- binary PIR status
      enable = '1/1/2', -- enable timer on/off
      output = '1/1/3', -- dimmer control (0..100%)
      status = '1/1/4', -- dimmer status (0..100%)
      onvaluehigh = 90, -- high output value in %
      onvaluelow = 10, -- low output value in %
      timeout = 5, -- in seconds
    }
  }

  function setoutputvalue(timer, value)
    timer.ignoresec, timer.ignoreusec = os.microtime()
    grp.write(timer.output, value, dt.scale)
  end

  function settimerstate(timer, event)
    timer.enabled = busdatatype.decode(event.datahex, dt.bool)
    timer.ticks = nil

    log('timer enabled', timer.enabled)
  end

  function setinputvalue(timer, event)
    local value = busdatatype.decode(event.datahex, dt.bool)

    if not value or not timer.enabled then
      return
    end

    log('pir trigger', timer.statvalue)

    if timer.statvalue < timer.onvaluelow then
      value = timer.onvaluelow
    elseif timer.statvalue < timer.onvaluehigh then
      value = timer.onvaluehigh
    else
      value = nil
    end

    if not timer.ticks and value then
      setoutputvalue(timer, value)
    end

    timer.ticks = timer.timeout
  end

  function setstatusvalue(timer, event)
    if timer.ignoresec then
      local delta = os.udifftime(timer.ignoresec, timer.ignoreusec)
      if delta >= 0 and delta <= 1 then
        return
      end

      timer.ignoresec = nil
    end

    if timer.enabled then
      timer.statvalue = busdatatype.decode(event.datahex, dt.scale)
      timer.ticks = nil -- stop timer
      log('output change', timer.statvalue)
    end
  end

  for _, timer in ipairs(timers) do
    timer.statvalue = grp.getvalue(timer.status) or 0

    if timer.enable then
      timer.enabled = grp.getvalue(timer.enable)
    else
      timer.enabled = true
    end

    log('timer init', timer.statvalue, timer.enabled)
  end

  sender = 'tm'
  grp.sender = sender

  client = require('localbus').new(0.1)
  client:sethandler('groupwrite', function(event)
    if event.sender == sender then
      return
    end

    for _, timer in ipairs(timers) do
      if event.dst == timer.enable then
        settimerstate(timer, event)
      elseif event.dst == timer.switch then
        setswitchvalue(timer, event)
      elseif event.dst == timer.input then
        setinputvalue(timer, event)
      elseif event.dst == timer.status then
        setstatusvalue(timer, event)
      end
    end
  end)
end

client:loop(1)

for _, timer in ipairs(timers) do
  if timer.ticks then
    timer.ticks = timer.ticks - 1

    if timer.ticks == 0 then
      log('timer stop', timer.statvalue)
      setoutputvalue(timer, timer.statvalue)
      timer.ticks = nil
    end
  end
end
Reply


Forum Jump: