Some cleanups and add support for browsing maps
parent
6d21e49cd5
commit
4eb24160a6
|
|
@ -48,6 +48,8 @@
|
|||
"name": "robot",
|
||||
"displayName": "Neato robot",
|
||||
"createMethods": ["auto"],
|
||||
"interfaces":["cleaningrobot", "battery", "connectable"],
|
||||
"browsable": true,
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "def9a4bb-7a7e-4e3a-a63c-c55a105abb5e",
|
||||
|
|
@ -60,16 +62,7 @@
|
|||
"name": "secret",
|
||||
"displayName": "Secret",
|
||||
"type": "QString"
|
||||
},
|
||||
{
|
||||
"id": "141f0d98-1806-432c-aaac-c0d3a89a8e58",
|
||||
"name": "mapId",
|
||||
"displayName": "Map ID",
|
||||
"type": "QString"
|
||||
}
|
||||
],
|
||||
"interfaces":[
|
||||
|
||||
],
|
||||
"settingsTypes": [
|
||||
{
|
||||
|
|
@ -95,14 +88,22 @@
|
|||
}
|
||||
],
|
||||
"stateTypes":[
|
||||
{
|
||||
"id": "4c319e4b-9206-48ed-9d24-56a536c58d61",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "dce4f7f3-a0a6-46bb-9216-c9089d9e9b0d",
|
||||
"name": "cleaning",
|
||||
"displayName": "Cleaning",
|
||||
"displayNameEvent": "Cleaning yes/no",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "0f925abf-396c-437e-b259-2fed7eafe7f4",
|
||||
|
|
@ -110,8 +111,7 @@
|
|||
"displayName": "Paused",
|
||||
"displayNameEvent": "Cleaning paused yes/no",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "1b8abd35-8276-44ba-8c75-a647877b2e11",
|
||||
|
|
@ -119,8 +119,7 @@
|
|||
"displayName": "Charging",
|
||||
"displayNameEvent": "Robot charging yes/no",
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"cached": false
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "805175ec-c2e4-4fbe-9505-282750ef1467",
|
||||
|
|
@ -128,8 +127,15 @@
|
|||
"displayName": "Docked",
|
||||
"displayNameEvent": "Robot docked yes/no",
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"cached": false
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "1985ce98-f387-47e0-a5f3-9b807f532ca1",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery critical",
|
||||
"displayNameEvent": "Battery entered or left critical state",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "20ed8767-806f-4ec2-8626-842cd398f9df",
|
||||
|
|
@ -137,10 +143,10 @@
|
|||
"displayName": "Battery level",
|
||||
"displayNameEvent": "Battery level percentage",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"cached": false
|
||||
"maxValue": 100
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
|
|
@ -151,8 +157,8 @@
|
|||
},
|
||||
{
|
||||
"id": "5178a803-5696-4ee1-80a4-2c7c20a5043a",
|
||||
"name": "goToBase",
|
||||
"displayName": "Go to base"
|
||||
"name": "returnToBase",
|
||||
"displayName": "Return to base"
|
||||
},
|
||||
{
|
||||
"id": "30775042-55a7-4f1b-9042-a9bdeadc4b0d",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import json
|
|||
|
||||
# pybotvac library: https://github.com/stianaske/pybotvac
|
||||
|
||||
thingsAndRobots = {}
|
||||
oauthSessions = {}
|
||||
accountsMap = {}
|
||||
thingsAndRobots = {}
|
||||
|
||||
pollTimer = None
|
||||
|
||||
|
|
@ -30,9 +31,12 @@ def confirmPairing(info, username, secret):
|
|||
del oauthSessions[info.transactionId]
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
|
||||
|
||||
def setupThing(info):
|
||||
# Setup for the account
|
||||
if info.thing.thingClassId == accountThingClassId:
|
||||
logger.log("SetupThing for account:", info.thing.name)
|
||||
|
||||
pluginStorage().beginGroup(info.thing.id)
|
||||
token = json.loads(pluginStorage().value("token"))
|
||||
logger.log("setup", token)
|
||||
|
|
@ -53,102 +57,83 @@ def setupThing(info):
|
|||
|
||||
# Create an account session on the session to get info about the login
|
||||
account = Account(oAuthSession)
|
||||
accountsMap[info.thing] = account
|
||||
|
||||
# List all robots associated with account
|
||||
logger.log("account created. Robots:", account.robots);
|
||||
|
||||
logger.log("Persistent maps: ", account.persistent_maps)
|
||||
mapDict = account.persistent_maps
|
||||
mapKeys = mapDict.keys()
|
||||
logger.log("Keys: ", mapKeys)
|
||||
mapValues = mapDict.values()
|
||||
logger.log("Values: ", mapValues)
|
||||
|
||||
thingDescriptors = []
|
||||
for robot in account.robots:
|
||||
logger.log(robot)
|
||||
logger.log("Found new robot:", robot.serial)
|
||||
# Check if this robot is already added in nymea
|
||||
found = False
|
||||
for thing in myThings():
|
||||
if thing.paramValue(robotThingSerialParamTypeId) == robot.serial:
|
||||
logger.log("Comparing to existing robot:", thing.name)
|
||||
if thing.thingClassId == robotThingClassId and thing.paramValue(robotThingSerialParamTypeId) == robot.serial:
|
||||
logger.log("Already have this robot in the system")
|
||||
# Yep, already here... skip it
|
||||
found = True
|
||||
continue
|
||||
break
|
||||
if found:
|
||||
continue
|
||||
|
||||
thingDescriptor = nymea.ThingDescriptor(robotThingClassId, robot.name)
|
||||
logger.log("MapID for Serial: ", robot.serial, mapDict[robot.serial])
|
||||
mapInfo = mapDict[robot.serial]
|
||||
logger.log("Type mapInfo: ", type(mapInfo))
|
||||
logger.log("Contents mapInfo: ", mapInfo)
|
||||
mapInfo2 = mapInfo[0]
|
||||
logger.log("MapInfo2 type: ", type(mapInfo2), " MapInfo2 contents: ", mapInfo2)
|
||||
mapId = mapInfo2['id']
|
||||
logger.log("MapId type: ", type(mapId), " MapId contents: ", mapId)
|
||||
mapName = mapInfo2['name']
|
||||
logger.log("MapName type: ", type(mapName), " MapName contents: ", mapName)
|
||||
rbtMapBound = robot.get_map_boundaries(mapId)
|
||||
logger.log("rbtMapBound Type: ", type(rbtMapBound), "rbtMapBound Contents: ", rbtMapBound)
|
||||
rbtBoundData = rbtMapBound.text
|
||||
logger.log("rbtBoundData Type: ", type(rbtBoundData), "rbtBoundData Contents: ", rbtBoundData)
|
||||
logger.log("Adding new robot to the system with parent", info.thing.id)
|
||||
thingDescriptor = nymea.ThingDescriptor(robotThingClassId, robot.name, parentId=info.thing.id)
|
||||
thingDescriptor.params = [
|
||||
nymea.Param(robotThingSerialParamTypeId, robot.serial),
|
||||
nymea.Param(robotThingSecretParamTypeId, robot.secret),
|
||||
nymea.Param(robotThingMapIdParamTypeId, mapId)
|
||||
nymea.Param(robotThingSecretParamTypeId, robot.secret)
|
||||
]
|
||||
thingDescriptors.append(thingDescriptor)
|
||||
|
||||
# And let nymea know about all the users robots
|
||||
autoThingsAppeared(thingDescriptors)
|
||||
# return
|
||||
|
||||
# If no poll timer is set up yet, start it now
|
||||
logger.log("Creating polltimer")
|
||||
global pollTimer
|
||||
if pollTimer is not None:
|
||||
pollTimer = threading.Timer(5, pollService)
|
||||
pollTimer.start()
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
# Setup for the robots
|
||||
if info.thing.thingClassId == robotThingClassId:
|
||||
logger.log("SetupThing for robot:", info.thing.name)
|
||||
|
||||
serial = info.thing.paramValue(robotThingSerialParamTypeId)
|
||||
secret = info.thing.paramValue(robotThingSecretParamTypeId)
|
||||
robot = Robot(serial, secret, info.thing.name)
|
||||
thingsAndRobots[info.thing] = robot;
|
||||
try:
|
||||
refreshRobot(info.thing)
|
||||
except:
|
||||
logger.warn("Error getting robot state");
|
||||
info.finish(nymea.ThingErrorHardwareFailure, "Unable to connect to neato API.")
|
||||
return;
|
||||
|
||||
logger.log(robot.get_robot_state())
|
||||
info.thing.setStateValue(robotConnectedStateTypeId, True)
|
||||
# set up polling for robot status
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
|
||||
|
||||
def pollService():
|
||||
logger.log("pollService!!!")
|
||||
|
||||
# Poll all robots we know
|
||||
for thing in myThings():
|
||||
if thing.thingClassId == robotThingClassId:
|
||||
def refreshRobot(thing):
|
||||
robot = thingsAndRobots[thing]
|
||||
logger.log("polling robot:", robot)
|
||||
logger.log("Refreshing robot:", robot)
|
||||
|
||||
# Get robot state
|
||||
rbtState = thingsAndRobots[thing].get_robot_state()
|
||||
rbtStateJson = rbtState.json()
|
||||
|
||||
logger.log("Robot state for %s: %s" % (thing.name, rbtStateJson))
|
||||
|
||||
# Set robot docked/charging state
|
||||
rbtStateDetails = rbtStateJson['details']
|
||||
rbtCharging = rbtStateDetails['isCharging']
|
||||
rbtDocked = rbtStateDetails['isDocked']
|
||||
rbtStateOfCharge = rbtStateDetails['charge']
|
||||
logger.log("Updating thing", thing.name, "Charging", rbtCharging)
|
||||
thing.setStateValue(robotChargingStateTypeId, rbtCharging)
|
||||
logger.log("Updating thing", thing.name, "Docked", rbtDocked)
|
||||
thing.setStateValue(robotDockedStateTypeId, rbtDocked)
|
||||
logger.log("Updating thing", thing.name, "Battery Charge Level", rbtStateOfCharge)
|
||||
thing.setStateValue(robotBatteryLevelStateTypeId, rbtStateOfCharge)
|
||||
thing.setStateValue(robotChargingStateTypeId, rbtStateDetails['isCharging'])
|
||||
thing.setStateValue(robotDockedStateTypeId, rbtStateDetails['isDocked'])
|
||||
thing.setStateValue(robotBatteryLevelStateTypeId, rbtStateDetails['charge'])
|
||||
|
||||
# Set robot cleaning/paused state
|
||||
rbtStateCommands = rbtStateJson['availableCommands']
|
||||
|
|
@ -156,18 +141,25 @@ def pollService():
|
|||
rbtPauseAv = rbtStateCommands['pause']
|
||||
rbtResumeAv = rbtStateCommands['resume']
|
||||
if rbtStartAv == True:
|
||||
logger.log("Updating thing", thing.name, "Cleaning: False")
|
||||
thing.setStateValue(robotCleaningStateTypeId, False)
|
||||
thing.setStateValue(robotPausedStateTypeId, False)
|
||||
elif rbtPauseAv == True:
|
||||
logger.log("Updating thing", thing.name, "Cleaning: True")
|
||||
thing.setStateValue(robotCleaningStateTypeId, True)
|
||||
thing.setStateValue(robotPausedStateTypeId, False)
|
||||
elif rbtResumeAv == True:
|
||||
logger.log("Updating thing", thing.name, "Paused: True")
|
||||
thing.setStateValue(robotCleaningStateTypeId, True)
|
||||
thing.setStateValue(robotPausedStateTypeId, True)
|
||||
|
||||
|
||||
def pollService():
|
||||
# Poll all robots we know
|
||||
for thing in myThings():
|
||||
if thing.thingClassId == robotThingClassId:
|
||||
try:
|
||||
refreshRobot(thing)
|
||||
except:
|
||||
logger.warn("Error refreshing robot state")
|
||||
|
||||
# restart the timer for next poll
|
||||
global pollTimer
|
||||
pollTimer = threading.Timer(60, pollService)
|
||||
|
|
@ -190,18 +182,18 @@ def executeAction(info):
|
|||
thingsAndRobots[info.thing].pause_cleaning()
|
||||
elif rbtResumeAv == True:
|
||||
thingsAndRobots[info.thing].resume_cleaning()
|
||||
threading.Timer(5, pollService).start()
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
if info.actionTypeId == robotGoToBaseActionTypeId:
|
||||
if info.actionTypeId == robotReturnToBaseActionTypeId:
|
||||
thingsAndRobots[info.thing].send_to_base()
|
||||
threading.Timer(5, pollService).start()
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
|
||||
if info.actionTypeId == robotStopCleaningActionTypeId:
|
||||
thingsAndRobots[info.thing].stop_cleaning()
|
||||
threading.Timer(5, pollService).start()
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
|
||||
# To do: get available boundaries to use with start_cleaning action
|
||||
|
|
@ -211,7 +203,54 @@ def executeAction(info):
|
|||
logger.log("rbtMapBound Type: ", type(rbtMapBound), "rbtMapBound Contents: ", rbtMapBound)
|
||||
rbtBoundData = rbtMapBound.text
|
||||
logger.log("rbtBoundData Type: ", type(rbtBoundData), "rbtBoundData Contents: ", rbtBoundData)
|
||||
threading.Timer(5, pollService).start()
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
|
||||
|
||||
def browseThing(browseResult):
|
||||
robotThing = browseResult.thing
|
||||
robot = thingsAndRobots[browseResult.thing]
|
||||
account = None
|
||||
for thing in myThings():
|
||||
logger.log("checking thing", thing.name, thing.id, robotThing.parentId)
|
||||
if thing.id == robotThing.parentId:
|
||||
account = accountsMap[thing]
|
||||
break
|
||||
if account is None:
|
||||
logger.warn("Cannot find account for robot", robotThing.name)
|
||||
browseResult.finish(nymea.ThingErrorAuthenticationFailure)
|
||||
return;
|
||||
|
||||
|
||||
# Top level entries -> return maps
|
||||
if browseResult.itemId == "":
|
||||
maps = account.persistent_maps
|
||||
|
||||
logger.log("maps", tpye(maps), maps)
|
||||
for map in maps[robot.serial]:
|
||||
logger.log("Type mapInfo: ", type(map))
|
||||
logger.log("map:", map)
|
||||
browseResult.addItem(nymea.BrowserItem("map-" + map["id"], map["name"], browsable=True, thumbnail=map["url"]))
|
||||
|
||||
browseResult.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
# browsing boundaries for a map
|
||||
if browseResult.itemId.startswith("map-"):
|
||||
mapId = browseResult.itemId[4:]
|
||||
boundaries = robot.get_map_boundaries(mapId)
|
||||
|
||||
logger.log("boundaries", type(boundaries), boundaries.json())
|
||||
for boundary in boundaries.json()["data"]["boundaries"]:
|
||||
browseResult.addItem(nymea.BrowserItem(boundary["id"], boundary["name"], boundary["type"], executable=True, disabled=not boundary["enabled"], icon=nymea.BrowserIconFavorites))
|
||||
|
||||
browseResult.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
|
||||
def executeBrowserItem(info):
|
||||
# TODO: An item in the browser has been clicked.
|
||||
logger.log("Browser item clicked:", info.itemId)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
TEMPLATE = aux
|
||||
|
||||
OTHER_FILES = integrationpluginneatobotvac.json \
|
||||
integrationpluginneatobotvac.py
|
||||
|
||||
|
|
@ -33,6 +33,7 @@ PLUGIN_DIRS = \
|
|||
lifx \
|
||||
mailnotification \
|
||||
mqttclient \
|
||||
neatobotvac \
|
||||
nanoleaf \
|
||||
netatmo \
|
||||
networkdetector \
|
||||
|
|
|
|||
Loading…
Reference in New Issue