Some cleanups and add support for browsing maps

master
Michael Zanetti 2021-04-08 00:58:33 +02:00
parent 6d21e49cd5
commit 4eb24160a6
4 changed files with 147 additions and 96 deletions

View File

@ -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",

View File

@ -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)

View File

@ -0,0 +1,5 @@
TEMPLATE = aux
OTHER_FILES = integrationpluginneatobotvac.json \
integrationpluginneatobotvac.py

View File

@ -33,6 +33,7 @@ PLUGIN_DIRS = \
lifx \
mailnotification \
mqttclient \
neatobotvac \
nanoleaf \
netatmo \
networkdetector \