From d8239c61eb83852ed29e595d72af5d448490f41e Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Sun, 2 May 2021 12:39:37 +0200 Subject: [PATCH] buttons are working, reformatting - mqtt log adjusts to topic length - split view into place-a and place-b - scene/update not implemented yet --- main.py | 114 ++++++++++++++++++++++++++++++++++++++----------------- utils.py | 33 ++++++++++++++++ 2 files changed, 113 insertions(+), 34 deletions(-) create mode 100644 utils.py diff --git a/main.py b/main.py index b86b0bc..6838b75 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,6 @@ import datetime import queue import threading import time -from threading import Thread import dash import dash_core_components as dcc @@ -11,44 +10,77 @@ import dash_html_components as html import paho.mqtt.client as mqtt from dash.dependencies import Input, Output, State +import utils + external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] 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-exit': ('place-a/exit', '1'), + 'send-place-b-model': ('place-b/model', '1'), + 'send-place-b-exit': ('place-b/exit', '1'), +} + app = dash.Dash(__name__, external_stylesheets=external_stylesheets) app.layout = html.Div([ - html.P("Send to scene/update"), - dcc.Textarea( - id='place-a-input-json', - placeholder='place-a-input-json', - value='{}', - style={'width': '100%'} - ), - html.Button('Send', id='send-place-a-update'), + html.Div([ + html.Div([ + dcc.Textarea( + id='place-a-input-json', + placeholder='place-a-input-json', + value='{}', + style={'width': '100%'} + ), + html.Button('Send to place-a/scene/update', id='send-place-a-update'), + ], className="six columns"), + html.Div([ + dcc.Textarea( + id='place-b-input-json', + placeholder='place-b-input-json', + value='{}', + style={'width': '100%'} + ), + html.Button('Send to place-b/scene/update', id='send-place-b-update'), + ], className="six columns"), + ], className='row'), dcc.Markdown("---"), html.H3("Commands"), - html.Button('Place A: Model', id='send-place-a-model', style={"margin-right": "15px"}), - html.Button('Place A: Exit', id='send-place-a-exit', style={"margin-right": "15px"}), - html.Button('Place B: Model', id='send-place-b-model', style={"margin-right": "15px"}), - html.Button('Place B: Exit', id='send-place-b-exit', style={"margin-right": "15px"}), + html.Div([ + html.Div([ + html.Button('Place A: Model', id='send-place-a-model', style={"margin-right": "15px"}), + html.Button('Place A: Exit', id='send-place-a-exit', style={"margin-right": "15px"}), + ], className="six columns"), + html.Div([ + html.Button('Place B: Model', id='send-place-b-model', style={"margin-right": "15px"}), + html.Button('Place B: Exit', id='send-place-b-exit', style={"margin-right": "15px"}), + ], className="six columns"), + ], className='row'), + dcc.Markdown("---"), html.H3("MQTT Log"), dcc.Textarea( id='mqtt-log', readOnly=True, rows=50, - style={'width': '100%', 'height': '200px'} + style={'width': '100%', 'height': '200px', 'font-family': 'Consolas, monospace'} ), dcc.Interval( id='every-1-second', interval=1000, # in milliseconds n_intervals=0 ), - html.Button("add to mqtt log", id="add-to-mqtt-log"), html.Div(id='hidden-div', style={'display': 'none'}) ]) @@ -56,7 +88,7 @@ app.layout = html.Div([ @app.callback( Output('mqtt-log', 'value'), Input('every-1-second', 'n_intervals'), - State('mqtt-log', 'value') + State('mqtt-log', 'value'), ) def append_to_mqtt_log(_, value): local_messages = [] @@ -67,49 +99,63 @@ def append_to_mqtt_log(_, value): value += "\n" else: value = "" - return value + ('\n'.join(local_messages)) + value += '\n'.join(local_messages) + if max_topic.get_and_clear(): + lines = value.split('\n') + reformatted_lines = [] + for line in lines: + timestamp, topic, message = utils.parse_log_msg(line) + print(f'{timestamp, topic, message}') + reformatted_lines.append(utils.format_log_msg(topic, max_topic.max_mqtt_topic_length, + message, timestamp=timestamp)) + value = '\n'.join(reformatted_lines) return value @app.callback( Output('hidden-div', 'children'), - Input('add-to-mqtt-log', 'n_clicks'), + Input('send-place-a-model', 'n_clicks'), + Input('send-place-a-exit', 'n_clicks'), + Input('send-place-b-model', 'n_clicks'), + Input('send-place-b-exit', 'n_clicks'), ) -def button_clicked_to_add_to_mqtt_log(n_clicks): - if n_clicks: - print("button clicked") - message_queue.put_nowait("Button clicked " + str(n_clicks) + " times") +def button_clicked_to_add_to_mqtt_log(*_): + 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 -def on_mqtt_connect(client, userdata, flags, rc, properties=None): +def on_mqtt_connect(_client, _userdata, _flags, _rc, _properties=None): print('Connected at ' + datetime.datetime.now().isoformat()) ready_event.set() -def on_mqtt_message(client, userdata, message): - payload = message.topic + ": " + message.payload.decode('utf-8') - print('Got mqtt message: ' + payload + ", userdata: " + str(userdata)) - message_queue.put_nowait(payload) - - -def on_mqtt_subscribe(client, userdata, mid, granted_qos, properties=None): - print("Subscribed") +def on_mqtt_message(_client, _userdata, message): + max_mqtt_topic_length = max_topic.process_topic(message.topic) + message_queue.put_nowait(utils.format_log_msg(message.topic, + max_mqtt_topic_length, + message.payload.decode("utf-8"))) def publish_test_message(): time.sleep(2) - mqttc.publish(topic="test", payload=datetime.datetime.now().isoformat()) + mqttc.publish(topic="init", payload=datetime.datetime.now().isoformat()) if __name__ == '__main__': mqttc.on_connect = on_mqtt_connect mqttc.on_message = on_mqtt_message - mqttc.on_subscribe = on_mqtt_subscribe 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!') mqttc.subscribe(topic='#') - Thread(target=publish_test_message).start() + threading.Thread(target=publish_test_message).start() app.run_server(debug=True) diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..c807eaf --- /dev/null +++ b/utils.py @@ -0,0 +1,33 @@ +import threading +from datetime import datetime + + +class MaxTopicLength: + def __init__(self): + self.max_mqtt_topic_length = 1 + self.mqtt_log_reformat_event = threading.Event() + + def process_topic(self, topic): + if len(topic) > self.max_mqtt_topic_length: + self.max_mqtt_topic_length = len(topic) + print(f'new long topic length: {self.max_mqtt_topic_length}') + self.mqtt_log_reformat_event.set() + return self.max_mqtt_topic_length + + def get_and_clear(self): + if self.mqtt_log_reformat_event.is_set(): + self.mqtt_log_reformat_event.clear() + return True + return False + + +def format_log_msg(topic: str, max_mqtt_topic_length: int, message: str, timestamp: datetime = None): + now = timestamp or datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # padding = max_mqtt_topic_length - len(topic) + 1 + return f'[{now} @ {topic:{max_mqtt_topic_length}}] {message}' + + +def parse_log_msg(entry: str): + at_index = entry.index('@') + closing_bracket_index = entry.index(']') + return entry[1:at_index].strip(), entry[at_index + 1:closing_bracket_index].strip(), entry[closing_bracket_index+2:] -- GitLab