Cool, I’m happy to share - if anyone has any suggestions for improving my mess, I won’t take offense
Notes:
-
I’m using the “MultiString” plugin to create persistent variables I can reference elsewhere and manipulate with the UI - this is essentially legacy now, and I’ll likely write around them shortly and just implement is a multi-dimensional lua table.
-
The MultiString objects I’m using currently are laid out as 1 conf device for a set of lights (interior vs exterior in my setup).
Example MS conf device (variable1 … variableN):
Enabled: [1|0]
ColorString: "h,s,b,k,t"
Example room device:
LIFXGroupName=Office
LastMovementTime=1444694044
Delay=300
Unoccupied=0
ConfDevice=67
-
My lightsd instance is running at 192.168.1.149, listening on tcp 8080
-
I’m using occupancy sensors throughout my house to turn up lights with lightsd, or trigger light events as alerts (ala “flash blue when porch motion sensed”), but the Lifx cloud to turn them off at the moment, as the cloud API has cool support for a very long duration fade then power off. I can do the fade with lightsd, but I’d have to read state to know when to power off, complicating things and exceeding my meager lua skills (~2 hours’ reading so far)
-
Each sensor has a scene which turns lights on (if the lights are set to auto), and there’s a cleanup process that starts them shutting down after occupancy isn’t detected for a period. I found this to work pretty well, and lights don’t turn off on me.
Here’s my Vera (UI7) startup script
function isempty(s)
return s == nil or s == ''
end
function lightsd_command (json)
socket = require "socket"
address, port = "192.168.1.149", 8080
tcp = socket.tcp()
tcp:connect(address, port)
tcp:send(json)
tcp:close()
end
function lifx_command (method, params)
local io = require "io"
local json = require "akb-json"
local mime = require "mime"
local devurandom = io.open("/dev/urandom", "r")
local urandom = devurandom:read(8)
devurandom:close()
json_id = mime.b64(urandom)
jt={jsonrpc="2.0",
method=method,
params=params,
id=json_id}
json_obj=json.encode(jt)
lightsd_command(json_obj)
end
function lifx_multi_command (batch)
local io = require "io"
local json = require "akb-json"
local mime = require "mime"
local jb = {}
local pos = 1
for method, params in pairs(batch) do
local devurandom = io.open("/dev/urandom", "r")
local urandom = devurandom:read(8)
devurandom:close()
json_id = mime.b64(urandom)
jt={jsonrpc="2.0",
method=method,
params=params,
id=json_id}
jb[pos]=jt
pos = pos + 1
end
json_obj=json.encode(jb)
lightsd_command(json_obj)
end
--example usage
--lifx_multi_command({power_off={"#Office"}, power_on={"OfficeTorch"}})
-- Color array managed in manual: autolights next color scene
lifx_colors = { "0,0,1,3700,1000" }
lifx_color_sel = 1
lifx_ext_colors = { "0,0,1,3700,1000" }
lifx_ext_color_sel = 1
local lifx_groups = {}
lifx_groups["BackYard"] = {"BackPorch"}
lifx_groups["DiningRoom"] = {"DiningRoom1", "DiningRoom2"}
lifx_groups["FrontBedroom"] = {"FrontBedroom1", "FrontBedTorch"}
lifx_groups["FrontYard"] = {"FrontPorchColor"}
lifx_groups["Garage"] = {"GarageNorthRear", "GarageNorthFront", "GarageRear", "GarageSideSouth"}
lifx_groups["Hallway"] = {"HallSconce"}
lifx_groups["Kitchen"] = {"KitchenColor1", "KitchenColor2", "KitchenColor3"}
lifx_groups["LivingRoom"] = {"LureLamp", "LivingRoomSconce"}
lifx_groups["MasterBedroom"] = {"MasterBedAneka", "MasterBedJon"}
lifx_groups["Office"] = {"OfficeTorch", "OfficeFloorLamp"}
lifx_groups["Interior"] = {"DiningRoom1", "DiningRoom2", "FrontBedroom1", "FrontBedTorch", "HallSconce", "KitchenColor1", "KitchenColor2", "KitchenColor3", "LureLamp", "LivingRoomSconce", "MasterBedAneka", "MasterBedJon", "OfficeTorch", "OfficeFloorLamp"}
lifx_groups["Exterior"] = {"BackPorch", "FrontPorchColor"}
lifx_groups["Alerts"] = {"BackPorch", "DiningRoom1", "FrontBedTorch", "GarageRear", "HallSconce", "KitchenColor1", "LivingRoomSconce", "MasterBedJon", "OfficeTorch"}
for k, v in pairs(lifx_groups) do
local tagname = k
local bulbs = v
i=1
for i=1, #bulbs do
local bulb = bulbs[i]
lifx_command("tag", {bulb, tagname})
end
end
function string:split( inSplitPattern, outResults )
if not outResults then
outResults = { }
end
local theStart = 1
local theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart )
while theSplitStart do
table.insert( outResults, string.sub( self, theStart, theSplitStart-1 ) )
theStart = theSplitEnd + 1
theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart )
end
table.insert( outResults, string.sub( self, theStart ) )
return outResults
end
function lifxLightGroupOn (device)
if (luup.is_ready(device) == false) then
return
end
local confdevice = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable5", device)
if isempty(confdevice) then
return
end
if (luup.is_ready(tonumber(confdevice)) == false) then
return
end
local enabled = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable1", tonumber(confdevice))
local room = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable1", device)
local color = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable2", tonumber(confdevice))
if isempty(room) then
return
end
if enabled and enabled ~= '' and enabled ~= "0" then
if isempty(color) then
color = "0,0,1,3700,200"
end
colortab=color:split(",")
i=1
for i=1, #colortab do
local color = tonumber(colortab[i])
colortab[i] = color
end
local t = os.time()
luup.variable_set("urn:upnp-org:serviceId:VContainer1", "Variable2", tostring(t), device)
luup.variable_set("urn:upnp-org:serviceId:VContainer1", "Variable4", "0", device)
lifx_command("set_light_from_hsbk", {"#" .. room, colortab[1], colortab[2], colortab[3], colortab[4], colortab[5]})
lifx_command("power_on", {"#" .. room})
lifx_command("set_light_from_hsbk", {"#" .. room, colortab[1], colortab[2], colortab[3], colortab[4], colortab[5]})
end
end
function lifxLightGroupOff (device)
if (luup.is_ready(device) == false) then
return
end
local confdev = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable5", device)
if isempty(confdev) then
return
end
if (luup.is_ready(tonumber(confdev)) == false) then
return
end
local enabled = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable1", tonumber(confdev))
if enabled and enabled ~= '' and enabled ~= "0" then
local room = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable1", device)
local delay = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable3", device)
local unoccupied, last_t = luup.variable_get("urn:upnp-org:serviceId:VContainer1","Variable4", device)
if isempty(room) then
return
end
if isempty(delay) then
return
end
local t = os.time()
if unoccupied and unoccupied ~= "" and unoccupied ~= "0" and unoccupied ~= "1" and t >= (last_t + tonumber(delay)) then
color = {0, 0, 0, 3700, 60000}
--lifx_command("set_light_from_hsbk", {"#" .. room, color[1], color[2], color[3], color[4], color[5]})
os.execute('curl -ku "token:" -X PUT -d "state=off;duration=30" "https://api.lifx.com/v1beta1/lights/group:' .. room .. '/power"')
luup.variable_set("urn:upnp-org:serviceId:VContainer1", "Variable4", "1", device)
end
end
end
function roomUnoccupied (device)
local t = os.time()
luup.variable_set("urn:upnp-org:serviceId:VContainer1", "Variable4", tostring(t), device)
end
The lights on scenes look like:
local device = 72
lifxLightGroupOn(device)
Each coupled with an unoccupied scene for the same sensor:
local device = 72
roomUnoccupied(device)
And this cleanup scene which is scheduled to run every 15 sec across the MultiString devices for all rooms (don’t go more frequent than you need - the UI7 doesn’t protect you from creating queue runaway):
local devices = {68, 69, 70, 71, 72, 73, 75, 76, 77, 86}
local i
local dev
i=1
for i=1, #devices do
local dev = devices[i]
lifxLightGroupOff(dev)
end
return true
Here’s an example notification scene - this one triggers when the front porch sensor is tripped (only notifies specific lights, if each light’s on):
local hue = 180
local count = 2
local freq = 200
lifx_command("set_waveform", { "#Alerts", "TRIANGLE", hue, 1, 1, 3700, freq, count, 0.5, true})
return true
I hope someone finds this valuable - feel free to ask for more info if I’ve left anything out!