From 115ce5168a307619c2c630d2c38d940246f01762 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Mar 2021 23:03:15 +0100 Subject: [PATCH 01/17] Merge standalone neato botvac plugin --- debian/control | 15 ++ debian/nymea-plugin-neatobotvac.install.in | 3 + neatobotvac/integrationpluginneatobotvac.json | 175 ++++++++++++++ neatobotvac/integrationpluginneatobotvac.py | 217 ++++++++++++++++++ neatobotvac/requirements.txt | 136 +++++++++++ 5 files changed, 546 insertions(+) create mode 100644 debian/nymea-plugin-neatobotvac.install.in create mode 100644 neatobotvac/integrationpluginneatobotvac.json create mode 100644 neatobotvac/integrationpluginneatobotvac.py create mode 100644 neatobotvac/requirements.txt diff --git a/debian/control b/debian/control index db1cc724..ce906773 100644 --- a/debian/control +++ b/debian/control @@ -480,6 +480,21 @@ 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, +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}, diff --git a/debian/nymea-plugin-neatobotvac.install.in b/debian/nymea-plugin-neatobotvac.install.in new file mode 100644 index 00000000..76a6c8b3 --- /dev/null +++ b/debian/nymea-plugin-neatobotvac.install.in @@ -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/ diff --git a/neatobotvac/integrationpluginneatobotvac.json b/neatobotvac/integrationpluginneatobotvac.json new file mode 100644 index 00000000..6554dfdf --- /dev/null +++ b/neatobotvac/integrationpluginneatobotvac.json @@ -0,0 +1,175 @@ +{ + "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", + "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": "a4b5f07f-e71a-4c3a-8d6b-50162a455159", + "name": "getMaps", + "displayName": "Get available maps" + } + ] + }, + { + "id": "b924c87a-f783-4f45-a3af-929684c24aea", + "name": "robot", + "displayName": "Neato robot", + "createMethods": ["auto"], + "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" + }, + { + "id": "141f0d98-1806-432c-aaac-c0d3a89a8e58", + "name": "mapId", + "displayName": "Map ID", + "type": "QString" + } + ], + "interfaces":[ + + ], + "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": "dce4f7f3-a0a6-46bb-9216-c9089d9e9b0d", + "name": "cleaning", + "displayName": "Cleaning", + "displayNameEvent": "Cleaning yes/no", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "0f925abf-396c-437e-b259-2fed7eafe7f4", + "name": "paused", + "displayName": "Paused", + "displayNameEvent": "Cleaning paused yes/no", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "1b8abd35-8276-44ba-8c75-a647877b2e11", + "name": "charging", + "displayName": "Charging", + "displayNameEvent": "Robot charging yes/no", + "type": "bool", + "defaultValue": true, + "cached": false + }, + { + "id": "805175ec-c2e4-4fbe-9505-282750ef1467", + "name": "docked", + "displayName": "Docked", + "displayNameEvent": "Robot docked yes/no", + "type": "bool", + "defaultValue": true, + "cached": false + }, + { + "id": "20ed8767-806f-4ec2-8626-842cd398f9df", + "name": "batteryLevel", + "displayName": "Battery level", + "displayNameEvent": "Battery level percentage", + "type": "int", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100, + "cached": false + } + ], + "actionTypes": [ + { + "id": "1f774998-5fa7-4e3b-8ab0-a8402dd561bb", + "name": "startCleaning", + "displayName": "Start/pause cleaning" + }, + { + "id": "5178a803-5696-4ee1-80a4-2c7c20a5043a", + "name": "goToBase", + "displayName": "Go to base" + }, + { + "id": "30775042-55a7-4f1b-9042-a9bdeadc4b0d", + "name": "stopCleaning", + "displayName": "Stop cleaning" + }, + { + "id": "95ba515b-0023-4a98-a867-ca8286512a4e", + "name": "getBoundaries", + "displayName": "Get No-go Lines" + } + ] + } + ] + } + ] +} + + + diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py new file mode 100644 index 00000000..d12411b6 --- /dev/null +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -0,0 +1,217 @@ +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 + +thingsAndRobots = {} +oauthSessions = {} + +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: + 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: + # Login error + info.finish(nymea.ThingErrorAuthenticationFailure, "Login error") + 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) + + # 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) + # Check if this robot is already added in nymea + found = False + for thing in myThings(): + if thing.paramValue(robotThingSerialParamTypeId) == robot.serial: + # Yep, already here... skip it + found = True + continue + 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) + mapIDshort = mapId + thingDescriptor.params = [ + nymea.Param(robotThingSerialParamTypeId, robot.serial), + nymea.Param(robotThingSecretParamTypeId, robot.secret), + nymea.Param(robotThingMapIdParamTypeId, mapIDshort) + ] + 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 + pollTimer = threading.Timer(5, pollService) + pollTimer.start() + return + + + # Setup for the robots + if info.thing.thingClassId == robotThingClassId: + + serial = info.thing.paramValue(robotThingSerialParamTypeId) + secret = info.thing.paramValue(robotThingSecretParamTypeId) + robot = Robot(serial, secret, info.thing.name) + thingsAndRobots[info.thing] = robot; + logger.log(robot.get_robot_state()) + # 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: + robot = thingsAndRobots[thing] + logger.log("polling robot:", robot) + + # Get robot state + rbtState = thingsAndRobots[thing].get_robot_state() + rbtStateJson = rbtState.json() + + # 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) + + # Set robot cleaning/paused state + rbtStateCommands = rbtStateJson['availableCommands'] + rbtStartAv = rbtStateCommands['start'] + 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) + + # restart the timer for next poll + global pollTimer + pollTimer = threading.Timer(60, pollService) + pollTimer.start() + + +def executeAction(info): + if info.actionTypeId == robotStartCleaningActionTypeId: + rbtState = thingsAndRobots[info.thing].get_robot_state() + rbtStateJson = rbtState.json() + rbtStateCommands = rbtStateJson['availableCommands'] + rbtStartAv = rbtStateCommands['start'] + rbtPauseAv = rbtStateCommands['pause'] + rbtResumeAv = rbtStateCommands['resume'] + if rbtStartAv == True: + logger.log("Start cleaning") + thingsAndRobots[info.thing].start_cleaning() + elif rbtPauseAv == True: + logger.log("Pause cleaning") + thingsAndRobots[info.thing].pause_cleaning() + elif rbtResumeAv == True: + thingsAndRobots[info.thing].resume_cleaning() + threading.Timer(5, pollService).start() + info.finish(nymea.ThingErrorNoError) + return + + if info.actionTypeId == robotGoToBaseActionTypeId: + thingsAndRobots[info.thing].send_to_base() + threading.Timer(5, pollService).start() + info.finish(nymea.ThingErrorNoError) + + if info.actionTypeId == robotStopCleaningActionTypeId: + thingsAndRobots[info.thing].stop_cleaning() + threading.Timer(5, pollService).start() + info.finish(nymea.ThingErrorNoError) + + # To do: get available boundaries to use with start_cleaning action + if info.actionTypeId == robotGetBoundariesActionTypeId: + # rbtMapBound = thingsAndRobots[info.thing].get_map_boundaries() + # rbtMapBoundJson = rbtMapBound.json() + # logger.log("Robot Map Boundaries", rbtMapBoundJson) + threading.Timer(5, pollService).start() + info.finish(nymea.ThingErrorNoError) + + +def deinit(): + global pollTimer + # If we started a poll timer, cancel it on shutdown. + if pollTimer is not None: + pollTimer.cancel() diff --git a/neatobotvac/requirements.txt b/neatobotvac/requirements.txt new file mode 100644 index 00000000..ae7b9306 --- /dev/null +++ b/neatobotvac/requirements.txt @@ -0,0 +1,136 @@ +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 +cryptography==3.4.6 \ + --hash=sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b \ + --hash=sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336 \ + --hash=sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87 \ + --hash=sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7 \ + --hash=sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799 \ + --hash=sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b \ + --hash=sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df \ + --hash=sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0 \ + --hash=sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3 \ + --hash=sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724 \ + --hash=sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2 \ + --hash=sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964 \ + --hash=sha256:926ae3dfd160050158b9ca25d419fb7ee658974564b01aa10c059a75dffab7e8 +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 From d1b00de5423740861f0bd20fbf7272eb70125c7c Mon Sep 17 00:00:00 2001 From: loosrob <79396812+loosrob@users.noreply.github.com> Date: Thu, 25 Mar 2021 17:48:30 +0100 Subject: [PATCH 02/17] obtain map boundary info added code to display boundary info in log -- it doesn't "do" anything yet but displays the string rbtBoundData to analyse how to integrate this into the start cleaning operation --- neatobotvac/integrationpluginneatobotvac.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index d12411b6..915ac221 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -88,11 +88,14 @@ def setupThing(info): logger.log("MapId type: ", type(mapId), " MapId contents: ", mapId) mapName = mapInfo2['name'] logger.log("MapName type: ", type(mapName), " MapName contents: ", mapName) - mapIDshort = mapId + 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) thingDescriptor.params = [ nymea.Param(robotThingSerialParamTypeId, robot.serial), nymea.Param(robotThingSecretParamTypeId, robot.secret), - nymea.Param(robotThingMapIdParamTypeId, mapIDshort) + nymea.Param(robotThingMapIdParamTypeId, mapId) ] thingDescriptors.append(thingDescriptor) @@ -203,9 +206,11 @@ def executeAction(info): # To do: get available boundaries to use with start_cleaning action if info.actionTypeId == robotGetBoundariesActionTypeId: - # rbtMapBound = thingsAndRobots[info.thing].get_map_boundaries() - # rbtMapBoundJson = rbtMapBound.json() - # logger.log("Robot Map Boundaries", rbtMapBoundJson) + mapIDparam = info.thing.paramValue(robotThingMapIdParamTypeId) + rbtMapBound = thingsAndRobots[info.thing].get_map_boundaries(mapIDparam) + 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() info.finish(nymea.ThingErrorNoError) From 4eb24160a6a09e82ff60dc0f1d8851d06139dc3e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 8 Apr 2021 00:58:33 +0200 Subject: [PATCH 03/17] Some cleanups and add support for browsing maps --- neatobotvac/integrationpluginneatobotvac.json | 48 +++-- neatobotvac/integrationpluginneatobotvac.py | 189 +++++++++++------- neatobotvac/neatobotvac.pro | 5 + nymea-plugins.pro | 1 + 4 files changed, 147 insertions(+), 96 deletions(-) create mode 100644 neatobotvac/neatobotvac.pro diff --git a/neatobotvac/integrationpluginneatobotvac.json b/neatobotvac/integrationpluginneatobotvac.json index 6554dfdf..01e515d1 100644 --- a/neatobotvac/integrationpluginneatobotvac.json +++ b/neatobotvac/integrationpluginneatobotvac.json @@ -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", diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index 915ac221..fc1852cb 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -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,120 +57,108 @@ 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 - pollTimer = threading.Timer(5, pollService) - pollTimer.start() - return + if pollTimer is not 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) 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 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(robotDockedStateTypeId, rbtStateDetails['isDocked']) + thing.setStateValue(robotBatteryLevelStateTypeId, rbtStateDetails['charge']) + + # Set robot cleaning/paused state + rbtStateCommands = rbtStateJson['availableCommands'] + rbtStartAv = rbtStateCommands['start'] + rbtPauseAv = rbtStateCommands['pause'] + rbtResumeAv = rbtStateCommands['resume'] + if rbtStartAv == True: + thing.setStateValue(robotCleaningStateTypeId, False) + thing.setStateValue(robotPausedStateTypeId, False) + elif rbtPauseAv == True: + thing.setStateValue(robotCleaningStateTypeId, True) + thing.setStateValue(robotPausedStateTypeId, False) + elif rbtResumeAv == True: + thing.setStateValue(robotCleaningStateTypeId, True) + thing.setStateValue(robotPausedStateTypeId, True) + def pollService(): - logger.log("pollService!!!") - # Poll all robots we know for thing in myThings(): if thing.thingClassId == robotThingClassId: - robot = thingsAndRobots[thing] - logger.log("polling robot:", robot) - - # Get robot state - rbtState = thingsAndRobots[thing].get_robot_state() - rbtStateJson = rbtState.json() - - # 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) - - # Set robot cleaning/paused state - rbtStateCommands = rbtStateJson['availableCommands'] - rbtStartAv = rbtStateCommands['start'] - 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) + try: + refreshRobot(thing) + except: + logger.warn("Error refreshing robot state") # restart the timer for next poll global pollTimer @@ -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,10 +203,57 @@ 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) + + def deinit(): global pollTimer # If we started a poll timer, cancel it on shutdown. diff --git a/neatobotvac/neatobotvac.pro b/neatobotvac/neatobotvac.pro new file mode 100644 index 00000000..5334cad8 --- /dev/null +++ b/neatobotvac/neatobotvac.pro @@ -0,0 +1,5 @@ +TEMPLATE = aux + +OTHER_FILES = integrationpluginneatobotvac.json \ + integrationpluginneatobotvac.py + diff --git a/nymea-plugins.pro b/nymea-plugins.pro index eb037d01..fba11d49 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -33,6 +33,7 @@ PLUGIN_DIRS = \ lifx \ mailnotification \ mqttclient \ + neatobotvac \ nanoleaf \ netatmo \ networkdetector \ From a5bf3238c38cf56bc817f4556c0f17703c3a5968 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 9 Apr 2021 10:23:00 +0200 Subject: [PATCH 04/17] More work on neato --- neatobotvac/integrationpluginneatobotvac.json | 52 +++++++--------- neatobotvac/integrationpluginneatobotvac.py | 60 ++++++++----------- 2 files changed, 48 insertions(+), 64 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.json b/neatobotvac/integrationpluginneatobotvac.json index 01e515d1..639e6735 100644 --- a/neatobotvac/integrationpluginneatobotvac.json +++ b/neatobotvac/integrationpluginneatobotvac.json @@ -99,33 +99,18 @@ }, { "id": "dce4f7f3-a0a6-46bb-9216-c9089d9e9b0d", - "name": "cleaning", - "displayName": "Cleaning", - "displayNameEvent": "Cleaning yes/no", - "type": "bool", - "defaultValue": false - }, - { - "id": "0f925abf-396c-437e-b259-2fed7eafe7f4", - "name": "paused", - "displayName": "Paused", - "displayNameEvent": "Cleaning paused yes/no", - "type": "bool", - "defaultValue": false + "name": "state", + "displayName": "Cleaning state", + "displayNameEvent": "Cleaning state changed", + "type": "QString", + "possibleValues": ["docked", "cleaning", "paused", "traveling", "stopped", "error"], + "defaultValue": "docked" }, { "id": "1b8abd35-8276-44ba-8c75-a647877b2e11", "name": "charging", "displayName": "Charging", - "displayNameEvent": "Robot charging yes/no", - "type": "bool", - "defaultValue": true - }, - { - "id": "805175ec-c2e4-4fbe-9505-282750ef1467", - "name": "docked", - "displayName": "Docked", - "displayNameEvent": "Robot docked yes/no", + "displayNameEvent": "Started or stopped charging", "type": "bool", "defaultValue": true }, @@ -141,7 +126,7 @@ "id": "20ed8767-806f-4ec2-8626-842cd398f9df", "name": "batteryLevel", "displayName": "Battery level", - "displayNameEvent": "Battery level percentage", + "displayNameEvent": "Battery level changed", "type": "int", "unit": "Percentage", "defaultValue": 0, @@ -153,7 +138,21 @@ { "id": "1f774998-5fa7-4e3b-8ab0-a8402dd561bb", "name": "startCleaning", - "displayName": "Start/pause cleaning" + "displayName": "Start cleaning", + "paramTypes": [ + { + "id": "08a86cca-cdc9-4520-af1d-8413a0c274b5", + "name": "zones", + "displayName": "Zones to clean", + "type": "QStringList", + "defaultValue": [] + } + ] + }, + { + "id": "e731faa6-88c9-406d-b505-f89b5f0868b0", + "name": "pauseCleaning", + "displayName": "Pause/resume cleaning" }, { "id": "5178a803-5696-4ee1-80a4-2c7c20a5043a", @@ -164,11 +163,6 @@ "id": "30775042-55a7-4f1b-9042-a9bdeadc4b0d", "name": "stopCleaning", "displayName": "Stop cleaning" - }, - { - "id": "95ba515b-0023-4a98-a867-ca8286512a4e", - "name": "getBoundaries", - "displayName": "Get No-go Lines" } ] } diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index fc1852cb..ddf398b4 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -132,7 +132,6 @@ def refreshRobot(thing): # Set robot docked/charging state rbtStateDetails = rbtStateJson['details'] thing.setStateValue(robotChargingStateTypeId, rbtStateDetails['isCharging']) - thing.setStateValue(robotDockedStateTypeId, rbtStateDetails['isDocked']) thing.setStateValue(robotBatteryLevelStateTypeId, rbtStateDetails['charge']) # Set robot cleaning/paused state @@ -140,15 +139,16 @@ def refreshRobot(thing): rbtStartAv = rbtStateCommands['start'] rbtPauseAv = rbtStateCommands['pause'] rbtResumeAv = rbtStateCommands['resume'] - if rbtStartAv == True: - thing.setStateValue(robotCleaningStateTypeId, False) - thing.setStateValue(robotPausedStateTypeId, False) + if rbtStateDetails['isDocked'] == True: + thing.setStateValue(robotStateStateTypeId, "docked") elif rbtPauseAv == True: - thing.setStateValue(robotCleaningStateTypeId, True) - thing.setStateValue(robotPausedStateTypeId, False) + thing.setStateValue(robotStateStateTypeId, "cleaning") elif rbtResumeAv == True: - thing.setStateValue(robotCleaningStateTypeId, True) - thing.setStateValue(robotPausedStateTypeId, True) + thing.setStateValue(robotStateStateTypeId, "paused") + elif rbtStartAv == True: + thing.setStateValue(robotStateStateTypeId, "stopped") + else: + thing.setStateValue(robotStateStateTypeId, "error") def pollService(): @@ -167,21 +167,17 @@ def pollService(): def executeAction(info): + return; # Temporary, to not accidentally start it + if info.actionTypeId == robotStartCleaningActionTypeId: - rbtState = thingsAndRobots[info.thing].get_robot_state() - rbtStateJson = rbtState.json() - rbtStateCommands = rbtStateJson['availableCommands'] - rbtStartAv = rbtStateCommands['start'] - rbtPauseAv = rbtStateCommands['pause'] - rbtResumeAv = rbtStateCommands['resume'] - if rbtStartAv == True: - logger.log("Start cleaning") - thingsAndRobots[info.thing].start_cleaning() - elif rbtPauseAv == True: - logger.log("Pause cleaning") - thingsAndRobots[info.thing].pause_cleaning() - elif rbtResumeAv == True: - thingsAndRobots[info.thing].resume_cleaning() + # To do: add a parameter to the start action which takes a zone id + thingsAndRobots[info.thing].start_cleaning() + refreshRobot(info.thing) + info.finish(nymea.ThingErrorNoError) + return + + if info.actionTypeId == robotPauseCleaningActionTypeId: + thingsAndRobots[info.thing].pause_cleaning() refreshRobot(info.thing) info.finish(nymea.ThingErrorNoError) return @@ -190,21 +186,13 @@ def executeAction(info): 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) - - # To do: get available boundaries to use with start_cleaning action - if info.actionTypeId == robotGetBoundariesActionTypeId: - mapIDparam = info.thing.paramValue(robotThingMapIdParamTypeId) - rbtMapBound = thingsAndRobots[info.thing].get_map_boundaries(mapIDparam) - logger.log("rbtMapBound Type: ", type(rbtMapBound), "rbtMapBound Contents: ", rbtMapBound) - rbtBoundData = rbtMapBound.text - logger.log("rbtBoundData Type: ", type(rbtBoundData), "rbtBoundData Contents: ", rbtBoundData) - refreshRobot(info.thing) - info.finish(nymea.ThingErrorNoError) + return def browseThing(browseResult): @@ -223,10 +211,10 @@ def browseThing(browseResult): # Top level entries -> return maps - if browseResult.itemId == "": + if browseResult.itemId == "" or browseResult.itemId == "maps": maps = account.persistent_maps - logger.log("maps", tpye(maps), maps) + logger.log("maps", type(maps), maps) for map in maps[robot.serial]: logger.log("Type mapInfo: ", type(map)) logger.log("map:", map) @@ -242,7 +230,9 @@ def browseThing(browseResult): 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)) +# if boundary["type"] == "polygon": + logger.log("vertices:", json.dumps(boundary["vertices"])) + browseResult.addItem(nymea.BrowserItem(boundary["id"], boundary["name"], json.dumps(boundary), executable=True, disabled=not boundary["enabled"], icon=nymea.BrowserIconFavorites)) browseResult.finish(nymea.ThingErrorNoError) return From 6e6c32bc134ca46fcef4cdba77d3101dba1bc0b7 Mon Sep 17 00:00:00 2001 From: loosrob <79396812+loosrob@users.noreply.github.com> Date: Sun, 11 Apr 2021 16:03:05 +0200 Subject: [PATCH 05/17] align actions with cleaning robot interface & use settings when cleaning --- neatobotvac/integrationpluginneatobotvac.json | 22 +++--- neatobotvac/integrationpluginneatobotvac.py | 74 +++++++++++++++---- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.json b/neatobotvac/integrationpluginneatobotvac.json index 639e6735..9e2ab636 100644 --- a/neatobotvac/integrationpluginneatobotvac.json +++ b/neatobotvac/integrationpluginneatobotvac.json @@ -16,6 +16,9 @@ "createMethods": ["User"], "interfaces": ["account"], "setupMethod": "oauth", + "settingsTypes": [ + + ], "stateTypes":[ { "id": "e8f47781-e3fd-416f-a9ac-51ef942d0573", @@ -36,11 +39,7 @@ } ], "actionTypes": [ - { - "id": "a4b5f07f-e71a-4c3a-8d6b-50162a455159", - "name": "getMaps", - "displayName": "Get available maps" - } + ] }, { @@ -99,13 +98,21 @@ }, { "id": "dce4f7f3-a0a6-46bb-9216-c9089d9e9b0d", - "name": "state", + "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", @@ -170,6 +177,3 @@ } ] } - - - diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index ddf398b4..fc9aae0e 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -139,16 +139,27 @@ def refreshRobot(thing): rbtStartAv = rbtStateCommands['start'] rbtPauseAv = rbtStateCommands['pause'] rbtResumeAv = rbtStateCommands['resume'] - if rbtStateDetails['isDocked'] == True: - thing.setStateValue(robotStateStateTypeId, "docked") - elif rbtPauseAv == True: - thing.setStateValue(robotStateStateTypeId, "cleaning") - elif rbtResumeAv == True: - thing.setStateValue(robotStateStateTypeId, "paused") - elif rbtStartAv == True: - thing.setStateValue(robotStateStateTypeId, "stopped") + if rbtStateJson['error'] == None: + rbtError = "no error" else: - thing.setStateValue(robotStateStateTypeId, "error") + 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(): @@ -159,7 +170,6 @@ def pollService(): refreshRobot(thing) except: logger.warn("Error refreshing robot state") - # restart the timer for next poll global pollTimer pollTimer = threading.Timer(60, pollService) @@ -167,17 +177,25 @@ def pollService(): def executeAction(info): - return; # Temporary, to not accidentally start it - + # return; # Temporary, to not accidentally start it + if info.actionTypeId == robotStartCleaningActionTypeId: - # To do: add a parameter to the start action which takes a zone id - thingsAndRobots[info.thing].start_cleaning() + refreshRobot(info.thing) + if info.thing.stateValue(robotRobotStateStateTypeId) == "paused": + thingsAndRobots[info.thing].resume_cleaning() + else: + # To do: add a parameter to the start action which takes a zone id + cleanWithRobot(info.thing, None, None) refreshRobot(info.thing) info.finish(nymea.ThingErrorNoError) return if info.actionTypeId == robotPauseCleaningActionTypeId: - thingsAndRobots[info.thing].pause_cleaning() + 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 @@ -194,6 +212,28 @@ def executeAction(info): 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 browseThing(browseResult): robotThing = browseResult.thing @@ -241,6 +281,8 @@ def browseThing(browseResult): def executeBrowserItem(info): # TODO: An item in the browser has been clicked. logger.log("Browser item clicked:", info.itemId) + logger.log("Parent item:", ) + # cleanWithRobot(info.thing, mapID, boundaryID) info.finish(nymea.ThingErrorNoError) @@ -248,4 +290,4 @@ def deinit(): global pollTimer # If we started a poll timer, cancel it on shutdown. if pollTimer is not None: - pollTimer.cancel() + pollTimer.cancel() \ No newline at end of file From 2a9a1f2d0bc0a44d3f7812b518332f752886f4c8 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 11 Apr 2021 20:04:09 +0200 Subject: [PATCH 06/17] Add support for cleaning individual rooms only --- neatobotvac/integrationpluginneatobotvac.json | 6 +- neatobotvac/integrationpluginneatobotvac.py | 58 ++++++++++++------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.json b/neatobotvac/integrationpluginneatobotvac.json index 9e2ab636..89855012 100644 --- a/neatobotvac/integrationpluginneatobotvac.json +++ b/neatobotvac/integrationpluginneatobotvac.json @@ -149,10 +149,10 @@ "paramTypes": [ { "id": "08a86cca-cdc9-4520-af1d-8413a0c274b5", - "name": "zones", + "name": "zone", "displayName": "Zones to clean", - "type": "QStringList", - "defaultValue": [] + "type": "QString", + "defaultValue": "" } ] }, diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index fc9aae0e..9ddada50 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -91,7 +91,7 @@ def setupThing(info): # If no poll timer is set up yet, start it now logger.log("Creating polltimer") global pollTimer - if pollTimer is not None: + if pollTimer is None: pollTimer = threading.Timer(5, pollService) pollTimer.start() @@ -112,7 +112,6 @@ def setupThing(info): 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) @@ -176,16 +175,24 @@ def pollService(): pollTimer.start() -def executeAction(info): - # return; # Temporary, to not accidentally start it - +def executeAction(info): if info.actionTypeId == robotStartCleaningActionTypeId: - refreshRobot(info.thing) - if info.thing.stateValue(robotRobotStateStateTypeId) == "paused": - thingsAndRobots[info.thing].resume_cleaning() + zone = info.paramValue(robotStartCleaningActionZoneParamTypeId) + logger.log("zone", zone) + + if zone != "": + ids = zone[9:]; + mapId = ids.split(";")[0] + boundaryId = ids.split(";")[1] + cleanWithRobot(info.thing, mapId, boundaryId) + else: - # To do: add a parameter to the start action which takes a zone id - cleanWithRobot(info.thing, None, None) + 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 @@ -212,10 +219,11 @@ def executeAction(info): 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) + logger.log("Cleaning with robot:", robot, robotThing, mapID, boundaryID) boolEco = robotThing.setting(robotSettingsEcoParamTypeId) boolCare = robotThing.setting(robotSettingsCareParamTypeId) boolNogo = robotThing.setting(robotSettingsNoGoLinesParamTypeId) @@ -233,7 +241,7 @@ def cleanWithRobot(robotThing, mapID, boundaryID): 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 browseThing(browseResult): robotThing = browseResult.thing @@ -270,24 +278,34 @@ def browseThing(browseResult): logger.log("boundaries", type(boundaries), boundaries.json()) for boundary in boundaries.json()["data"]["boundaries"]: -# if boundary["type"] == "polygon": - logger.log("vertices:", json.dumps(boundary["vertices"])) - browseResult.addItem(nymea.BrowserItem(boundary["id"], boundary["name"], json.dumps(boundary), executable=True, disabled=not boundary["enabled"], icon=nymea.BrowserIconFavorites)) + 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): - # TODO: An item in the browser has been clicked. logger.log("Browser item clicked:", info.itemId) - logger.log("Parent item:", ) - # cleanWithRobot(info.thing, mapID, boundaryID) - info.finish(nymea.ThingErrorNoError) + 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() \ No newline at end of file + pollTimer.cancel() From e69aa17a8a69dd956da1bae9e7a7f2d67712fe01 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 11 Apr 2021 22:15:54 +0200 Subject: [PATCH 07/17] Better error handling --- neatobotvac/integrationpluginneatobotvac.py | 24 +++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index 9ddada50..6ca81316 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -46,9 +46,10 @@ def setupThing(info): oAuthSession = OAuthSession(token=token) # Login went well, finish the setup info.finish(nymea.ThingErrorNoError) - except: + except Exception as e: # Login error - info.finish(nymea.ThingErrorAuthenticationFailure, "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 @@ -103,14 +104,14 @@ def setupThing(info): serial = info.thing.paramValue(robotThingSerialParamTypeId) secret = info.thing.paramValue(robotThingSecretParamTypeId) - robot = Robot(serial, secret, info.thing.name) - thingsAndRobots[info.thing] = robot; try: + robot = Robot(serial, secret, info.thing.name) + thingsAndRobots[info.thing] = robot; refreshRobot(info.thing) - except: - logger.warn("Error getting robot state"); - info.finish(nymea.ThingErrorHardwareFailure, "Unable to connect to neato API.") - return; + 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 @@ -274,7 +275,12 @@ def browseThing(browseResult): # browsing boundaries for a map if browseResult.itemId.startswith("map-"): mapId = browseResult.itemId[4:] - boundaries = robot.get_map_boundaries(mapId) + + 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"]: From 2b07c19315273fb72bae9caf5b61b49d7a932c2d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 11 Apr 2021 22:41:44 +0200 Subject: [PATCH 08/17] Update to interface changes --- neatobotvac/integrationpluginneatobotvac.json | 11 +---------- neatobotvac/integrationpluginneatobotvac.py | 17 +++++------------ 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.json b/neatobotvac/integrationpluginneatobotvac.json index 89855012..c69deb9c 100644 --- a/neatobotvac/integrationpluginneatobotvac.json +++ b/neatobotvac/integrationpluginneatobotvac.json @@ -145,16 +145,7 @@ { "id": "1f774998-5fa7-4e3b-8ab0-a8402dd561bb", "name": "startCleaning", - "displayName": "Start cleaning", - "paramTypes": [ - { - "id": "08a86cca-cdc9-4520-af1d-8413a0c274b5", - "name": "zone", - "displayName": "Zones to clean", - "type": "QString", - "defaultValue": "" - } - ] + "displayName": "Start cleaning" }, { "id": "e731faa6-88c9-406d-b505-f89b5f0868b0", diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index 6ca81316..530d728f 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -181,18 +181,11 @@ def executeAction(info): zone = info.paramValue(robotStartCleaningActionZoneParamTypeId) logger.log("zone", zone) - if zone != "": - ids = zone[9:]; - mapId = ids.split(";")[0] - boundaryId = ids.split(";")[1] - cleanWithRobot(info.thing, mapId, boundaryId) - + refreshRobot(info.thing) + if info.thing.stateValue(robotRobotStateStateTypeId) == "paused": + thingsAndRobots[info.thing].resume_cleaning() else: - refreshRobot(info.thing) - if info.thing.stateValue(robotRobotStateStateTypeId) == "paused": - thingsAndRobots[info.thing].resume_cleaning() - else: - cleanWithRobot(info.thing, None, None) + cleanWithRobot(info.thing, None, None) refreshRobot(info.thing) info.finish(nymea.ThingErrorNoError) @@ -241,7 +234,7 @@ def cleanWithRobot(robotThing, mapID, boundaryID): 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) + # thingsAndRobots[robotThing].start_cleaning(mode=intEco, navigation_mode=intCare, category=intNogo, boundary_id=boundaryID, map_id=mapID) def browseThing(browseResult): From 93574d969364857b3cde939ff183a66239b6d559 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 13 Apr 2021 16:19:55 +0200 Subject: [PATCH 09/17] Fix and activate the start_cleaning call --- neatobotvac/integrationpluginneatobotvac.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index 530d728f..3085fbd7 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -178,9 +178,6 @@ def pollService(): def executeAction(info): if info.actionTypeId == robotStartCleaningActionTypeId: - zone = info.paramValue(robotStartCleaningActionZoneParamTypeId) - logger.log("zone", zone) - refreshRobot(info.thing) if info.thing.stateValue(robotRobotStateStateTypeId) == "paused": thingsAndRobots[info.thing].resume_cleaning() @@ -234,7 +231,7 @@ def cleanWithRobot(robotThing, mapID, boundaryID): 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) + thingsAndRobots[robotThing].start_cleaning(mode=intEco, navigation_mode=intCare, category=intNogo, boundary_id=boundaryID, map_id=mapID) def browseThing(browseResult): From 7187d54b3b2fd06b8969a4c1de5b00335a21553e Mon Sep 17 00:00:00 2001 From: loosrob <79396812+loosrob@users.noreply.github.com> Date: Sun, 11 Apr 2021 16:03:05 +0200 Subject: [PATCH 10/17] align actions with cleaning robot interface & use settings when cleaning --- neatobotvac/integrationpluginneatobotvac.py | 25 +++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/neatobotvac/integrationpluginneatobotvac.py b/neatobotvac/integrationpluginneatobotvac.py index 3085fbd7..ef033245 100644 --- a/neatobotvac/integrationpluginneatobotvac.py +++ b/neatobotvac/integrationpluginneatobotvac.py @@ -176,7 +176,7 @@ def pollService(): pollTimer.start() -def executeAction(info): +def executeAction(info): if info.actionTypeId == robotStartCleaningActionTypeId: refreshRobot(info.thing) if info.thing.stateValue(robotRobotStateStateTypeId) == "paused": @@ -210,6 +210,28 @@ def executeAction(info): 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 @@ -299,7 +321,6 @@ def executeBrowserItem(info): info.finish(nymea.ThingErrorItemNotExecutable) - def deinit(): global pollTimer # If we started a poll timer, cancel it on shutdown. From 4a519c9751dbf0acac25dc4ee95ec545fbd787ae Mon Sep 17 00:00:00 2001 From: loosrob <79396812+loosrob@users.noreply.github.com> Date: Wed, 14 Apr 2021 21:05:57 +0200 Subject: [PATCH 11/17] add meta.json & README.md --- neatobotvac/README.md | 17 +++++++++++++++++ neatobotvac/meta.json | 14 ++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 neatobotvac/README.md create mode 100644 neatobotvac/meta.json diff --git a/neatobotvac/README.md b/neatobotvac/README.md new file mode 100644 index 00000000..4cab2e18 --- /dev/null +++ b/neatobotvac/README.md @@ -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/) diff --git a/neatobotvac/meta.json b/neatobotvac/meta.json new file mode 100644 index 00000000..cacff2c6 --- /dev/null +++ b/neatobotvac/meta.json @@ -0,0 +1,14 @@ +{ + "title": "Neato Botvac", + "tagline": "Connect to and control your Neato Botvac connected cleaning robots.", + "icon": "cleaningrobot.png", + "stability": "consumer", + "offline": true, + "technologies": [ + "network" + ], + "categories": [ + "account", + "cleaningrobot" + ] +} From 3b486dbf27729138fecda5bb8388c2d58bb4d9b1 Mon Sep 17 00:00:00 2001 From: loosrob <79396812+loosrob@users.noreply.github.com> Date: Fri, 16 Apr 2021 12:04:21 +0200 Subject: [PATCH 12/17] update logo --- neatobotvac/meta.json | 2 +- neatobotvac/neato.png | Bin 0 -> 9146 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 neatobotvac/neato.png diff --git a/neatobotvac/meta.json b/neatobotvac/meta.json index cacff2c6..b7589f29 100644 --- a/neatobotvac/meta.json +++ b/neatobotvac/meta.json @@ -1,7 +1,7 @@ { "title": "Neato Botvac", "tagline": "Connect to and control your Neato Botvac connected cleaning robots.", - "icon": "cleaningrobot.png", + "icon": "neato.png", "stability": "consumer", "offline": true, "technologies": [ diff --git a/neatobotvac/neato.png b/neatobotvac/neato.png new file mode 100644 index 0000000000000000000000000000000000000000..391c570f92182ed81849499f89e43ae890ea6bbf GIT binary patch literal 9146 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 Date: Fri, 16 Apr 2021 23:11:54 +0200 Subject: [PATCH 13/17] Add neato to plugins meta package --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index ce906773..e15cdd96 100644 --- a/debian/control +++ b/debian/control @@ -1169,6 +1169,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, From edec1ad8b1f7935c1b85abbedfed2a835fddf81a Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 19 Apr 2021 11:36:47 +0200 Subject: [PATCH 14/17] Add missing depenency to pip3 --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index e15cdd96..a49b58b0 100644 --- a/debian/control +++ b/debian/control @@ -485,6 +485,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, nymea-plugins-translations, + pip3, 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 From fb2ea1c12770b1f91702eb40bb63ebb0de5e14d1 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 22 Apr 2021 17:27:29 +0200 Subject: [PATCH 15/17] fix dependency to pip --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index a49b58b0..edb0fcec 100644 --- a/debian/control +++ b/debian/control @@ -485,7 +485,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, nymea-plugins-translations, - pip3, + 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 From 756b3802fed2b485862f8b5a636f1c0f36dcf52a Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 28 Apr 2021 12:16:01 +0200 Subject: [PATCH 16/17] Downgrade python crpytography to 3.3 in order to not fail install on older distros --- neatobotvac/requirements.txt | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/neatobotvac/requirements.txt b/neatobotvac/requirements.txt index ae7b9306..ffe43b48 100644 --- a/neatobotvac/requirements.txt +++ b/neatobotvac/requirements.txt @@ -71,20 +71,6 @@ distro==1.5.0 \ zipp==3.4.1 \ --hash=sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76 \ --hash=sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098 -cryptography==3.4.6 \ - --hash=sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b \ - --hash=sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336 \ - --hash=sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87 \ - --hash=sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7 \ - --hash=sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799 \ - --hash=sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b \ - --hash=sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df \ - --hash=sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0 \ - --hash=sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3 \ - --hash=sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724 \ - --hash=sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2 \ - --hash=sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964 \ - --hash=sha256:926ae3dfd160050158b9ca25d419fb7ee658974564b01aa10c059a75dffab7e8 cffi==1.14.5 \ --hash=sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813 \ --hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \ @@ -134,3 +120,18 @@ 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 From c91f085e93f0465085d85522cf9287fea5155e8f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 28 Apr 2021 13:10:54 +0200 Subject: [PATCH 17/17] Add rpi hash --- neatobotvac/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neatobotvac/requirements.txt b/neatobotvac/requirements.txt index ffe43b48..238cc1b9 100644 --- a/neatobotvac/requirements.txt +++ b/neatobotvac/requirements.txt @@ -134,4 +134,5 @@ cryptography==3.3 \ --hash=sha256:d9f1e520f2ee08c5a88e1ae0b31159bdb13da40a486bea3e9f7d338564850ea6 \ --hash=sha256:ee0084f6d62f083316b08111b122dc4fbe16e534059b92b5ddc3d73dc52dd39c \ --hash=sha256:f43d6e72e3cdf983b5e5e65938c0519ce4eeb42b6af766f714b1414ae7b9f8ef \ - --hash=sha256:f95ca692fafea80f1815bbfecf57c6833c0b21432d17026a077341debee76e79 + --hash=sha256:f95ca692fafea80f1815bbfecf57c6833c0b21432d17026a077341debee76e79 \ + --hash=sha256:19c2cbff0434b4e345d08737a7c706d0ba3c2aeecb89936525481e866fd71cea