Select Git revision
run-diff-to-old.py
main.py 27.72 KiB
# coding=utf-8
import datetime
import queue
import threading
import time
import base64
import flask
import dash
from dash import dcc
from dash import html
import paho.mqtt.client as mqtt
import visdcc
from dash.dependencies import Input, Output, State
from google.protobuf import json_format
import cgv_connector_pb2
import utils
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
fixed_svg_filename = "ros3rag/ros3rag.placeB/images/2022-05-04T10-19-30-9155.svg"
fixed_svg_filename = "2022-05-04T10-19-30-9155.svg"
# fixed_svg_filename = ""
image_directory = '/data/git/jastadd/ros3rag/ros3rag.placeB/images/'
static_image_route = '/static/'
MQTT_SERVER = '192.168.0.122'
# MQTT_SERVER = 'localhost'
mqttc = mqtt.Client()
# mqtt client connected?
ready_event = threading.Event()
# mqtt log reformat object
max_topic = utils.MaxTopicLength()
# buffer for mqtt log
message_queue = queue.Queue()
# button-id: (topic, payload)
commands = {
'send-place-a-model': ('place-a/model', '1'),
'send-place-a-model-details': ('place-a/model', 'details'),
'send-place-a-rewind': ('place-a/rewind', '1'),
'send-place-a-start': ('coordinating/rag-a/command', 'start'),
'send-place-a-exit': ('place-a/exit', '1'),
'send-place-b-model': ('place-b/model', '1'),
'send-place-b-model-details': ('place-b/model', 'details'),
'send-place-b-rewind': ('place-b/rewind', '1'),
'send-place-b-start': ('coordinating/rag-b/command', 'start'),
'send-place-b-exit': ('place-b/exit', '1'),
# 'send-place-a-demo-objRed-blue': ('place-a/demo/move/objectRed1/blue', '1'),
# 'send-place-a-demo-objRed-red': ('place-a/demo/move/objectRed1/red', '1'),
# 'send-place-b-demo-objRed-red': ('place-b/demo', 'objectRed1/red'),
'send-place-b-demo-initial_scene': ('place-b/demo', 'initial_scene'),
# 'send-place-b-demo-arm1-moving': ('place-b/demo', 'arm1/moving'),
# 'send-place-b-demo-arm1-idle': ('place-b/demo', 'arm1/idle'),
# 'send-place-b-demo-arm2-moving': ('place-b/demo', 'arm2/moving'),
# 'send-place-b-demo-arm2-idle': ('place-b/demo', 'arm2/idle'),
# 'send-place-b-demo-big-blue-cz': ('place-b/demo', 'big-blue/cz'),
# 'send-place-b-demo-big-blue-g1': ('place-b/demo', 'big-blue/g1'),
'send-coordinator-model': ('coordinator/model', '1'),
'send-coordinator-model-details': ('coordinator/model', 'details'),
'send-coordinator-exit': ('coordinator/exit', '1'),
# 'send-place-a-robot-ctrl-up': ('ros-place-a/status', 'up'),
# 'send-place-b-robot-ctrl-up': ('ros-place-b/status', 'up'),
'send-place-a-rag-up': ('rag-a/status', 'up'),
'send-place-b-rag-up': ('rag-b/status', 'up'),
'send-dummy-up': ('random/status', 'up'),
# 'send-place-a-robot-ctrl-ready': ('ros-place-a/status', 'ready'),
# 'send-place-b-robot-ctrl-ready': ('ros-place-b/status', 'ready'),
'send-place-a-rag-ready': ('rag-a/status', 'ready'),
'send-place-b-rag-ready': ('rag-b/status', 'ready'),
'send-dummy-ready': ('random/status', 'ready'),
}
# button-id: (topic, textarea-content-id, protobuf-object)
complex_commands = {
'send-place-a-update':
('place-a/scene/update', 'place-a-input-json', cgv_connector_pb2.Scene()),
'send-place-b-update':
('place-b/scene/update', 'place-b-input-json', cgv_connector_pb2.Scene()),
'send-place-b-reachability-1-update':
('place-b/reachability/arm1', 'place-b-reachability-1-json', cgv_connector_pb2.Reachability()),
'send-place-b-reachability-2-update':
('place-b/reachability/arm2', 'place-b-reachability-2-json', cgv_connector_pb2.Reachability()),
}
other_complex_commands = {
'send-arm-state': ('send-arm-state-robot', 'send-arm-state-state'),
'send-obj-pos': ('send-obj-pos-obj', 'send-obj-pos-pos')
}
conversion_topics = {
'/ceti_cell_placeworld/scene/update': (cgv_connector_pb2.Scene(), utils.format_scene),
'/ceti_cell_2_placeworld/scene/update': (cgv_connector_pb2.Scene(), utils.format_scene),
'/moveit_sorting_controller/scene/update': (cgv_connector_pb2.Scene(), utils.format_scene),
'place-a/scene/update': (cgv_connector_pb2.Scene(), utils.format_scene),
'place-b/scene/update': (cgv_connector_pb2.Scene(), utils.format_scene),
'place-b/command': (cgv_connector_pb2.Command(), utils.format_command),
'/ceti_cell_placeworld/command': (cgv_connector_pb2.Command(), utils.format_command),
'place-b/reachability/arm1': (cgv_connector_pb2.Reachability(), utils.format_reachability),
'place-b/reachability/arm2': (cgv_connector_pb2.Reachability(), utils.format_reachability),
'place-b/reachability/arm3': (cgv_connector_pb2.Reachability(), utils.format_reachability),
}
bytes_topics = [
]
topics_enabled_once_seen = [
"coordinating/rag-a/status",
"coordinating/rag-b/status",
"place-b/model/svg/path",
"place-b/arm1/position",
"place-b/arm2/position",
"place-b/demo"
]
svg_image_topic = 'place-b/model/svg/path'
button_style_normal = {"marginRight": "15px", "padding": "2px", "height": "20px", "line-height": "0", "marginTop": "5px"}
button_style_exit = {**button_style_normal, "backgroundColor": "red", "color": "white"}
textarea_style_normal = {'width': '100%', 'height': '200px', 'resize': 'vertical'}
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Tabs([
dcc.Tab(label="Buttons and Log", children=[
html.Div([ # First Row
html.Div([ # Column for place a
dcc.Textarea(
id='place-a-input-json',
placeholder='place-a-input-json',
value='{}',
style=textarea_style_normal
),
html.Button('Send to place-a/scene/update', id='send-place-a-update'),
html.Div(id='hidden-div-scene-a', style={'display': 'none'})
], className="six columns"),
html.Div([ # Column for place b
dcc.Textarea(
id='place-b-input-json',
placeholder='place-b-input-json',
value='{}',
style=textarea_style_normal
),
html.Button('Send to place-b/scene/update', id='send-place-b-update'),
html.Div(id='hidden-div-scene-b', style={'display': 'none'}),
html.Div([ # Row for reachability
html.Div([ # Column for reachability arm 1
dcc.Textarea(
id='place-b-reachability-1-json',
placeholder='place-b-reachability-1-json',
value='{}',
style=textarea_style_normal
),
html.Button('Send to place-b/reachability/arm1', id='send-place-b-reachability-1-update'),
], className="six columns"),
html.Div([ # Column for reachability arm 2
dcc.Textarea(
id='place-b-reachability-2-json',
placeholder='place-b-reachability-2-json',
value='{}',
style=textarea_style_normal
),
html.Button('Send to place-b/reachability/arm2', id='send-place-b-reachability-2-update'),
], className="six columns"),
], className='row', style={'marginTop': '15px'}),
], className="six columns"),
], className='row', style={'display': 'none'}),
# dcc.Markdown("---"),
html.Div([ # Row for commands
html.Div([ # Column for commands of place b
html.H3("Coordinator"),
# html.Div([ # Row for commands coordinator
html.Button('Model', id='send-coordinator-model', style=button_style_normal),
html.Button('Model (Details)', id='send-coordinator-model-details', style=button_style_normal),
html.Button('Exit', id='send-coordinator-exit', style=button_style_exit),
# ], className='row'),
# html.Div([ # Row for commands up
# html.Button('Robot Control A Up', id='send-place-a-robot-ctrl-up', style=button_style_normal),
# html.Button('Robot Control B Up', id='send-place-b-robot-ctrl-up', style=button_style_normal),
html.Button('RAG A Up', id='send-place-a-rag-up', style=button_style_normal),
html.Button('RAG B Up', id='send-place-b-rag-up', style=button_style_normal),
html.Button('Dummy Up', id='send-dummy-up', style=button_style_normal),
# ], className='row'),
# html.Div([ # Row for commands ready
# html.Button('Robot Control A Ready', id='send-place-a-robot-ctrl-ready', style=button_style_normal),
# html.Button('Robot Control B Ready', id='send-place-b-robot-ctrl-ready', style=button_style_normal),
html.Button('RAG A Ready', id='send-place-a-rag-ready', style=button_style_normal),
html.Button('RAG B Ready', id='send-place-b-rag-ready', style=button_style_normal),
html.Button('Dummy Ready', id='send-dummy-ready', style=button_style_normal),
# ], className='row'),
], className="four columns"),
html.Div([ # Column for commands of place a
html.H3("Commands Place A"),
html.Button('Model', id='send-place-a-model', style=button_style_normal),
html.Button('Model (Details)', id='send-place-a-model-details', style=button_style_normal),
html.Button('Rewind', id='send-place-a-rewind', style=button_style_normal),
html.Button('Start', id='send-place-a-start', style=button_style_normal),
html.Button('Exit', id='send-place-a-exit', style=button_style_exit),
# html.Button('obj-Red -> Red', id='send-place-a-demo-objRed-red', style=button_style_normal),
# html.Button('obj-Red -> Blue', id='send-place-a-demo-objRed-blue', style=button_style_normal),
], className="four columns"),
html.Div([ # Column for commands of place b
html.H3("Commands Place B"),
html.Button('Model', id='send-place-b-model', style=button_style_normal),
html.Button('Model (Details)', id='send-place-b-model-details', style=button_style_normal),
html.Button('Rewind', id='send-place-b-rewind', style=button_style_normal),
html.Button('Start', id='send-place-b-start', style=button_style_normal),
html.Button('Exit', id='send-place-b-exit', style=button_style_exit),
# html.Button('obj-Red -> Red', id='send-place-b-demo-objRed-red', style=button_style_normal),
html.Button('initial_scene', id='send-place-b-demo-initial_scene', style=button_style_normal),
# html.Button('arm1-moving', id='send-place-b-demo-arm1-moving', style=button_style_normal),
# html.Button('arm1-idle', id='send-place-b-demo-arm1-idle', style=button_style_normal),
# html.Button('arm2-moving', id='send-place-b-demo-arm2-moving', style=button_style_normal),
# html.Button('arm2-idle', id='send-place-b-demo-arm2-idle', style=button_style_normal),
# html.Button('big-blue-cz', id='send-place-b-demo-big-blue-cz', style=button_style_normal),
# html.Button('big-blue-g1', id='send-place-b-demo-big-blue-g1', style=button_style_normal),
html.Div([
dcc.RadioItems(['arm1', 'arm2'], 'arm1', id='send-arm-state-robot', inline=True, style={"marginRight": "10px", "marginTop": "5px"}),
dcc.RadioItems([{'label': 'Idle', 'value': 'STATE_IDLE'},
{'label': 'Picking', 'value': 'STATE_PICKING'},
{'label': 'Placing', 'value': 'STATE_PLACING'},
{'label': 'Moving', 'value': 'STATE_MOVING'}]
, 'STATE_MOVING', id='send-arm-state-state', inline=True, style={"marginRight": "10px", "marginTop": "5px"}),
html.Button('Send', id='send-arm-state', style=button_style_normal)
], className='row', style={"display":"flex", "border": "1px black solid", "border-radius": "5px", "marginTop": "5px"}),
html.Div([
dcc.RadioItems(['blue1', 'green1'], 'blue1',
id='send-obj-pos-obj', inline=True, style={"marginRight": "10px", "marginTop": "5px"}),
dcc.RadioItems(['cz1', 'G1', 'G2', 'B1', 'B2'], 'cz1',
id='send-obj-pos-pos', inline=True, style={"marginRight": "10px", "marginTop": "5px"}),
html.Button('Send', id='send-obj-pos', style=button_style_normal)
], className='row', style={"display":"flex", "border": "1px black solid", "border-radius": "5px", "marginTop": "5px"})
], className="four columns"),
], className='row'),
# dcc.Markdown("---"),
html.H2("Filtered MQTT Log"),
dcc.Textarea(
id='filtered-mqtt-log',
value="",
readOnly=True,
style={**textarea_style_normal, 'height': '400px', 'fontFamily': 'Consolas, monospace'}
),
dcc.Checklist(
id='topics-to-filter',
options=[{'label': topic, 'value': topic} for topic in conversion_topics],
value=[topic for topic in conversion_topics],
labelStyle={'display': 'inline-block'}
),
html.H2("MQTT Log"),
dcc.Textarea(
id='mqtt-log',
value="",
readOnly=True,
style={**textarea_style_normal, 'height': '400px', 'fontFamily': 'Consolas, monospace'}
),
dcc.Checklist(
id="should-scroll-mqtt-log",
options=[{"label": "Auto-Scroll", "value": "Auto-Scroll"}],
value=["Auto-Scroll"],
labelStyle={"display": "inline-block"},
),
dcc.Checklist(
id="mqtt-connected",
options=[{"label": "MQTT connected?", "value": "yes", "disabled": True}],
value=[],
labelStyle={"display": "inline-block"},
),
html.Button('Clear log', id='clear-mqtt-log', style=button_style_normal),
dcc.Markdown("---"),
html.Div([
html.P("Topic"),
dcc.Input(id='manual-mqtt-topic', style={"margin-left": "5px"}),
html.P("Message", style={"margin-left": "15px"}),
dcc.Input(id='manual-mqtt-message', style={"margin-left": "5px"}),
# dcc.Textarea(id='manual-mqtt-message', style={'width': '50%', 'font-family': 'Consolas, monospace'}),
html.Button('Send', id='manual-mqtt-send', style={"margin-left": "15px"})
], className='row', style=dict(display='flex')),
# -- Invisible elements --
dcc.Location(id='url', refresh=False),
dcc.Interval(
id='every-1-second',
interval=1000, # in milliseconds
n_intervals=0
),
visdcc.Run_js(id='javascriptLog', run=""),
html.Div(id='hidden-div', style={'display': 'none'})
]), # Tab "Buttons and Log"
dcc.Tab(label="SVG Model B", children=[
html.Div([
html.P("Name:"),
html.P(id='model-b-svg-name', style={"margin-left": "15px"}),
], className='row', style=dict(display='flex')),
html.Div([
html.Img(id='model-b-svg-img', src="/static/{}".format(fixed_svg_filename))
], style=dict(position='fixed', overflow='scroll', width='100%')),
# html.Img(src="data:image/svg;base64,{}".format(base64.b64encode(open(fixed_svg_filename, 'rb').read()).decode()))
]) # Tab "SVG Model B"
]) # Tabs
]) # Div
@app.callback(
Output('place-a-input-json', 'value'),
Output('place-b-input-json', 'value'),
Output('place-b-reachability-1-json', 'value'),
Output('place-b-reachability-2-json', 'value'),
Input('url', 'pathname')
)
def set_initial_json_content(_pathname):
"""
Set initial JSON content from files in config/ directory
:param _pathname: Unused value of page URL
:return: list of contents
"""
with open('config/config-scene-a.json') as fdr:
json_content_a = fdr.read()
with open('config/config-scene-b.json') as fdr:
json_content_b = fdr.read()
with open('config/dummy-reachability-b-arm1.json') as fdr:
json_content_b_reachability_1 = fdr.read()
with open('config/dummy-reachability-b-arm2.json') as fdr:
json_content_b_reachability_2 = fdr.read()
return json_content_a, json_content_b, json_content_b_reachability_1, json_content_b_reachability_2
def send_json_for_object_to_topic(json_content: str, obj, topic: str):
"""
Use JSON to populate protobuf object and send its serialization to the given topic
:param json_content: Content to initialize the object represented in JSON
:param obj: The object to send
:param topic: The MQTT topic to send the serialization
:return: None
"""
json_format.Parse(json_content, obj)
mqttc.publish(topic=topic, payload=obj.SerializeToString())
# @app.callback(
# Output('hidden-div-scene-a', 'children'),
# Input('send-place-a-update', 'n_clicks'),
# State('place-a-input-json', 'value')
# )
# def send_scene_a(n_clicks, json_content):
# """
# Send scene in place a
# :param n_clicks: button.clicks
# :param json_content: state of place-a-input-json
# :return: no_update
# """
# if n_clicks:
# scene = cgv_connector_pb2.Scene()
# send_json_for_object_to_topic(json_content, scene, 'place-a/scene/update')
# return dash.no_update
#
#
# @app.callback(
# Output('hidden-div-scene-b', 'children'),
# Input('send-place-b-update', 'n_clicks'),
# State('place-b-input-json', 'value')
# )
# def send_scene_a(n_clicks, json_content):
# """
# Send scene in place b
# :param n_clicks: button.clicks
# :param json_content:state of place-b-input-json
# :return:no_update
# """
# if n_clicks:
# scene = cgv_connector_pb2.Scene()
# send_json_for_object_to_topic(json_content, scene, 'place-b/scene/update')
# return dash.no_update
@app.callback(
Output('hidden-div-scene-b', 'children'),
[Input(button_id, 'n_clicks') for button_id in complex_commands],
[Input(button_id, 'n_clicks') for button_id in other_complex_commands],
[State(value[1], 'value') for value in complex_commands.values()],
[State(state, 'value') for states in other_complex_commands.values() for state in states]
)
def send_complex(*_):
ctx = dash.callback_context
if not ctx.triggered:
button_id = None
else:
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id:
if button_id == 'send-arm-state':
robot = ctx.states["send-arm-state-robot.value"]
state = ctx.states["send-arm-state-state.value"]
mqttc.publish(topic='place-b/demo', payload=robot + '/' + state[6:].lower())
elif button_id == 'send-obj-pos':
obj = ctx.states["send-obj-pos-obj.value"]
pos = ctx.states["send-obj-pos-pos.value"]
mqttc.publish(topic='place-b/demo', payload=obj + '/' + pos)
else:
topic, state_id, protobuf_obj = complex_commands[button_id]
json_content = ctx.states[state_id + ".value"]
send_json_for_object_to_topic(json_content, protobuf_obj, topic)
return dash.no_update
@app.callback(
Output('mqtt-connected', 'value'),
Input('every-1-second', 'n_intervals'),
)
def check_connection(_n_intervals):
if not ready_event.is_set():
print('.', end='', flush=True)
return []
return ['yes']
@app.callback(
Output('filtered-mqtt-log', 'value'),
Output('mqtt-log', 'value'),
Output('javascriptLog', 'run'),
Output('topics-to-filter', 'options'),
Output('topics-to-filter', 'value'),
Output('model-b-svg-img', 'src'),
Input('every-1-second', 'n_intervals'),
Input('clear-mqtt-log', 'n_clicks'),
Input('topics-to-filter', 'options'),
Input('topics-to-filter', 'value'),
Input('model-b-svg-img', 'src'),
State('filtered-mqtt-log', 'value'),
State('mqtt-log', 'value'),
State('should-scroll-mqtt-log', 'value')
)
def append_to_mqtt_log(_n_intervals, clear_n_clicks, filter_options, topics_to_filter, previous_svg_img_src,
filtered_value, value, should_scroll):
"""
Periodically update mqtt log
:param (Input) _n_intervals: Unused value of intervals
:param (Input) clear_n_clicks: clear.n_clicks
:param (Input) filter_options: displayed topics to show in filtered log
:param (Input) topics_to_filter: topics to show in filtered log
:param (Input) previous_svg_img_src: previous 'src' of the svg showing latest model of site B
:param (State) value: current content of mqtt log
:param (State) filtered_value: current content of filtered mqtt log
:param (State) should_scroll: checkbox value whether to scroll to the end after update
:return: new content of mqtt log
"""
new_svg_img_src = previous_svg_img_src
ctx = dash.callback_context
if not ctx.triggered:
return dash.no_update
trigger_id = ctx.triggered[0]['prop_id'].split('.')[0]
if trigger_id == 'clear-mqtt-log':
return "", "", "", filter_options, topics_to_filter, new_svg_img_src
if trigger_id == 'topics-to-filter':
filtered_value = ""
last_match = True
for line in value.split('\n'):
if utils.topic_match(topics_to_filter, line, last_match=last_match)[0]:
filtered_value += line + '\n'
last_match = True
else:
last_match = False
else:
# assume trigger_id == 'every-1-second'
local_messages = []
while not message_queue.empty():
local_messages.append(message_queue.get_nowait())
if local_messages:
if value:
value += "\n"
else:
value = ""
value += '\n'.join(local_messages)
if not filtered_value:
filtered_value = ""
for msg in local_messages:
topic_match, topic = utils.topic_match(topics_to_filter, msg)
if topic == svg_image_topic:
new_svg_img_src = '/static/' + utils.parse_log_msg(msg)[2]
print('found new svg: ' + new_svg_img_src)
if topic not in (option['label'] for option in filter_options):
filter_options.append({'label': topic, 'value': topic})
if topic in topics_enabled_once_seen:
topics_to_filter.append(topic)
topic_match = True
if topic_match:
filtered_value += msg + "\n"
else:
return dash.no_update
log_cmd = '''
var filtered_textarea = document.getElementById('filtered-mqtt-log');
filtered_textarea.scrollTop = filtered_textarea.scrollHeight;
var textarea = document.getElementById('mqtt-log');
textarea.scrollTop = textarea.scrollHeight;
''' if should_scroll else ""
return filtered_value, value, log_cmd, filter_options, topics_to_filter, new_svg_img_src
@app.callback(
Output('hidden-div', 'children'),
[Input(button_id, 'n_clicks') for button_id in commands]
)
def button_clicked_to_add_to_mqtt_log(*_):
"""
Based on dict "commands", send message to topic
:param _: Unused "catch-all" variable for n_clicks of all buttons
:return: no_update
"""
ctx = dash.callback_context
if not ctx.triggered:
button_id = None
else:
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id:
topic, payload = commands[button_id]
mqttc.publish(topic=topic, payload=payload)
return dash.no_update
@app.callback(
Output('manual-mqtt-topic', 'value'),
Input('manual-mqtt-send', 'n_clicks'),
State('manual-mqtt-topic', 'value'),
State('manual-mqtt-message', 'value'),
)
def send_manual(n_clicks, topic, message):
"""
Manual sending of mqtt message
:param n_clicks: button.n_clicks
:param topic: value of input field manual-mqtt-topic
:param message: value of input field manual-mqtt-message
:return: no_update
"""
if n_clicks:
mqttc.publish(topic=topic, payload=message)
return dash.no_update
@app.callback(
Output('model-b-svg-name', 'children'),
Input('model-b-svg-img', 'src'),
)
def update_svg_name(name):
return name
# Add a static image route that serves images from desktop
# Be *very* careful here - you don't want to serve arbitrary files
# from your computer or server
@app.server.route('{}<image_path>.svg'.format(static_image_route))
def serve_image(image_path):
image_name = '{}.svg'.format(image_path)
print('{}/{}'.format(image_directory, image_name))
# if image_name not in list_of_images:
# raise Exception('"{}" is excluded from the allowed static files'.format(image_path))
return flask.send_from_directory(image_directory, image_name)
def on_mqtt_connect(_client, _userdata, _flags, _rc, _properties=None):
# Callback for mqtt client when connected
print(f'\nConnected to {MQTT_SERVER} at {datetime.datetime.now().isoformat()}')
ready_event.set()
mqttc.subscribe(topic='#')
threading.Thread(target=publish_test_message).start()
def on_mqtt_disconnect(_client, _userdata, _rc):
print('Lost connection at ' + datetime.datetime.now().isoformat())
ready_event.clear()
def on_mqtt_message(_client, _userdata, message):
# Callback for mqtt client when message was received
max_mqtt_topic_length = max_topic.process_topic(message.topic)
if message.topic in conversion_topics:
try:
obj, conversion = conversion_topics[message.topic]
obj.ParseFromString(message.payload)
payload = conversion(obj)
except Exception as e:
payload = f"(failed to parse {e})"
elif message.topic in bytes_topics:
payload = "(ignored bytes)"
else:
try:
payload = message.payload.decode("utf-8")
except UnicodeDecodeError:
payload = "(unreadable bytes)"
message_queue.put_nowait(utils.format_log_msg(message.topic,
max_mqtt_topic_length,
payload)) # .replace("\n", " ~ "))
def publish_test_message():
"""
Publish test message to see that app has started and mqtt client has connected (both successfully)
:return: None
"""
time.sleep(2)
mqttc.publish(topic="init", payload=datetime.datetime.now().isoformat())
if __name__ == '__main__':
print('Starting web-ros3rag')
mqttc.on_connect = on_mqtt_connect
mqttc.on_disconnect = on_mqtt_disconnect
mqttc.on_message = on_mqtt_message
mqttc.reconnect_delay_set(max_delay=2)
mqttc.connect_async(MQTT_SERVER)
mqttc.loop_start()
if not ready_event.wait(2.0): # wait 2 seconds
print('Could not connect to mqtt in time!')
app.run_server(debug=True)