Hibernate your home

Advanced LUA scene to automatically turn off all lights (and save state)

Advanced LUA scene to automatically turn off all lights (and save state)

Januari 12, 2019
Applies to: Fibaro Home Center 2 and Philips HUE plug-in.

Goals

  • Check all z-wave lights that are on.
  • Check all Philips Hue lights that are on.
  • Turn these lights off when everybody is away from home.
  • Save the lights that where turned off to turn them back on when someone arrives at home.
  • Exclude lights to turn off when using a go to bed scene trigger (make scene for global use).

After I build reliable presence detection I wanted to automatically turn off the lights when nobody is at home. I though it would be nice if the controller remembers the state of the house and returns to that state when the first person arrives back at home. When there is a lot of time passed a standard arrival routine will run, because the previous light state is not important anymore right then.

The scene checking for lights needs to be universal, so I can use it for a go to bed scene also. For this purpose the scene must handle exclusions. (you don’t want to turn of the light that you just turned on for your bedtime scene).

TL;DR

  • Create an universal scene for checking the lights.
  • Create a scene for leaving home.
  • Create a scene for arriving at home.
  • Optionally create a go to bed scene._
  • Save the light state to a global variable

How I implemented it

In words

When Node-RED detects that somebody left the house it sets a personal presence global variable to No in Home Center 2. In my case, if both JoepPresent and MoniquePresent are set to No the leave home scene is triggered. This scene sets another global variable HomeState to away. This is done to tackle the issue when you arrive at home and on the driveway your phone get’s connected to your home Wi-Fi the system responds immediately. You don’t want that to happen. You want to trigger the arrive home scene with a door- or motion sensor. Then it’s triggered as you physically step into your house.

The leaving home scene triggers the turn off all lights scene to save the light state. If the first person arrive’s back at home the arriving home scene is triggered by the earlier mentioned door- or motion sensor.

Devices used

I can enter my house from three doors. At the front door I use a Fibaro Door/Window Sensor and the other rooms are equipped with a Fibaro Motion Sensor. You have to change the sensors in my LUA scenes to your own use case.

Global variables

These are the global variables you need for the routine described on this page only. Presence detection variables are not described as you can have another presence detection system than what I use.

Global Variable Type Value
HomeState Predefined athome, away
TurnOffExclusions Normal 0
SleepingLights Normal 0

Scene 1: check and turn off all lights explained

You can download the full LUA scenes at the bottom of this page. I only describe snippets of my code to make you understand what it does and show the challenges I ran into.

Function to check if value exists in array

I want to keep a table with all rooms that have lights on. This information is for future use, when I want to send a push message with rooms with lights still on message for example. To only add new rooms to the table I wrote the following function:

local function has_value (tab, val)
  for index, value in ipairs(tab) do
    if value == val then
      return true
    end
  end
  return false
end

Get exclusions from the global variable

Before starting this scene from within the leave home scene I set a global variable with the id’s of the lights that not have to turn off automatically (see later on this page), I read this global variable with the following code:

local exclusionsVar = fibaro:getGlobalValue("TurnOffExclusions")
local exclusions = {}
for match in (exclusionsVar..'|'):gmatch("(.-)"..'|') do
  table.insert(exclusions, match);
end

Check Z-Wave lights

When the exclusions are read I can loop over all devices and read the isLight value to determine if the device is a light:

if fibaro:getValue(i, "isLight") == "1" then
...

With this check I don’t have to keep a table with all light source id’s. When you add a light to your house it’s automatically incorporated in the scenes. Then I check if the device is on:

if (fibaro:getValue(i, "value") >= "1")
...

If both conditions match the device is turned off and the id is saved (if it is not excluded). The full code block for the check is:

