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)