Fix the things

XMPP is now async and works - Closes #27
Full async - Closes #22
Individual log files - Closes #21
This commit is contained in:
Brian Martin 2019-05-23 09:01:30 -04:00
parent ceb8cf2651
commit 2460182f4f
5 changed files with 220 additions and 402 deletions

View file

@ -1,9 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from .confserver import ConfServer from bumper.confserver import ConfServer
from .mqttserver import MQTTServer from bumper.mqttserver import MQTTServer, MQTTHelperBot
from .mqttserver import MQTTHelperBot from bumper.xmppserver import XMPPServer
from .xmppserver import XMPPServer
import asyncio import asyncio
import json import json
import time import time
@ -66,6 +65,9 @@ xmppserverlog.addHandler(xmpp_rotate)
# xmppserverlog.setLevel(logging.INFO) # xmppserverlog.setLevel(logging.INFO)
logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger
def get_milli_time(timetoconvert): def get_milli_time(timetoconvert):
return int(round(timetoconvert * 1000)) return int(round(timetoconvert * 1000))
@ -663,11 +665,12 @@ def bot_add(sn, did, devclass, resource, company):
newbot.company = company newbot.company = company
bot = bot_get(did) bot = bot_get(did)
if not bot: if not bot: # Not existing bot in database
bumperlog.info( if not devclass == "" or "@" not in sn or "tmp" not in sn: # try to prevent bad additions to the bot list
"Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did) bumperlog.info(
) "Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did)
bot_full_upsert(newbot.asdict()) )
bot_full_upsert(newbot.asdict())
def bot_remove(did): def bot_remove(did):

View file