local i = 0
local maxNodeID = 1000
for i = 0, maxNodeID do
  if fibaro:getValue(i, "isLight") == "1" then
    if (fibaro:getValue(i, "value") >= "1") then
      local DeviceName = fibaro:getName(i)
      local RoomName = fibaro:getRoomNameByDeviceID(i)
      if not has_value(roomsWithLightsOn, RoomName) and RoomName ~= "unassigned" then
        table.insert(roomsWithLightsOn, RoomName)
      end
      if RoomName ~= "unassigned" then
        devicesOn = devicesOn .. i .. '|'
        fibaro:debug(os.date("%a, %b %d") .. " Device is on: " .. i .. " " .. DeviceName .. " " .. RoomName)
        -- If device if not on the exclusion list, turn it off
        local excluded = has_value(exclusions, tostring(i))
        if not has_value(exclusions, tostring(i)) then
          fibaro:call(i , "turnOff")
        else
          fibaro:debug(os.date("%a, %b %d") .. " Excluded device: " .. i)
        end
      end
    end
  end
end

Check Philips Hue lights

If you have Philips Hue devices in your home and use the Fibaro Hue plug-in the Hue light sources are not detected with the code above. You can detect Hue devices with the following line of code:

if fibaro:getType(j) == "com.fibaro.philipsHueLight" then
...

To check if a Hue light is on you can use the following line of code:

if (fibaro:getValue(j, "on") == "1") then
...

For Hue devices I created another code block that also save the id’s from the lights that are on:

local j = 0
local maxHueID = 1000
for j = 0, maxHueID do
  if fibaro:getType(j) == "com.fibaro.philipsHueLight" then
    if (fibaro:getValue(j, "on") == "1") then
      local DeviceName = fibaro:getName(j)
      local RoomName = fibaro:getRoomNameByDeviceID(j)
      if not has_value(roomsWithLightsOn, RoomName) and RoomName ~= "unassigned" then
        table.insert(roomsWithLightsOn, RoomName)
      end
      devicesOn = devicesOn .. j .. '|'
      fibaro:debug(os.date("%a, %b %d") .. " Hue device is on: " .. j .. " " .. DeviceName .. " " .. RoomName)
      -- If Hue device if not on the exclusion list, turn it off
      if not has_value(exclusions, tostring(j)) then
        fibaro:call(j , "turnOff")
      end
    end
  end
end

Saving the light state

The above scripts checked the lights that are still on in our house and turned them off (if they where not excluded). I saved a table with these lights and now I write this table to a global variable to use with the arrive home scene. With this info I can turn all lights back on in the same state when we left the house.

fibaro:setGlobal("SleepingLights", devicesOn:sub(1, -2)) -- remove last |

Scene 2: leave home explained

This scene is pretty simple. If both JoepPresent and MoniquePresent are set to No the HomeState variable is set to away and the TurnOffAllLights scene is started. These variables are set with my presence detection scene.

if fibaro:getGlobalValue("JoepPresent") == "No" and fibaro:getGlobalValue("MoniquePresent") == "No" then
  fibaro:setGlobal("HomeState", "away")
  fibaro:debug(os.date("%a, %b %d") .. " Set HomeState to away.")
  fibaro:setGlobal("TurnOffExclusions", "0") -- no exclusions
  if fibaro:countScenes(33) < 1 then
    fibaro:startScene(33) -- run Turn All Lights Off Scene
  else
    fibaro:debug(os.date("%a, %b %d") .. " Turn Of All Lights scene already running!")
  end
end

Note: the TurnOffAllLights scene has id 33 in my system, you have to change that to the id on your own Home Center 2.

Scene 3: arrive home explained

The LUA scene to run when the first person arrive’s at home is a little bit more complex. At home I have tree entrances:

Entrance Sensor
Front door Fibaro Door/Window Sensor
Garage door Fibaro Motion Sensor
Terrace door Fibaro Motion Sensor

As a house member can arrive at any of these doors I have to check all sensors for activity and if one sensor sends a breached state the Home Center 2 needs to act and start the arrive home scene.

Reading the sensors

With the following line I check all three sensors for activity:

