
Vera Users’ Convention, 2019, colorized
OK. With all due seriousness, this is actually a fairly involved programming project, not because it’s hard in concept, but just because of the way you have to structure it around the delays. Here’s my attempt, which of course I cannot run since I don’t have your hardware, so likely errors to be found, but hopefully it’s close. This goes in Startup Lua (Apps > Develop apps > Edit Startup Lua – but I recommend considering installing the LuaView plugin and using it).
-- This global array will store the last known position of every shade.
shadeStatus = {}
function getShadeStatus(fnshadeNUM)
local timeout = 5
local testnum = tonumber(fnshadeNUM)
local _, res = luup.inet.wget("http://xxx.xxx.xxx.xxx/api/shade/" .. testnum, timeout)
luup.log("getShadeStatus() shade "..testnum.." status "..tostring(res))
local json = require "dkjson"
local st = json.decode( res )
shadeStatus[testnum] = st.position1 or -1 -- save current position
return st.position1
end
-- Move shade to desired position. This is the function you should call from your scene Lua, Reactor, PLEG, etc.
function setShadePosition( shadeNum, posNum )
shadeNum = tonumber( shadeNum ) or error "Invalid shade number "..tostring(shadeNum)
posNum = tonumber( posNum ) or error "Invalid position "..tostring(posNum)
-- If shade is not already in that position, move it.
if shadeStatus[shadeNum] ~= posNum then
-- Start shade in motion to desired position.
local json = require "dkjson"
local st = { shade={ positions= { posKind1=1, posKind2=2, position1=posNum, position2=posNum } } }
os.execute( [[curl -X PUT -H "Content-type: application/json" -d ']] ..
json.encode( st ) .. "' http://xxx.xxx.xxx.xxx/api/shade/" .. shadeNum )
luup.call_delay( 'checkShade', 5, shadeNum .. ":" .. ( shadeStatus[shadeNum] or -1 ) .. ":" .. posNum )
end
end
-- Recalibrate shade (utility function).
function recalibrateShade( shadeNum )
os.execute( [[curl -X PUT -H "Content-type: application/json" -d '{ "shade": { "motion": "recalibrate" } }' http://xxx.xxx.xxx.xxx/api/shade/]]
.. shadeNum )
-- Do not remove the line below (but if the shade position is known after recal, change the value)
shadeStatus[shadeNum] = -1
end
-- This is the timer callback to check the shade
function checkShade( parg )
local shadeNum,startPos,targetPos = parg:match("^(%d+):(%d+):(%d+)")
shadeNum = tonumber( shadeNum )
startPos = tonumber( startPos )
targetPos = tonumber( targetPos )
local newPos = getShadeStatus( shadeNum )
if startPos == newPos then
-- Shade has not moved after 5 seconds! Recalibrate.
recalibrateShade( shadeNum )
luup.call_delay( 'retryShade', shadeNum .. ":" .. targetPos, 10 ) -- adjust delay here for however long calibration takes, plus a bit
else
shadeStatus[shadeNum] = targetPos -- assume success
end
end
-- This is the timer callback to retry positioning the shade after recalibration.
function retryShade( parg )
local shadeNum,targetPos = parg:match("^(%d+):(%d+)")
setShadePosition( shadeNum, targetPos )
end
To move your shades, you need to call setShadePosition( shadeNum, posNum )
from your scene Lua, Reactor actions (Run Lua), PLEG, etc.
For theory of operation, let’s take it a chunk at a time. First, this goes in Startup Lua, so all of this is defined and set up at Luup startup. It runs once, and mostly just defines functions to be used later.
The first part:
-- This global array will store the last known position of every shade.
shadeStatus = {}
function getShadeStatus(fnshadeNUM)
local timeout = 5
local testnum = tonumber(fnshadeNUM)
local _, res = luup.inet.wget("http://xxx.xxx.xxx.xxx/api/shade/" .. testnum, timeout)
luup.log("getShadeStatus() shade "..testnum.." status "..tostring(res))
local json = require "dkjson"
local st = json.decode( res )
shadeStatus[testnum] = st.position1 or -1 -- save current position
return st.position1
end
Most of this should look familiar, with two additions:
- The first line defines a global variable (array/table) called
shadeStatus
that we’re going to use to hold the last known position of each shade. No preconfiguration is needed; each shade will be initialized/learned as you use it (i.e. when you call setShadePosition()
)
- The request returns a JSON string that has to be parsed, and the position value extracted from it.
- I’ve added a line before the
return
to save the retrieved position in the new array.
-- Move shade to desired position. This is the function you should call from your scene Lua, Reactor, PLEG, etc.
function setShadePosition( shadeNum, posNum )
shadeNum = tonumber( shadeNum ) or error "Invalid shade number "..tostring(shadeNum)
posNum = tonumber( posNum ) or error "Invalid position "..tostring(posNum)
-- If shade is not already in that position, move it.
if shadeStatus[shadeNum] ~= posNum then
-- Start shade in motion to desired position.
local json = require "dkjson"
local st = { shade={ positions= { posKind1=1, posKind2=2, position1=posNum, position2=posNum } } }
os.execute( [[curl -X PUT -H "Content-type: application/json" -d ']] ..
json.encode( st ) .. "' http://xxx.xxx.xxx.xxx/api/shade/" .. shadeNum )
luup.call_delay( 'checkShade', 5, shadeNum .. ":" .. ( shadeStatus[shadeNum] or -1 ) .. ":" .. posNum )
end
end
This is setShadePosition()
. You pass it the shade device number and position you want. If the shade is not already in that position, it will issue the command to move it. I just guessed on what that might look like for your shade API–you will need to fix that to make it right. But the idea is that it sets the shade in motion if needed, and then schedules the checkShade
function for five seconds out. It needs to pass three values to checkShade
–since Luup delay callbacks can only receive one string argument, it creates a string of the three values, separated by colons (:s), which checkShade
will need to parse and turn back into separate values. A shortcoming of the Luup delay API, but at least it passes something.
-- Recalibrate shade (utility function).
function recalibrateShade( shadeNum )
os.execute( [[curl -X PUT -H "Content-type: application/json" -d '{ "shade": { "motion": "recalibrate" } }' http://xxx.xxx.xxx.xxx/api/shade/]]
.. shadeNum )
-- Do not remove the line below (but if the shade position is known after recal, change the value)
shadeStatus[shadeNum] = -1
end
We recalibrate the shade. Make sure you don’t remove the last line–we must tell the other functions that the shade position is unknown after recalibration. We use -1 to indicate that. If it happens that the shades are always closed (0) or open (65535) after recalibration, you can put that value in there instead of -1.
-- This is the timer function to check the shade
function checkShade( parg )
local shadeNum,startPos,targetPos = parg:match("^(%d+):(%d+):(%d+)")
shadeNum = tonumber( shadeNum )
startPos = tonumber( startPos )
targetPos = tonumber( targetPos )
local newPos = getShadeStatus( shadeNum )
if startPos == newPos then
-- Shade has not moved after 5 seconds! Recalibrate.
recalibrateShade( shadeNum )
luup.call_delay( 'retryShade', shadeNum .. ":" .. targetPos, 10 ) -- adjust delay here for however long calibration takes, plus a bit
else
shadeStatus[shadeNum] = targetPos -- assume success
end
end
Here’s checkShade
, which is called five seconds after (hopefully) starting the shade moving. It starts by unpacking the string that setShadePosition
had to create back into three values: the shade number, the starting position when setShadePosition
was called, and the target shade position. It then gets the current shade position, and checks to see if starting position is different from current. If it is, we’re done here–the shade is moving or has moved and all is well. Otherwise, it calls recalibrateShade
to get that process rolling, and then schedules retryShade
to try to get it to the target position again. You may need to adjust the delay there so that it’s always a few seconds longer than recalibration can take worst-case.
Finally, here’s retryShade
…
-- This is the timer function to retry positioning the shade after calibration. It is only called
-- after calibration is used.
function retryShade( parg )
local shadeNum,targetPos = parg:match("^(%d+):(%d+)")
setShadePosition( shadeNum, targetPos )
end
This just unpacks its argument (we need two values, but delay callbacks can only receive one, so again, we made a string of the two values with a colon between, and we have to split that back up). Then it calls setShadePosition
and hopefully gets the shade positioned correctly.
There will likely be additional specifics to work out here, but that’s my first attempt at something that might work for you, as a starting point.
Edit: Updated for (hopefully close) API docs I found here: indigo-powerview/PowerView API.md at master · drbrain/indigo-powerview · GitHub
Edit 2: There’s an HD plugin. Maybe it’s author would be willing to incorporate the movement check and recalibration attempt?