1
6
cr
main
local json = require("json-rw")
local http = require("socket.http")
local mime = require("mime")
local ROOMBA_SID = "urn:undertoe-us:serviceId:Roomba1"
local HAD_SID = "urn:micasaverde-com:serviceId:HaDevice1"
local CURRENT_VERSION = "1.7"
local DEFAULT_ADDRESS = "Enter IP of Roowifi"
local BatteryStatus = 0
local OriginalPoll = 0
local NormalPoll = 120
local QuickPoll = 10
local PendingCMDaction = "Nothing"
local HTTPtimeout = 10
http.TIMEOUT = HTTPtimeout
local PollLongRunning = false
local PollShortRunning = false
local GUsername = ""
local GPassword = ""
local ScheduleCleanStarted = false
local ScheduleTimeoutDef = 7200
local ScheduleLoop = 0
local PingDownCount = 0
local RoombaVars = {["Status"] = "Updating", ["MStatus"] = "", ["SStatus"] = "", ["Docked"] = false, ["T"] = -100, ["S"] = -100} -- default control state
local DEBUG = false
local MSG_CLASS = "Roomba"
local taskHandle = -1
local TASK_ERROR = 2
local TASK_ERROR_PERM = -2
local TASK_SUCCESS = 4
local TASK_BUSY = 1
local function log(text)
local id = PARENT_DEVICE or "unknown"
luup.log("Roomba Plugin #" .. id .. " " .. text)
end
local function task(text, mode)
local mode = mode or TASK_ERROR
if (mode ~= TASK_SUCCESS) then
log("task: " .. text, 50)
end
taskHandle = luup.task(text, (mode == TASK_ERROR_PERM) and TASK_ERROR or mode, MSG_CLASS, taskHandle)
end
function task_clear()
task("", TASK_SUCCESS)
end
function Roomba_Move2SysVarsIfNeeded()
local Vval = luup.variable_get(ROOMBA_SID, "Address", parentDevice)
local Aval = luup.attr_get("ip", parentDevice)
if ( (Aval == nil) and (Vval ~= nil) ) then
luup.attr_set("ip", Vval, parentDevice)
end
if ( Vval ~= nil ) then
Roomba_UpdateVar( "Address", "Not Used" )
end
end
local function Roomba_GetIP()
local aIP = luup.attr_get("ip", parentDevice)
if ( aIP == nil or aIP == "" ) then
luup.attr_set("ip", DEFAULT_ADDRESS, parentDevice)
return DEFAULT_ADDRESS
end
return aIP
end
local function Roomba_GetVar( VarName, DefaultVal )
local Gval = luup.variable_get(ROOMBA_SID, VarName, parentDevice)
if ( Gval == nil ) then
luup.variable_set(ROOMBA_SID, VarName, DefaultVal, parentDevice)
Gval = DefaultVal
end
return Gval
end
function Roomba_UpdateVar( VarName, NewValue, SID )
if (SID == nil ) then SID = ROOMBA_SID end
local Cval = luup.variable_get(SID, VarName, parentDevice)
if ( (Cval ~= nil) and (tostring(Cval) ~= tostring(NewValue) ) ) then
luup.variable_set(SID, VarName, NewValue, parentDevice)
end
end
function Roomba_Status( Stext, Level)
RoombaVars["Status"] = Stext
RoombaVars["T"] = tonumber(Level)
RoombaVars["S"] = tonumber(Level)
Roomba_UpdateVar( "CmdStatus", Stext )
Roomba_UpdateVar( "LoadlevelTarget", tonumber(Level) )
Roomba_UpdateVar( "LoadLevelStatus", tonumber(Level) )
end
local function Roomba_URLauthPrefix(User, Pass)
local addresspre = ""
if ( User ~= "" and User ~= nil) then
addresspre = User .. ":" .. Pass .."@"
GUsername = User
GPassword = Pass
end
return addresspre
end
local function RoombaPingStatus()
if(DEBUG) then log("========= RoombaPingStatus ===========") end
local PingStatus = Roomba_GetVar( "PingStatus", "down" )
-- if ( PingStatus == "down" ) then
-- return true
-- else
-- return false
-- end
return false
end
local function Rommba_HTTP_Command( CMD )
if ( RoombaPingStatus() ) then
log("FAILED: Rommba_HTTP_Command [".. CMD .."] IP is down")
return false
end
local urlstatus = luup.inet.wget("http://" .. address .. "/roomba.cgi?button=" .. CMD, HTTPtimeout, GUsername, GPassword)
if urlstatus ~= 0 then
log("FAILED: Rommba_HTTP_Command [".. CMD .."] NO Response")
return false
end
return true
end
local function Roomba_Schedule_Start()
local ScheduledClean = Roomba_GetVar( "ScheduledClean", 0 )
if ( (ScheduleCleanStarted ~= true) and (tonumber(ScheduledClean) == 1) ) then
ScheduleCleanStarted = true
-- Restart Schedule if already filled in
luup.variable_set(ROOMBA_SID, "ScheduledCleanStatus", "Nothing", parentDevice)
luup.variable_set(ROOMBA_SID, "ScheduledCleanStatus", "Started", parentDevice)
-- Init the Timeout for schedule VAR ScheduleTimeout
local STimeOut = Roomba_GetVar( "ScheduledCleanTimeOut", ScheduleTimeoutDef )
luup.call_delay ("Roomba_Schedule_Timeout", tonumber(STimeOut), "")
ScheduleLoop = 0
return true
else
return false
end
end
local function Roomba_Schedule_End( Docked, MotionStatus )
if ( (Docked) and (MotionStatus == "Dock" ) and (ScheduleCleanStarted) ) then
ScheduleCleanStarted = false
luup.variable_set(ROOMBA_SID, "ScheduledClean", 0, parentDevice)
luup.variable_set(ROOMBA_SID, "ScheduledCleanStatus", "Completed", parentDevice)
return true
end
return false
end
local function Roomba_Schedule_Interupted()
if ( ScheduleCleanStarted ) then
ScheduleCleanStarted = false
luup.variable_set(ROOMBA_SID, "ScheduledClean", 0, parentDevice)
luup.variable_set(ROOMBA_SID, "ScheduledCleanStatus", "Interupted", parentDevice)
log(" **SCHEDULED CLEAN** Event : Interupted")
return true
end
end
function Roomba_Schedule_Timeout()
log(" ========== SCHEDULE TIME OUT! =============")
if ( ScheduleCleanStarted ) then
ScheduleCleanStarted = false
luup.variable_set(ROOMBA_SID, "ScheduledClean", 0, parentDevice)
luup.variable_set(ROOMBA_SID, "ScheduledCleanStatus", "Failed", parentDevice)
log(" **SCHEDULED CLEAN** Event : Timed Out")
end
end
function Roomba_Schedule_Watcher(Docked, MotionStatus)
if(DEBUG) then log("========= Roomba_Schedule_Watcher ===========") end
local ScheduledClean = Roomba_GetVar( "ScheduledClean", 0 )
if (ScheduledClean == 0) then
return true
end
if ( ScheduleCleanStarted ) then
if ( ScheduleLoop > 3 ) then
ScheduleLoop = 4
else
ScheduleLoop = ScheduleLoop + 1
end
end
-- log("==========> Scheduled Clean [" .. ScheduledClean .. "] LOOP: [".. tostring(ScheduleLoop) .."] ===========")
if ( RoombaPingStatus() ) then
log("Can not connect: " .. address .. " Will try again later")
return true
end
if ( Roomba_Schedule_Start() ) then
log(" **SCHEDULED CLEAN** Event : Started")
return true
elseif ( ScheduleLoop > 2 ) then
if( Roomba_Schedule_End( Docked, MotionStatus ) ) then
log(" **SCHEDULED CLEAN** Event : Achieved!")
return true
end
end
return false
end
local function RoombaShortPoll()
if(DEBUG) then log("========= RoombaShortPoll ===========") end
luup.variable_set(ROOMBA_SID, "Poll", QuickPoll, parentDevice)
if( not PollShortRunning ) then
luup.call_delay ("GetRoombaDataShort", 1, "")
end
end
local function RoombaShortPollReset()
if(DEBUG) then log("========= RoombaShortPollReset ===========") end
PendingCMDaction = "Nothing"
luup.variable_set(ROOMBA_SID, "Poll", NormalPoll, parentDevice)
return Poll
end
local function Roomba_Init_Vars()
local Address = Roomba_GetIP()
local Username = Roomba_GetVar("Username", "admin" )
local Password = Roomba_GetVar("Password", "roombawifi" )
local Poll = Roomba_GetVar( "Poll", NormalPoll )
local CmdStatus = Roomba_GetVar( "CmdStatus", "Device Setup")
local SimpleStatus = Roomba_GetVar( "SimpleStatus", "")
Roomba_GetVar( "ScheduledClean", 0 )
Roomba_GetVar( "ScheduledCleanStatus", "Nothing" )
Roomba_GetVar( "ScheduledCleanTimeOut", ScheduleTimeoutDef )
Roomba_URLauthPrefix(Username, Password)
end
local function readRoombaSettings(parentDevice)
if(DEBUG) then log("========= readRoombaSettings ===========") end
local Address = Roomba_GetIP()
local Username = Roomba_GetVar("Username", "admin" )
local Password = Roomba_GetVar("Password", "roombawifi" )
local Poll = Roomba_GetVar( "Poll", NormalPoll )
local CmdStatus = Roomba_GetVar( "CmdStatus", "Device Setup")
local SimpleStatus = Roomba_GetVar( "SimpleStatus", "")
Roomba_GetVar( "ScheduledClean", 0 )
Roomba_GetVar( "ScheduledCleanStatus", "Nothing" )
Roomba_GetVar( "ScheduledCleanTimeOut", ScheduleTimeoutDef )
Roomba_URLauthPrefix(Username, Password)
return Address, Poll, addresspre
end
local function RoombaRobotStatus()
if(DEBUG) then log("========= RoombaRobotStatus ===========") end
local address, Poll, addresspre = readRoombaSettings(parentDevice)
local PrevSimpStatus = luup.variable_get(ROOMBA_SID, "SimpleStatus", parentDevice)
if ( RoombaPingStatus() ) then
log("Can not connect: " .. address .. " Will try again in " .. Poll .. " seconds")
return true
end
-- Connect to Robot and read JSON
addresspre = Roomba_URLauthPrefix(GUsername, GPassword)
local status, result = luup.inet.wget("http://".. addresspre .. address .."/roomba.json", HTTPtimeout, GUsername, GPassword)
if (status == 0) then
local data = json.decode(result)
local ChargeStatus = tostring( tonumber(data.response.r14.value, 10) or 0 )
local CurrentValue = (data.response.r16.value * -1)
local CliffSensor = (data.response.r4.value + data.response.r5.value)
local Charge = data.response.r18.value
local Capacity = data.response.r19.value
BatteryStatus = math.floor( tonumber( (Charge / Capacity) * 100 ) ) or 1
Roomba_UpdateVar( "BatteryLevel", BatteryStatus, HAD_SID )
if(DEBUG) then log(" == Current: " .. CurrentValue .. " | Charge Status: " .. ChargeStatus .. " | Cliff : " .. CliffSensor) end
if ( CliffSensor >= 2) then
-- Robot is docked
RoombaVars = {["Status"] = "Docked", ["MStatus"] = "Dock", ["SStatus"] = "Docked", ["Docked"] = true, ["T"] = 0, ["S"] = 0}
if ( ChargeStatus == "1" ) then
RoombaVars = {["Status"] = "Charging??", ["MStatus"] = "Dock", ["SStatus"] = "Charging Error", ["Docked"] = true, ["T"] = -75, ["S"] = -75}
elseif ( ChargeStatus == "2" ) then
RoombaVars = {["Status"] = "Charging", ["MStatus"] = "Dock", ["SStatus"] = "Charging", ["Docked"] = true, ["T"] = 0, ["S"] = 0}
elseif ( ChargeStatus == "3" ) then
RoombaVars = {["Status"] = "Trickle Charge", ["MStatus"] = "Dock", ["SStatus"] = "Charging", ["Docked"] = true, ["T"] = -50, ["S"] = -50}
elseif ( ChargeStatus == "4" ) then
RoombaVars = {["Status"] = "Docked", ["MStatus"] = "Dock", ["SStatus"] = "Charging", ["Docked"] = true, ["T"] = -25, ["S"] = -25}
end
elseif ( CurrentValue > 200 ) then
RoombaVars = {["Status"] = "Cleaning", ["MStatus"] = "Clean", ["SStatus"] = "Cleaning", ["Docked"] = false, ["T"] = 100, ["S"] = 100}
if ( PendingCMDaction == "Dock") then
RoombaVars = {["Status"] = "Docking..", ["MStatus"] = "Clean", ["SStatus"] = "Cleaning", ["Docked"] = false, ["T"] = 50, ["S"] = 50}
end
else
RoombaVars = {["Status"] = "Paused", ["MStatus"] = "Pause", ["SStatus"] = "Paused", ["Docked"] = false, ["T"] = 125, ["S"] = 125}
if ( PendingCMDaction == "Dock") then
RoombaVars = {["Status"] = "Docking..", ["MStatus"] = "Clean", ["SStatus"] = "Cleaning", ["Docked"] = false, ["T"] = 50, ["S"] = 50}
elseif ( (PendingCMDaction == "Clean") and ( (PrevSimpStatus == "Docked") or (PrevSimpStatus == "Charging") ) ) then
RoombaVars = {["Status"] = "Undocking", ["MStatus"] = "Clean", ["SStatus"] = "Cleaning", ["Docked"] = false, ["T"] = 25, ["S"] = 25}
end
end
-- Set Simple Status
Roomba_UpdateVar( "SimpleStatus", RoombaVars["SStatus"] )
-- Handles Start and Stopping Scheduled Clean Triggers
Roomba_Schedule_Watcher(RoombaVars["Docked"], RoombaVars["MStatus"])
-- Handle Pending Commands
if ( PendingCMDaction == RoombaVars["MStatus"]) then
if( DEBUG) then log("=== > PENDING CMD ACHIVED! ".. PendingCMDaction .. " Back to Normal Poll: " .. OriginalPoll ) end
RoombaShortPollReset()
else
if( DEBUG) then log(" === > PENDING CMD NO GOOD [".. RoombaVars["MStatus"] .." != ".. PendingCMDaction .."]") end
end
return RoombaVars["Docked"], RoombaVars["MStatus"], RoombaVars["Status"], RoombaVars["T"], RoombaVars["S"]
else
if ( status == 401 ) then
task( "Invalid Username or Password")
end
log("JSON URL fetch error [".. status .."]")
return false
end
end
function GetRoombaData()
if( DEBUG) then log("========= GetRoombaData ===========") end
local address, Poll, addresspre = readRoombaSettings(parentDevice)
if( tostring(Poll) == tostring(QuickPoll) ) then
luup.call_delay ("GetRoombaData", 120, "")
return true
end
local Docked, MotionStatus, StatusTxt, TargetLevel, StatusLevel = RoombaRobotStatus()
Roomba_UpdateVar( "CmdStatus", StatusTxt )
Roomba_UpdateVar( "LoadlevelTarget", TargetLevel )
Roomba_UpdateVar( "LoadLevelStatus", StatusLevel )
Roomba_GetVar( "800dblstart", 0 )
luup.call_delay ("GetRoombaData", tonumber(Poll), "")
end
function GetRoombaDataShort()
if( DEBUG) then log("========= GetRoombaDataShort ===========") end
local address, Poll, addresspre = readRoombaSettings(parentDevice)
if( tostring(Poll) ~= tostring(QuickPoll) ) then
return true
end
PollLongRunning = true
local Docked, MotionStatus, StatusTxt, TargetLevel, StatusLevel = RoombaRobotStatus()
Roomba_UpdateVar( "CmdStatus", StatusTxt )
Roomba_UpdateVar( "LoadlevelTarget", TargetLevel )
Roomba_UpdateVar( "LoadLevelStatus", StatusLevel )
luup.call_delay ("GetRoombaDataShort", tonumber(Poll), "")
end
function RoombaUp()
if(DEBUG) then log("========= RoombaUp ===========") end
local PingStatus = Roomba_GetVar( "PingStatus", "down" )
local address = Roomba_GetIP()
addresspre = Roomba_URLauthPrefix(GUsername, GPassword)
local urlstatus, result = luup.inet.wget("http://".. addresspre .. address .."/index.html", HTTPtimeout, GUsername, GPassword)
if ( urlstatus == 0 ) then
log("HTTP Ping Not Responding")
end
pingresponse = os.execute("ping -c 1 " .. address)
if ( pingresponse == 0 ) then
task_clear()
Roomba_UpdateVar( "PingStatus", "up" )
if( DEBUG) then log("Ping reply ") end
PingDownCount = 0
luup.call_delay("RoombaUp", 120, "")
return true
else
PingDownCount = PingDownCount + 1
if( PingDownCount > 3 ) then
Roomba_UpdateVar( "PingStatus", "down" )
task( luup.attr_get("name", parentDevice) .. " IP Down")
elseif (PingDownCount > 0) then
task( luup.attr_get("name", parentDevice) .. " Trouble Communicating")
end
log("No ping reply : " .. address .." PingDownCount : " .. tostring(PingDownCount) )
if ( PingDownCount > 6) then
luup.call_delay("RoombaUp", 60, "")
elseif ( PingDownCount > 12 ) then
luup.call_delay("RoombaUp", 120, "")
PingDownCount = 15
else
luup.call_delay("RoombaUp", 20, "")
end
return false
end
end
local function Roomba_Issue( Cmd )
local address, Poll, addresspre = readRoombaSettings(parentDevice)
luup.inet.wget("http://" .. address .. "/roomba.cgi?button=".. Cmd , HTTPtimeout, GUsername, GPassword)
end
local function RoombaClean()
if( DEBUG) then log(" ====== RoombaClean ===========") end
local address, Poll, addresspre = readRoombaSettings(parentDevice)
local Docked, MotionStatus, StatusTxt = RoombaRobotStatus()
local DblStart = Roomba_GetVar( "800dblstart", 0 )
if( MotionStatus == "Clean") then
PendingCMDaction = "Pause"
Roomba_Schedule_Interupted()
else
PendingCMDaction = "Clean"
end
luup.inet.wget("http://" .. address .. "/roomba.cgi?button=CLEAN", HTTPtimeout, GUsername, GPassword)
if( ( tonumber(DblStart) == 1) and (PendingCMDaction == "Clean") ) then
if( DEBUG) then log(" ====== Double CLean Sent ===========") end
luup.sleep(1000)
luup.inet.wget("http://" .. address .. "/roomba.cgi?button=CLEAN", HTTPtimeout, GUsername, GPassword)
end
RoombaShortPoll()
end
local function RoombaDock()
if( DEBUG) then log(" ====== RoombaDock ===========") end
local address, Poll, addresspre = readRoombaSettings(parentDevice)
local Docked, MotionStatus, StatusTxt = RoombaRobotStatus()
if (Docked) then
-- Just for shits and giggles
PendingCMDaction = "Dock"
RoombaShortPoll()
return true
end
if ( MotionStatus ~= "Pause") then
luup.inet.wget("http://" .. address .. "/roomba.cgi?button=CLEAN", HTTPtimeout, GUsername, GPassword )
luup.sleep(2500)
end
luup.inet.wget("http://" .. address .. "/roomba.cgi?button=DOCK", HTTPtimeout, GUsername, GPassword )
PendingCMDaction = "Dock"
Roomba_Schedule_Interupted()
RoombaShortPoll()
end
function main(parentDevice)
PARENT_DEVICE = parentDevice
MSG_CLASS = MSG_CLASS .. '-' .. parentDevice
log("VERSION " .. CURRENT_VERSION .. " starting up..")
Roomba_Move2SysVarsIfNeeded()
if( DEBUG) then log("DEBUG : ENABLED!") end
luup.variable_set(HAD_SID, "LastUpdate", os.time(os.date('*t')), parentDevice)
luup.variable_set(HAD_SID, "Configured", "1", parentDevice)
Roomba_Init_Vars()
local address = Roomba_GetIP()
if ( (address == nil) or (address == DEFAULT_ADDRESS) ) then
if( DEBUG) then log("could not be started.") end
Roomba_Status( "Set your IP", -100, -100)
task("Please specify your Roomba IP")
luup.set_failure(true, parentDevice)
return false
else
Roomba_UpdateVar( "CmdStatus", "Updating" )
Roomba_Status( "Updating", -100, -100)
end
local CurrentNPoll = Roomba_GetVar( "Poll", NormalPoll )
if ( (tonumber(CurrentNPoll) >= 30) ~= true ) then
Roomba_UpdateVar( "Poll", "30" )
log('Polling set to low defaulting to 30')
end
luup.call_delay ("GetRoombaData", 1, "")
luup.call_delay("RoombaUp", 1, "")
return true
end
urn:undertoe-us:serviceId:Roomba1
Clean
RoombaClean()
urn:undertoe-us:serviceId:Roomba1
Dock
RoombaDock()
urn:undertoe-us:serviceId:Roomba1
GetAddress
luup.attr_get(ROOMBA_SID, "ip", parentDevice)
urn:undertoe-us:serviceId:Roomba1
SetAddress
luup.attr_set(ROOMBA_SID, "ip", lul_settings.newAddressValue, parentDevice)