From 58b35678876201acfe55b309dea61b478cb97213 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Wed, 19 Jun 2019 09:29:45 -0400 Subject: [PATCH] split models and db from init Move models into their own file Move db functions into their own file Update tests --- bumper/__init__.py | 744 +-------------------------------------- bumper/confserver.py | 90 ++--- bumper/db.py | 373 ++++++++++++++++++++ bumper/models.py | 401 +++++++++++++++++++++ bumper/mqttserver.py | 12 +- tests/test_confserver.py | 14 + tests/test_db.py | 145 ++++++++ tests/test_init.py | 156 +------- tests/test_mqttserver.py | 60 ++-- tests/test_xmppserver.py | 49 ++- tests/test_z_problem.py | 1 - 11 files changed, 1064 insertions(+), 981 deletions(-) create mode 100644 bumper/db.py create mode 100644 bumper/models.py create mode 100644 tests/test_db.py diff --git a/bumper/__init__.py b/bumper/__init__.py index 8ee6006..f7e73c2 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -3,14 +3,12 @@ from bumper.confserver import ConfServer from bumper.mqttserver import MQTTServer, MQTTHelperBot from bumper.xmppserver import XMPPServer +from bumper.models import * +from bumper.db import * import asyncio -import json -from datetime import datetime, timedelta import os import logging from logging.handlers import RotatingFileHandler -from tinydb import TinyDB, Query -from tinydb.storages import MemoryStorage import socket import sys @@ -171,7 +169,7 @@ async def start(): global conf_server_2 conf_server_2 = ConfServer( (bumper_listen, conf2_listen_port), usessl=False, helperbot=mqtt_helperbot - ) + ) global xmpp_server xmpp_server = XMPPServer((bumper_listen, xmpp_listen_port)) @@ -232,739 +230,6 @@ async def shutdown(): bumperlog.info("Shutdown complete") -def get_milli_time(timetoconvert): - return int(round(timetoconvert * 1000)) - - -def db_file(): - if db: - return db - - return os_db_path() - - -def os_db_path(): # createdir=True): - return os.path.join(data_dir, "bumper.db") - - -def db_get(): - try: - # Will create the database if it doesn't exist - db = TinyDB(db_file()) - - # Will create the tables if they don't exist - db.table("users", cache_size=0) - db.table("clients", cache_size=0) - db.table("bots", cache_size=0) - db.table("tokens", cache_size=0) - - return db - - except json.decoder.JSONDecodeError as jerr: - bumperlog.error("JsonErr: {} - Doc: {}".format(jerr.msg, jerr.doc)) - - except Exception as ex: - bumperlog.error(ex) - - -class BumperUser(object): - def __init__(self, userid=""): - self.userid = userid - self.devices = [] - self.bots = [] - - def asdict(self): - return {"userid": self.userid, "devices": self.devices, "bots": self.bots} - - -def user_add(userid): - newuser = BumperUser() - newuser.userid = userid - - user = user_get(userid) - if not user: - bumperlog.info("Adding new user with userid: {}".format(newuser.userid)) - user_full_upsert(newuser.asdict()) - - -def user_get(userid): - users = db_get().table("users") - User = Query() - return users.get(User.userid == userid) - - -def user_by_deviceid(deviceid): - users = db_get().table("users") - User = Query() - return users.get(User.devices.any([deviceid])) - - -def user_full_upsert(user): - opendb = db_get() - with opendb: - users = opendb.table("users") - User = Query() - users.upsert(user, User.did == user["userid"]) - - -def user_add_device(userid, devid): - opendb = db_get() - with opendb: - users = opendb.table("users") - User = Query() - user = users.get(User.userid == userid) - userdevices = list(user["devices"]) - if not devid in userdevices: - userdevices.append(devid) - - users.upsert({"devices": userdevices}, User.userid == userid) - - -def user_remove_device(userid, devid): - opendb = db_get() - with opendb: - users = opendb.table("users") - User = Query() - user = users.get(User.userid == userid) - userdevices = list(user["devices"]) - if devid in userdevices: - userdevices.remove(devid) - - users.upsert({"devices": userdevices}, User.userid == userid) - -def user_add_bot(userid, did): - opendb = db_get() - with opendb: - users = opendb.table("users") - User = Query() - user = users.get(User.userid == userid) - userbots = list(user["bots"]) - if not did in userbots: - userbots.append(did) - - users.upsert({"bots": userbots}, User.userid == userid) - -def user_remove_bot(userid, did): - opendb = db_get() - with opendb: - users = opendb.table("users") - User = Query() - user = users.get(User.userid == userid) - userbots = list(user["bots"]) - if did in userbots: - userbots.remove(did) - - users.upsert({"bots": userbots}, User.userid == userid) - -def user_get_tokens(userid): - tokens = db_get().table("tokens") - return tokens.search((Query().userid == userid)) - - -def user_get_token(userid, token): - tokens = db_get().table("tokens") - return tokens.get((Query().userid == userid) & (Query().token == token)) - - -def user_add_token(userid, token): - opendb = db_get() - with opendb: - tokens = opendb.table("tokens") - tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) - if not tmptoken: - bumperlog.debug("Adding token {} for userid {}".format(token, userid)) - tokens.insert( - { - "userid": userid, - "token": token, - "expiration": "{}".format( - datetime.now() + timedelta(seconds=token_validity_seconds) - ), - } - ) - -def user_revoke_all_tokens(userid): - opendb = db_get() - with opendb: - tokens = opendb.table("tokens") - tsearch = tokens.search(Query().userid == userid) - for i in tsearch: - tokens.remove(doc_ids=[i.doc_id]) - -def user_revoke_expired_tokens(userid): - opendb = db_get() - with opendb: - tokens = opendb.table("tokens") - tsearch = tokens.search(Query().userid == userid) - for i in tsearch: - if datetime.now() >= datetime.fromisoformat(i["expiration"]): - bumperlog.debug("Removing token {} due to expiration".format(i["token"])) - tokens.remove(doc_ids=[i.doc_id]) - - -def user_revoke_token(userid, token): - opendb = db_get() - with opendb: - tokens = opendb.table("tokens") - tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) - if tmptoken: - tokens.remove(doc_ids=[tmptoken.doc_id]) - -def user_add_authcode(userid, token, authcode): - opendb = db_get() - with opendb: - tokens = opendb.table("tokens") - tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) - if tmptoken: - tokens.upsert( - {"authcode": authcode}, - ((Query().userid == userid) & (Query().token == token)), - ) - -def user_revoke_authcode(userid, token, authcode): - opendb = db_get() - with opendb: - tokens = opendb.table("tokens") - tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) - if tmptoken: - tokens.upsert( - {"authcode": ""}, ((Query().userid == userid) & (Query().token == token)) - ) - - -class VacBotDevice(object): - def __init__( - self, did="", vac_bot_device_class="", resource="", name="", nick="", company="" - ): - self.vac_bot_device_class = vac_bot_device_class - self.company = company - self.did = did - self.name = name - self.nick = nick - self.resource = resource - self.mqtt_connection = False - self.xmpp_connection = False - - def asdict(self): - return { - "class": self.vac_bot_device_class, - "company": self.company, - "did": self.did, - "name": self.name, - "nick": self.nick, - "resource": self.resource, - "mqtt_connection": self.mqtt_connection, - "xmpp_connection": self.xmpp_connection, - } - - -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=""): - self.userid = userid - self.realm = realm - self.resource = token - self.mqtt_connection = False - self.xmpp_connection = False - - def asdict(self): - return { - "userid": self.userid, - "realm": self.realm, - "resource": self.resource, - "mqtt_connection": self.mqtt_connection, - "xmpp_connection": self.xmpp_connection, - } - - -def get_disconnected_xmpp_clients(): - clients = db_get().table("clients") - Client = Query() - return clients.search(Client.xmpp_connection == False) - - -def check_authcode(uid, 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 True - - 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)) - tokens = db_get().table("tokens") - tmpauth = tokens.get( - (Query().token == token) - & ( # Match token - (Query().userid == uid.replace("fuid_", "")) - | (Query().userid == "fuid_{}".format(uid)) - ) # Userid with or without fuid_ - ) - if tmpauth: - return True - - return False - - -def revoke_expired_tokens(): - tokens = db_get().table("tokens").all() - for i in tokens: - if datetime.now() >= datetime.fromisoformat(i["expiration"]): - bumperlog.debug("Removing token {} due to expiration".format(i["token"])) - db_get().table("tokens").remove(doc_ids=[i.doc_id]) - - -def bot_add(sn, did, devclass, resource, company): - newbot = VacBotDevice() - newbot.did = did - newbot.name = sn - newbot.vac_bot_device_class = devclass - newbot.resource = resource - newbot.company = company - - bot = bot_get(did) - if not bot: # Not existing bot in database - if ( - not devclass == "" or "@" not in sn or "tmp" not in sn - ): # try to prevent bad additions to the bot list - bumperlog.info( - "Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did) - ) - bot_full_upsert(newbot.asdict()) - - -def bot_remove(did): - bots = db_get().table("bots") - bot = bot_get(did) - bots.remove(doc_ids=[bot.doc_id]) - - -def bot_get(did): - bots = db_get().table("bots") - Bot = Query() - 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() - bots.upsert(vacbot, Bot.did == vacbot["did"]) - - -def bot_set_nick(did, nick): - bots = db_get().table("bots") - Bot = Query() - bots.upsert({"nick": nick}, Bot.did == did) - - -def bot_set_mqtt(did, mqtt): - bots = db_get().table("bots") - Bot = Query() - bots.upsert({"mqtt_connection": mqtt}, Bot.did == did) - - -def bot_set_xmpp(did, xmpp): - bots = db_get().table("bots") - Bot = Query() - bots.upsert({"xmpp_connection": xmpp}, Bot.did == did) - - -def client_add(userid, realm, resource): - newclient = VacBotClient() - newclient.userid = userid - newclient.realm = realm - newclient.resource = resource - - client = client_get(resource) - if not client: - bumperlog.info("Adding new client with resource {}".format(newclient.resource)) - client_full_upsert(newclient.asdict()) - - -def client_get(resource): - clients = db_get().table("clients") - Client = Query() - return clients.get(Client.resource == resource) - - -def client_full_upsert(client): - clients = db_get().table("clients") - Client = Query() - clients.upsert(client, Client.resource == client["resource"]) - - -def client_set_mqtt(resource, mqtt): - clients = db_get().table("clients") - Client = Query() - clients.upsert({"mqtt_connection": mqtt}, Client.resource == resource) - - -def client_set_xmpp(resource, xmpp): - clients = db_get().table("clients") - Client = Query() - clients.upsert({"xmpp_connection": xmpp}, Client.resource == resource) - - -RETURN_API_SUCCESS = "0000" -ERR_ACTIVATE_TOKEN_TIMEOUT = "1006" -ERR_COMMON = "0001" -ERR_DEFAULT = "9000" -ERR_EMAIL_NON_EXIST = "1002" -ERR_EMAIL_SEND_TIME_LIMIT = "1011" -ERR_EMAIL_USED = "1001" -ERR_INTERFACE_AUTH = "0002" -ERR_PARAM_INVALID = "0003" -ERR_PWD_WRONG = "1005" -ERR_RESET_PWD_TOKEN_TIMEOUT = "1007" -ERR_TIMESTAMP_INVALID = "0005" -ERR_TOKEN_INVALID = "0004" -ERR_USER_DISABLE = "1004" -ERR_USER_NOT_ACTIVATED = "1003" -ERR_WRONG_COMFIRM_PWD = "10010" -ERR_WRONG_EMAIL_ADDRESS = "1008" -ERR_WRONG_PWD_FROMATE = "1009" - -API_ERRORS = { - RETURN_API_SUCCESS: "0000", - ERR_ACTIVATE_TOKEN_TIMEOUT: "1006", - ERR_COMMON: "0001", - ERR_DEFAULT: "9000", - ERR_EMAIL_NON_EXIST: "1002", - ERR_EMAIL_SEND_TIME_LIMIT: "1011", - ERR_EMAIL_USED: "1001", - ERR_INTERFACE_AUTH: "0002", - ERR_PARAM_INVALID: "0003", - ERR_PWD_WRONG: "1005", - ERR_RESET_PWD_TOKEN_TIMEOUT: "1007", - ERR_TIMESTAMP_INVALID: "0005", - ERR_TOKEN_INVALID: "0004", - ERR_USER_DISABLE: "1004", - ERR_USER_NOT_ACTIVATED: "1003", - ERR_WRONG_COMFIRM_PWD: "10010", - ERR_WRONG_EMAIL_ADDRESS: "1008", - ERR_WRONG_PWD_FROMATE: "1009", -} - - def create_certs(): import platform import os @@ -1002,7 +267,7 @@ def create_certs(): print("Certificates created") os.chdir(odir) - + if "__main__.py" in sys.argv[0]: os.execv( sys.executable, ["python", "-m", "bumper"] + sys.argv[1:] @@ -1015,6 +280,7 @@ def create_certs(): def first_run(): create_certs() + def main(argv=None): import argparse diff --git a/bumper/confserver.py b/bumper/confserver.py index c211ba4..f1fab45 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -6,6 +6,7 @@ import ssl import string import random import bumper +from bumper.models import * from datetime import datetime, timedelta import asyncio from aiohttp import web @@ -36,25 +37,6 @@ logging.getLogger("aiohttp.access").addFilter( ) # Add logging filter above to aiohttp.access -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): self.helperbot = helperbot @@ -66,6 +48,9 @@ class ConfServer: self.site = None self.runner = None + def get_milli_time(self, timetoconvert): + return int(round(timetoconvert * 1000)) + def confserver_app(self): self.app = web.Application(loop=asyncio.get_event_loop()) @@ -190,9 +175,9 @@ class ConfServer: await self.site.start() except PermissionError as e: - confserverlog.error(e.strerror) + confserverlog.error(e.strerror) asyncio.create_task(bumper.shutdown()) - + except asyncio.CancelledError: pass @@ -202,7 +187,7 @@ class ConfServer: async def stop_server(self): try: - await self.runner.shutdown() + await self.runner.shutdown() except Exception as e: confserverlog.exception("{}".format(e)) @@ -210,13 +195,16 @@ class ConfServer: async def handle_base(self, request): try: # TODO - API Options here for viewing clients, tokens, restarting the server, etc. - #text = "Bumper!" + # text = "Bumper!" bots = bumper.db_get().table("bots").all() clients = bumper.db_get().table("clients").all() helperbot = self.helperbot.Client.session.transitions.state - all = {'bots':bots, 'clients':clients, 'helperbot': [{'state': helperbot}]} - - + all = { + "bots": bots, + "clients": clients, + "helperbot": [{"state": helperbot}], + } + return web.json_response(all) except Exception as e: @@ -261,7 +249,7 @@ class ConfServer: login_details.email = "null@null.com" body = { - "code": bumper.RETURN_API_SUCCESS, + "code": API_ERRORS[RETURN_API_SUCCESS], "data": json.loads(login_details.toJSON()), # { # "accessToken": self.generate_token(tmpuser), # Generate a token @@ -271,7 +259,7 @@ class ConfServer: # "username": "fusername_{}".format(tmpuser["userid"]), # }, "msg": "操作成功", - "time": bumper.get_milli_time( + "time": self.self.get_milli_time( datetime.utcnow().timestamp() ), } @@ -282,7 +270,7 @@ class ConfServer: "code": bumper.ERR_USER_NOT_ACTIVATED, "data": None, "msg": "当前密码错误", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -328,7 +316,7 @@ class ConfServer: "loginName": login_details.loginName, }, "msg": "操作成功", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -364,7 +352,7 @@ class ConfServer: # "username": "fusername_{}".format(tmpuser["userid"]), # }, "msg": "操作成功", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -373,7 +361,7 @@ class ConfServer: "code": bumper.ERR_TOKEN_INVALID, "data": None, "msg": "当前密码错误", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -464,7 +452,7 @@ class ConfServer: # "username": "fusername_{}".format(tmpuser["userid"]), # }, "msg": "操作成功", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return body @@ -488,7 +476,7 @@ class ConfServer: "code": bumper.RETURN_API_SUCCESS, "data": None, "msg": "操作成功", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -530,7 +518,7 @@ class ConfServer: }, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time( + "time": self.get_milli_time( datetime.utcnow().timestamp() ), } @@ -542,7 +530,7 @@ class ConfServer: "ecovacsUid": request.query["uid"], }, "msg": "操作成功", - "time": bumper.get_milli_time( + "time": self.get_milli_time( datetime.utcnow().timestamp() ), } @@ -552,7 +540,7 @@ class ConfServer: "code": bumper.ERR_TOKEN_INVALID, "data": None, "msg": "当前密码错误", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -574,7 +562,7 @@ class ConfServer: "v": None, }, "msg": "操作成功", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -599,7 +587,7 @@ class ConfServer: }, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -614,7 +602,7 @@ class ConfServer: "data": None, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -629,7 +617,7 @@ class ConfServer: "data": None, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -644,7 +632,7 @@ class ConfServer: "data": None, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -659,7 +647,7 @@ class ConfServer: "data": "N", "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -683,7 +671,7 @@ class ConfServer: "data": {"hasNextPage": 0, "items": []}, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -703,7 +691,7 @@ class ConfServer: }, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -725,7 +713,7 @@ class ConfServer: }, "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -757,14 +745,14 @@ class ConfServer: ], "msg": "操作成功", "success": True, - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } else: body = { "code": bumper.RETURN_API_SUCCESS, "data": [], "msg": "操作成功", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) @@ -774,7 +762,7 @@ class ConfServer: async def handle_homePageAlert(self, request): try: - nextAlert = bumper.get_milli_time( + nextAlert = self.get_milli_time( (datetime.now() + timedelta(hours=12)).timestamp() ) @@ -786,10 +774,10 @@ class ConfServer: "hasCampaign": "N", "imageUrl": None, "nextAlertTime": nextAlert, - "serverTime": bumper.get_milli_time(datetime.utcnow().timestamp()), + "serverTime": self.get_milli_time(datetime.utcnow().timestamp()), }, "msg": "操作成功", - "time": bumper.get_milli_time(datetime.utcnow().timestamp()), + "time": self.get_milli_time(datetime.utcnow().timestamp()), } return web.json_response(body) diff --git a/bumper/db.py b/bumper/db.py new file mode 100644 index 0000000..e33574a --- /dev/null +++ b/bumper/db.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +import bumper +from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts +from tinydb import TinyDB, Query +from tinydb.storages import MemoryStorage +from datetime import datetime, timedelta +import os +import json +import logging + + +bumperlog = logging.getLogger("bumper") + + +def db_file(): + if bumper.db: + return bumper.db + + return os_db_path() + + +def os_db_path(): # createdir=True): + return os.path.join(bumper.data_dir, "bumper.db") + + +def db_get(): + try: + # Will create the database if it doesn't exist + db = TinyDB(db_file()) + + # Will create the tables if they don't exist + db.table("users", cache_size=0) + db.table("clients", cache_size=0) + db.table("bots", cache_size=0) + db.table("tokens", cache_size=0) + + return db + + except json.decoder.JSONDecodeError as jerr: + bumperlog.error("JsonErr: {} - Doc: {}".format(jerr.msg, jerr.doc)) + + except Exception as ex: + bumperlog.error(ex) + + +def user_add(userid): + newuser = BumperUser() + newuser.userid = userid + + user = user_get(userid) + if not user: + bumperlog.info("Adding new user with userid: {}".format(newuser.userid)) + user_full_upsert(newuser.asdict()) + + +def user_get(userid): + users = db_get().table("users") + User = Query() + return users.get(User.userid == userid) + + +def user_by_deviceid(deviceid): + users = db_get().table("users") + User = Query() + return users.get(User.devices.any([deviceid])) + + +def user_full_upsert(user): + opendb = db_get() + with opendb: + users = opendb.table("users") + User = Query() + users.upsert(user, User.did == user["userid"]) + + +def user_add_device(userid, devid): + opendb = db_get() + with opendb: + users = opendb.table("users") + User = Query() + user = users.get(User.userid == userid) + userdevices = list(user["devices"]) + if not devid in userdevices: + userdevices.append(devid) + + users.upsert({"devices": userdevices}, User.userid == userid) + + +def user_remove_device(userid, devid): + opendb = db_get() + with opendb: + users = opendb.table("users") + User = Query() + user = users.get(User.userid == userid) + userdevices = list(user["devices"]) + if devid in userdevices: + userdevices.remove(devid) + + users.upsert({"devices": userdevices}, User.userid == userid) + + +def user_add_bot(userid, did): + opendb = db_get() + with opendb: + users = opendb.table("users") + User = Query() + user = users.get(User.userid == userid) + userbots = list(user["bots"]) + if not did in userbots: + userbots.append(did) + + users.upsert({"bots": userbots}, User.userid == userid) + + +def user_remove_bot(userid, did): + opendb = db_get() + with opendb: + users = opendb.table("users") + User = Query() + user = users.get(User.userid == userid) + userbots = list(user["bots"]) + if did in userbots: + userbots.remove(did) + + users.upsert({"bots": userbots}, User.userid == userid) + + +def user_get_tokens(userid): + tokens = db_get().table("tokens") + return tokens.search((Query().userid == userid)) + + +def user_get_token(userid, token): + tokens = db_get().table("tokens") + return tokens.get((Query().userid == userid) & (Query().token == token)) + + +def user_add_token(userid, token): + opendb = db_get() + with opendb: + tokens = opendb.table("tokens") + tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) + if not tmptoken: + bumperlog.debug("Adding token {} for userid {}".format(token, userid)) + tokens.insert( + { + "userid": userid, + "token": token, + "expiration": "{}".format( + datetime.now() + + timedelta(seconds=bumper.token_validity_seconds) + ), + } + ) + + +def user_revoke_all_tokens(userid): + opendb = db_get() + with opendb: + tokens = opendb.table("tokens") + tsearch = tokens.search(Query().userid == userid) + for i in tsearch: + tokens.remove(doc_ids=[i.doc_id]) + + +def user_revoke_expired_tokens(userid): + opendb = db_get() + with opendb: + tokens = opendb.table("tokens") + tsearch = tokens.search(Query().userid == userid) + for i in tsearch: + if datetime.now() >= datetime.fromisoformat(i["expiration"]): + bumperlog.debug( + "Removing token {} due to expiration".format(i["token"]) + ) + tokens.remove(doc_ids=[i.doc_id]) + + +def user_revoke_token(userid, token): + opendb = db_get() + with opendb: + tokens = opendb.table("tokens") + tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) + if tmptoken: + tokens.remove(doc_ids=[tmptoken.doc_id]) + + +def user_add_authcode(userid, token, authcode): + opendb = db_get() + with opendb: + tokens = opendb.table("tokens") + tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) + if tmptoken: + tokens.upsert( + {"authcode": authcode}, + ((Query().userid == userid) & (Query().token == token)), + ) + + +def user_revoke_authcode(userid, token, authcode): + opendb = db_get() + with opendb: + tokens = opendb.table("tokens") + tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) + if tmptoken: + tokens.upsert( + {"authcode": ""}, + ((Query().userid == userid) & (Query().token == token)), + ) + + +def get_disconnected_xmpp_clients(): + clients = db_get().table("clients") + Client = Query() + return clients.search(Client.xmpp_connection == False) + + +def check_authcode(uid, 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 True + + 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)) + tokens = db_get().table("tokens") + tmpauth = tokens.get( + (Query().token == token) + & ( # Match token + (Query().userid == uid.replace("fuid_", "")) + | (Query().userid == "fuid_{}".format(uid)) + ) # Userid with or without fuid_ + ) + if tmpauth: + return True + + return False + + +def revoke_expired_tokens(): + tokens = db_get().table("tokens").all() + for i in tokens: + if datetime.now() >= datetime.fromisoformat(i["expiration"]): + bumperlog.debug("Removing token {} due to expiration".format(i["token"])) + db_get().table("tokens").remove(doc_ids=[i.doc_id]) + + +def bot_add(sn, did, devclass, resource, company): + newbot = VacBotDevice() + newbot.did = did + newbot.name = sn + newbot.vac_bot_device_class = devclass + newbot.resource = resource + newbot.company = company + + bot = bot_get(did) + if not bot: # Not existing bot in database + if ( + not devclass == "" or "@" not in sn or "tmp" not in sn + ): # try to prevent bad additions to the bot list + bumperlog.info( + "Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did) + ) + bot_full_upsert(newbot.asdict()) + + +def bot_remove(did): + bots = db_get().table("bots") + bot = bot_get(did) + bots.remove(doc_ids=[bot.doc_id]) + + +def bot_get(did): + bots = db_get().table("bots") + Bot = Query() + 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() + bots.upsert(vacbot, Bot.did == vacbot["did"]) + + +def bot_set_nick(did, nick): + bots = db_get().table("bots") + Bot = Query() + bots.upsert({"nick": nick}, Bot.did == did) + + +def bot_set_mqtt(did, mqtt): + bots = db_get().table("bots") + Bot = Query() + bots.upsert({"mqtt_connection": mqtt}, Bot.did == did) + + +def bot_set_xmpp(did, xmpp): + bots = db_get().table("bots") + Bot = Query() + bots.upsert({"xmpp_connection": xmpp}, Bot.did == did) + + +def client_add(userid, realm, resource): + newclient = VacBotClient() + newclient.userid = userid + newclient.realm = realm + newclient.resource = resource + + client = client_get(resource) + if not client: + bumperlog.info("Adding new client with resource {}".format(newclient.resource)) + client_full_upsert(newclient.asdict()) + + +def client_get(resource): + clients = db_get().table("clients") + Client = Query() + return clients.get(Client.resource == resource) + + +def client_full_upsert(client): + clients = db_get().table("clients") + Client = Query() + clients.upsert(client, Client.resource == client["resource"]) + + +def client_set_mqtt(resource, mqtt): + clients = db_get().table("clients") + Client = Query() + clients.upsert({"mqtt_connection": mqtt}, Client.resource == resource) + + +def client_set_xmpp(resource, xmpp): + clients = db_get().table("clients") + Client = Query() + clients.upsert({"xmpp_connection": xmpp}, Client.resource == resource) diff --git a/bumper/models.py b/bumper/models.py new file mode 100644 index 0000000..25e091b --- /dev/null +++ b/bumper/models.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python3 +import json + + +class VacBotDevice(object): + def __init__( + self, did="", vac_bot_device_class="", resource="", name="", nick="", company="" + ): + self.vac_bot_device_class = vac_bot_device_class + self.company = company + self.did = did + self.name = name + self.nick = nick + self.resource = resource + self.mqtt_connection = False + self.xmpp_connection = False + + def asdict(self): + return { + "class": self.vac_bot_device_class, + "company": self.company, + "did": self.did, + "name": self.name, + "nick": self.nick, + "resource": self.resource, + "mqtt_connection": self.mqtt_connection, + "xmpp_connection": self.xmpp_connection, + } + + +class BumperUser(object): + def __init__(self, userid=""): + self.userid = userid + self.devices = [] + self.bots = [] + + def asdict(self): + return {"userid": self.userid, "devices": self.devices, "bots": self.bots} + + +class GlobalVacBotDevice(VacBotDevice): # EcoVacs Home + UILogicId = "" + ota = True + updateInfo = {"changeLog": "", "needUpdate": False} + icon = "" + deviceName = "" + + +class VacBotClient(object): + def __init__(self, userid="", realm="", token=""): + self.userid = userid + self.realm = realm + self.resource = token + self.mqtt_connection = False + self.xmpp_connection = False + + def asdict(self): + return { + "userid": self.userid, + "realm": self.realm, + "resource": self.resource, + "mqtt_connection": self.mqtt_connection, + "xmpp_connection": self.xmpp_connection, + } + + +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 = "" + + +# 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, + }, + }, + }, +] + + +RETURN_API_SUCCESS = "0000" +ERR_ACTIVATE_TOKEN_TIMEOUT = "1006" +ERR_COMMON = "0001" +ERR_DEFAULT = "9000" +ERR_EMAIL_NON_EXIST = "1002" +ERR_EMAIL_SEND_TIME_LIMIT = "1011" +ERR_EMAIL_USED = "1001" +ERR_INTERFACE_AUTH = "0002" +ERR_PARAM_INVALID = "0003" +ERR_PWD_WRONG = "1005" +ERR_RESET_PWD_TOKEN_TIMEOUT = "1007" +ERR_TIMESTAMP_INVALID = "0005" +ERR_TOKEN_INVALID = "0004" +ERR_USER_DISABLE = "1004" +ERR_USER_NOT_ACTIVATED = "1003" +ERR_WRONG_COMFIRM_PWD = "10010" +ERR_WRONG_EMAIL_ADDRESS = "1008" +ERR_WRONG_PWD_FROMATE = "1009" + +API_ERRORS = { + RETURN_API_SUCCESS: "0000", + ERR_ACTIVATE_TOKEN_TIMEOUT: "1006", + ERR_COMMON: "0001", + ERR_DEFAULT: "9000", + ERR_EMAIL_NON_EXIST: "1002", + ERR_EMAIL_SEND_TIME_LIMIT: "1011", + ERR_EMAIL_USED: "1001", + ERR_INTERFACE_AUTH: "0002", + ERR_PARAM_INVALID: "0003", + ERR_PWD_WRONG: "1005", + ERR_RESET_PWD_TOKEN_TIMEOUT: "1007", + ERR_TIMESTAMP_INVALID: "0005", + ERR_TOKEN_INVALID: "0004", + ERR_USER_DISABLE: "1004", + ERR_USER_NOT_ACTIVATED: "1003", + ERR_WRONG_COMFIRM_PWD: "10010", + ERR_WRONG_EMAIL_ADDRESS: "1008", + ERR_WRONG_PWD_FROMATE: "1009", +} + diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 843397e..9e5de56 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -103,17 +103,19 @@ class MQTTHelperBot: ) elif str(message.topic).split("/")[1] == "atr": # Broadcast message received on atr - helperbotlog.debug( - "Received Broadcast - Topic: {} - Message: {}".format( - message.topic, str(message.data.decode("utf-8")) - ) - ) if str(message.topic).split("/")[2] == "errors": boterrorlog.error( "Received Error - Topic: {} - Message: {}".format( message.topic, str(message.data.decode("utf-8")) ) ) + else: + helperbotlog.debug( + "Received Broadcast - Topic: {} - Message: {}".format( + message.topic, str(message.data.decode("utf-8")) + ) + ) + else: helperbotlog.debug( "Received Message - Topic: {} - Message: {}".format( diff --git a/tests/test_confserver.py b/tests/test_confserver.py index 0bb6f43..abfaa57 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -7,6 +7,7 @@ import json import tinydb import pytest_aiohttp import pytest_asyncio +import datetime, time from aiohttp import web @@ -42,6 +43,19 @@ async def test_confserver_no_ssl(): conf_server.confserver_app() asyncio.create_task(conf_server.start_server()) + +def test_get_milli_time(): + cserv = create_confserver() + assert ( + cserv.get_milli_time( + datetime.datetime( + 2018, 1, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc + ).timestamp() + ) + == 1514768400000 + ) + + # Comment out test_base until api changes are complete """ async def test_base(aiohttp_client): remove_existing_db() diff --git a/tests/test_db.py b/tests/test_db.py new file mode 100644 index 0000000..52c49a4 --- /dev/null +++ b/tests/test_db.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +import bumper +from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts +from tinydb import TinyDB, Query +from tinydb.storages import MemoryStorage +from datetime import datetime, timedelta +import os +import json +import logging + + +def test_db_path(): + bumper.db = None + assert bumper.db_file() == os.path.join(bumper.data_dir, "bumper.db") + + +def test_user_db(): + + bumper.db = "tests/tmp.db" # Set db location for testing + bumper.user_add("testuser") # Add testuser + + assert ( + bumper.user_get("testuser")["userid"] == "testuser" + ) # Test that testuser was created and returned + + bumper.user_add_device("testuser", "dev_1234") # Add device to testuser + + assert ( + bumper.user_by_deviceid("dev_1234")["userid"] == "testuser" + ) # Test that testuser was found by deviceid + + bumper.user_remove_device("testuser", "dev_1234") # Remove device from testuser + + assert "dev_1234" not in bumper.user_get("testuser")["devices"] + # Test that dev_1234 was not found in testuser devices + + bumper.user_add_bot("testuser", "bot_1234") # Add bot did to testuser + + assert "bot_1234" in bumper.user_get("testuser")["bots"] + # Test that bot was found in testuser's bot list + + bumper.user_remove_bot("testuser", "bot_1234") # Remove bot did from testuser + + assert "bot_1234" not in bumper.user_get("testuser")["bots"] + # Test that bot was not found in testuser's bot list + + bumper.user_add_token("testuser", "token_1234") # Add token to testuser + + assert bumper.check_token("testuser", "token_1234") + # Test that token was found for testuser + + assert bumper.user_get_token("testuser", "token_1234") + # Test that token was returned for testuser + + bumper.user_add_authcode( + "testuser", "token_1234", "auth_1234" + ) # Add authcode to token_1234 for testuser + assert bumper.check_authcode("testuser", "auth_1234") + # Test that authcode was found for testuser + + bumper.user_revoke_authcode( + "testuser", "token_1234", "auth_1234" + ) # Remove authcode from testuser + assert bumper.check_authcode("testuser", "auth_1234") == False + # Test that authcode was not found for testuser + bumper.user_revoke_token("testuser", "token_1234") # Remove token from testuser + assert ( + bumper.check_token("testuser", "token_1234") == False + ) # Test that token was not found for testuser + bumper.user_add_token("testuser", "token_1234") # Add token_1234 + bumper.user_add_token("testuser", "token_4321") # Add token_4321 + assert len(bumper.user_get_tokens("testuser")) == 2 # Test 2 tokens are available + bumper.user_revoke_all_tokens("testuser") # Revoke all tokens + assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available + + db = TinyDB("tests/tmp.db") + tokens = db.table("tokens") + tokens.insert( + { + "userid": "testuser", + "token": "token_1234", + "expiration": "{}".format(datetime.now() + timedelta(seconds=-10)), + } + ) # Add expired token + db.close() + assert len(bumper.user_get_tokens("testuser")) == 1 # Test 1 tokens are available + bumper.user_revoke_expired_tokens("testuser") # Revoke expired tokens + assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available + + db = TinyDB("tests/tmp.db") + tokens = db.table("tokens") + tokens.insert( + { + "userid": "testuser", + "token": "token_1234", + "expiration": "{}".format(datetime.now() + timedelta(seconds=-10)), + } + ) # Add expired token + db.close() + assert len(bumper.user_get_tokens("testuser")) == 1 # Test 1 tokens are available + bumper.revoke_expired_tokens() # Revoke expired tokens + assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available + + +def test_bot_db(): + bumper.db = "tests/tmp.db" # Set db location for testing + bumper.bot_add("sn_123", "did_123", "dev_123", "res_123", "co_123") + assert bumper.bot_get("did_123") # Test that bot was added to db + + bumper.bot_set_nick("did_123", "nick_123") + assert ( + bumper.bot_get("did_123")["nick"] == "nick_123" + ) # Test that nick was added to bot + + bumper.bot_set_mqtt("did_123", True) + assert bumper.bot_get("did_123")[ + "mqtt_connection" + ] # Test that mqtt was set True for bot + + bumper.bot_set_xmpp("did_123", True) + assert bumper.bot_get("did_123")[ + "xmpp_connection" + ] # Test that xmpp was set True for bot + + bumper.bot_remove("did_123") + assert bumper.bot_get("did_123") == None # Test that bot is no longer in db + + +def test_client_db(): + bumper.db = "tests/tmp.db" # Set db location for testing + bumper.client_add("user_123", "realm_123", "resource_123") + assert bumper.client_get("resource_123") # Test client was added + + bumper.client_set_mqtt("resource_123", True) + assert bumper.client_get("resource_123")[ + "mqtt_connection" + ] # Test that mqtt was set True for client + + bumper.client_set_xmpp("resource_123", False) + assert ( + bumper.client_get("resource_123")["xmpp_connection"] == False + ) # Test that xmpp was set False for client + assert ( + len(bumper.get_disconnected_xmpp_clients()) == 1 + ) # Test len of connected xmpp clients is 1 diff --git a/tests/test_init.py b/tests/test_init.py index 7c1218e..67779e0 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -5,29 +5,18 @@ from tinydb.storages import MemoryStorage from tinydb import TinyDB, Query import bumper import os -import datetime, time import platform import json import asyncio from testfixtures import LogCapture -def test_get_milli_time(): - assert ( - bumper.get_milli_time( - datetime.datetime( - 2018, 1, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ).timestamp() - ) - == 1514768400000 - ) - - def test_strtobool(): assert bumper.strtobool("t") == True assert bumper.strtobool("f") == False assert bumper.strtobool(0) == False + async def test_start_stop(): with LogCapture() as l: if os.path.exists("tests/tmp.db"): @@ -36,12 +25,12 @@ async def test_start_stop(): b = bumper b.db = "tests/tmp.db" # Set db location for testing b.conf1_listen_address = "127.0.0.1" - b.conf1_listen_port = 444 + b.conf1_listen_port = 444 asyncio.create_task(b.start()) await asyncio.sleep(0.1) l.check_present(("bumper", "INFO", "Starting Bumper")) l.clear() - + asyncio.create_task(b.shutdown()) await asyncio.sleep(0.1) l.check_present( @@ -71,142 +60,3 @@ async def test_start_stop_debug(): ) assert b.shutting_down == True - -def test_db_path(): - bumper.db = None - assert bumper.db_file() == os.path.join(bumper.data_dir, "bumper.db") - - -def test_user_db(): - - bumper.db = "tests/tmp.db" # Set db location for testing - bumper.user_add("testuser") # Add testuser - - assert ( - bumper.user_get("testuser")["userid"] == "testuser" - ) # Test that testuser was created and returned - - bumper.user_add_device("testuser", "dev_1234") # Add device to testuser - - assert ( - bumper.user_by_deviceid("dev_1234")["userid"] == "testuser" - ) # Test that testuser was found by deviceid - - bumper.user_remove_device("testuser", "dev_1234") # Remove device from testuser - - assert "dev_1234" not in bumper.user_get("testuser")["devices"] - # Test that dev_1234 was not found in testuser devices - - bumper.user_add_bot("testuser", "bot_1234") # Add bot did to testuser - - assert "bot_1234" in bumper.user_get("testuser")["bots"] - # Test that bot was found in testuser's bot list - - bumper.user_remove_bot("testuser", "bot_1234") # Remove bot did from testuser - - assert "bot_1234" not in bumper.user_get("testuser")["bots"] - # Test that bot was not found in testuser's bot list - - bumper.user_add_token("testuser", "token_1234") # Add token to testuser - - assert bumper.check_token("testuser", "token_1234") - # Test that token was found for testuser - - assert bumper.user_get_token("testuser", "token_1234") - # Test that token was returned for testuser - - bumper.user_add_authcode( - "testuser", "token_1234", "auth_1234" - ) # Add authcode to token_1234 for testuser - assert bumper.check_authcode("testuser", "auth_1234") - # Test that authcode was found for testuser - - bumper.user_revoke_authcode( - "testuser", "token_1234", "auth_1234" - ) # Remove authcode from testuser - assert bumper.check_authcode("testuser", "auth_1234") == False - # Test that authcode was not found for testuser - bumper.user_revoke_token("testuser", "token_1234") # Remove token from testuser - assert ( - bumper.check_token("testuser", "token_1234") == False - ) # Test that token was not found for testuser - bumper.user_add_token("testuser", "token_1234") # Add token_1234 - bumper.user_add_token("testuser", "token_4321") # Add token_4321 - assert len(bumper.user_get_tokens("testuser")) == 2 # Test 2 tokens are available - bumper.user_revoke_all_tokens("testuser") # Revoke all tokens - assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available - - db = TinyDB("tests/tmp.db") - tokens = db.table("tokens") - tokens.insert( - { - "userid": "testuser", - "token": "token_1234", - "expiration": "{}".format( - datetime.datetime.now() + datetime.timedelta(seconds=-10) - ), - } - ) # Add expired token - db.close() - assert len(bumper.user_get_tokens("testuser")) == 1 # Test 1 tokens are available - bumper.user_revoke_expired_tokens("testuser") # Revoke expired tokens - assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available - - db = TinyDB("tests/tmp.db") - tokens = db.table("tokens") - tokens.insert( - { - "userid": "testuser", - "token": "token_1234", - "expiration": "{}".format( - datetime.datetime.now() + datetime.timedelta(seconds=-10) - ), - } - ) # Add expired token - db.close() - assert len(bumper.user_get_tokens("testuser")) == 1 # Test 1 tokens are available - bumper.revoke_expired_tokens() # Revoke expired tokens - assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available - - -def test_bot_db(): - bumper.db = "tests/tmp.db" # Set db location for testing - bumper.bot_add("sn_123", "did_123", "dev_123", "res_123", "co_123") - assert bumper.bot_get("did_123") # Test that bot was added to db - - bumper.bot_set_nick("did_123", "nick_123") - assert ( - bumper.bot_get("did_123")["nick"] == "nick_123" - ) # Test that nick was added to bot - - bumper.bot_set_mqtt("did_123", True) - assert bumper.bot_get("did_123")[ - "mqtt_connection" - ] # Test that mqtt was set True for bot - - bumper.bot_set_xmpp("did_123", True) - assert bumper.bot_get("did_123")[ - "xmpp_connection" - ] # Test that xmpp was set True for bot - - bumper.bot_remove("did_123") - assert bumper.bot_get("did_123") == None # Test that bot is no longer in db - - -def test_client_db(): - bumper.db = "tests/tmp.db" # Set db location for testing - bumper.client_add("user_123", "realm_123", "resource_123") - assert bumper.client_get("resource_123") # Test client was added - - bumper.client_set_mqtt("resource_123", True) - assert bumper.client_get("resource_123")[ - "mqtt_connection" - ] # Test that mqtt was set True for client - - bumper.client_set_xmpp("resource_123", False) - assert ( - bumper.client_get("resource_123")["xmpp_connection"] == False - ) # Test that xmpp was set False for client - assert ( - len(bumper.get_disconnected_xmpp_clients()) == 1 - ) # Test len of connected xmpp clients is 1 diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index cf2232b..0f0ccb2 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -14,10 +14,11 @@ import time async def test_helperbot_message(): - with LogCapture("helperbot") as l: - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) - await mqtt_server.broker_coro() + mqtt_address = ("127.0.0.1", 8883) + mqtt_server = bumper.MQTTServer(mqtt_address) + await mqtt_server.broker_coro() + + with LogCapture() as l: # Test broadcast message mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) @@ -125,20 +126,42 @@ async def test_helperbot_message(): ) # Check received message was logged l.clear() mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() + + # Received error message + mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + await mqtt_helperbot.start_helper_bot() + assert ( + mqtt_helperbot.Client._connected_state._value == True + ) # Check helperbot is connected + msg_payload = "" + msg_topic_name = "iot/atr/errors/bot_serial/ls1ok3/wC3g/x" + await mqtt_helperbot.Client.publish( + msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 + ) + try: + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) + except asyncio.TimeoutError: + pass + + l.check_present( + ( + "boterror", + "ERROR", + "Received Error - Topic: iot/atr/errors/bot_serial/ls1ok3/wC3g/x - Message: ", + ) + ) # Check received message was logged + l.clear() + mqtt_helperbot.Client.disconnect() + + await mqtt_server.broker.shutdown() async def test_helperbot_expire_message(): + mqtt_address = ("127.0.0.1", 8883) + mqtt_server = bumper.MQTTServer(mqtt_address) + await mqtt_server.broker_coro() + with LogCapture("helperbot") as l: - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) - await mqtt_server.broker_coro() - # mqtt_address = ("127.0.0.1", 8883) - # mqtt_server = bumper.MQTTServer(mqtt_address) - # broker = hbmqtt.broker.Broker( - # mqtt_server.default_config, plugin_namespace="hbmqtt.test.plugins" - # ) - # await broker.start() # Test broadcast message mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) @@ -195,19 +218,14 @@ async def test_helperbot_expire_message(): ) ) # Check received message was logged mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() + + await mqtt_server.broker.shutdown() async def test_helperbot_sendcommand(): mqtt_address = ("127.0.0.1", 8883) mqtt_server = bumper.MQTTServer(mqtt_address) await mqtt_server.broker_coro() - # mqtt_address = ("127.0.0.1", 8883) - # mqtt_server = bumper.MQTTServer(mqtt_address) - # broker = hbmqtt.broker.Broker( - # mqtt_server.default_config, plugin_namespace="hbmqtt.test.plugins" - # ) - # await broker.start() mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) await mqtt_helperbot.start_helper_bot() diff --git a/tests/test_xmppserver.py b/tests/test_xmppserver.py index 74d2a0c..ef48025 100644 --- a/tests/test_xmppserver.py +++ b/tests/test_xmppserver.py @@ -21,10 +21,11 @@ def mock_transport_extra_info(*args, **kwargs): async def test_xmpp_server(): + xmpp_address = ("127.0.0.1", 5223) + xmpp_server = bumper.XMPPServer(xmpp_address) + await xmpp_server.start_async_server() + with LogCapture("xmppserver") as l: - xmpp_address = ("127.0.0.1", 5223) - xmpp_server = bumper.XMPPServer(xmpp_address) - await xmpp_server.start_async_server() reader, writer = await asyncio.open_connection("127.0.0.1", 5223) @@ -52,10 +53,8 @@ async def test_xmpp_server(): await writer.drain() await asyncio.sleep(0.1) - xmpp_server.disconnect() - await asyncio.sleep(0.1) - assert len(xmpp_server.clients) == 0 # Client count decreased - print(l) + + xmpp_server.disconnect() async def test_client_connect_no_starttls(*args, **kwargs): @@ -209,6 +208,11 @@ async def test_client_connect_starttls_called(*args, **kwargs): async def test_xmpp_server_client_tls(): + + xmpp_address = ("127.0.0.1", 5223) + xmpp_server = bumper.XMPPServer(xmpp_address) + await xmpp_server.start_async_server() + with LogCapture("xmppserver") as l: async def do_stuff_after_start_tls( @@ -227,10 +231,6 @@ async def test_xmpp_server_client_tls(): await writer.drain() - xmpp_address = ("127.0.0.1", 5223) - xmpp_server = bumper.XMPPServer(xmpp_address) - await xmpp_server.start_async_server() - reader, writer = await asyncio.open_connection("127.0.0.1", 5223) writer.write( @@ -616,3 +616,30 @@ async def test_client_send_iq(*args, **kwargs): # Reset mock calls mock_send.reset_mock() + # Bot error report + test_data = "".encode( + "utf-8" + ) + xmppclient2._parse_data(test_data) + + assert ( + mock_send.mock_calls[0].args[0] + == '' + ) # result sent to ecouser.net + + # Reset mock calls + mock_send.reset_mock() + + # Bot "DorpError" to all + test_data = "".encode( + "utf-8" + ) + xmppclient2._parse_data(test_data) + assert ( + mock_send.mock_calls[0].args[0] + == '' + ) # result sent to ecouser.net + + # Reset mock calls + mock_send.reset_mock() + diff --git a/tests/test_z_problem.py b/tests/test_z_problem.py index f3f00f2..3bd2ca5 100644 --- a/tests/test_z_problem.py +++ b/tests/test_z_problem.py @@ -100,4 +100,3 @@ def test_main(mock_firstrun): bumper.main() assert mock_firstrun.called == True bumper.ca_cert = "tests/test_certs/ca.crt" -