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