SissiFeng commited on
Commit
8714c43
Β·
verified Β·
1 Parent(s): 8a48018

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +568 -0
app.py ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import gradio as gr
3
+ import paho.mqtt.client as mqtt
4
+ import time
5
+ import random
6
+ from queue import Queue
7
+ import numpy as np
8
+ import pandas as pd
9
+ from datetime import datetime, timedelta
10
+ import plotly.graph_objects as go
11
+
12
+ # Global configuration
13
+ MQTT_HOST = "broker.hivemq.com"
14
+ MQTT_PORT = 1883
15
+
16
+ # Global state
17
+ response_queue = Queue()
18
+ command_queue = Queue()
19
+ mqtt_ping_client = None
20
+ mqtt_pong_client = None
21
+ session_id = None
22
+ device_state = {
23
+ "rgb": {"r": 0, "g": 0, "b": 0},
24
+ "temperature": 25.0,
25
+ "rpm": 0
26
+ }
27
+
28
+ # Add historical data storage
29
+ history_data = {
30
+ "rgb": [],
31
+ "temperature": [],
32
+ "timestamps": []
33
+ }
34
+
35
+ # MQTT callback functions
36
+ def on_ping_connect(client, userdata, flags, rc):
37
+ print(f"Ping connected with result code {rc}")
38
+ if session_id:
39
+ client.subscribe(f"pong/{session_id}/response")
40
+
41
+ def on_pong_connect(client, userdata, flags, rc):
42
+ print(f"Pong connected with result code {rc}")
43
+ client.subscribe("ping/command")
44
+
45
+ def on_ping_message(client, userdata, msg):
46
+ try:
47
+ response = json.loads(msg.payload.decode())
48
+ response_queue.put(response)
49
+ print(f"Ping received: {response}")
50
+ except Exception as e:
51
+ print(f"Ping error: {e}")
52
+
53
+ def on_pong_message(client, userdata, msg):
54
+ try:
55
+ command = json.loads(msg.payload.decode())
56
+ command_queue.put(command)
57
+ print(f"Pong received: {command}")
58
+ except Exception as e:
59
+ print(f"Pong error: {e}")
60
+
61
+ # Ping functionality
62
+ def initialize_ping():
63
+ global mqtt_ping_client, session_id
64
+ session_id = f"ping_{int(time.time())}"
65
+ mqtt_ping_client = mqtt.Client()
66
+ mqtt_ping_client.on_connect = on_ping_connect
67
+ mqtt_ping_client.on_message = on_ping_message
68
+ mqtt_ping_client.connect(MQTT_HOST, MQTT_PORT, 60)
69
+ mqtt_ping_client.loop_start()
70
+ return f"Ping initialized: {session_id}"
71
+
72
+ def send_command(command_type, data=None):
73
+ if not mqtt_ping_client:
74
+ return "Please initialize ping first"
75
+
76
+ payload = {
77
+ "type": command_type,
78
+ "data": data or {},
79
+ "session_id": session_id,
80
+ "timestamp": time.time()
81
+ }
82
+ mqtt_ping_client.publish("ping/command", json.dumps(payload))
83
+ return f"Sent {command_type}"
84
+
85
+ def send_rgb(r, g, b):
86
+ if not mqtt_ping_client:
87
+ return "Please initialize ping first"
88
+
89
+ payload = {
90
+ "type": "RGB Command",
91
+ "data": {"r": r, "g": g, "b": b},
92
+ "session_id": session_id,
93
+ "timestamp": time.time()
94
+ }
95
+ # Send command
96
+ mqtt_ping_client.publish("ping/command", json.dumps(payload))
97
+ # Put command into command queue for pong display
98
+ command_queue.put(payload)
99
+ return f"Sent RGB Command: R={r}, G={g}, B={b}"
100
+
101
+ def send_weight_request(rpm):
102
+ return send_command("Weight Data", {"set_rpm": rpm, "request_weight": True})
103
+
104
+ # Pong functionality
105
+ def initialize_pong():
106
+ global mqtt_pong_client
107
+ mqtt_pong_client = mqtt.Client()
108
+ mqtt_pong_client.on_connect = on_pong_connect
109
+ mqtt_pong_client.on_message = on_pong_message
110
+ mqtt_pong_client.connect(MQTT_HOST, MQTT_PORT, 60)
111
+ mqtt_pong_client.loop_start()
112
+ return "Pong started"
113
+
114
+ def process_command(command):
115
+ global device_state
116
+ command_type = command.get("type")
117
+ data = command.get("data", {})
118
+ session_id = command.get("session_id")
119
+ timestamp = datetime.fromtimestamp(command.get("timestamp", time.time()))
120
+
121
+ if command_type == "RGB Command":
122
+ # Update device state
123
+ device_state = {
124
+ **device_state,
125
+ "rgb": {
126
+ "r": int(data.get("r", 0)),
127
+ "g": int(data.get("g", 0)),
128
+ "b": int(data.get("b", 0))
129
+ }
130
+ }
131
+ print(f"Processing RGB command: {data}")
132
+ response_data = {
133
+ "current_state": "applied",
134
+ "power_consumption": random.uniform(0.1, 1.0),
135
+ "applied_values": device_state["rgb"]
136
+ }
137
+ # Record RGB value (using average for simplicity)
138
+ rgb_avg = sum([int(data.get(k, 0)) for k in ['r', 'g', 'b']]) / 3
139
+ record_data("rgb", rgb_avg, timestamp)
140
+ elif command_type == "Temperature Reading":
141
+ device_state["temperature"] += random.uniform(-0.5, 0.5)
142
+ response_data = {
143
+ "current_temperature": device_state["temperature"],
144
+ "humidity": random.uniform(40, 60),
145
+ "pressure": random.uniform(980, 1020)
146
+ }
147
+ # Record temperature
148
+ record_data("temperature", device_state["temperature"], timestamp)
149
+ elif command_type == "Weight Data":
150
+ if "set_rpm" in data:
151
+ device_state["rpm"] = data["set_rpm"]
152
+ response_data = {
153
+ "calibrated_weight": random.uniform(95, 105),
154
+ "current_rpm": device_state["rpm"],
155
+ "stability": random.uniform(0.98, 1.02)
156
+ }
157
+ else:
158
+ response_data = {"error": "Unknown command type"}
159
+
160
+ response = {
161
+ "type": command_type,
162
+ "data": response_data,
163
+ "timestamp": time.time(),
164
+ "session_id": session_id
165
+ }
166
+
167
+ if mqtt_pong_client:
168
+ mqtt_pong_client.publish(f"pong/{session_id}/response", json.dumps(response))
169
+ return json.dumps(response, indent=2)
170
+
171
+ def check_ping_responses():
172
+ """Check the ping response queue"""
173
+ responses = []
174
+ while not response_queue.empty():
175
+ response = response_queue.get_nowait()
176
+ responses.append(json.dumps(response, indent=2))
177
+ return "\n".join(responses) if responses else "No new responses"
178
+
179
+ def check_pong_commands():
180
+ """Check and process command queue"""
181
+ responses = []
182
+ while not command_queue.empty():
183
+ command = command_queue.get_nowait()
184
+ response = process_command(command)
185
+ responses.append(response)
186
+ return "\n".join(responses) if responses else "No new commands"
187
+
188
+ # Add stop_mqtt function
189
+ def stop_mqtt():
190
+ global mqtt_ping_client, mqtt_pong_client
191
+ if mqtt_ping_client:
192
+ mqtt_ping_client.loop_stop()
193
+ mqtt_ping_client.disconnect()
194
+ if mqtt_pong_client:
195
+ mqtt_pong_client.loop_stop()
196
+ mqtt_pong_client.disconnect()
197
+ return "Both Ping and Pong clients stopped"
198
+
199
+ # Add real-time update functions
200
+ def update_rgb_preview(r, g, b):
201
+ """Real-time update RGB preview values"""
202
+ global device_state
203
+ device_state = {
204
+ **device_state,
205
+ "rgb": {
206
+ "r": int(r),
207
+ "g": int(g),
208
+ "b": int(b)
209
+ }
210
+ }
211
+ return gr.update(value=device_state)
212
+
213
+ def update_rpm_preview(rpm):
214
+ """Real-time update RPM preview values"""
215
+ global device_state
216
+ device_state = {
217
+ **device_state,
218
+ "rpm": int(rpm)
219
+ }
220
+ return gr.update(value=device_state)
221
+
222
+ def update_temperature_preview(temperature):
223
+ """Real-time update temperature preview values"""
224
+ global device_state
225
+ device_state = {
226
+ **device_state,
227
+ "temperature": float(temperature)
228
+ }
229
+ return gr.update(value=device_state)
230
+
231
+ # Add data recording and prediction functions
232
+ def record_data(data_type, value, timestamp):
233
+ """Record historical data"""
234
+ history_data[data_type].append(value)
235
+ history_data["timestamps"].append(timestamp)
236
+ # Keep only the last 100 data points
237
+ if len(history_data[data_type]) > 100:
238
+ history_data[data_type] = history_data[data_type][-100:]
239
+ history_data["timestamps"] = history_data["timestamps"][-100:]
240
+
241
+ def generate_prediction(data_type):
242
+ """Generate simple prediction"""
243
+ if len(history_data[data_type]) < 2:
244
+ return None
245
+
246
+ # Use simple linear regression for prediction
247
+ x = np.arange(len(history_data[data_type]))
248
+ y = np.array(history_data[data_type])
249
+ z = np.polyfit(x, y, 1)
250
+ p = np.poly1d(z)
251
+
252
+ # Predict the next 5 points
253
+ future_x = np.arange(len(x), len(x) + 5)
254
+ future_y = p(future_x)
255
+
256
+ return {
257
+ "historical": history_data[data_type],
258
+ "predicted": future_y.tolist(),
259
+ "timestamps": history_data["timestamps"]
260
+ }
261
+
262
+ def plot_data_with_prediction(data_type):
263
+ """Create chart with prediction"""
264
+ if len(history_data[data_type]) < 2:
265
+ return None
266
+
267
+ # Use simple linear regression for prediction
268
+ x = np.arange(len(history_data[data_type]))
269
+ y = np.array(history_data[data_type])
270
+ z = np.polyfit(x, y, 1)
271
+ p = np.poly1d(z)
272
+
273
+ # Predict the next 5 points
274
+ future_x = np.arange(len(x), len(x) + 5)
275
+ future_y = p(future_x)
276
+
277
+ # Create Plotly chart
278
+ fig = go.Figure()
279
+
280
+ # Add historical data
281
+ fig.add_trace(go.Scatter(
282
+ x=[t.strftime('%H:%M:%S') for t in history_data["timestamps"]],
283
+ y=history_data[data_type],
284
+ mode='lines+markers',
285
+ name='Historical'
286
+ ))
287
+
288
+ # Add predicted data
289
+ future_times = [
290
+ (history_data["timestamps"][-1] + timedelta(minutes=i)).strftime('%H:%M:%S')
291
+ for i in range(1, 6)
292
+ ]
293
+ fig.add_trace(go.Scatter(
294
+ x=future_times,
295
+ y=future_y,
296
+ mode='lines',
297
+ line=dict(dash='dash'),
298
+ name='Predicted'
299
+ ))
300
+
301
+ # Update layout
302
+ fig.update_layout(
303
+ title=f"{data_type.upper()} Trend Analysis",
304
+ xaxis_title="Time",
305
+ yaxis_title="Value",
306
+ showlegend=True
307
+ )
308
+
309
+ return fig
310
+
311
+ # Add manual refresh function
312
+ def refresh_all():
313
+ """Manually refresh all states"""
314
+ commands = check_pong_commands()
315
+ print(f"Current device state: {device_state}")
316
+ return [commands, gr.update(value=device_state)]
317
+
318
+ # Gradio interface
319
+ with gr.Blocks(title="MQTT Ping-Pong System", theme=gr.themes.Base(
320
+ primary_hue=gr.themes.colors.Color(
321
+ c50="#edf2f0",
322
+ c100="#dbE5E1",
323
+ c200="#B8CCC6",
324
+ c300="#96B3AB",
325
+ c400="#7FA595", # Right main color
326
+ c500="#5C8072", # Left main color
327
+ c600="#4A665B",
328
+ c700="#374D45",
329
+ c800="#25332E",
330
+ c900="#121917",
331
+ c950="#080C0B", # Add this
332
+ )
333
+ )) as demo:
334
+ gr.Markdown("# πŸ“ MQTT Ping-Pong Communication System <span class='pong-emoji'>πŸ“</span>")
335
+
336
+ # Add CSS, including font settings
337
+ gr.HTML("""
338
+ <style>
339
+ /* Basic styles */
340
+ .ping-panel {
341
+ background-color: #5C8072 !important;
342
+ border-radius: 8px;
343
+ padding: 20px;
344
+ }
345
+ .pong-panel {
346
+ background-color: #7FA595 !important;
347
+ border-radius: 8px;
348
+ padding: 20px;
349
+ }
350
+
351
+ /* Font settings */
352
+ * {
353
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
354
+ "Helvetica Neue", Arial, sans-serif;
355
+ }
356
+
357
+ /* Title styles */
358
+ h1, h2, h3, .header {
359
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
360
+ "Helvetica Neue", Arial, sans-serif;
361
+ font-weight: 600;
362
+ color: #2c3e50;
363
+ }
364
+
365
+ /* Button styles */
366
+ .gr-button {
367
+ margin: 10px 0;
368
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
369
+ "Helvetica Neue", Arial, sans-serif;
370
+ font-weight: 500;
371
+ }
372
+
373
+ /* Group styles */
374
+ .gr-group {
375
+ background-color: rgba(255, 255, 255, 0.1);
376
+ border-radius: 8px;
377
+ padding: 15px;
378
+ margin: 10px 0;
379
+ }
380
+
381
+ /* Label styles */
382
+ label {
383
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
384
+ "Helvetica Neue", Arial, sans-serif;
385
+ font-weight: 500;
386
+ }
387
+
388
+ /* Pong emoji flip - applied to all elements with pong-emoji class */
389
+ .pong-emoji {
390
+ display: inline-block;
391
+ transform: scaleX(-1);
392
+ margin-left: 5px; /* Add a little space */
393
+ }
394
+
395
+ /* Step title styles */
396
+ .step-header {
397
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
398
+ "Helvetica Neue", Arial, sans-serif;
399
+ font-weight: 600;
400
+ color: #2c3e50;
401
+ margin: 15px 0 10px 0;
402
+ }
403
+ </style>
404
+ """)
405
+
406
+ with gr.Row():
407
+ # Ping part (left)
408
+ with gr.Column(scale=1, variant="panel", elem_classes=["ping-panel"]):
409
+ gr.Markdown("### πŸ“ Ping Control (Sender)")
410
+ with gr.Group():
411
+ gr.Markdown("**Step 1: Initialize Connection**")
412
+ ping_init_btn = gr.Button("Initialize Ping", variant="primary", size="lg")
413
+ ping_status = gr.Textbox(label="Connection Status", lines=2)
414
+
415
+ with gr.Tabs():
416
+ with gr.TabItem("RGB Control"):
417
+ with gr.Group():
418
+ gr.Markdown("**Step 2: Configure RGB Values**")
419
+ r = gr.Slider(0, 255, 128, label="Red Value", interactive=True)
420
+ g = gr.Slider(0, 255, 128, label="Green Value", interactive=True)
421
+ b = gr.Slider(0, 255, 128, label="Blue Value", interactive=True)
422
+ gr.Markdown("**Step 3: Send Command**")
423
+ send_rgb_btn = gr.Button("Send RGB Command", variant="secondary", size="lg")
424
+ rgb_status = gr.Textbox(label="RGB Status", lines=2)
425
+
426
+ with gr.TabItem("Weight Control"):
427
+ with gr.Group():
428
+ gr.Markdown("**Step 2: Set RPM Value**")
429
+ rpm = gr.Slider(0, 5000, 1000, label="RPM Setting", interactive=True)
430
+ gr.Markdown("**Step 3: Send Request**")
431
+ send_weight_btn = gr.Button("Send Weight Request", variant="secondary", size="lg")
432
+ weight_status = gr.Textbox(label="Weight Status", lines=2)
433
+
434
+ # Add temperature and humidity control tab
435
+ with gr.TabItem("Temperature Control"):
436
+ with gr.Group():
437
+ gr.Markdown("**Step 2: Set Temperature Value**")
438
+ temperature = gr.Slider(0, 50, 25, label="Temperature (Β°C)", interactive=True)
439
+ gr.Markdown("**Step 3: Send Request**")
440
+ send_temp_btn = gr.Button("Send Temperature Request", variant="secondary", size="lg")
441
+ temp_status = gr.Textbox(label="Temperature Status", lines=2)
442
+
443
+ with gr.Group():
444
+ gr.Markdown("**Step 4: Check Responses**")
445
+ check_ping_btn = gr.Button("Check Responses", variant="secondary", size="lg")
446
+ ping_responses = gr.Textbox(
447
+ label="Response Log",
448
+ lines=10,
449
+ show_copy_button=True
450
+ )
451
+
452
+ # Pong part (right)
453
+ with gr.Column(scale=1, variant="panel", elem_classes=["pong-panel"]):
454
+ gr.Markdown("### Pong Monitor (Receiver) <span class='pong-emoji'>πŸ“</span>")
455
+ with gr.Group():
456
+ gr.Markdown("**Step 1: Start System**")
457
+ with gr.Row():
458
+ pong_init_btn = gr.Button("Start Pong", variant="primary", size="lg")
459
+ pong_stop_btn = gr.Button("Stop Pong", variant="secondary", size="lg")
460
+ pong_status = gr.Textbox(label="System Status", lines=2)
461
+
462
+ with gr.Group():
463
+ gr.Markdown("**Step 2: Monitor Device Status**")
464
+ refresh_btn = gr.Button("πŸ”„ Refresh All", variant="primary", size="lg")
465
+ device_info = gr.JSON(
466
+ label="Current Device State",
467
+ value=device_state,
468
+ show_label=True
469
+ )
470
+
471
+ with gr.Group():
472
+ gr.Markdown("**Step 3: Check Incoming Commands**")
473
+ check_pong_btn = gr.Button("Check Commands", variant="secondary", size="lg")
474
+ pong_commands = gr.Textbox(
475
+ label="Command Log",
476
+ lines=10,
477
+ show_copy_button=True
478
+ )
479
+
480
+ # Add Step 4: Data Analysis (Optional)
481
+ with gr.Group():
482
+ gr.Markdown("**Step 4: Data Analysis (Optional)**")
483
+ with gr.Row():
484
+ analyze_rgb_btn = gr.Button("Analyze RGB Trend", variant="secondary", size="lg")
485
+ analyze_temp_btn = gr.Button("Analyze Temperature Trend", variant="secondary", size="lg")
486
+
487
+ plot_output = gr.Plot(
488
+ label="Trend Analysis",
489
+ show_label=True
490
+ )
491
+
492
+ # Add event handling for analysis buttons
493
+ analyze_rgb_btn.click(
494
+ lambda: plot_data_with_prediction("rgb"),
495
+ outputs=plot_output
496
+ )
497
+ analyze_temp_btn.click(
498
+ lambda: plot_data_with_prediction("temperature"),
499
+ outputs=plot_output
500
+ )
501
+
502
+ # Modify event handling
503
+ ping_init_btn.click(initialize_ping, outputs=ping_status)
504
+ pong_init_btn.click(initialize_pong, outputs=pong_status)
505
+ pong_stop_btn.click(stop_mqtt, outputs=pong_status)
506
+
507
+ # Automatic refresh of pong state when sending commands
508
+ send_rgb_btn.click(
509
+ send_rgb,
510
+ [r, g, b],
511
+ rgb_status
512
+ ).then(
513
+ check_pong_commands, # Update pong command display
514
+ outputs=[pong_commands]
515
+ ).then(
516
+ check_ping_responses, # Update ping response display
517
+ outputs=[ping_responses]
518
+ )
519
+
520
+ send_weight_btn.click(
521
+ send_weight_request,
522
+ rpm,
523
+ weight_status
524
+ ).then(
525
+ refresh_all, # Use refresh_all
526
+ outputs=[pong_commands, device_info]
527
+ )
528
+
529
+ # Modify manual refresh button event handling
530
+ refresh_btn.click(
531
+ refresh_all,
532
+ outputs=[pong_commands, device_info]
533
+ )
534
+
535
+ check_ping_btn.click(
536
+ check_ping_responses,
537
+ outputs=ping_responses
538
+ )
539
+ check_pong_btn.click(check_pong_commands, outputs=pong_commands)
540
+
541
+ # Add real-time update when slider values change
542
+ r.change(update_rgb_preview, inputs=[r, g, b], outputs=device_info)
543
+ g.change(update_rgb_preview, inputs=[r, g, b], outputs=device_info)
544
+ b.change(update_rgb_preview, inputs=[r, g, b], outputs=device_info)
545
+
546
+ # Add RPM slider real-time update
547
+ rpm.change(update_rpm_preview, inputs=rpm, outputs=device_info)
548
+
549
+ # Add temperature slider real-time update
550
+ temperature.change(update_temperature_preview, inputs=temperature, outputs=device_info)
551
+
552
+ # Add temperature command sending handling
553
+ send_temp_btn.click(
554
+ lambda temp: send_command("Temperature Reading", {"temperature": temp}),
555
+ inputs=[temperature],
556
+ outputs=temp_status
557
+ ).then(
558
+ check_pong_commands,
559
+ outputs=[pong_commands]
560
+ ).then(
561
+ check_ping_responses,
562
+ outputs=[ping_responses]
563
+ )
564
+
565
+ demo.load(lambda: None) # Initialize
566
+
567
+ if __name__ == "__main__":
568
+ demo.launch()