replace busy waiting with asyncio.Event
This commit is contained in:
parent
6c94484068
commit
aaf34097b4
3 changed files with 86 additions and 84 deletions
|
|
@ -113,9 +113,9 @@ async def start():
|
||||||
|
|
||||||
bumperlog.info("Starting Bumper")
|
bumperlog.info("Starting Bumper")
|
||||||
global mqtt_server
|
global mqtt_server
|
||||||
mqtt_server = MQTTServer((bumper_listen, mqtt_listen_port))
|
mqtt_server = MQTTServer(bumper_listen, mqtt_listen_port)
|
||||||
global mqtt_helperbot
|
global mqtt_helperbot
|
||||||
mqtt_helperbot = MQTTHelperBot((bumper_listen, mqtt_listen_port))
|
mqtt_helperbot = MQTTHelperBot(bumper_listen, mqtt_listen_port)
|
||||||
global conf_server
|
global conf_server
|
||||||
conf_server = ConfServer((bumper_listen, conf1_listen_port), usessl=True)
|
conf_server = ConfServer((bumper_listen, conf1_listen_port), usessl=True)
|
||||||
global conf_server_2
|
global conf_server_2
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
from typing import MutableMapping
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
import hbmqtt
|
import hbmqtt
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
from cachetools import TTLCache
|
||||||
from hbmqtt.broker import Broker
|
from hbmqtt.broker import Broker
|
||||||
from hbmqtt.client import MQTTClient
|
from hbmqtt.client import MQTTClient
|
||||||
from hbmqtt.mqtt.constants import QOS_0
|
from hbmqtt.mqtt.constants import QOS_0
|
||||||
|
|
@ -21,26 +21,47 @@ helperbotlog = get_logger("helperbot")
|
||||||
boterrorlog = get_logger("boterror")
|
boterrorlog = get_logger("boterror")
|
||||||
|
|
||||||
|
|
||||||
|
class CommandDto:
|
||||||
|
|
||||||
|
def __init__(self, payload_type: str) -> None:
|
||||||
|
self._payload_type = payload_type
|
||||||
|
self._event = asyncio.Event()
|
||||||
|
self._response = None
|
||||||
|
|
||||||
|
async def wait_for_response(self):
|
||||||
|
await self._event.wait()
|
||||||
|
if self._payload_type == "j":
|
||||||
|
return json.loads(self._response)
|
||||||
|
else:
|
||||||
|
return str(self._response)
|
||||||
|
|
||||||
|
def add_response(self, response):
|
||||||
|
self._response = response
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
|
||||||
class MQTTHelperBot:
|
class MQTTHelperBot:
|
||||||
Client = None
|
Client = None
|
||||||
wait_resp_timeout_seconds = 60
|
wait_resp_timeout_seconds = 60
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, host: str, port: int):
|
||||||
self.address = address
|
self._commands: MutableMapping[str, CommandDto] = TTLCache(maxsize=self.wait_resp_timeout_seconds * 60,
|
||||||
|
ttl=self.wait_resp_timeout_seconds + 10)
|
||||||
|
self._host = host
|
||||||
|
self._port = port
|
||||||
self.client_id = "helperbot@bumper/helperbot"
|
self.client_id = "helperbot@bumper/helperbot"
|
||||||
self.command_responses = []
|
|
||||||
|
@property
|
||||||
|
def commands(self) -> MutableMapping[str, CommandDto]:
|
||||||
|
return self._commands
|
||||||
|
|
||||||
async def start_helper_bot(self):
|
async def start_helper_bot(self):
|
||||||
try:
|
try:
|
||||||
if self.Client is None:
|
if self.Client is None:
|
||||||
self.Client = MQTTClient(
|
self.Client = MQTTClient(client_id=self.client_id,
|
||||||
client_id=self.client_id, config={"check_hostname": False, "reconnect_retries": 20}
|
config={"check_hostname": False, "reconnect_retries": 20})
|
||||||
)
|
|
||||||
|
|
||||||
await self.Client.connect(
|
await self.Client.connect(f"mqtts://{self._host}:{self._port}/", cafile=bumper.ca_cert)
|
||||||
"mqtts://{}:{}/".format(self.address[0], self.address[1]),
|
|
||||||
cafile=bumper.ca_cert,
|
|
||||||
)
|
|
||||||
await self.Client.subscribe(
|
await self.Client.subscribe(
|
||||||
[
|
[
|
||||||
("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0),
|
("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0),
|
||||||
|
|
@ -51,32 +72,23 @@ class MQTTHelperBot:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
helperbotlog.exception("{}".format(e))
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
async def wait_for_resp(self, requestid):
|
async def _wait_for_resp(self, command_dto: CommandDto, request_id: str):
|
||||||
try:
|
try:
|
||||||
t_end = (
|
payload = await asyncio.wait_for(command_dto.wait_for_response(), timeout=self.wait_resp_timeout_seconds)
|
||||||
datetime.now() + timedelta(seconds=self.wait_resp_timeout_seconds)
|
return {
|
||||||
).timestamp()
|
"id": request_id,
|
||||||
|
"ret": "ok",
|
||||||
while time.time() < t_end:
|
"resp": payload
|
||||||
await asyncio.sleep(0.1)
|
}
|
||||||
if len(self.command_responses) > 0:
|
except asyncio.TimeoutError:
|
||||||
for msg in self.command_responses:
|
helperbotlog.debug("wait_for_resp timeout reached")
|
||||||
topic = str(msg["topic"]).split("/")
|
except asyncio.CancelledError as e:
|
||||||
if topic[6] == "helperbot" and topic[10] == requestid:
|
helperbotlog.debug("wait_for_resp cancelled by asyncio", e, exc_info=True)
|
||||||
if topic[11] == "j":
|
|
||||||
resppayload = json.loads(msg["payload"])
|
|
||||||
else:
|
|
||||||
resppayload = str(msg["payload"])
|
|
||||||
resp = {"id": requestid, "ret": "ok", "resp": resppayload}
|
|
||||||
self.command_responses.remove(msg)
|
|
||||||
return resp
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
helperbotlog.debug("wait_for_resp cancelled by asyncio")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
helperbotlog.exception("{}".format(e))
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": requestid,
|
"id": request_id,
|
||||||
"errno": 500,
|
"errno": 500,
|
||||||
"ret": "fail",
|
"ret": "fail",
|
||||||
"debug": "wait for response timed out",
|
"debug": "wait for response timed out",
|
||||||
|
|
@ -85,7 +97,7 @@ class MQTTHelperBot:
|
||||||
async def send_command(self, cmdjson, requestid):
|
async def send_command(self, cmdjson, requestid):
|
||||||
if not self.Client._handler.writer is None:
|
if not self.Client._handler.writer is None:
|
||||||
try:
|
try:
|
||||||
ttopic = "iot/p2p/{}/helperbot/bumper/helperbot/{}/{}/{}/q/{}/{}".format(
|
topic = "iot/p2p/{}/helperbot/bumper/helperbot/{}/{}/{}/q/{}/{}".format(
|
||||||
cmdjson["cmdName"],
|
cmdjson["cmdName"],
|
||||||
cmdjson["toId"],
|
cmdjson["toId"],
|
||||||
cmdjson["toType"],
|
cmdjson["toType"],
|
||||||
|
|
@ -93,16 +105,17 @@ class MQTTHelperBot:
|
||||||
requestid,
|
requestid,
|
||||||
cmdjson["payloadType"],
|
cmdjson["payloadType"],
|
||||||
)
|
)
|
||||||
if cmdjson["payloadType"] == "x":
|
command_dto = CommandDto(cmdjson["payloadType"])
|
||||||
await self.Client.publish(
|
self.commands[requestid] = command_dto
|
||||||
ttopic, str(cmdjson["payload"]).encode(), QOS_0
|
|
||||||
)
|
|
||||||
elif cmdjson["payloadType"] == "j":
|
|
||||||
await self.Client.publish(
|
|
||||||
ttopic, json.dumps(cmdjson["payload"]).encode(), QOS_0
|
|
||||||
)
|
|
||||||
|
|
||||||
resp = await self.wait_for_resp(requestid)
|
if cmdjson["payloadType"] == "j":
|
||||||
|
payload = json.dumps(cmdjson["payload"])
|
||||||
|
else:
|
||||||
|
payload = str(cmdjson["payload"])
|
||||||
|
|
||||||
|
await self.Client.publish(topic, payload.encode(), QOS_0)
|
||||||
|
|
||||||
|
resp = await self._wait_for_resp(command_dto, requestid)
|
||||||
return resp
|
return resp
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
helperbotlog.exception("{}".format(e))
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
@ -112,39 +125,25 @@ class MQTTHelperBot:
|
||||||
"ret": "fail",
|
"ret": "fail",
|
||||||
"debug": "exception occurred please check bumper logs",
|
"debug": "exception occurred please check bumper logs",
|
||||||
}
|
}
|
||||||
|
finally:
|
||||||
|
self.commands.pop(requestid, None)
|
||||||
|
|
||||||
|
|
||||||
class MQTTServer:
|
class MQTTServer:
|
||||||
default_config = None
|
default_config = None
|
||||||
broker = None
|
broker = None
|
||||||
|
|
||||||
async def broker_coro(self):
|
def __init__(self, host: str, port: int, **kwargs):
|
||||||
|
|
||||||
mqttserverlog.info(
|
|
||||||
"Starting MQTT Server at {}:{}".format(self.address[0], self.address[1])
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.broker.start()
|
self._host = host
|
||||||
|
self._port = port
|
||||||
except hbmqtt.broker.BrokerException as e:
|
|
||||||
mqttserverlog.exception(e)
|
|
||||||
# asyncio.create_task(bumper.shutdown())
|
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
mqttserverlog.exception("{}".format(e))
|
|
||||||
# asyncio.create_task(bumper.shutdown())
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, address, **kwargs):
|
|
||||||
try:
|
|
||||||
self.address = address
|
|
||||||
|
|
||||||
# Default config opts
|
# Default config opts
|
||||||
passwd_file = os.path.join(
|
passwd_file = os.path.join(
|
||||||
os.path.join(bumper.data_dir, "passwd")
|
os.path.join(bumper.data_dir, "passwd")
|
||||||
) # For file auth, set user:hash in passwd file see (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example)
|
)
|
||||||
|
# For file auth, set user:hash in passwd file see
|
||||||
|
# (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example)
|
||||||
|
|
||||||
allow_anon = False
|
allow_anon = False
|
||||||
|
|
||||||
|
|
@ -168,7 +167,7 @@ class MQTTServer:
|
||||||
"listeners": {
|
"listeners": {
|
||||||
"default": {"type": "tcp"},
|
"default": {"type": "tcp"},
|
||||||
"tls1": {
|
"tls1": {
|
||||||
"bind": "{}:{}".format(address[0], address[1]),
|
"bind": f"{host}:{port}",
|
||||||
"ssl": "on",
|
"ssl": "on",
|
||||||
"certfile": bumper.server_cert,
|
"certfile": bumper.server_cert,
|
||||||
"keyfile": bumper.server_key,
|
"keyfile": bumper.server_key,
|
||||||
|
|
@ -188,6 +187,22 @@ class MQTTServer:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mqttserverlog.exception("{}".format(e))
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def broker_coro(self):
|
||||||
|
mqttserverlog.info(f"Starting MQTT Server at {self._host}:{self._port}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.broker.start()
|
||||||
|
|
||||||
|
except hbmqtt.broker.BrokerException as e:
|
||||||
|
mqttserverlog.exception(e)
|
||||||
|
# asyncio.create_task(bumper.shutdown())
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
# asyncio.create_task(bumper.shutdown())
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BumperMQTTServer_Plugin:
|
class BumperMQTTServer_Plugin:
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
|
|
@ -314,13 +329,8 @@ class BumperMQTTServer_Plugin:
|
||||||
if topic_split[6] == "helperbot":
|
if topic_split[6] == "helperbot":
|
||||||
# Response to command
|
# Response to command
|
||||||
helperbotlog.debug(f"Received Response - Topic: {topic} - Message: {data_decoded}")
|
helperbotlog.debug(f"Received Response - Topic: {topic} - Message: {data_decoded}")
|
||||||
bumper.mqtt_helperbot.command_responses.append(
|
if topic_split[10] in bumper.mqtt_helperbot.commands:
|
||||||
{
|
bumper.mqtt_helperbot.commands[topic_split[10]].add_response(data_decoded)
|
||||||
"time": time.time(),
|
|
||||||
"topic": topic,
|
|
||||||
"payload": data_decoded,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif topic_split[3] == "helperbot":
|
elif topic_split[3] == "helperbot":
|
||||||
# Helperbot sending command
|
# Helperbot sending command
|
||||||
helperbotlog.debug(f"Send Command - Topic: {topic} - Message: {data_decoded}")
|
helperbotlog.debug(f"Send Command - Topic: {topic} - Message: {data_decoded}")
|
||||||
|
|
@ -333,14 +343,5 @@ class BumperMQTTServer_Plugin:
|
||||||
else:
|
else:
|
||||||
helperbotlog.debug(f"Received Message - Topic: {topic} - Message: {data_decoded}")
|
helperbotlog.debug(f"Received Message - Topic: {topic} - Message: {data_decoded}")
|
||||||
|
|
||||||
# Cleanup "expired messages" > 60 seconds from time
|
|
||||||
for msg in bumper.mqtt_helperbot.command_responses:
|
|
||||||
expire_time = (
|
|
||||||
datetime.fromtimestamp(msg["time"]) + timedelta(seconds=MQTTHelperBot.wait_resp_timeout_seconds)
|
|
||||||
).timestamp()
|
|
||||||
if time.time() > expire_time:
|
|
||||||
helperbotlog.debug(f"Pruning Message Due To Expiration - Message Topic: {msg['topic']}")
|
|
||||||
bumper.mqtt_helperbot.command_responses.remove(msg)
|
|
||||||
|
|
||||||
async def on_broker_client_disconnected(self, client_id):
|
async def on_broker_client_disconnected(self, client_id):
|
||||||
self._set_client_connected(client_id, False)
|
self._set_client_connected(client_id, False)
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,4 @@ tinydb==3.15.2
|
||||||
transitions==0.7.1
|
transitions==0.7.1
|
||||||
websockets==8.1
|
websockets==8.1
|
||||||
yarl==1.3.0
|
yarl==1.3.0
|
||||||
|
cachetools==4.2.2
|
||||||
Loading…
Add table
Add a link
Reference in a new issue