if tonumber(fibaro:getValue(176, "value") > 0 or tonumber(fibaro:getValue(164, "value") > 0 or tonumber(fibaro:getValue(130, "value") > 0 then
...

Tackling the motion sensor alarm cancellation delay

The door sensor sets it’s value to 1 when I open the door, and sets it’s value back to 0 when I close the front door. The motion sensors keep their value at 1 until the alarm cancellation delay is reached (set with parameter 6).

This is a problem. When I enter the garage to get some stuff before leaving the house the value of the motion sensor stays 1 during the alarm cancellation delay, even if there is no motion anymore. If I grab something the garage and leave through the front door within this period the value of the motion sensor is still 1 and the lights go immediately back on when opening the front door. It took a while to figure this out!

I found out that the lastBreached parameter keeps a timestamp value from the last time motion was detected. In the LUA scene I check if the active breach is within the alarm cancellation delay parameter value (6). In my case this is 5 minutes (or 300 seconds). If this is the case I report no movement and it doesn’t trigger the arrival scenario.

Another problem is that if the sensor is not breached the lastBreached value is 0. The sensor is so fast the scene reads 0 as soon there is motion detected and it thinks there is no movement. I fix this behaviour with the following code:

local lastBreachedGarage = currentime - tonumber(fibaro:getValue(164, "lastBreached"))
local currentime = os.time(os.date("!*t"))
local noMovement = 1
if lastBreachedGarage > 2 and lastBreachedGarage < 310 then
  noMovement = 0
end

Retrieving the light state and turn the lights back on when arriving home

If all these parameters are correct and the HomeState global variable is away I set the HomeState back to athome and check with a Fibaro motion sensor if the illuminance is below 10 to check if it’s dark and the light need to be turned back on:

if fibaro:getGlobalValue("HomeState") == "away" and noMovement == 1 then
  fibaro:setGlobal("HomeState", "athome")
  fibaro:debug(os.date("%a, %b %d") .. " Set HomeState to \"athome\".")

  local currentLux = fibaro:getValue(160, "value")
  if ( tonumber(currentLux) < 10 ) then
    fibaro:debug(os.date("%a, %b %d") .. " Welcome home! Illuminance measuring " .. currentLux .. " lx, turn lights back on!")
...

If the illuminance is below 10 lux the Home Center 2 needs to know which lights to turn back on. I read the SleepingLights global variable and put the id’s into a table:

local sleepinglights = fibaro:getGlobalValue("SleepingLights")
local lights = {}
for match in (sleepinglights..'|'):gmatch("(.-)"..'|') do
  table.insert(lights, match);
end

Then I check the elapsed time between leaving and arriving back home. If it is less than 4 hours and there where lights left on, turn them back on:

local currentime =  os.time(os.date("!*t"))
local sleepingtime = lights[1]
local elapsedtime = currentime - sleepingtime

if elapsedtime < 14400 and lights[2] ~= nill then -- if elapsed time is less than 4 hours.
fibaro:debug(os.date("%a, %b %d") .. " Last member of the family left less than 4 hours ago, return lights to previous state!")
for k, v in pairs(lights) do
  if k ~= 1 then -- skip first, is unixtimestamp
    fibaro:call(v , "turnOn")
    fibaro:debug("turn on: " .. v)
  end
else
...

If the elapsed time between leaving and arriving back home is more then 4 hours a standard arrival routine will run, because the previous light state is not important anymore. This routine you write in the else statement of the previous if condition:

...
else
  fibaro:debug(os.date("%a, %b %d") .. " Last member of the family left more than 4 hours ago, start arrive home scene!")

  fibaro:call(49, "setValue", "8") -- Spots garderobe
  fibaro:call(172, "turnOn") -- Gurken garden

  fibaro:call(140, "changeHue", "5021") -- Ledstip overloop
  fibaro:call(140, "changeSaturation", "199") -- Ledstip overloop
  fibaro:call(140, "changeBrightness", "114") -- Ledstip overloop
  fibaro:call(140, "turnOn") -- Ledstip overloop

  fibaro:call(44, "setValue", "8") -- Spots keuken
  fibaro:call(39, "setValue", "35") -- Kookeiland
end

More awesome usecases

Because I made the TurnOffAllLights universal and supports exclusions you can make cool other scenes using it like a Go to Bed scene trigged by a scene activition. Also the arrive home scene can be edited to automatically create a nice cup of coffee when arriving home from a long day.

I’ll describe these cool features soon as I’m implementing these at the moment.

Download my scenes complete LUA code

You can download the full LUA scene code from here:

  • Scene 1: full code published soon!
  • Scene 2: full code published soon!
  • Scene 3: full code published soon!

You have to change the device id’s from my motion sensors in this scene to your own id’s!