Merge PR #407: New plugin: Neato botvac
commit
ba5c391cb6
|
|
@ -480,6 +480,22 @@ Description: nymea.io plugin for a generic MQTT client
|
|||
This package will install a generic MQTT client plugin for nymea.io
|
||||
|
||||
|
||||
Package: nymea-plugin-neatobotvac
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
nymea-plugins-translations,
|
||||
python3-pip,
|
||||
Description: nymea.io plugin for neato
|
||||
The nymea daemon is a plugin based IoT (Internet of Things) server. The
|
||||
server works like a translator for devices, things and services and
|
||||
allows them to interact.
|
||||
With the powerful rule engine you are able to connect any device available
|
||||
in the system and create individual scenes and behaviors for your environment.
|
||||
.
|
||||
This package will install the nymea.io plugin for Neato Botvac robots.
|
||||
|
||||
|
||||
Package: nymea-plugin-netatmo
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
|
|
@ -1163,6 +1179,7 @@ Depends: nymea-plugin-anel,
|
|||
nymea-plugin-texasinstruments,
|
||||
nymea-plugin-telegram,
|
||||
nymea-plugin-nanoleaf,
|
||||
nymea-plugin-neatobotvac,
|
||||
nymea-plugin-netatmo,
|
||||
nymea-plugin-networkdetector,
|
||||
nymea-plugin-nuki,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
neatobotvac/integrationpluginneatobotvac.json usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/neatobotvac/
|
||||
neatobotvac/integrationpluginneatobotvac.py usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/neatobotvac/
|
||||
neatobotvac/requirements.txt usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/neatobotvac/
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Neato Botvac
|
||||
|
||||
This plugin allows to interact with your Neato Botvac cleaning robots. Each robot linkd to your account will appear automatically in the system, once the account is added to nymea.
|
||||
|
||||
## Supported Things
|
||||
|
||||
* Neato D7 (Tested)
|
||||
* Other Neato Dx should also work, but haven't been tested
|
||||
|
||||
## Requirements
|
||||
|
||||
* The robot must be added to a personal Neato account, that can be created via the Neato Robotics website.
|
||||
* The package “nymea-plugin-neatobotvac” must be installed
|
||||
|
||||
## More
|
||||
|
||||
[Neato Robotics](https://neatorobotics.com/)
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
{
|
||||
"name": "neato",
|
||||
"displayName": "Neato",
|
||||
"id": "4f6ecb6f-a7fe-4fdb-b8d8-45b1f235110c",
|
||||
"apiKeys": ["neato"],
|
||||
"vendors": [
|
||||
{
|
||||
"name": "neato",
|
||||
"displayName": "Neato Robotics",
|
||||
"id": "d2a234a5-0aeb-4c04-98d5-6428cd266433",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "fe594fb0-b712-4f23-8267-649eb459747b",
|
||||
"name": "account",
|
||||
"displayName": "Neato account",
|
||||
"createMethods": ["User"],
|
||||
"interfaces": ["account"],
|
||||
"setupMethod": "oauth",
|
||||
"settingsTypes": [
|
||||
|
||||
],
|
||||
"stateTypes":[
|
||||
{
|
||||
"id": "e8f47781-e3fd-416f-a9ac-51ef942d0573",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected/disconnected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "b0db7079-49f0-444a-9c55-4bb4c764f3cb",
|
||||
"name": "loggedIn",
|
||||
"displayName": "Logged in",
|
||||
"displayNameEvent": "Logged in or out",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b924c87a-f783-4f45-a3af-929684c24aea",
|
||||
"name": "robot",
|
||||
"displayName": "Neato robot",
|
||||
"createMethods": ["auto"],
|
||||
"interfaces":["cleaningrobot", "battery", "connectable"],
|
||||
"browsable": true,
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "def9a4bb-7a7e-4e3a-a63c-c55a105abb5e",
|
||||
"name": "serial",
|
||||
"displayName": "Robot Serial",
|
||||
"type": "QString"
|
||||
},
|
||||
{
|
||||
"id": "3793e48b-043e-43cb-b672-7c1e2e90bc8e",
|
||||
"name": "secret",
|
||||
"displayName": "Secret",
|
||||
"type": "QString"
|
||||
}
|
||||
],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "dabaafd3-908f-4f06-8039-5a7a729346da",
|
||||
"name": "eco",
|
||||
"displayName": "Eco",
|
||||
"type": "bool",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "86694abb-5633-4e62-bd6c-325eb246c683",
|
||||
"name": "care",
|
||||
"displayName": "Extra Care",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "f72bcfbd-a262-44b3-ad75-9bb094aa2bb1",
|
||||
"name": "noGoLines",
|
||||
"displayName": "No-go Lines Enabled",
|
||||
"type": "bool",
|
||||
"defaultValue": true
|
||||
}
|
||||
],
|
||||
"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": "robotState",
|
||||
"displayName": "Cleaning state",
|
||||
"displayNameEvent": "Cleaning state changed",
|
||||
"type": "QString",
|
||||
"possibleValues": ["docked", "cleaning", "paused", "traveling", "stopped", "error"],
|
||||
"defaultValue": "docked"
|
||||
},
|
||||
{
|
||||
"id": "cb22b48c-1c21-4d52-bde6-847287435685",
|
||||
"name": "errorMessage",
|
||||
"displayName": "Error message",
|
||||
"displayNameEvent": "Error message changes",
|
||||
"type": "QString",
|
||||
"defaultValue": "no error"
|
||||
},
|
||||
{
|
||||
"id": "1b8abd35-8276-44ba-8c75-a647877b2e11",
|
||||
"name": "charging",
|
||||
"displayName": "Charging",
|
||||
"displayNameEvent": "Started or stopped charging",
|
||||
"type": "bool",
|
||||
"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",
|
||||
"name": "batteryLevel",
|
||||
"displayName": "Battery level",
|
||||
"displayNameEvent": "Battery level changed",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 100
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "1f774998-5fa7-4e3b-8ab0-a8402dd561bb",
|
||||
"name": "startCleaning",
|
||||
"displayName": "Start cleaning"
|
||||
},
|
||||
{
|
||||
"id": "e731faa6-88c9-406d-b505-f89b5f0868b0",
|
||||
"name": "pauseCleaning",
|
||||
"displayName": "Pause/resume cleaning"
|
||||
},
|
||||
{
|
||||
"id": "5178a803-5696-4ee1-80a4-2c7c20a5043a",
|
||||
"name": "returnToBase",
|
||||
"displayName": "Return to base"
|
||||
},
|
||||
{
|
||||
"id": "30775042-55a7-4f1b-9042-a9bdeadc4b0d",
|
||||
"name": "stopCleaning",
|
||||
"displayName": "Stop cleaning"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
import nymea
|
||||
import time
|
||||
import threading
|
||||
from pybotvac import Account, Neato, OAuthSession, PasswordlessSession, PasswordSession, Vorwerk, Robot
|
||||
import json
|
||||
|
||||
# pybotvac library: https://github.com/stianaske/pybotvac
|
||||
|
||||
oauthSessions = {}
|
||||
accountsMap = {}
|
||||
thingsAndRobots = {}
|
||||
|
||||
pollTimer = None
|
||||
|
||||
def startPairing(info):
|
||||
# Start OAuth2 session
|
||||
apiKey = apiKeyStorage().requestKey("neato")
|
||||
oauthSession = OAuthSession(client_id=apiKey.data("clientId"), client_secret=apiKey.data("clientSecret"), redirect_uri="https://127.0.0.1:8888", vendor=Neato())
|
||||
oauthSessions[info.transactionId] = oauthSession;
|
||||
authorizationUrl = oauthSession.get_authorization_url()
|
||||
info.oAuthUrl = authorizationUrl
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
|
||||
|
||||
def confirmPairing(info, username, secret):
|
||||
# The user has successfully logged in at neato. Obtain the token from the OAuth session
|
||||
token = oauthSessions[info.transactionId].fetch_token(secret)
|
||||
pluginStorage().beginGroup(info.thingId)
|
||||
pluginStorage().setValue("token", json.dumps(token))
|
||||
pluginStorage().endGroup();
|
||||
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)
|
||||
pluginStorage().endGroup();
|
||||
|
||||
try:
|
||||
oAuthSession = OAuthSession(token=token)
|
||||
# Login went well, finish the setup
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
except Exception as e:
|
||||
# Login error
|
||||
logger.warn("Error setting up neato account:", str(e))
|
||||
info.finish(nymea.ThingErrorAuthenticationFailure, str(e))
|
||||
return
|
||||
|
||||
# Mark the account as logged-in and connected
|
||||
info.thing.setStateValue(accountLoggedInStateTypeId, True)
|
||||
info.thing.setStateValue(accountConnectedStateTypeId, True)
|
||||
|
||||
# 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);
|
||||
|
||||
thingDescriptors = []
|
||||
for robot in account.robots:
|
||||
logger.log("Found new robot:", robot.serial)
|
||||
# Check if this robot is already added in nymea
|
||||
found = False
|
||||
for thing in myThings():
|
||||
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
|
||||
break
|
||||
if found:
|
||||
continue
|
||||
|
||||
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)
|
||||
]
|
||||
thingDescriptors.append(thingDescriptor)
|
||||
|
||||
# And let nymea know about all the users robots
|
||||
autoThingsAppeared(thingDescriptors)
|
||||
|
||||
# If no poll timer is set up yet, start it now
|
||||
logger.log("Creating polltimer")
|
||||
global pollTimer
|
||||
if pollTimer is None:
|
||||
pollTimer = threading.Timer(5, pollService)
|
||||
pollTimer.start()
|
||||
|
||||
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)
|
||||
try:
|
||||
robot = Robot(serial, secret, info.thing.name)
|
||||
thingsAndRobots[info.thing] = robot;
|
||||
refreshRobot(info.thing)
|
||||
except Exception as e:
|
||||
logger.warn("Error setting up robot:", e)
|
||||
info.finish(nymea.ThingErrorHardwareFailure, str(e))
|
||||
return
|
||||
|
||||
info.thing.setStateValue(robotConnectedStateTypeId, True)
|
||||
# set up polling for robot status
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
|
||||
def refreshRobot(thing):
|
||||
robot = thingsAndRobots[thing]
|
||||
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']
|
||||
thing.setStateValue(robotChargingStateTypeId, rbtStateDetails['isCharging'])
|
||||
thing.setStateValue(robotBatteryLevelStateTypeId, rbtStateDetails['charge'])
|
||||
|
||||
# Set robot cleaning/paused state
|
||||
rbtStateCommands = rbtStateJson['availableCommands']
|
||||
rbtStartAv = rbtStateCommands['start']
|
||||
rbtPauseAv = rbtStateCommands['pause']
|
||||
rbtResumeAv = rbtStateCommands['resume']
|
||||
if rbtStateJson['error'] == None:
|
||||
rbtError = "no error"
|
||||
else:
|
||||
rbtError = rbtStateJson['error']
|
||||
# alert state hasn't been implemented yet (not sure what would trigger an alert, haven't seen one yet)
|
||||
if rbtStateJson['alert'] == None:
|
||||
rbtAlert = "no alert"
|
||||
else:
|
||||
rbtAlert = rbtStateJson['alert']
|
||||
logger.log("error: %s: -- alert: %s" % (rbtError, rbtAlert))
|
||||
if rbtStateDetails['isDocked'] == True:
|
||||
thing.setStateValue(robotRobotStateStateTypeId, "docked")
|
||||
elif rbtPauseAv == True:
|
||||
thing.setStateValue(robotRobotStateStateTypeId, "cleaning")
|
||||
elif rbtResumeAv == True:
|
||||
thing.setStateValue(robotRobotStateStateTypeId, "paused")
|
||||
elif rbtStartAv == True:
|
||||
thing.setStateValue(robotRobotStateStateTypeId, "stopped")
|
||||
else:
|
||||
thing.setStateValue(robotRobotStateStateTypeId, "error")
|
||||
thing.setStateValue(robotErrorMessageStateTypeId, rbtError)
|
||||
|
||||
|
||||
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)
|
||||
pollTimer.start()
|
||||
|
||||
|
||||
def executeAction(info):
|
||||
if info.actionTypeId == robotStartCleaningActionTypeId:
|
||||
refreshRobot(info.thing)
|
||||
if info.thing.stateValue(robotRobotStateStateTypeId) == "paused":
|
||||
thingsAndRobots[info.thing].resume_cleaning()
|
||||
else:
|
||||
cleanWithRobot(info.thing, None, None)
|
||||
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
if info.actionTypeId == robotPauseCleaningActionTypeId:
|
||||
refreshRobot(info.thing)
|
||||
if info.thing.stateValue(robotRobotStateStateTypeId) == "paused":
|
||||
thingsAndRobots[info.thing].resume_cleaning()
|
||||
else:
|
||||
thingsAndRobots[info.thing].pause_cleaning()
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
if info.actionTypeId == robotReturnToBaseActionTypeId:
|
||||
thingsAndRobots[info.thing].send_to_base()
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
if info.actionTypeId == robotStopCleaningActionTypeId:
|
||||
thingsAndRobots[info.thing].stop_cleaning()
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
def cleanWithRobot(robotThing, mapID, boundaryID):
|
||||
# To do: add a parameter to the start action which takes a zone id --> this should now be represented by mapID & boundaryID
|
||||
robot = thingsAndRobots[robotThing]
|
||||
logger.log("Cleaning with robot:", robot, robotThing)
|
||||
boolEco = robotThing.setting(robotSettingsEcoParamTypeId)
|
||||
boolCare = robotThing.setting(robotSettingsCareParamTypeId)
|
||||
boolNogo = robotThing.setting(robotSettingsNoGoLinesParamTypeId)
|
||||
if boolEco == False:
|
||||
intEco = 2
|
||||
else:
|
||||
intEco = 1
|
||||
if boolCare == False:
|
||||
intCare = 1
|
||||
else:
|
||||
intCare = 2
|
||||
if boolNogo == False:
|
||||
intNogo = 2
|
||||
else:
|
||||
intNogo = 4
|
||||
logger.log("Settings: Eco:", boolEco, "Care:", boolCare, "Enable nogo:", boolNogo, "mapID:", mapID, "boundaryID:", boundaryID)
|
||||
thingsAndRobots[robotThing].start_cleaning(mode=intEco, navigation_mode=intCare, category=intNogo, boundary_id=boundaryID, map_id=mapID)
|
||||
refreshRobot(robotThing)
|
||||
|
||||
def cleanWithRobot(robotThing, mapID, boundaryID):
|
||||
# To do: add a parameter to the start action which takes a zone id --> this should now be represented by mapID & boundaryID
|
||||
robot = thingsAndRobots[robotThing]
|
||||
logger.log("Cleaning with robot:", robot, robotThing, mapID, boundaryID)
|
||||
boolEco = robotThing.setting(robotSettingsEcoParamTypeId)
|
||||
boolCare = robotThing.setting(robotSettingsCareParamTypeId)
|
||||
boolNogo = robotThing.setting(robotSettingsNoGoLinesParamTypeId)
|
||||
if boolEco == False:
|
||||
intEco = 2
|
||||
else:
|
||||
intEco = 1
|
||||
if boolCare == False:
|
||||
intCare = 1
|
||||
else:
|
||||
intCare = 2
|
||||
if boolNogo == False:
|
||||
intNogo = 2
|
||||
else:
|
||||
intNogo = 4
|
||||
logger.log("Settings: Eco:", boolEco, "Care:", boolCare, "Enable nogo:", boolNogo, "mapID:", mapID, "boundaryID:", boundaryID)
|
||||
thingsAndRobots[robotThing].start_cleaning(mode=intEco, navigation_mode=intCare, category=intNogo, boundary_id=boundaryID, map_id=mapID)
|
||||
|
||||
|
||||
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 == "" or browseResult.itemId == "maps":
|
||||
maps = account.persistent_maps
|
||||
|
||||
logger.log("maps", type(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:]
|
||||
|
||||
try:
|
||||
boundaries = robot.get_map_boundaries(mapId)
|
||||
except Exception as e:
|
||||
logger.warn("Error fetching boundaries from robot:", e)
|
||||
info.finish(nymea.ThingErrorHardwareFailure, "Unable to fetch boundaries from robot.")
|
||||
|
||||
logger.log("boundaries", type(boundaries), boundaries.json())
|
||||
for boundary in boundaries.json()["data"]["boundaries"]:
|
||||
if boundary["type"] == "polygon":
|
||||
logger.log("vertices:", boundary)
|
||||
browseResult.addItem(nymea.BrowserItem("boundary-" + mapId + ";" + boundary["id"], boundary["name"], json.dumps(boundary), executable=True, disabled=not boundary["enabled"], icon=nymea.BrowserIconFavorites))
|
||||
|
||||
browseResult.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
|
||||
def executeBrowserItem(info):
|
||||
logger.log("Browser item clicked:", info.itemId)
|
||||
if info.itemId.startswith("boundary-"):
|
||||
ids = info.itemId[9:];
|
||||
logger.log("IDS:", ids)
|
||||
mapId = ids.split(";")[0]
|
||||
boundaryId = ids.split(";")[1]
|
||||
logger.log("Cleaning boundary:", mapId, boundaryId)
|
||||
cleanWithRobot(info.thing, mapId, boundaryId)
|
||||
refreshRobot(info.thing)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
return
|
||||
|
||||
logger.warn("Can't execute browser item:", info.itemId)
|
||||
info.finish(nymea.ThingErrorItemNotExecutable)
|
||||
|
||||
|
||||
def deinit():
|
||||
global pollTimer
|
||||
# If we started a poll timer, cancel it on shutdown.
|
||||
if pollTimer is not None:
|
||||
pollTimer.cancel()
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"title": "Neato Botvac",
|
||||
"tagline": "Connect to and control your Neato Botvac connected cleaning robots.",
|
||||
"icon": "neato.png",
|
||||
"stability": "consumer",
|
||||
"offline": true,
|
||||
"technologies": [
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"account",
|
||||
"cleaningrobot"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
TEMPLATE = aux
|
||||
|
||||
OTHER_FILES = integrationpluginneatobotvac.json \
|
||||
integrationpluginneatobotvac.py
|
||||
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
pybotvac==0.0.20 \
|
||||
--hash=sha256:d4d9d348ee2f3b7472b78bd80f9653406af6e351aa0362ac191055a304930b33 \
|
||||
--hash=sha256:e49a3258f251da0d56764797efb01ba730537b6344ff03b57542142f0640b273
|
||||
setuptools==54.1.1 \
|
||||
--hash=sha256:1ce82798848a978696465866bb3aaab356003c42d6143e1111fcf069ac838274 \
|
||||
--hash=sha256:75c5c4479f4961f1ffdb597c98aa4e4077e6813685025e8bdebf7598aa84e859
|
||||
requests-oauthlib==1.3.0 \
|
||||
--hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \
|
||||
--hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \
|
||||
--hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc
|
||||
oauthlib==3.1.0 \
|
||||
--hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \
|
||||
--hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea
|
||||
requests==2.25.1 \
|
||||
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
|
||||
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
|
||||
voluptuous==0.12.1 \
|
||||
--hash=sha256:663572419281ddfaf4b4197fd4942d181630120fb39b333e3adad70aeb56444b \
|
||||
--hash=sha256:8ace33fcf9e6b1f59406bfaf6b8ec7bcc44266a9f29080b4deb4fe6ff2492386
|
||||
urllib3==1.26.3 \
|
||||
--hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \
|
||||
--hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73
|
||||
chardet==4.0.0 \
|
||||
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
|
||||
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
|
||||
certifi==2020.12.5 \
|
||||
--hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \
|
||||
--hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830
|
||||
launchpadlib==1.10.13 \
|
||||
--hash=sha256:5804d68ec93247194449d17d187e949086da0a4d044f12155fad269ef8515435 \
|
||||
--hash=sha256:f61a591aff60a9315da09131eeb000bfb8287b788d1899a161e727ca22b1989f
|
||||
httplib2==0.19.0 \
|
||||
--hash=sha256:749c32603f9bf16c1277f59531d502e8f1c2ca19901ae653b49c4ed698f0820e \
|
||||
--hash=sha256:e0d428dad43c72dbce7d163b7753ffc7a39c097e6788ef10f4198db69b92f08e
|
||||
keyring==23.0.0 \
|
||||
--hash=sha256:237ff44888ba9b3918a7dcb55c8f1db909c95b6f071bfb46c6918f33f453a68a \
|
||||
--hash=sha256:29f407fd5509c014a6086f17338c70215c8d1ab42d5d49e0254273bc0a64bbfc
|
||||
lazr.uri==1.0.5 \
|
||||
--hash=sha256:f36e7e40d5f8f2cf20ff2c81784a14a546e6c19c216d40a6617ebe0c96c92c49 \
|
||||
--hash=sha256:71f2faf04b148cf63d78da08ee5d8d6a7a7dbda8c9016b389a16f790d080c06f
|
||||
six==1.15.0 \
|
||||
--hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \
|
||||
--hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced
|
||||
testresources==2.0.1 \
|
||||
--hash=sha256:67a361c3a2412231963b91ab04192209aa91a1aa052f0ab87245dbea889d1282 \
|
||||
--hash=sha256:ee9d1982154a1e212d4e4bac6b610800bfb558e4fb853572a827bc14a96e4417
|
||||
wadllib==1.3.5 \
|
||||
--hash=sha256:84fecbaec2fef5ae2d7717a8115d271f18c6b5441eac861c58be8ca57f63c1d3 \
|
||||
--hash=sha256:67d3102b40eefdd6c3007cfbcc4c07f6948fec0666ba5c17d703eab21f054692
|
||||
lazr.restfulclient==0.14.3 \
|
||||
--hash=sha256:9f28bbb7c00374159376bd4ce36b4dacde7c6b86a0af625aa5e3ae214651a690 \
|
||||
--hash=sha256:2320e6d132c9a5148895e85be03274bc9305e4605439b03541ee3a618e00fb94
|
||||
pyparsing==2.4.7 \
|
||||
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \
|
||||
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b
|
||||
jeepney==0.6.0 \
|
||||
--hash=sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657 \
|
||||
--hash=sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae
|
||||
importlib-metadata==3.7.0 \
|
||||
--hash=sha256:24499ffde1b80be08284100393955842be4a59c7c16bbf2738aad0e464a8e0aa \
|
||||
--hash=sha256:c6af5dbf1126cd959c4a8d8efd61d4d3c83bddb0459a17e554284a077574b614
|
||||
pbr==5.5.1 \
|
||||
--hash=sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9 \
|
||||
--hash=sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00
|
||||
SecretStorage==3.3.1 \
|
||||
--hash=sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f \
|
||||
--hash=sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195
|
||||
distro==1.5.0 \
|
||||
--hash=sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92 \
|
||||
--hash=sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799
|
||||
zipp==3.4.1 \
|
||||
--hash=sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76 \
|
||||
--hash=sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098
|
||||
cffi==1.14.5 \
|
||||
--hash=sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813 \
|
||||
--hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \
|
||||
--hash=sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea \
|
||||
--hash=sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee \
|
||||
--hash=sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396 \
|
||||
--hash=sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73 \
|
||||
--hash=sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315 \
|
||||
--hash=sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1 \
|
||||
--hash=sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49 \
|
||||
--hash=sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892 \
|
||||
--hash=sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482 \
|
||||
--hash=sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058 \
|
||||
--hash=sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5 \
|
||||
--hash=sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53 \
|
||||
--hash=sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045 \
|
||||
--hash=sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3 \
|
||||
--hash=sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5 \
|
||||
--hash=sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e \
|
||||
--hash=sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c \
|
||||
--hash=sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369 \
|
||||
--hash=sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827 \
|
||||
--hash=sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053 \
|
||||
--hash=sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa \
|
||||
--hash=sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4 \
|
||||
--hash=sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322 \
|
||||
--hash=sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132 \
|
||||
--hash=sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62 \
|
||||
--hash=sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa \
|
||||
--hash=sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0 \
|
||||
--hash=sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396 \
|
||||
--hash=sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e \
|
||||
--hash=sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991 \
|
||||
--hash=sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6 \
|
||||
--hash=sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1 \
|
||||
--hash=sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406 \
|
||||
--hash=sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d \
|
||||
--hash=sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c \
|
||||
--hash=sha256:9338beed13d880320450d95c9e07ccf839faa3ea7b75d788f4ed46d845044a71
|
||||
pycparser==2.20 \
|
||||
--hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \
|
||||
--hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705
|
||||
idna==2.5 \
|
||||
--hash=sha256:3cb5ce08046c4e3a560fc02f138d0ac63e00f8ce5901a56b32ec8b7994082aab \
|
||||
--hash=sha256:cc19709fd6d0cbfed39ea875d29ba6d4e22c0cebc510a76d6302a28385e8bb70
|
||||
typing-extensions==3.7.4.3 \
|
||||
--hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \
|
||||
--hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \
|
||||
--hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f
|
||||
cryptography==3.3 \
|
||||
--hash=sha256:1366e6fff96bb1d320e3ef3c531b0428cb780c517b6059ffe8820e2a30bf5858 \
|
||||
--hash=sha256:2c28e69e8c2620869425420e466b224d351d5dc242d3e3293062cffa7fcdd276 \
|
||||
--hash=sha256:402273e7f78e01f5c42452acef56bd52fa73fa0f312e4160db7ad29bbc90335d \
|
||||
--hash=sha256:41892759f13c7dfc329573cabd3a513f1e7b5d309ca55c931ffefc3b2f304899 \
|
||||
--hash=sha256:4935d0603b118dc036c477917e5e8a020f7309bc11c363a4d572407dcbd53c80 \
|
||||
--hash=sha256:55574bd84ff551fd6f7617e5eeda0b2d129f84788340ab57904f0c7f13f8f149 \
|
||||
--hash=sha256:651eff09297e4518287f711b4e28523567cbde7beaa794d06d0b35ac9adc1172 \
|
||||
--hash=sha256:66f41aa7642f97d35976750a2c0a6d5915733ba6c9ca7a0f327e56f037c1236e \
|
||||
--hash=sha256:9518da854136181407d946b971dd27a0e941d5d07f87f87a44c598b3da7a77d2 \
|
||||
--hash=sha256:ad8dc319f876273b474a59797344d5986beaaaf18f7cc0ab9255155da057d979 \
|
||||
--hash=sha256:d9f1e520f2ee08c5a88e1ae0b31159bdb13da40a486bea3e9f7d338564850ea6 \
|
||||
--hash=sha256:ee0084f6d62f083316b08111b122dc4fbe16e534059b92b5ddc3d73dc52dd39c \
|
||||
--hash=sha256:f43d6e72e3cdf983b5e5e65938c0519ce4eeb42b6af766f714b1414ae7b9f8ef \
|
||||
--hash=sha256:f95ca692fafea80f1815bbfecf57c6833c0b21432d17026a077341debee76e79 \
|
||||
--hash=sha256:19c2cbff0434b4e345d08737a7c706d0ba3c2aeecb89936525481e866fd71cea
|
||||
|
|
@ -33,6 +33,7 @@ PLUGIN_DIRS = \
|
|||
lifx \
|
||||
mailnotification \
|
||||
mqttclient \
|
||||
neatobotvac \
|
||||
nanoleaf \
|
||||
netatmo \
|
||||
networkdetector \
|
||||
|
|
|
|||
Loading…
Reference in New Issue