@ -31,9 +31,7 @@ class aiohttp_filter(logging.Filter):
confserverlog = logging.getLogger("confserver") confserverlog = logging.getLogger("confserver")
logging.getLogger("aiohttp.access").addFilter(aiohttp_filter()) #Add logging filter above to aiohttp.access
#logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger
logging.getLogger("aiohttp.access").addFilter(aiohttp_filter())
class EcoVacs_Login: class EcoVacs_Login:
accessToken = "" accessToken = ""
@ -60,42 +58,6 @@ class ConfServer:
self.run_async = False self.run_async = False
self.app = None self.app = None
def run(self, run_async=False):
try:
if run_async:
self.run_async = True
confserverlog.debug("Starting ConfServer Thread: 1")
self.confthread = Thread(
name="ConfServer_{}_Thread".format(self.address[1]),
target=self.run_server,
)
self.confthread.setDaemon(True)
self.confthread.start()
else:
try:
self.run_server()
except KeyboardInterrupt:
self.disconnect()
except Exception as e:
confserverlog.exception("{}".format(e))
def run_server(self):
logging.info("Starting ConfServer at {}".format(self.address))
print("Starting ConfServer at {}".format(self.address))
try:
loop = asyncio.get_event_loop()
except:
loop = asyncio.new_event_loop()
try:
self.confserver_app()
loop.run_until_complete(self.start_server())
loop.run_forever()
except Exception as e:
confserverlog.exception("{}".format(e))
def confserver_app(self): def confserver_app(self):
self.app = web.Application() self.app = web.Application()
@ -196,6 +158,7 @@ class ConfServer:
async def start_server(self): async def start_server(self):
try: try:
confserverlog.info("Starting ConfServer at {}:{}".format(self.address[0], self.address[1]))
runner = web.AppRunner(self.app) runner = web.AppRunner(self.app)
await runner.setup() await runner.setup()
@ -351,55 +314,67 @@ class ConfServer:
def check_token(self, apptype, countrycode, user, token): def check_token(self, apptype, countrycode, user, token):
if bumper.check_token(user["userid"], token): try:
if bumper.check_token(user["userid"], token):
if "global_" in apptype: #EcoVacs Home
login_details = EcoVacsHome_Login() if "global_" in apptype: #EcoVacs Home
login_details.ucUid = "fuid_{}".format(user["userid"]) login_details = EcoVacsHome_Login()
login_details.loginName = "fusername_{}".format(user["userid"]) login_details.ucUid = "fuid_{}".format(user["userid"])
login_details.mobile = None 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()),
}
return web.json_response(body)
else: else:
login_details = EcoVacs_Login() body = {
"code": bumper.ERR_TOKEN_INVALID,
login_details.accessToken = token "data": None,
login_details.uid = "fuid_{}".format(user["userid"]) "msg": "当前密码错误",
login_details.username = "fusername_{}".format(user["userid"]) "time": bumper.get_milli_time(datetime.utcnow().timestamp()),
login_details.country = countrycode }
login_details.email = "null@null.com" return web.json_response(body)
body = { except Exception as e:
"code": bumper.RETURN_API_SUCCESS, confserverlog.exception("{}".format(e))
"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)
else:
body = {
"code": bumper.ERR_TOKEN_INVALID,
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(datetime.utcnow().timestamp()),
}
return web.json_response(body)
def generate_token(self, user): def generate_token(self, user):
tmpaccesstoken = uuid.uuid4().hex try:
bumper.user_add_token(user["userid"], tmpaccesstoken) tmpaccesstoken = uuid.uuid4().hex
return tmpaccesstoken bumper.user_add_token(user["userid"], tmpaccesstoken)
return tmpaccesstoken
except Exception as e:
confserverlog.exception("{}".format(e))
def generate_authcode(self, user, countrycode, token): def generate_authcode(self, user, countrycode, token):
tmpauthcode = "{}_{}".format(countrycode, uuid.uuid4().hex) try:
bumper.user_add_authcode(user["userid"], token, tmpauthcode) tmpauthcode = "{}_{}".format(countrycode, uuid.uuid4().hex)
return tmpauthcode bumper.user_add_authcode(user["userid"], token, tmpauthcode)
return tmpauthcode
except Exception as e:
confserverlog.exception("{}".format(e))
def _auth_any(self, devid, apptype, country, request): def _auth_any(self, devid, apptype, country, request):
try: try:
@ -815,7 +790,6 @@ class ConfServer:
confserverlog.exception("{}".format(e)) confserverlog.exception("{}".format(e))
async def handle_getProductIotMap(self, request): async def handle_getProductIotMap(self, request):
user_devid = request.match_info.get("devid", "")
try: try:
body = { body = {
"code": bumper.RETURN_API_SUCCESS, "code": bumper.RETURN_API_SUCCESS,
@ -909,13 +883,23 @@ class ConfServer:
if todo == "FindBest": if todo == "FindBest":
service = postbody["service"] service = postbody["service"]
if service == "EcoMsgNew": if service == "EcoMsgNew":
srvip = socket.gethostbyname(socket.gethostname())
srvport = 5223
confserverlog.info(
"Reporting FindBest-EcoMsgNew Server to Bot as: {}:{}".format(srvip, srvport)
)
body = { body = {
"result": "ok", "result": "ok",
"ip": socket.gethostbyname(socket.gethostname()), "ip": srvip,
"port": 5223, "port": srvport,
} }
elif service == "EcoUpdate": elif service == "EcoUpdate":
body = {"result": "ok", "ip": "47.88.66.164", "port": 8005} srvip = "47.88.66.164" #EcoVacs Server
srvport = 8005
confserverlog.info(
"Reporting FindBest-EcoUpdate Server to Bot as: {}:{}".format(srvip, srvport)
)
body = {"result": "ok", "ip": srvip, "port": srvport}
elif todo == "loginByItToken": elif todo == "loginByItToken":
if "userId" in postbody: if "userId" in postbody:
@ -995,7 +979,8 @@ class ConfServer:
for bot in bots: for bot in bots:
if bot["class"] != "": if bot["class"] != "":
b = bumper.bot_toEcoVacsHome_JSON(bot) b = bumper.bot_toEcoVacsHome_JSON(bot)
botlist.append(json.loads(b)) if not b is None: #Happens if the bot isn't on the EcoVacs Home list
botlist.append(json.loads(b))
body = { body = {
"code": 0, "code": 0,
@ -1035,9 +1020,12 @@ class ConfServer:
if todo == "FindBest": if todo == "FindBest":
service = postbody["service"] service = postbody["service"]
if service == "EcoMsgNew": if service == "EcoMsgNew":
srvip = socket.gethostbyname(socket.gethostname()) srvip = socket.gethostbyname(socket.gethostname())
msgserver = {"ip": srvip, "port": 5223, "result": "ok"} srvport = 5223
confserverlog.info(
"Reporting FindBest-EcoMsgNew Server to Bot as: {}:{}".format(srvip, srvport)
)
msgserver = {"ip": srvip, "port": srvport, "result": "ok"}
msgserver = json.dumps(msgserver) msgserver = json.dumps(msgserver)
msgserver = msgserver.replace( msgserver = msgserver.replace(
" ", "" " ", ""
@ -1094,11 +1082,16 @@ class ConfServer:
if did != "": if did != "":
confserverlog.debug("BotCommand: {}".format(json_body))
bot = bumper.bot_get(did) bot = bumper.bot_get(did)
if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True:
body = "" body = ""
retcmd = await self.helperbot.send_command(json_body, randomid) retcmd = await self.helperbot.send_command(json_body, randomid)
confserverlog.debug(
"Send Bot - {}".format(json_body)
)
confserverlog.debug(
"Bot Response - {}".format(body)
)
logs = [] logs = []
logsroot = ET.fromstring(retcmd["resp"]) logsroot = ET.fromstring(retcmd["resp"])
if logsroot.attrib["ret"] == "ok": if logsroot.attrib["ret"] == "ok":
@ -1147,13 +1140,15 @@ class ConfServer:
did = json_body["toId"] did = json_body["toId"]
if did != "": if did != "":
confserverlog.debug("BotCommand: {}".format(json_body))
bot = bumper.bot_get(did) bot = bumper.bot_get(did)
if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True:
retcmd = await self.helperbot.send_command(json_body, randomid) retcmd = await self.helperbot.send_command(json_body, randomid)
body = retcmd body = retcmd
confserverlog.debug( confserverlog.debug(
"\r\n POST: {} \r\n Response: {}".format(json_body, body) "Send Bot - {}".format(json_body)
)
confserverlog.debug(
"Bot Response - {}".format(body)
) )
return web.json_response(body) return web.json_response(body)
else: else:
@ -1180,7 +1175,7 @@ class ConfServer:
confserverlog.exception("{}".format(e)) confserverlog.exception("{}".format(e))
async def handle_dim_devmanager(self, request): async def handle_dim_devmanager(self, request): #Used in EcoVacs Home App
try: try:
json_body = json.loads(await request.text()) json_body = json.loads(await request.text())
@ -1190,13 +1185,15 @@ class ConfServer:
did = json_body["toId"] did = json_body["toId"]
if did != "": if did != "":
confserverlog.debug("BotCommand: {}".format(json_body))
bot = bumper.bot_get(did) bot = bumper.bot_get(did)
if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True:
retcmd = await self.helperbot.send_command(json_body, randomid) retcmd = await self.helperbot.send_command(json_body, randomid)
body = retcmd body = retcmd
confserverlog.debug( confserverlog.debug(
"\r\n POST: {} \r\n Response: {}".format(json_body, body) "Send Bot - {}".format(json_body)
)
confserverlog.debug(
"Bot Response - {}".format(body)
) )
return web.json_response(body) return web.json_response(body)
else: else:

View file

@ -14,6 +14,7 @@ import ssl
import bumper import bumper
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
import bumper
helperbotlog = logging.getLogger("helperbot") helperbotlog = logging.getLogger("helperbot")
mqttserverlog = logging.getLogger("mqttserver") mqttserverlog = logging.getLogger("mqttserver")
@ -42,33 +43,6 @@ class MQTTHelperBot:
self.command_responses = [] self.command_responses = []
self.helperthread = None self.helperthread = None
def run(self, run_async=False):
if run_async:
hloop = asyncio.new_event_loop()
helperbotlog.debug("Starting MQTT HelperBot Thread: 1")
self.helperthread = Thread(
name="MQTTHelperBot_Thread", target=self.run_helperbot, args=(hloop,)
)
self.helperthread.setDaemon(True)
self.helperthread.start()
else:
self.run_helperbot(asyncio.get_event_loop())
def run_helperbot(self, loop):
logging.info("Starting MQTT HelperBot")
print("Starting MQTT HelperBot")
try:
asyncio.set_event_loop(loop)
self.Client = MQTTClient(
client_id=self.client_id, config={"check_hostname": False}
)
loop.run_until_complete(self.start_helper_bot())
loop.run_until_complete(self.get_msg())
loop.run_forever()
except Exception as e:
helperbotlog.exception("{}".format(e))
async def start_helper_bot(self): async def start_helper_bot(self):
try: try:
@ -87,8 +61,7 @@ class MQTTHelperBot:
("iot/atr/+", QOS_0), ("iot/atr/+", QOS_0),
] ]
) )
asyncio.create_task(self.get_msg())
asyncio.ensure_future(self.get_msg())
except Exception as e: except Exception as e:
helperbotlog.exception("{}".format(e)) helperbotlog.exception("{}".format(e))
@ -126,8 +99,6 @@ class MQTTHelperBot:
helperbotlog.debug("Pruning Message Time: {}, MsgTime: {}, MsgTime+60: {}".format(time.time(), msg['time'], expire_time)) helperbotlog.debug("Pruning Message Time: {}, MsgTime: {}, MsgTime+60: {}".format(time.time(), msg['time'], expire_time))
self.command_responses.remove(msg) self.command_responses.remove(msg)
# helperbotlog.debug("MQTT Command Response List Count: %s" %len(cresp))
except Exception as e: except Exception as e:
helperbotlog.exception("{}".format(e)) helperbotlog.exception("{}".format(e))
@ -190,6 +161,7 @@ class MQTTServer:
async def broker_coro(self): async def broker_coro(self):
try: try:
mqttserverlog.info("Starting MQTT Server at {}:{}".format(self.address[0], self.address[1]))
broker = hbmqtt.broker.Broker(config=self.default_config) broker = hbmqtt.broker.Broker(config=self.default_config)
await broker.start() await broker.start()
@ -248,32 +220,6 @@ class MQTTServer:
except Exception as e: except Exception as e:
mqttserverlog.exception("{}".format(e)) mqttserverlog.exception("{}".format(e))
def run(self, run_async=False):
if run_async:
sloop = asyncio.new_event_loop()
mqttserverlog.debug("Starting MQTTServer Thread: 1")
self.mqttserverthread = Thread(
name="MQTTServer_Thread", target=self.run_server, args=(sloop,)
)
self.mqttserverthread.setDaemon(True)
self.mqttserverthread.start()
else:
self.run_server(asyncio.get_event_loop())
def run_server(self, loop):
logging.info("Starting MQTT Server at {}".format(self.address))
print("Starting MQTT Server at {}".format(self.address))
try:
asyncio.set_event_loop(loop)
loop.run_until_complete(self.broker_coro())
# loop.run_until_complete(self.active_bot_listing())
loop.run_forever()
except Exception as e:
mqttserverlog.exception("{}".format(e))
class BumperMQTTServer_Plugin: class BumperMQTTServer_Plugin:
def __init__(self, context): def __init__(self, context):
@ -310,11 +256,9 @@ class BumperMQTTServer_Plugin:
client_id = session.client_id client_id = session.client_id
didsplit = str(client_id).split("@") didsplit = str(client_id).split("@")
# If this isn't a fake user (fuid) then add as a bot if not ( # if ecouser or bumper aren't in details it is a bot
if not ( "ecouser" in didsplit[1]
str(didsplit[0]).startswith("fuid") or "bumper" in didsplit[1]):
or str(didsplit[0]).startswith("helper")
):
tmpbotdetail = str(didsplit[1]).split("/") tmpbotdetail = str(didsplit[1]).split("/")
bumper.bot_add( bumper.bot_add(
username, username,

View file

@ -5,7 +5,7 @@ import sys, socket, threading, re, time, logging, uuid, xml.etree.ElementTree as
import base64 import base64
import ssl import ssl
import bumper import bumper
import asyncio import asyncio, functools
xmppserverlog = logging.getLogger("xmppserver") xmppserverlog = logging.getLogger("xmppserver")
@ -19,11 +19,10 @@ class XMPPServer:
def __init__(self, address): def __init__(self, address):
# Initialize bot server # Initialize bot server
self.address = address self.address = address
self.aclients = {} # task -> (reader, writer)
async def async_server(self): async def async_server(self):
xmppserverlog.debug( xmppserverlog.info(
"listening on {}:{}".format(self.address[0], self.address[1]) "Starting XMPP Server at {}:{}".format(self.address[0], self.address[1])
) )
server = await asyncio.start_server( server = await asyncio.start_server(
self.accept_client, self.address[0], self.address[1] self.accept_client, self.address[0], self.address[1]
@ -34,101 +33,26 @@ class XMPPServer:
# self.clients = {} # task -> (reader, writer) # self.clients = {} # task -> (reader, writer)
def accept_client(self, client_reader, client_writer): def accept_client(self, client_reader, client_writer):
# task = asyncio.Task(self.handle_client(client_reader, client_writer)) try:
aclient = XMPPAsyncClient(client_reader, client_writer) aclient = XMPPAsyncClient(client_reader, client_writer)
task = asyncio.Task(aclient.handle_async_client()) task = asyncio.Task(aclient.handle_async_client())
self.aclients[task] = (client_reader, client_writer) aclient._async_task = task
self.clients.append(aclient)
def client_done(task): def client_done(aclient, task):
del self.aclients[task] try:
xmppserverlog.info("End Connection for {}".format(client_writer.get_extra_info("peername"))) self.clients.remove(aclient)
client_writer.close() xmppserverlog.debug("End Connection for ({}:{} | {})".format(client_writer.get_extra_info("peername")[0], client_writer.get_extra_info("peername")[1], aclient.bumper_jid))
client_writer.close()
except Exception as e:
xmppserverlog.error("{}".format(e))
clientaddr = client_writer.get_extra_info("peername") clientaddr = client_writer.get_extra_info("peername")
xmppserverlog.info("New Connection from {}".format(clientaddr)) xmppserverlog.debug("New Connection from {}:{}".format(clientaddr[0],clientaddr[1]))
task.add_done_callback(client_done) task.add_done_callback(functools.partial(client_done, aclient))
# def run(self, run_async=False): except Exception as e:
# if run_async: xmppserverlog.error("{}".format(e))
# xmppserverlog.debug("Starting XMPPServer Thread: 1")
# self.xmppthread = Thread(name="XMPPServer_Thread", target=self.run_server)
# self.xmppthread.setDaemon(True)
# self.xmppthread.start()
# else:
# try:
# self.run_server()
# except KeyboardInterrupt:
# self.disconnect()
# def run_server(self):
# logging.info("Starting XMPP Server at {}".format(self.address))
# print("Starting XMPP Server at {}".format(self.address))
# # xmppserverlog.setLevel(logging.DEBUG)
# # Set SSL Context
# self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
# self.ssl_ctx.load_cert_chain(
# certfile=bumper.server_cert, keyfile=bumper.server_key
# )
# self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# try:
# self.socket.bind(self.address)
# self.socket.listen(5)
# xmppserverlog.debug(
# "listening on {}:{}".format(self.address[0], self.address[1])
# )
# while not self.exit_flag:
# connection, client_address = self.socket.accept()
# # disconnect any clients with this ip
# for client in self.clients:
# if client.address == client_address[0]:
# xmppserverlog.debug(
# "disconnecting existing client {} with resource {}".format(
# client.address, client.clientresource
# )
# )
# client._disconnect()
# self.remove_client_byip(client.address)
# xmppserverlog.debug(
# "starting new client with ip {}".format(client_address[0])
# )
# thread_id = uuid.uuid4()
# client = XMPPAsyncClient(thread_id, connection, client_address)
# client.setDaemon(True)
# client.start()
# self.clients.append(client)
# except PermissionError as e:
# if "bind" in e.strerror:
# xmppserverlog.exception(
# "Error binding XMPPServer, exiting. Try using a different hostname or IP - {}".format(
# e
# )
# )
# exit(1)
# except Exception as e:
# xmppserverlog.exception("{}".format(e))
# exit(1)
# except KeyboardInterrupt as e:
# xmppserverlog.exception("{}".format(e))
# finally:
# connection.shutdown(socket.SHUT_RDWR)
# connection.close()
# self.disconnect()
# xmppserverlog.info("disconnecting")
# self.socket.close()
def disconnect(self): def disconnect(self):
try: try:
@ -140,41 +64,7 @@ class XMPPServer:
xmppserverlog.debug("shutting down") xmppserverlog.debug("shutting down")
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.error("{}".format(e))
def remove_client_byip(self, ip):
for client in self.clients:
if client.address == ip:
xmppserverlog.debug(
"removing client from client list with ip {} and resource {}".format(
client.address, client.clientresource
)
)
client._disconnect()
self.clients.remove(client)
def remove_client_byresource(self, resource):
for client in self.clients:
if str(client.clientresource).lower() == str(resource).lower():
xmppserverlog.debug(
"removing client from client list with ip {} and resource {}".format(
client.address, client.clientresource
)
)
client._disconnect()
self.clients.remove(client)
def remove_client_byuid(self, uid):
for client in self.clients:
if str(client.uid).lower() == str(uid).lower():
xmppserverlog.debug(
"removing client from client list with ip {} and resource {}".format(
client.address, client.clientresource
)
)
client._disconnect()
self.clients.remove(client)
class XMPPAsyncClient: class XMPPAsyncClient:
IDLE = 0 IDLE = 0
@ -186,6 +76,7 @@ class XMPPAsyncClient:
UNKNOWN = 0 UNKNOWN = 0
BOT = 1 BOT = 1
CONTROLLER = 2 CONTROLLER = 2
_async_task = None
def __init__(self, client_reader, client_writer): def __init__(self, client_reader, client_writer):
self.type = self.UNKNOWN self.type = self.UNKNOWN
@ -204,20 +95,17 @@ class XMPPAsyncClient:
async def handle_async_client(self): async def handle_async_client(self):
# xmppserverlog.info('client connected - {}'.format(self.address)) # xmppserverlog.info('client connected - {}'.format(self.address))
#await self._set_state("READY") await self._set_state("CONNECT")
await self._set_state("CONNECT")
#asyncio.Task(self.send_ping(30))
while True: while True:
await asyncio.sleep(0.1) await asyncio.sleep(0.05)
if not self.state == self.DISCONNECT: if not self.state == self.DISCONNECT:
data = await self.client_reader.read(4096) data = await self.client_reader.read(4096)
# data = await asyncio.wait_for(client_reader.readline(), timeout=10.0) if data is None or data == b'':
if data is None: xmppserverlog.debug("Received no data")
xmppserverlog.warning("Received no data")
# exit loop and disconnect # exit loop and disconnect
return return
else:
await self._parse_data(data) await self._parse_data(data)
else: else:
break break
@ -228,25 +116,25 @@ class XMPPAsyncClient:
try: try:
# if not self.connection._closed: # if not self.connection._closed:
if self.log_sent_message: if self.log_sent_message:
xmppserverlog.debug("send {} - {}".format(self.address, command)) xmppserverlog.debug("send to ({}:{} | {}) - {}".format(self.address[0], self.address[1], self.bumper_jid, command))
# self.connection.send(command.encode())
self.client_writer.write(command.encode()) self.client_writer.write(command.encode())
await self.client_writer.drain() await self.client_writer.drain()
except BrokenPipeError as e: except BrokenPipeError as e:
xmppserverlog.debug("{}".format(e)) #xmppserverlog.debug("{}".format(e))
await self._set_state("DISCONNECT") await self._set_state("DISCONNECT")
except ConnectionResetError as e: except ConnectionResetError as e:
xmppserverlog.debug("{}".format(e)) #xmppserverlog.debug("{}".format(e))
await self._set_state("DISCONNECT") await self._set_state("DISCONNECT")
except ConnectionAbortedError as e: except ConnectionAbortedError as e:
xmppserverlog.debug("{}".format(e)) #xmppserverlog.debug("{}".format(e))
await self._set_state("DISCONNECT") await self._set_state("DISCONNECT")
except OSError as e: except OSError as e:
xmppserverlog.debug("{}".format(e)) xmppserverlog.error("{}".format(e))
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.exception("{}".format(e))
@ -265,7 +153,7 @@ class XMPPAsyncClient:
self.client_writer.close() self.client_writer.close()
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.error("{}".format(e))
async def _tag_strip_uri(self, tag): async def _tag_strip_uri(self, tag):
try: try:
@ -274,7 +162,7 @@ class XMPPAsyncClient:
return tag return tag
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.error("{}".format(e))
async def _set_state(self, state): async def _set_state(self, state):
try: try:
@ -286,7 +174,7 @@ class XMPPAsyncClient:
) )
) )
xmppserverlog.debug("{} state: {}".format(self.address, state)) xmppserverlog.debug("({}:{} | {}) state: {}".format(self.address[0],self.address[1],self.bumper_jid, state))
self.state = new_state self.state = new_state
@ -294,7 +182,7 @@ class XMPPAsyncClient:
await self._disconnect() await self._disconnect()
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.error("{}".format(e))
async def _handle_ctl(self, xml, data): async def _handle_ctl(self, xml, data):
try: try:
@ -365,13 +253,13 @@ class XMPPAsyncClient:
if client.type == self.BOT: if client.type == self.BOT:
if client.uid.lower() in ctl_to.lower(): if client.uid.lower() in ctl_to.lower():
xmppserverlog.info( xmppserverlog.debug(
"Sending ctl to bot: {}".format(rxmlstring) "Sending ctl to bot: {}".format(rxmlstring)
) )
client.send(rxmlstring) await client.send(rxmlstring)
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.error("{}".format(e))
async def _handle_ping(self, xml, data): async def _handle_ping(self, xml, data):
try: try:
@ -387,37 +275,40 @@ class XMPPAsyncClient:
pingto = xml.get("to") pingto = xml.get("to")
pingfrom = self.bumper_jid pingfrom = self.bumper_jid
xml.attrib["from"] = pingfrom xml.attrib["from"] = pingfrom
pingstring = ET.tostring(xml).decode("utf-8") pingstring = ET.tostring(xml).decode("utf-8")
# clean up string to remove namespaces added by ET # clean up string to remove namespaces added by ET
pingstring = pingstring.replace("xmlns:ns0=", "xmlns=") pingstring = pingstring.replace("xmlns:ns0=", "xmlns=")
pingstring = pingstring.replace("ns0:", "") pingstring = pingstring.replace("ns0:", "")
pingstring = pingstring.replace('iq xmlns="com:ctl"', "iq") pingstring = pingstring.replace('iq xmlns="urn:xmpp:ping"', "iq")
pingstring = pingstring.replace("<query", '<query xmlns="com:ctl"') pingstring = pingstring.replace("<ping", '<ping xmlns="urn:xmpp:ping"')
for client in XMPPServer.clients: for client in XMPPServer.clients:
if ( if (
client.bumper_jid != self.bumper_jid client.bumper_jid != self.bumper_jid
and client.state == client.READY and client.state == client.READY
): ):
if pingto.lower() in client.bumper_jid.lower(): if pingto.lower() in client.bumper_jid.lower():
pingstring = '<iq type="result" id="{}" from="{}" to="{}" />'.format( #pingstring = '<iq type="result" id="{}" from="{}" to="{}" />'.format(
xml.get("id"), pingfrom, pingto # xml.get("id"), pingfrom, pingto
) #)
xmppserverlog.debug( #xmppserverlog.debug(
"ping from {} to {}".format(pingfrom, pingto) # "ping from {} to {} with {}".format(pingfrom, pingto, pingstring)
) #)
client.send(pingstring)
await client.send(pingstring)
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.exception("{}".format(e))
async def send_ping(self, time):
async def schedule_ping(self, time):
if not self.state == 5: #disconnected if not self.state == 5: #disconnected
pingstring = "<iq from='{}' to='{}' id='s2c1' type='get'><ping xmlns='urn:xmpp:ping'/></iq>".format(XMPPServer.server_id, self.bumper_jid) pingstring = "<iq from='{}' to='{}' id='s2c1' type='get'><ping xmlns='urn:xmpp:ping'/></iq>".format(XMPPServer.server_id, self.bumper_jid)
await self.send(pingstring) await self.send(pingstring)
await asyncio.sleep(time) await asyncio.sleep(time)
asyncio.Task(self.send_ping(time)) asyncio.Task(self.schedule_ping(time))
async def _handle_result(self, xml, data): async def _handle_result(self, xml, data):
try: try:
@ -433,7 +324,7 @@ class XMPPAsyncClient:
adminuser = ctlerr.replace("permission denied, please contact ", "") adminuser = ctlerr.replace("permission denied, please contact ", "")
adminuser = adminuser.replace(" ", "") adminuser = adminuser.replace(" ", "")
if not ( if not (
adminuser.startswith("fuid_") or bumper.use_auth adminuser.startswith("fuid_") or adminuser.startswith("fusername_") or bumper.use_auth
): # if not fuid_ then its ecovacs OR ignore bumper auth ): # if not fuid_ then its ecovacs OR ignore bumper auth
# TODO: Implement auth later, should this user have access to bot? # TODO: Implement auth later, should this user have access to bot?
@ -474,7 +365,7 @@ class XMPPAsyncClient:
) )
) )
for client in XMPPServer.clients: for client in XMPPServer.clients:
client.send(rxmlstring) await client.send(rxmlstring)
if xml.get("to").find("@") == -1: # No to address if xml.get("to").find("@") == -1: # No to address
ctl_to = xml.get("to") ctl_to = xml.get("to")
@ -488,7 +379,7 @@ class XMPPAsyncClient:
): ):
if not "@" in ctl_to: # No user@, send to all clients? if not "@" in ctl_to: # No user@, send to all clients?
# TODO: Revisit later, this may be wrong # TODO: Revisit later, this may be wrong
client.send(rxmlstring) await client.send(rxmlstring)
elif ( elif (
client.uid.lower() in ctl_to.lower() client.uid.lower() in ctl_to.lower()
@ -498,7 +389,7 @@ class XMPPAsyncClient:
self.uid, client.uid, rxmlstring self.uid, client.uid, rxmlstring
) )
) )
client.send(rxmlstring) await client.send(rxmlstring)
except Exception as e: except Exception as e:
xmppserverlog.exception("{}".format(e)) xmppserverlog.exception("{}".format(e))
@ -617,11 +508,9 @@ class XMPPAsyncClient:
self.clientresource = aitem.text self.clientresource = aitem.text
resource = self.clientresource resource = self.clientresource
if not self.uid.startswith("fuid"): if self.devclass: # if there is a devclass it is a bot
# Need sample data to see details here
bumper.bot_add("", self.uid, "", resource, "eco-legacy") bumper.bot_add("", self.uid, "", resource, "eco-legacy")
xmppserverlog.info("bot authenticated {}".format(self.uid)) xmppserverlog.debug("bot authenticated {}".format(self.uid))
# Client authenticated, move to next state # Client authenticated, move to next state
await self._set_state("INIT") await self._set_state("INIT")
@ -690,12 +579,11 @@ class XMPPAsyncClient:
if len(saslauth) > 2: if len(saslauth) > 2:
authcode = saslauth[2] authcode = saslauth[2]
if not self.uid.startswith("fuid"): if self.devclass: # if there is a devclass it is a bot
# Need sample data to see details here
bumper.bot_add(self.uid, self.uid, self.devclass, "atom", "eco-legacy") bumper.bot_add(self.uid, self.uid, self.devclass, "atom", "eco-legacy")
self.type = self.BOT self.type = self.BOT
xmppserverlog.info("bot authenticated {}".format(self.uid)) xmppserverlog.debug("bot authenticated {}".format(self.uid))
# Send response # Send response
await self.send( await self.send(
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>' '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
@ -751,7 +639,7 @@ class XMPPAsyncClient:
self.bumper_jid = "{}@{}.ecorobot.net/atom".format( self.bumper_jid = "{}@{}.ecorobot.net/atom".format(
self.uid, self.devclass self.uid, self.devclass
) )
xmppserverlog.debug("new bot {}".format(self.uid)) xmppserverlog.debug("new bot ({}:{} | {})".format(self.address[0],self.address[1], self.bumper_jid))
res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}</jid></bind></iq>'.format( res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}</jid></bind></iq>'.format(
xml.get("id"), self.bumper_jid xml.get("id"), self.bumper_jid
) )
@ -761,18 +649,14 @@ class XMPPAsyncClient:
self.bumper_jid = "{}@{}/{}".format( self.bumper_jid = "{}@{}/{}".format(
self.uid, XMPPServer.server_id, self.clientresource self.uid, XMPPServer.server_id, self.clientresource
) )
xmppserverlog.debug( xmppserverlog.debug("new client ({}:{} | {})".format(self.address[0],self.address[1], self.bumper_jid))
"new client {} using resource {}".format(
self.uid, self.clientresource
)
)
res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}</jid></bind></iq>'.format( res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}</jid></bind></iq>'.format(
xml.get("id"), self.bumper_jid xml.get("id"), self.bumper_jid
) )
else: else:
self.name = "XMPP_Client_{}_{}".format(self.uid, self.address) self.name = "XMPP_Client_{}_{}".format(self.uid, self.address)
self.bumper_jid = "{}@{}".format(self.uid, XMPPServer.server_id) self.bumper_jid = "{}@{}".format(self.uid, XMPPServer.server_id)
xmppserverlog.debug("new client {}".format(self.uid)) xmppserverlog.debug("new client ({}:{} | {})".format(self.address[0],self.address[1], self.bumper_jid))
res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}</jid></bind></iq>'.format( res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}</jid></bind></iq>'.format(
xml.get("id"), self.bumper_jid xml.get("id"), self.bumper_jid
) )
@ -788,7 +672,7 @@ class XMPPAsyncClient:
res = '<iq type="result" id="{}" />'.format(xml.get("id")) res = '<iq type="result" id="{}" />'.format(xml.get("id"))
await self._set_state("READY") await self._set_state("READY")
await self.send(res) await self.send(res)
asyncio.Task(self.send_ping(30)) asyncio.Task(self.schedule_ping(30))
except Exception as e: except Exception as e:
@ -799,7 +683,7 @@ class XMPPAsyncClient:
if len(xml) and xml[0].tag == "status": if len(xml) and xml[0].tag == "status":
xmppserverlog.debug( xmppserverlog.debug(
"bot presence {} ".format(ET.tostring(xml, encoding="utf-8")) "bot presence {} ".format(ET.tostring(xml, encoding="utf-8").decode("utf-8"))
) )
# Most likely a bot, possibly hello world in text # Most likely a bot, possibly hello world in text
@ -823,15 +707,15 @@ class XMPPAsyncClient:
else: else:
xmppserverlog.debug( xmppserverlog.debug(
"client presence - {} ".format(ET.tostring(xml, encoding="utf-8")) "client presence - {} ".format(ET.tostring(xml, encoding="utf-8").decode("utf-8"))
) )
if xml.get("type") == "available": if xml.get("type") == "available":
xmppserverlog.debug( xmppserverlog.debug(
"client presence available - {} ".format( "client presence available - {} ".format(
ET.tostring(xml, encoding="utf-8") ET.tostring(xml, encoding="utf-8").decode("utf-8"))
)
) )
# Send dummy return # Send dummy return
await self.send( await self.send(
'<presence to="{}"> dummy </presence>'.format(self.bumper_jid) '<presence to="{}"> dummy </presence>'.format(self.bumper_jid)
@ -839,9 +723,8 @@ class XMPPAsyncClient:
elif xml.get("type") == "unavailable": elif xml.get("type") == "unavailable":
xmppserverlog.debug( xmppserverlog.debug(
"client presence unavailable (DISCONNECT) - {} ".format( "client presence unavailable (DISCONNECT) - {} ".format(
ET.tostring(xml, encoding="utf-8") ET.tostring(xml, encoding="utf-8").decode("utf-8"))
) )
)
await self._set_state("DISCONNECT") await self._set_state("DISCONNECT")
else: else:
@ -880,8 +763,8 @@ class XMPPAsyncClient:
if item.tag == "iq": if item.tag == "iq":
if self.log_incoming_data: if self.log_incoming_data:
xmppserverlog.debug( xmppserverlog.debug(
"from {} - {}".format( "from ({}:{} | {}) - {}".format(
self.address, self.address[0],self.address[1],self.bumper_jid,
str( str(
ET.tostring(item, encoding="utf-8").decode( ET.tostring(item, encoding="utf-8").decode(
"utf-8" "utf-8"

View file

@ -6,11 +6,16 @@ import sys, socket
import time import time
import platform import platform
import os import os
os.environ['PYTHONASYNCIODEBUG'] = '1' #os.environ['PYTHONASYNCIODEBUG'] = '1' # Uncomment to enable ASYNCIODEBUG
import asyncio import asyncio
def main(): async def main():
try:
loop = asyncio.get_event_loop()
except:
loop = asyncio.new_event_loop()
args = sys.argv args = sys.argv
listen_host = "" listen_host = ""
@ -20,12 +25,13 @@ def main():
level=logging.DEBUG, level=logging.DEBUG,
format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s", format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s",
) )
loop.set_debug(True) # Set asyncio loop to debug
#logging.getLogger("asyncio").setLevel(logging.DEBUG) # Show debug asyncio logs (disabled in init, uncomment for debugging asyncio)
else: else:
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(message)s", format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(message)s",
) )
# format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s")
if "--listen" in args: if "--listen" in args:
listen_host = args[args.index("--listen") + 1] listen_host = args[args.index("--listen") + 1]
@ -53,54 +59,39 @@ def main():
conf_address_8007, usessl=False, helperbot=mqtt_helperbot conf_address_8007, usessl=False, helperbot=mqtt_helperbot
) )
try:
loop = asyncio.get_event_loop()
except:
loop = asyncio.new_event_loop()
# Start web servers # Start web servers
loop.set_debug(True) conf_server.confserver_app()
conf_server.confserver_app() task_conf_server = asyncio.create_task(conf_server.start_server())
bumper.bumperlog.debug("task_conf_server added")
await task_conf_server
conf_server_2.confserver_app() conf_server_2.confserver_app()
asyncio.ensure_future(conf_server.start_server(),loop=loop) task_conf_server2 = asyncio.create_task(conf_server_2.start_server())
asyncio.ensure_future(conf_server_2.start_server(),loop=loop) bumper.bumperlog.debug("task_conf_server2 added")
await task_conf_server2
# Start MQTT Server # Start MQTT Server
asyncio.ensure_future(mqtt_server.broker_coro()) task_mqtt_server = asyncio.create_task(mqtt_server.broker_coro())
bumper.bumperlog.debug("task_mqtt_server added")
await task_mqtt_server
# Start MQTT Helperbot # Start MQTT Helperbot
asyncio.ensure_future(mqtt_helperbot.start_helper_bot()) task_mqtt_helperbot = asyncio.create_task(mqtt_helperbot.start_helper_bot())
bumper.bumperlog.debug("task_mqtt_helperbot added")
await task_mqtt_helperbot
# Start XMPP Server # Start XMPP Server
asyncio.ensure_future(xmpp_server.async_server()) task_xmpp_server = asyncio.create_task(xmpp_server.async_server())
bumper.bumperlog.debug("task_xmpp_server added")
loop.run_forever() await task_xmpp_server
# start xmpp server on port 5223 (sync)
#xmpp_server.run(run_async=True) # Start in new thread
# start mqtt server on port 8883 (async)
#mqtt_server.run(run_async=True) # Start in new thread
#time.sleep(1.5) # Wait for broker startup
# start mqtt_helperbot (async)
#mqtt_helperbot.run(run_async=True) # Start in new thread
# start conf server on port 443 (async) - Used for most https calls
#conf_server.run(run_async=True) # Start in new thread
# start conf server on port 8007 (async) - Used for a load balancer request
#conf_server_2.run(run_async=True) # Start in new thread
while True: while True:
try: try:
time.sleep(30) await asyncio.sleep(30)
bumper.revoke_expired_tokens() bumper.revoke_expired_tokens()
disconnected_clients = bumper.get_disconnected_xmpp_clients() #disconnected_clients = bumper.get_disconnected_xmpp_clients()
for client in disconnected_clients: #for client in disconnected_clients:
xmpp_server.remove_client_byuid(client["userid"]) # xmpp_server.remove_client_byuid(client["userid"])
except KeyboardInterrupt: except KeyboardInterrupt:
bumper.bumperlog.info("Bumper Exiting - Keyboard Interrupt") bumper.bumperlog.info("Bumper Exiting - Keyboard Interrupt")
@ -109,4 +100,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() asyncio.run(main())