diff --git a/README.md b/README.md index 2550070..2b2f078 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,14 @@ # Bumper A standalone implementation of the central server used by Ecovacs Deebot cleaning robots to relay data between the robot and client. +A big thanks to the original project creator @torbjornaxelsson, without his work this project would have taken much longer to build. The project reached its original goal and remained in a stable, but stale state with the last commit in Dec 2017. Since the original release of bumper newer bots have been released that use different protocols, and a growing Ecovacs user base has sparked further interest in the project. + +In early 2019 after making massive changes to the codebase and with ideas for additional feature development, it was decided that @bmartin5692 would take over active development. The fork was detached and all future development of bumper will take place here. + The current master branch is unstable and based on the work for adding D900 series support to bumper, along with other code changes. Additional testing is needed with bots that use the XMPP protocols to ensure compatibility, so at this time it can only be guaranteed for 900-series bots (900/901/907). -Bumper needs users of models that use XMPP (M81 Pro, N79S, Ozmo) to assist with development efforts and testing in order to ensure compatability as bumper moves forward. - -The original bumper code base has been branched off as [v0.1.0](https://github.com/bmartin5692/bumper/tree/v0.1.0 -) and will remain in the original state, this will work for XMPP bots (M81 Pro, and others). +If you want a working version of bumper for XMPP bots (M81 Pro, and others), the original code base has been branched off as [v0.1.0](https://github.com/bmartin5692/bumper/tree/v0.1.0 +) and will remain in the original state. Bumper needs users of models that use XMPP to assist with development efforts in order to ensure compatability as bumper moves forward. *Please note: this software is experimental and not ready for production use. Use at your own risk.* @@ -38,8 +40,7 @@ But seriously, there are a several reasons for eliminating the central server: - Python 3 and pipenv - A network router that has functionality for overriding DNS queries - A client that can connect to Bumper and talk to the robot over the Ecovacs protocol. - - The "Ecovacs" Android or iOS apps can be used if configured properly. See [Using with the official Android/iOS App](#using-with-the-official-androidios-app) below. - - **Note:** Bumper is not compatible with the "Ecovacs Home" app at this time. + - The Android or iOS apps can be used if configured properly. See [Using with the official Android/iOS App](#using-with-the-official-androidios-app) below. - [Sucks](https://github.com/wpietri/sucks) can also be used, which can act as a client and control the robots via command-line. ## Usage @@ -82,9 +83,7 @@ Instructions (verified to work with Sucks 0.8.3) - See the [example script](examples/sucks.py) for how to connect ## Using with the official Android/iOS App -Bumper *can* be used with the official "Ecovacs" app, but with limitations. Your phone needs to use your DNS server with custom settings, and you ***must*** import Bumper's CA cert and trust it before the app will work. - - **Note:** Bumper is not compatible with the "Ecovacs Home" app at this time. - +Bumper *can* be used with the official app, but with limitations. Your phone needs to use your DNS server with custom settings, and you ***must*** import Bumper's CA cert and trust it before the app will work. ### DNS - Configure your DNS server as described above in the [DNS](#dns) section. ### Import the Bumper CA Cert @@ -198,9 +197,6 @@ The Rest API provided by `portal-{countrycode}.ecouser.net` receives the command ***Bumper*** provides a simulated RestAPI and "helper" bot, performing the same function as the central server above. ## Thanks - -A big thanks to the original project creator @torbjornaxelsson, without his work this project would have taken much longer to build. The project reached its original goal and remained in a stable, but stale state with the last commit in Dec 2017. Since the original release of bumper newer bots have been released that use different protocols, and a growing Ecovacs user base has sparked further interest in the project. - -In early 2019 after making massive changes to the codebase and with ideas for additional feature development, it was decided that @bmartin5692 would take over active development. The fork was detached and all future development of bumper will take place here. +A big thanks to the original project creator @torbjornaxelsson, without his work this project would have taken much longer to build. Bumper wouldn't exist without [Sucks](https://github.com/wpietri/sucks), an open source client for Ecovacs robots. Big thanks to @wpietri and contributors! diff --git a/bumper/__init__.py b/bumper/__init__.py index db1a44a..8501819 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -6,6 +6,7 @@ from .mqttserver import MQTTHelperBot from .xmppserver import XMPPServer import asyncio import contextvars +import json import time from datetime import datetime, timedelta import platform @@ -249,6 +250,299 @@ class VacBotDevice(object): "xmpp_connection": self.xmpp_connection, } + def toJSON(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=False)#, indent=4) + +class GlobalVacBotDevice(VacBotDevice): #EcoVacs Home + UILogicId = "" + ota = True + updateInfo = { + "changeLog": "", + "needUpdate": False + } + icon = "" + deviceName = "" + + +#EcoVacs Home Product IOT Map - 2019-05-20 +#https://portal-ww.ecouser.net/api/pim/product/getProductIotMap +EcoVacsHomeProducts = [ + { + "classid": "dl8fht", + "product": { + "UILogicId": "D_600", + "_id": "5acb0fa87c295c0001876ecf", + "icon": "5acc32067c295c0001876eea", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea", + "materialNo": "702-0000-0170", + "name": "DEEBOT 600 Series", + "ota": False, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "02uwxm", + "product": { + "UILogicId": "D_OZMO_SLIM10", + "_id": "5ae1481e7ccd1a0001e1f69e", + "icon": "5b1dddc48bc45700014035a1", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1", + "materialNo": "110-1715-0201", + "name": "DEEBOT OZMO Slim10 Series", + "ota": False, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "y79a7u", + "product": { + "UILogicId": "D_OZMO_900", + "_id": "5b04c0227ccd1a0001e1f6a8", + "icon": "5b04c0217ccd1a0001e1f6a7", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7", + "materialNo": "110-1810-0101", + "name": "DEEBOT OZMO 900 Series", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "jr3pqa", + "product": { + "UILogicId": "D_700", + "_id": "5b43077b8bc457000140363e", + "icon": "5b5ac4cc8d5a56000111e769", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769", + "materialNo": "702-0000-0202", + "name": "DEEBOT 711", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "uv242z", + "product": { + "UILogicId": "D_700", + "_id": "5b5149b4ac0b87000148c128", + "icon": "5b5ac4e45f21100001882bb9", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9", + "materialNo": "702-0000-0205", + "name": "DEEBOT 710", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "ls1ok3", + "product": { + "UILogicId": "D_900", + "_id": "5b6561060506b100015c8868", + "icon": "5ba4a2cb6c2f120001c32839", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839", + "materialNo": "110-1711-0201", + "name": "DEEBOT 900 Series", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "eyi9jv", + "product": { + "UILogicId": "D_700", + "_id": "5b7b65f364e1680001a08b54", + "icon": "5b7b65f176f7f10001e9a0c2", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7b65f176f7f10001e9a0c2", + "materialNo": "715", + "name": "DEEBOT 715", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "4zfacv", + "product": { + "UILogicId": "DN_2G", + "_id": "5bf2596f23244a00013f2f13", + "icon": "5c778731280fda0001770ba0", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c778731280fda0001770ba0", + "materialNo": "910", + "name": "DEEBOT 910", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "vi829v", + "product": { + "UILogicId": "DX_5G", + "_id": "5c19a8f3a1e6ee0001782247", + "icon": "5c9c7995e9e9270001354ab4", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c9c7995e9e9270001354ab4", + "materialNo": "920", + "name": "DEEBOT OZMO 920 Series", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "gd4uut", + "product": { + "UILogicId": "DR_935G", + "_id": "5bc8189d68142800016a6937", + "icon": "5c7384767b93c700013f12e7", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c7384767b93c700013f12e7", + "materialNo": "960", + "name": "DEEBOT OZMO 960", + "ota": True, + "supportType": { + "alexa": True, + "assistant": True, + "share": False, + "tmjl": False + } + } + }, + { + "classid": "9akc61", + "product": { + "UILogicId": "D_500", + "_id": "5c763f8263023c0001e7f855", + "icon": "5c932067280fda0001770d7f", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c932067280fda0001770d7f", + "materialNo": "D505", + "name": "DEEBOT 505", + "ota": False, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "r8ead0", + "product": { + "UILogicId": "D_500", + "_id": "5c763f63280fda0001770b88", + "icon": "5c93204b63023c0001e7faa7", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c93204b63023c0001e7faa7", + "materialNo": "D502", + "name": "DEEBOT 502", + "ota": False, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "emzppx", + "product": { + "UILogicId": "D_500", + "_id": "5c763f35280fda0001770b84", + "icon": "5c931fef280fda0001770d7e", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c931fef280fda0001770d7e", + "materialNo": "D501", + "name": "DEEBOT 501", + "ota": False, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "vsc5ia", + "product": { + "UILogicId": "D_500", + "_id": "5c763eba280fda0001770b81", + "icon": "5c874326280fda0001770d2a", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c874326280fda0001770d2a", + "materialNo": "D500", + "name": "DEEBOT 500", + "ota": False, + "supportType": { + "alexa": True, + "assistant": True, + "share": True, + "tmjl": False + } + } + }, + { + "classid": "aqdd5p", + "product": { + "UILogicId": "D_900", + "_id": "5cb7cfba179839000114d762", + "icon": "5cb7cfbab72c4d00010e5fc7", + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cb7cfbab72c4d00010e5fc7", + "materialNo": "110-1711-0001", + "name": "DEEBOT DE55", + "ota": True, + "supportType": { + "alexa": False, + "assistant": False, + "share": False, + "tmjl": False + } + } + } + ] + + + + class VacBotClient(object): def __init__(self, userid="", realm="", token=""): @@ -289,6 +583,21 @@ def check_authcode(uid, authcode): return False +def loginByItToken(authcode): + bumperlog.debug("Checking for authcode: {}".format(authcode)) + tokens = db_get().table("tokens") + tmpauth = tokens.get( + (Query().authcode == authcode) + #& ( # Match authcode + # (Query().userid == uid.replace("fuid_", "")) + # | (Query().userid == "fuid_{}".format(uid)) + #) # Userid with or without fuid_ + ) + if tmpauth: + return {"token":tmpauth["token"], "userid":tmpauth["userid"]} + + return {} + def check_token(uid, token): bumperlog.debug("Checking for token: {}".format(token)) @@ -342,6 +651,16 @@ def bot_get(did): return bots.get(Bot.did == did) +def bot_toEcoVacsHome_JSON(bot): #EcoVacs Home + for botprod in EcoVacsHomeProducts: + if botprod["classid"] == bot["class"]: + bot["UILogicId"] = botprod["product"]["UILogicId"] + bot["ota"] = botprod["product"]["ota"] + bot["icon"] = botprod["product"]["iconUrl"] + return json.dumps(bot, default=lambda o: o.__dict__, + sort_keys=False)#, indent=4) + + def bot_full_upsert(vacbot): bots = db_get().table("bots") Bot = Query() diff --git a/bumper/confserver.py b/bumper/confserver.py index 375e931..beb25da 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -11,6 +11,7 @@ import asyncio import contextvars from aiohttp import web import uuid +import xml.etree.ElementTree as ET class aiohttp_filter(logging.Filter): @@ -35,6 +36,21 @@ confserverlog = logging.getLogger("confserver") logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger logging.getLogger("aiohttp.access").addFilter(aiohttp_filter()) +class EcoVacs_Login: + accessToken = "" + country = "" + email = "" + uid = "" + username = "" + + def toJSON(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=False)#, indent=4) + +class EcoVacsHome_Login(EcoVacs_Login): + loginName = "" + mobile = "" + ucUid = "" class ConfServer: def __init__(self, address, usessl=False, helperbot=None): @@ -95,6 +111,10 @@ class ConfServer: "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", self.handle_login, ), + web.get( #EcoVacs Home GetUserAccountInfo + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserAccountInfo", + self.handle_getUserAccountInfo, + ), web.get( "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout", self.handle_logout, @@ -103,24 +123,72 @@ class ConfServer: "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode", self.handle_getAuthCode, ), + web.get( #EcoVacs Home GetAuthCode + "/{apiversion}/{apptype}/auth/getAuthCode", + self.handle_getAuthCode + ), web.get( "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement", self.handle_checkAgreement, ), + web.get( #EcoVacs Home CheckAgreement + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreementBatch", + self.handle_checkAgreement, + ), web.get( "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion", self.handle_checkVersion, ), + web.get( #EcoVacs Home CheckAPPVersion + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkAPPVersion", + self.handle_checkAPPVersion, + ), + web.get( #EcoVacs Home Upload Device Info + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/uploadDeviceInfo", + self.handle_uploadDeviceInfo, + ), + web.get( #EcoVacs Home GetAdByPositionType + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getAdByPositionType", + self.handle_getAdByPositionType, + ), + web.get( #EcoVacs Home Get Boot Screen + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getBootScreen", + self.handle_getBootScreen, + ), + web.get( #EcoVacs Home message hasUnreadMsg + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/hasUnreadMsg", + self.handle_hasUnreadMessage, + ), + web.get( #EcoVacs Home neng message hasUnreadMsg + "/api/neng/message/hasUnreadMsg", + self.handle_neng_hasUnreadMessage, + ), + web.get( #EcoVacs Home message getMsgList + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/getMsgList", + self.handle_getMsgList, + ), + web.get( #EcoVacs Home common getSystemReminder + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getSystemReminder", + self.handle_getSystemReminder, + ), + web.get( #EcoVacs Home shop getCnWapShopConfig + "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/shop/getCnWapShopConfig", + self.handle_getCnWapShopConfig, + ), web.get( "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/campaign/homePageAlert", self.handle_homePageAlert, ), web.post("/api/users/user.do", self.handle_usersapi), web.get("/api/users/user.do", self.handle_usersapi), + web.post("/api/appsvr/app.do", self.handle_appsvr_api), #EcoVacs Home + web.get("/api/appsvr/app.do", self.handle_appsvr_api), #EcoVacs Home web.post( "/api/pim/product/getProductIotMap", self.handle_getProductIotMap ), + web.post("/api/lg/log.do", self.handle_lg_log), #EcoVacs Home web.post("/api/iot/devmanager.do", self.handle_devmanager_botcommand), + web.post("/api/dim/devmanager.do", self.handle_dim_devmanager), #EcoVacs Home web.post("/lookup.do", self.handle_lookup), ] ) @@ -174,6 +242,7 @@ class ConfServer: try: user_devid = request.match_info.get("devid", "") countrycode = request.match_info.get("country", "us") + apptype = request.match_info.get("apptype", "") confserverlog.info( "client with devid {} attempting login".format(user_devid) ) @@ -182,30 +251,45 @@ class ConfServer: not user_devid == "" ): # Performing basic "auth" using devid, super insecure user = bumper.user_by_deviceid(user_devid) - if "checkLogin" in request.path: + if "checkLogin" in request.path: self.check_token( - countrycode, user, request.query["accessToken"] + apptype, countrycode, user, request.query["accessToken"] ) - else: + else: + if "global_" in apptype: #EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(user["userid"]) + login_details.loginName = "fusername_{}".format(user["userid"]) + login_details.mobile = None + + else: + login_details = EcoVacs_Login() + # Deactivate old tokens and authcodes bumper.user_revoke_expired_tokens(user["userid"]) + login_details.accessToken = self.generate_token(user) + login_details.uid = "fuid_{}".format(user["userid"]) + login_details.username = "fusername_{}".format(user["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + body = { "code": bumper.RETURN_API_SUCCESS, - "data": { - "accessToken": self.generate_token( - user - ), # generate a new token - "country": countrycode, - "email": "null@null.com", - "uid": "fuid_{}".format(user["userid"]), - "username": "fusername_{}".format(user["userid"]), - }, + "data": json.loads(login_details.toJSON()), + #{ + # "accessToken": self.generate_token(tmpuser), # Generate a token + # "country": countrycode, + # "email": "null@null.com", + # "uid": "fuid_{}".format(tmpuser["userid"]), + # "username": "fusername_{}".format(tmpuser["userid"]), + #}, "msg": "操作成功", "time": bumper.get_milli_time( datetime.utcnow().timestamp() ), } + return web.json_response(body) body = { @@ -219,23 +303,81 @@ class ConfServer: else: return web.json_response( - self._auth_any(user_devid, countrycode, request) + self._auth_any(user_devid, apptype, countrycode, request) ) except Exception as e: confserverlog.exception("{}".format(e)) - def check_token(self, countrycode, user, token): - if bumper.check_token(user["userid"], token): + def handle_getUserAccountInfo(self, request): + try: + user_devid = request.match_info.get("devid", "") + countrycode = request.match_info.get("country", "us") + apptype = request.match_info.get("apptype", "") + user = bumper.user_by_deviceid(user_devid) + + if "global_" in apptype: #EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(user["userid"]) + login_details.loginName = "fusername_{}".format(user["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.uid = "fuid_{}".format(user["userid"]) + login_details.username = "fusername_{}".format(user["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + body = { "code": bumper.RETURN_API_SUCCESS, - "data": { - "accessToken": token, - "country": countrycode, - "email": "null@null.com", - "uid": "fuid_{}".format(user["userid"]), - "username": "fusername_{}".format(user["userid"]), - }, + "data": + { + "email": login_details.email, + "hasMobile": "N", + "hasPassword": "Y", + "uid": login_details.uid, + "userName": login_details.username, + "obfuscatedMobile": None, + "mobile": None, + "loginName": login_details.loginName + }, + "msg": "操作成功", + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + + def check_token(self, apptype, countrycode, user, token): + if bumper.check_token(user["userid"], token): + + if "global_" in apptype: #EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(user["userid"]) + login_details.loginName = "fusername_{}".format(user["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.accessToken = token + login_details.uid = "fuid_{}".format(user["userid"]) + login_details.username = "fusername_{}".format(user["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": json.loads(login_details.toJSON()), + #{ + # "accessToken": self.generate_token(tmpuser), # Generate a token + # "country": countrycode, + # "email": "null@null.com", + # "uid": "fuid_{}".format(tmpuser["userid"]), + # "username": "fusername_{}".format(tmpuser["userid"]), + #}, "msg": "操作成功", "time": bumper.get_milli_time(datetime.utcnow().timestamp()), } @@ -260,7 +402,7 @@ class ConfServer: bumper.user_add_authcode(user["userid"], token, tmpauthcode) return tmpauthcode - def _auth_any(self, devid, country, request): + def _auth_any(self, devid, apptype, country, request): try: user_devid = devid countrycode = country @@ -269,10 +411,36 @@ class ConfServer: if user: # Default to user 0 tmpuser = user + if "global_" in apptype: #EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) + login_details.loginName = "fusername_{}".format(tmpuser["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.accessToken = self.generate_token(tmpuser) + login_details.uid = "fuid_{}".format(tmpuser["userid"]) + login_details.username = "fusername_{}".format(tmpuser["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" bumper.user_add_device(tmpuser["userid"], user_devid) else: bumper.user_add("tmpuser") # Add a new user tmpuser = bumper.user_get("tmpuser") + if "global_" in apptype: #EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) + login_details.loginName = "fusername_{}".format(tmpuser["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.accessToken = self.generate_token(tmpuser) + login_details.uid = "fuid_{}".format(tmpuser["userid"]) + login_details.username = "fusername_{}".format(tmpuser["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" bumper.user_add_device(tmpuser["userid"], user_devid) for bot in bots: # Add all bots to the user @@ -280,7 +448,7 @@ class ConfServer: if "checkLogin" in request.path: # If request was to check a token do so checkToken = self.check_token( - countrycode, tmpuser, request.query["accessToken"] + apptype, countrycode, tmpuser, request.query["accessToken"] ) isGood = json.loads(checkToken.text) if isGood["code"] == "0000": @@ -288,16 +456,18 @@ class ConfServer: # Deactivate old tokens and authcodes bumper.user_revoke_expired_tokens(tmpuser["userid"]) + body = { "code": bumper.RETURN_API_SUCCESS, - "data": { - "accessToken": self.generate_token(tmpuser), # Generate a token - "country": countrycode, - "email": "null@null.com", - "uid": "fuid_{}".format(tmpuser["userid"]), - "username": "fusername_{}".format(tmpuser["userid"]), - }, + "data": json.loads(login_details.toJSON()), + #{ + # "accessToken": self.generate_token(tmpuser), # Generate a token + # "country": countrycode, + # "email": "null@null.com", + # "uid": "fuid_{}".format(tmpuser["userid"]), + # "username": "fusername_{}".format(tmpuser["userid"]), + #}, "msg": "操作成功", "time": bumper.get_milli_time(datetime.utcnow().timestamp()), } @@ -333,14 +503,19 @@ class ConfServer: async def handle_getAuthCode(self, request): try: + apptype = request.match_info.get("apptype", "") + user_devid = request.match_info.get("devid", "") #Ecovacs + if user_devid == "": + user_devid = request.query["deviceId"] #Ecovacs Home - user_devid = request.match_info.get("devid", "") if not user_devid == "": user = bumper.user_by_deviceid(user_devid) + token = "" if user: - token = bumper.user_get_token( - user["userid"], request.query["accessToken"] - ) + if "accessToken" in request.query: + token = bumper.user_get_token( + user["userid"], request.query["accessToken"] + ) if token: authcode = "" if not "authcode" in token: @@ -351,18 +526,31 @@ class ConfServer: ) else: authcode = token["authcode"] - - body = { + if "global" in apptype: + body = { "code": bumper.RETURN_API_SUCCESS, "data": { "authCode": authcode, "ecovacsUid": request.query["uid"], }, "msg": "操作成功", + "success": True, "time": bumper.get_milli_time( datetime.utcnow().timestamp() ), } + else: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "authCode": authcode, + "ecovacsUid": request.query["uid"], + }, + "msg": "操作成功", + "time": bumper.get_milli_time( + datetime.utcnow().timestamp() + ), + } return web.json_response(body) body = { @@ -399,17 +587,206 @@ class ConfServer: except Exception as e: confserverlog.exception("{}".format(e)) - async def handle_checkAgreement(self, request): + async def handle_checkAPPVersion(self, request): #EcoVacs Home try: body = { "code": bumper.RETURN_API_SUCCESS, - "data": [], + "data": { + "c": None, + "downPageUrl": None, + "img": None, + "nextAlertTime": None, + "r": 0, + "t": None, + "u": None, + "ut": 0, + "v": None, + }, "msg": "操作成功", + "success": True, "time": bumper.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) + except Exception as e: + confserverlog.exception("{}".format(e)) + + async def handle_uploadDeviceInfo(self, request): #EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + async def handle_getAdByPositionType(self, request): #EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + async def handle_getBootScreen(self, request): #EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + async def handle_hasUnreadMessage(self, request): #EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": "N", + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + + async def handle_neng_hasUnreadMessage(self, request): #EcoVacs Home + try: + body = { + "code": 0, + "data": { + "hasUnRead": True + }, + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + + async def handle_getMsgList(self, request): #EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "hasNextPage": 0, + "items": [] + }, + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + async def handle_getCnWapShopConfig(self, request): #EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "myShopShowFlag": "N", + "myShopUrl": "", + "shopIndexShowFlag": "N", + "shopIndexUrl": "" + }, + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + async def handle_getSystemReminder(self, request): #EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": + { + "iosGradeTime": { + "iodGradeFlag": "N" + }, + "openNotification": { + "openNotificationContent": None, + "openNotificationFlag": "N", + "openNotificationTitle": None + } + }, + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + async def handle_checkAgreement(self, request): + try: + apptype = request.match_info.get("apptype", "") + if "global_" in apptype: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": [ + { + "force": "N", + "id": "20180804040641_7d746faf18b8cb22a50d145598fe4c90", + "type": "USER", + "url": "https://bumper.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN", #"https://gl-us-wap.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN + "version": "1.01" + }, + { + "force": "N", + "id": "20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac", + "type": "PRIVACY", + "url": "https://bumper.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN", #"https://gl-us-wap.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN" + "version": "1.01" + } + ], + "msg": "操作成功", + "success": True, + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + else: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": [], + "msg": "操作成功", + "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + except Exception as e: confserverlog.exception("{}".format(e)) @@ -439,6 +816,7 @@ class ConfServer: confserverlog.exception("{}".format(e)) async def handle_getProductIotMap(self, request): + user_devid = request.match_info.get("devid", "") try: body = { "code": bumper.RETURN_API_SUCCESS, @@ -541,14 +919,28 @@ class ConfServer: body = {"result": "ok", "ip": "47.88.66.164", "port": 8005} elif todo == "loginByItToken": - if bumper.check_authcode(postbody["userId"], postbody["token"]): - body = { - "resource": postbody["resource"], - "result": "ok", - "todo": "result", - "token": postbody["token"], - "userId": postbody["userId"], - } + if "userId" in postbody: + if bumper.check_authcode(postbody["userId"], postbody["token"]): + body = { + "resource": postbody["resource"], + "result": "ok", + "todo": "result", + "token": postbody["token"], + "userId": postbody["userId"], + } + else: #EcoVacs Home LoginByITToken + loginToken = bumper.loginByItToken(postbody["token"]) + if not loginToken == {}: + body = { + "resource": postbody["resource"], + "result": "ok", + "todo": "result", + "token": loginToken["token"], + "userId": loginToken["userid"], + } + else: + body = {"result": "fail", "todo": "result"} + elif todo == "GetDeviceList": body = { @@ -582,6 +974,51 @@ class ConfServer: body = {"result": "fail", "todo": "result"} return web.json_response(body) + + + async def handle_appsvr_api(self, request): + if not request.method == "GET": # Skip GET for now + try: + + body = {} + postbody = {} + if request.content_type == "application/x-www-form-urlencoded": + postbody = await request.post() + + else: + postbody = json.loads(await request.text()) + + todo = postbody["todo"] + + if todo == "GetGlobalDeviceList": #EcoVacs Home + bots = bumper.db_get().table("bots").all() + botlist = [] + for bot in bots: + if bot["class"] != "": + b = bumper.bot_toEcoVacsHome_JSON(bot) + botlist.append(json.loads(b)) + + body = { + "code": 0, + "devices": botlist, + "ret": "ok", + "todo": "result", + } + + confserverlog.debug( + "\r\n POST: {} \r\n Response: {}".format(postbody, body) + ) + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + + # Return fail for GET + body = {"result": "fail", "todo": "result"} + return web.json_response(body) + + async def handle_lookup(self, request): try: @@ -623,14 +1060,96 @@ class ConfServer: except Exception as e: confserverlog.exception("{}".format(e)) + + async def handle_lg_log(self, request): #EcoVacs Home + try: + json_body = json.loads(await request.text()) + + randomid = "".join(random.sample(string.ascii_letters, 6)) + did = json_body["did"] + + botdetails = bumper.bot_get(did) + if botdetails: + if not "cmdName" in json_body: + if "td" in json_body: + json_body["cmdName"] = json_body["td"] + #json_body["td"] = "q" + + if not "toId" in json_body: + json_body["toId"] = did + + if not "toType" in json_body: + json_body["toType"] = botdetails["class"] + + if not "toRes" in json_body: + json_body["toRes"] = botdetails["resource"] + + if not "payloadType" in json_body: + json_body["payloadType"] = "x" + + if not "payload" in json_body: + json_body["payload"] = "" + if json_body["td"] == "GetCleanLogs": + json_body["td"] = "q" + json_body["payload"] = '' #" + + + if did != "": + confserverlog.debug("BotCommand: {}".format(json_body)) + bot = bumper.bot_get(did) + if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: + body = "" + retcmd = await self.helperbot.send_command(json_body, randomid) + logs = [] + logsroot = ET.fromstring(retcmd["resp"]) + if logsroot.attrib["ret"] == "ok": + cleanlogs = logsroot.getchildren() + for l in cleanlogs: + logs.append(l.attrib) + + body = { + "ret": "ok", + #"logs": logs, #TODO: Doesn't parse correctly, new protocol & server side processing + "logs": [] + } + + else: + body = { + "ret": "ok", + "logs": [], + } + + confserverlog.debug( + "\r\n POST: {} \r\n Response: {}".format(json_body, body) + ) + + return web.json_response(body) + else: + # No response, send error back + confserverlog.error( + "No bots with DID: {} connected to MQTT".format( + json_body["toId"] + ) + ) + body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} + return web.json_response(body) + + + except Exception as e: + confserverlog.exception("{}".format(e)) + async def handle_devmanager_botcommand(self, request): try: json_body = json.loads(await request.text()) - confserverlog.debug("BotCommand: {}".format(json_body)) + randomid = "".join(random.sample(string.ascii_letters, 6)) + did = "" + if "toId" in json_body: # Its a command + did = json_body["toId"] - if "toId" in json_body: # Its a command - bot = bumper.bot_get(json_body["toId"]) + if did != "": + confserverlog.debug("BotCommand: {}".format(json_body)) + bot = bumper.bot_get(did) if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: retcmd = await self.helperbot.send_command(json_body, randomid) body = retcmd @@ -647,15 +1166,63 @@ class ConfServer: ) body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} return web.json_response(body) + else: if "td" in json_body: # Seen when doing initial wifi config if json_body["td"] == "PollSCResult": body = {"ret": "ok"} return web.json_response(body) + + if json_body["td"] == "HasUnreadMsg": #EcoVacs Home + body = {"ret":"ok","unRead":False} + return web.json_response(body) except Exception as e: confserverlog.exception("{}".format(e)) + + async def handle_dim_devmanager(self, request): + try: + json_body = json.loads(await request.text()) + + randomid = "".join(random.sample(string.ascii_letters, 6)) + did = "" + if "toId" in json_body: # Its a command + did = json_body["toId"] + + if did != "": + confserverlog.debug("BotCommand: {}".format(json_body)) + bot = bumper.bot_get(did) + if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: + retcmd = await self.helperbot.send_command(json_body, randomid) + body = retcmd + confserverlog.debug( + "\r\n POST: {} \r\n Response: {}".format(json_body, body) + ) + return web.json_response(body) + else: + # No response, send error back + confserverlog.error( + "No bots with DID: {} connected to MQTT".format( + json_body["toId"] + ) + ) + body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} + return web.json_response(body) + + else: + if "td" in json_body: # Seen when doing initial wifi config + if json_body["td"] == "PollSCResult": + body = {"ret": "ok"} + return web.json_response(body) + + if json_body["td"] == "HasUnreadMsg": #EcoVacs Home + body = {"ret":"ok","unRead":False} + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) + def disconnect(self): try: confserverlog.info("shutting down")