Neil-YL commited on
Commit
911cfbb
·
verified ·
1 Parent(s): bdcfebb

Back to latest

Browse files
Files changed (1) hide show
  1. app.py +83 -589
app.py CHANGED
@@ -2,27 +2,16 @@ import gradio as gr
2
  import paho.mqtt.client as mqtt
3
  import json
4
  import time
5
- import threading
6
  import os
7
- import base64
 
8
  from PIL import Image
9
  import io
10
- import numpy as np
11
- import logging
12
- import sys
13
- import random
14
- import cv2
15
- from PIL import ImageDraw
16
- import requests
17
-
18
 
19
- logging.basicConfig(
20
- level=logging.INFO,
21
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
22
- handlers=[logging.StreamHandler(sys.stdout), logging.FileHandler("app.log")],
23
- )
24
- logger = logging.getLogger("bambu-analysis")
25
 
 
26
  BAMBU_HOST = os.environ.get("BAMBU_HOST", "default_host")
27
  BAMBU_PORT = int(os.environ.get("BAMBU_PORT", 1883))
28
  BAMBU_USERNAME = os.environ.get("BAMBU_USERNAME", "default_user")
@@ -34,615 +23,120 @@ RPI_PORT = int(os.environ.get("RPI_PORT", 1883))
34
  RPI_USERNAME = os.environ.get("RPI_USERNAME", "default_user")
35
  RPI_PASSWORD = os.environ.get("RPI_PASSWORD", "default_pass")
36
 
37
- logger.info(
38
- f"BAMBU MQTT Configuration: HOST={BAMBU_HOST}, PORT={BAMBU_PORT}, USERNAME={BAMBU_USERNAME}, PWD={'*'*len(BAMBU_PASSWORD)}"
39
- )
40
- logger.info(
41
- f"RPI MQTT Configuration: HOST={RPI_HOST}, PORT={RPI_PORT}, USERNAME={RPI_USERNAME}, PWD={'*'*len(RPI_PASSWORD)}"
42
- )
43
-
44
- latest_data = {
45
- "bed_temperature": "N/A",
46
- "nozzle_temperature": "N/A",
47
- "status": "N/A",
48
- "update_time": "Waiting for data...",
49
- "image_url": "N/A",
50
- "progress": 0,
51
- "message": "",
52
- }
53
-
54
- bambu_client = None
55
- rpi_client = None
56
- response_topic = None # Will be set dynamically
57
-
58
- # add a global variable to track if the application just started
59
- is_first_load = True
60
 
61
 
62
- def create_client(client_type):
63
- global bambu_client, rpi_client
64
- if client_type == "bambu":
65
- bambu_client = mqtt.Client()
66
- bambu_client.username_pw_set(BAMBU_USERNAME, BAMBU_PASSWORD)
67
- bambu_client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS)
68
- bambu_client.on_connect = on_connect
69
- bambu_client.on_message = bambu_on_message
70
- bambu_client.connect(BAMBU_HOST, BAMBU_PORT)
71
- bambu_client.loop_start()
72
- elif client_type == "rpi":
73
- rpi_client = mqtt.Client()
74
- rpi_client.username_pw_set(RPI_USERNAME, RPI_PASSWORD)
75
- rpi_client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS)
76
- rpi_client.on_connect = on_connect
77
- rpi_client.on_message = rpi_on_message
78
- rpi_client.connect(RPI_HOST, RPI_PORT)
79
- rpi_client.loop_start()
80
-
81
-
82
- def on_connect(client, userdata, flags, rc):
83
- logger.info(f"Connected with result code {rc}")
84
 
85
 
 
86
  def bambu_on_message(client, userdata, message):
87
  global latest_data
88
- logger.info("Received message")
89
- try:
90
- data = json.loads(message.payload)
91
- latest_data["bed_temperature"] = data.get("bed_temperature", "N/A")
92
- latest_data["nozzle_temperature"] = data.get("nozzle_temperature", "N/A")
93
- latest_data["status"] = data.get("status", "N/A")
94
- latest_data["update_time"] = time.strftime(
95
- "%Y-%m-%d %H:%M:%S", time.localtime()
96
- )
97
- latest_data["image_url"] = data.get("image_url", "N/A")
98
- except Exception as e:
99
- logger.error(f"Error parsing MQTT message: {e}")
100
 
101
 
102
- def rpi_on_message(client, userdata, msg):
103
  global latest_data
 
 
104
 
105
- try:
106
- payload = msg.payload.decode("utf-8")
107
- logger.info(f"Received message from RPI: {payload}")
108
-
109
- data = json.loads(payload)
110
- status = data.get("status", "Unknown")
111
 
112
- latest_data["status"] = status
113
- latest_data["update_time"] = data.get(
114
- "update_time", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
 
 
 
115
  )
116
-
117
- if "nozzle_temperature" in data:
118
- latest_data["nozzle_temperature"] = data["nozzle_temperature"]
119
-
120
- if "bed_temperature" in data:
121
- latest_data["bed_temperature"] = data["bed_temperature"]
122
-
123
- if "error" in data:
124
- logger.error(f"Error from RPI: {data['error']}")
125
-
126
- if status == "Ready":
127
- latest_data["progress"] = 0
128
- latest_data["message"] = "Printer ready"
129
- elif status == "Processing":
130
- latest_data["progress"] = 25
131
- latest_data["message"] = "Processing G-code..."
132
-
133
- except Exception as e:
134
- logger.error(f"Error processing message from RPI: {e}")
135
-
136
- try:
137
- result = json.loads(payload)
138
-
139
- if "status" in result:
140
- status = result["status"]
141
- latest_data["status"] = status
142
-
143
- if (
144
- "command" in result
145
- and result["command"] == "capture_image"
146
- and result.get("auto_triggered", False)
147
- ):
148
- logger.info("receive capture command")
149
- threading.Thread(
150
- target=handle_auto_capture, args=(result,), daemon=True
151
- ).start()
152
-
153
- except json.JSONDecodeError:
154
- logger.error(f"Invalid JSON in message: {payload}")
155
-
156
-
157
- def get_data(serial=DEFAULT_SERIAL):
158
- global bambu_client, response_topic
159
-
160
- if bambu_client is None:
161
- create_client("bambu")
162
-
163
- request_topic = f"bambu_a1_mini/request/{serial}"
164
- response_topic = f"bambu_a1_mini/response/{serial}"
165
-
166
- logger.info(f"Subscribing to {response_topic}")
167
- bambu_client.subscribe(response_topic)
168
-
169
- logger.info(f"Publishing request to {request_topic}")
170
- bambu_client.publish(request_topic, json.dumps("HI"))
171
-
172
- global latest_data
173
- latest_data["bed_temperature"] = "N/A"
174
- timeout = 10
175
- while latest_data["bed_temperature"] == "N/A" and timeout > 0:
176
- time.sleep(1)
177
- timeout -= 1
178
-
179
- return (
180
- latest_data["status"],
181
- latest_data["bed_temperature"],
182
- latest_data["nozzle_temperature"],
183
- latest_data["update_time"],
184
- )
185
 
186
 
 
187
  def send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed):
188
- global rpi_client
189
-
190
- serial = DEFAULT_SERIAL
191
-
192
- logger.info(
193
- f"Sending parameters to RPi for G-code generation: nozzle={nozzle_temp}, bed={bed_temp}, "
194
- f"speed={print_speed}, fan={fan_speed}"
195
- )
196
-
197
  try:
198
  params = {
199
  "nozzle_temp": nozzle_temp,
200
  "bed_temp": bed_temp,
201
  "print_speed": print_speed,
202
  "fan_speed": fan_speed,
 
203
  }
 
 
 
 
 
 
 
204
 
205
- request_topic = f"bambu_a1_mini/request/{serial}"
206
-
207
- if rpi_client:
208
- rpi_client.publish(
209
- request_topic,
210
- json.dumps({"command": "generate_gcode", "parameters": params}),
211
- )
212
- logger.info("Parameters sent successfully to RPi for G-code generation")
213
-
214
- global latest_data
215
- latest_data["status"] = "Sent"
216
- latest_data["update_time"] = time.strftime(
217
- "%Y-%m-%d %H:%M:%S", time.localtime()
218
- )
219
-
220
- return "Parameters sent successfully to RPi. Status: Sent"
221
- else:
222
- logger.warning("MQTT not connected, parameters not sent")
223
- return "MQTT not connected, parameters not sent"
224
- except Exception as e:
225
- logger.error(f"Error sending parameters: {e}")
226
- return f"Error sending parameters: {e}"
227
-
228
- latest_data["status"] = "Sending"
229
- latest_data["progress"] = 10
230
- latest_data["message"] = "Sending parameters to printer..."
231
-
232
-
233
- def get_image_base64(image):
234
- if image is None:
235
- logger.warning("No image to encode")
236
- return None
237
-
238
- try:
239
- if isinstance(image, np.ndarray):
240
- image = Image.fromarray(image)
241
-
242
- buffer = io.BytesIO()
243
- image.save(buffer, format="PNG")
244
- img_str = base64.b64encode(buffer.getvalue()).decode("utf-8")
245
- logger.info(f"Image encoded to base64 (length: {len(img_str)})")
246
- return img_str
247
- except Exception as e:
248
- logger.error(f"Error encoding image: {e}")
249
- return None
250
-
251
-
252
- def handle_auto_capture(message):
253
- try:
254
- logger.info(f"receive capture command: {message}")
255
-
256
- print_job = message.get("print_job", "unknown_job")
257
- timestamp = message.get("timestamp", time.strftime("%Y-%m-%d %H:%M:%S"))
258
-
259
- latest_data["auto_capture_requested"] = True
260
- latest_data["last_capture_job"] = print_job
261
- latest_data["last_capture_time"] = timestamp
262
-
263
- return capture_image()
264
- except Exception as e:
265
- logger.error(f"error: {e}")
266
- return None, f"capture failed: {str(e)}"
267
-
268
-
269
- def capture_image(url=None):
270
- global rpi_client, latest_data
271
-
272
- if rpi_client is None:
273
- create_client("rpi")
274
-
275
- serial = DEFAULT_SERIAL
276
- request_topic = f"bambu_a1_mini/request/{serial}"
277
- response_topic = f"bambu_a1_mini/response/{serial}"
278
-
279
- logger.info(f"Subscribing to {response_topic}")
280
- rpi_client.subscribe(response_topic)
281
 
282
- rpi_client.publish(
283
- request_topic,
284
- json.dumps(
285
- {
286
- "command": "capture_image",
287
- }
288
- ),
289
  )
290
 
291
- latest_data["image_url"] = "N/A"
292
- timeout = 45
293
- while latest_data["image_url"] == "N/A" and timeout > 0:
294
- # print("timeout", timeout)
295
- time.sleep(1)
296
- timeout -= 1
297
-
298
- url = latest_data["image_url"]
299
 
300
- if url != "N/A":
301
- try:
302
- logger.info(f"Capturing image from URL: {url}")
303
- response = requests.get(url, timeout=10)
 
 
304
  if response.status_code == 200:
305
  return Image.open(io.BytesIO(response.content))
306
- else:
307
- logger.error(f"Failed to get image from URL: {response.status_code}")
308
- except Exception as e:
309
- logger.error(f"Error capturing image from URL: {e}")
310
- else:
311
- raise Exception("url is 'N/A'")
312
-
313
-
314
- def update_print_status():
315
- global latest_data
316
- return (
317
- latest_data["status"],
318
- latest_data["nozzle_temperature"],
319
- latest_data["bed_temperature"],
320
- latest_data["update_time"],
321
- latest_data.get("progress", 0),
322
- latest_data.get("message", ""),
323
- )
324
-
325
-
326
- def health_check():
327
- status = {
328
- "app": "running",
329
- "time": time.strftime("%Y-%m-%d %H:%M:%S"),
330
- "bambu_mqtt_connected": bambu_client is not None,
331
- "rpi_mqtt_connected": rpi_client is not None,
332
- "latest_update": latest_data["update_time"],
333
- }
334
- logger.info(f"Health check: {status}")
335
- return status
336
-
337
-
338
- demo = gr.Blocks(title="Bambu A1 Mini Print Control")
339
-
340
- with demo:
341
- gr.Markdown("# Bambu A1 Mini Print Control")
342
-
343
- with gr.Row():
344
- refresh_btn = gr.Button("Refresh Status")
345
-
346
- with gr.Row():
347
- current_status = gr.Textbox(
348
- label="Printer Status", value="N/A", interactive=False
349
- )
350
- current_bed_temp = gr.Textbox(
351
- label="Current Bed Temperature", value="N/A", interactive=False
352
- )
353
- current_nozzle_temp = gr.Textbox(
354
- label="Current Nozzle Temperature", value="N/A", interactive=False
355
- )
356
- last_update = gr.Textbox(label="Last Update", value="N/A", interactive=False)
357
-
358
- with gr.Row():
359
- # Left column for image and capture button
360
- with gr.Column(scale=2):
361
- captured_image = gr.Image(label="Current Print Image", type="pil")
362
- capture_btn = gr.Button("Capture Image")
363
-
364
- # Right column for queue status and livestream
365
- with gr.Column(scale=1):
366
- gr.Markdown("### YouTube Livestream")
367
- iframe_html = """
368
- <div style="position: relative; width: 100%; padding-top: 56.25%;">
369
- <iframe
370
- style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
371
- src="https://www.youtube.com/embed/ZzYOO6y6cJU"
372
- title="Bambu A1mini Livestream"
373
- frameborder="0"
374
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
375
- referrerpolicy="strict-origin-when-cross-origin"
376
- allowfullscreen>
377
- </iframe>
378
- </div>
379
- """
380
- gr.HTML(iframe_html)
381
 
382
- with gr.Row():
383
- with gr.Column():
384
- nozzle_temp = gr.Slider(
385
- minimum=180,
386
- maximum=250,
387
- step=1,
388
- value=200,
389
- label="Nozzle Temperature (°C)",
390
- )
391
- bed_temp = gr.Slider(
392
- minimum=40, maximum=100, step=1, value=60, label="Bed Temperature (°C)"
393
- )
394
- print_speed = gr.Slider(
395
- minimum=20, maximum=150, step=1, value=60, label="Print Speed (mm/s)"
396
- )
397
- fan_speed = gr.Slider(
398
- minimum=0, maximum=100, step=1, value=100, label="Fan Speed (%)"
399
- )
400
 
401
- send_params_btn = gr.Button("Send Print Parameters")
 
 
 
 
 
402
 
403
- refresh_btn.click(
404
- fn=get_data,
405
- outputs=[current_status, current_bed_temp, current_nozzle_temp, last_update],
406
  )
 
 
 
407
 
408
- capture_btn.click(
409
- fn=lambda: gr.update(interactive=False), outputs=capture_btn
410
- ).then(fn=capture_image, outputs=[captured_image]).then(
411
- fn=lambda: gr.update(interactive=True), outputs=capture_btn
412
- )
413
 
414
- def api_send_print_parameters(
415
- nozzle_temp=200, bed_temp=60, print_speed=60, fan_speed=100
416
- ):
417
- """API endpoint to send print parameters to the Bambu printer via RPI MQTT"""
418
- global is_first_load
419
-
420
- # if the application just started, skip the actual operation
421
- if is_first_load:
422
- logger.info("First load detected, skipping automatic parameter sending")
423
- is_first_load = False
424
- return "Ready to send parameters. Click 'Submit' to send."
425
-
426
- # the original function, only execute when the user explicitly requests it
427
- return send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed)
428
 
 
429
  send_params_btn.click(
430
- fn=api_send_print_parameters,
431
- inputs=[nozzle_temp, bed_temp, print_speed, fan_speed],
432
- outputs=[current_status],
433
- )
434
-
435
- def api_get_data():
436
- logger.info("API call: get_data")
437
- return get_data()
438
-
439
- def api_capture_frame(url=None):
440
- logger.info(f"API call: capture_frame with URL: {url}")
441
-
442
- try:
443
- img = capture_image(url)
444
- if img:
445
- if img.mode == "RGBA":
446
- img = img.convert("RGB")
447
-
448
- buffered = io.BytesIO()
449
- img.save(buffered, format="JPEG")
450
- img_str = base64.b64encode(buffered.getvalue()).decode()
451
-
452
- return {"success": True, "image": img_str}
453
- else:
454
- return {"success": False, "error": "Failed to capture image"}
455
- except Exception as e:
456
- logger.error(f"Error in capture_frame: {e}")
457
- return {"success": False, "error": str(e)}
458
-
459
- def api_lambda(
460
- img_data=None,
461
- param_1=200,
462
- param_2=60,
463
- param_3=60,
464
- param_4=100,
465
- ):
466
- logger.info(
467
- f"API call: lambda with params: {param_1}, {param_2}, {param_3}, {param_4}"
468
- )
469
- try:
470
- img = None
471
-
472
- if (
473
- img_data
474
- and isinstance(img_data, str)
475
- and (img_data.startswith("http://") or img_data.startswith("https://"))
476
- ):
477
- logger.info(f"Lambda received image URL: {img_data}")
478
- img = capture_image(img_data)
479
-
480
- elif img_data and isinstance(img_data, str):
481
- try:
482
- logger.info("Lambda received base64 image data")
483
- img_bytes = base64.b64decode(img_data)
484
- img = Image.open(io.BytesIO(img_bytes))
485
- except Exception as e:
486
- logger.error(f"Failed to decode base64 image: {e}")
487
-
488
- if img:
489
- img_array = np.array(img)
490
-
491
- quality_level = "low"
492
- if 190 <= param_1 <= 210 and param_3 <= 50 and param_4 >= 80:
493
- quality_level = "high"
494
- elif 185 <= param_1 <= 215 and param_3 <= 70 and param_4 >= 60:
495
- quality_level = "medium"
496
-
497
- if quality_level == "high":
498
- missing_rate = 0.02
499
- excess_rate = 0.01
500
- stringing_rate = 0.01
501
- elif quality_level == "medium":
502
- missing_rate = 0.05
503
- excess_rate = 0.03
504
- stringing_rate = 0.02
505
- else: # low
506
- missing_rate = 0.10
507
- excess_rate = 0.07
508
- stringing_rate = 0.05
509
-
510
- uniformity_score = 1.0 - (missing_rate + excess_rate + stringing_rate)
511
-
512
- print_quality_score = 1.0 - (
513
- missing_rate * 2.0 + excess_rate * 1.5 + stringing_rate * 1.0
514
- )
515
- print_quality_score = max(0, min(1, print_quality_score))
516
-
517
- print_speed_score = param_3 / 150.0
518
- print_speed_score = max(0, min(1, print_speed_score))
519
-
520
- material_efficiency_score = 1.0 - excess_rate * 3.0
521
- material_efficiency_score = max(0, min(1, material_efficiency_score))
522
-
523
- total_performance_score = (
524
- 0.5 * print_quality_score
525
- + 0.3 * print_speed_score
526
- + 0.2 * material_efficiency_score
527
- )
528
-
529
- img_draw = img.copy()
530
- draw = ImageDraw.Draw(img_draw)
531
- draw.text(
532
- (10, 10), f"Quality: {quality_level.upper()}", fill=(255, 0, 0)
533
- )
534
- draw.text((10, 30), f"Missing: {missing_rate:.2f}", fill=(255, 0, 0))
535
- draw.text((10, 50), f"Excess: {excess_rate:.2f}", fill=(255, 0, 0))
536
- draw.text(
537
- (10, 70), f"Stringing: {stringing_rate:.2f}", fill=(255, 0, 0)
538
- )
539
-
540
- result = {
541
- "success": True,
542
- "missing_rate": missing_rate,
543
- "excess_rate": excess_rate,
544
- "stringing_rate": stringing_rate,
545
- "uniformity_score": uniformity_score,
546
- "print_quality_score": print_quality_score,
547
- "print_speed_score": print_speed_score,
548
- "material_efficiency_score": material_efficiency_score,
549
- "total_performance_score": total_performance_score,
550
- }
551
-
552
- if img_draw.mode == "RGBA":
553
- img_draw = img_draw.convert("RGB")
554
-
555
- buffered = io.BytesIO()
556
- img_draw.save(buffered, format="JPEG")
557
- img_str = base64.b64encode(buffered.getvalue()).decode()
558
- result["image"] = img_str
559
-
560
- return result
561
- else:
562
- return {"success": False, "error": "Failed to get image"}
563
- except Exception as e:
564
- logger.error(f"Error in lambda: {e}")
565
- return {"error": str(e)}
566
-
567
-
568
- api_json_output = gr.JSON()
569
- api_text_output = gr.Textbox()
570
-
571
- capture_frame_api = demo.load(
572
- fn=api_capture_frame,
573
- inputs=[gr.Text(label="Camera URL (Optional)")],
574
- outputs=[gr.Image(label="Captured Frame"), gr.JSON()],
575
- api_name="capture_frame"
576
- )
577
-
578
- lambda_api = demo.load(
579
- fn=api_lambda,
580
- inputs=[
581
- gr.Image(label="Image Data"),
582
- gr.Number(label="Nozzle Temperature", value=200),
583
- gr.Number(label="Bed Temperature", value=60),
584
- gr.Number(label="Print Speed", value=60),
585
- gr.Number(label="Fan Speed", value=100),
586
- ],
587
- outputs=[gr.Image(label="Visualization"), gr.JSON()],
588
- api_name="lambda"
589
- )
590
-
591
- get_data_api = demo.load(
592
- fn=api_get_data,
593
- inputs=[],
594
- outputs=gr.JSON(),
595
- api_name="get_data"
596
- )
597
-
598
- send_params_api = demo.load(
599
- fn=api_send_print_parameters,
600
- inputs=[
601
- gr.Number(label="Nozzle Temperature", value=200),
602
- gr.Number(label="Bed Temperature", value=60),
603
- gr.Number(label="Print Speed", value=60),
604
- gr.Number(label="Fan Speed", value=100),
605
- ],
606
- outputs=api_text_output,
607
- api_name="send_print_parameters"
608
- )
609
-
610
- get_status_api = demo.load(
611
- fn=update_print_status,
612
- inputs=[],
613
- outputs=[
614
- gr.Textbox(label="Status"),
615
- gr.Textbox(label="Nozzle Temperature"),
616
- gr.Textbox(label="Bed Temperature"),
617
- gr.Textbox(label="Update Time"),
618
- gr.Number(label="Progress"),
619
- gr.Textbox(label="Message"),
620
- ],
621
- api_name="get_status"
622
- )
623
-
624
- health_check_api = demo.load(
625
- fn=health_check,
626
- inputs=[],
627
- outputs=gr.JSON(),
628
- api_name="health_check"
629
  )
630
 
631
- if __name__ == "__main__":
632
- logger.info("Starting Bambu A1 Mini Print Control application")
633
-
634
- try:
635
- logger.info("Initializing Bambu MQTT client")
636
- create_client("bambu")
637
- except Exception as e:
638
- logger.error(f"Failed to initialize Bambu MQTT: {e}")
639
-
640
- try:
641
- logger.info("Initializing RPI MQTT client")
642
- create_client("rpi")
643
- except Exception as e:
644
- logger.error(f"Failed to initialize RPI MQTT: {e}")
645
-
646
- demo.queue().launch(
647
- show_error=True, share=False, server_name="0.0.0.0", server_port=7860
648
- )
 
2
  import paho.mqtt.client as mqtt
3
  import json
4
  import time
 
5
  import os
6
+ import requests
7
+ import traceback
8
  from PIL import Image
9
  import io
10
+ import threading
 
 
 
 
 
 
 
11
 
12
+ print("Starting MQTT Print Control...")
 
 
 
 
 
13
 
14
+ # Environment variables
15
  BAMBU_HOST = os.environ.get("BAMBU_HOST", "default_host")
16
  BAMBU_PORT = int(os.environ.get("BAMBU_PORT", 1883))
17
  BAMBU_USERNAME = os.environ.get("BAMBU_USERNAME", "default_user")
 
23
  RPI_USERNAME = os.environ.get("RPI_USERNAME", "default_user")
24
  RPI_PASSWORD = os.environ.get("RPI_PASSWORD", "default_pass")
25
 
26
+ latest_data = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
 
29
+ # Centralized MQTT client setup
30
+ def setup_mqtt_client(host, port, username, password, on_message):
31
+ client = mqtt.Client()
32
+ client.username_pw_set(username, password)
33
+ client.on_message = on_message
34
+ client.connect(host, port)
35
+ client.loop_start()
36
+ return client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
 
39
+ # Single callback responsibility
40
  def bambu_on_message(client, userdata, message):
41
  global latest_data
42
+ latest_data.update(json.loads(message.payload))
43
+ latest_data["update_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
 
 
 
 
 
 
 
 
 
 
44
 
45
 
46
+ def rpi_on_message(client, userdata, message):
47
  global latest_data
48
+ latest_data.update(json.loads(message.payload))
49
+ latest_data["update_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
50
 
 
 
 
 
 
 
51
 
52
+ # Centralized error handling
53
+ def main_logic():
54
+ global bambu_client, rpi_client
55
+ try:
56
+ bambu_client = setup_mqtt_client(
57
+ BAMBU_HOST, BAMBU_PORT, BAMBU_USERNAME, BAMBU_PASSWORD, bambu_on_message
58
  )
59
+ rpi_client = setup_mqtt_client(
60
+ RPI_HOST, RPI_PORT, RPI_USERNAME, RPI_PASSWORD, rpi_on_message
61
+ )
62
+ bambu_client.subscribe(f"bambu_a1_mini/response/{DEFAULT_SERIAL}")
63
+ rpi_client.subscribe(f"bambu_a1_mini/response/{DEFAULT_SERIAL}")
64
+ except Exception:
65
+ print("MQTT setup failed:")
66
+ traceback.print_exc()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
 
69
+ # Sending print parameters functionality
70
  def send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed):
 
 
 
 
 
 
 
 
 
71
  try:
72
  params = {
73
  "nozzle_temp": nozzle_temp,
74
  "bed_temp": bed_temp,
75
  "print_speed": print_speed,
76
  "fan_speed": fan_speed,
77
+ "command": "generate_gcode",
78
  }
79
+ topic = f"bambu_a1_mini/request/{DEFAULT_SERIAL}"
80
+ rpi_client.publish(topic, json.dumps(params))
81
+ return "Parameters sent successfully."
82
+ except Exception:
83
+ print("Failed to send parameters:")
84
+ traceback.print_exc()
85
+ return "Failed to send parameters."
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
+ # Simple Gradio interface
89
+ def get_status():
90
+ return (
91
+ latest_data.get("status", "N/A"),
92
+ latest_data.get("bed_temperature", "N/A"),
93
+ latest_data.get("nozzle_temperature", "N/A"),
94
+ latest_data.get("update_time", "Waiting for data..."),
95
  )
96
 
 
 
 
 
 
 
 
 
97
 
98
+ # Image capture functionality
99
+ def capture_image():
100
+ try:
101
+ img_url = latest_data.get("image_url", None)
102
+ if img_url and img_url != "N/A":
103
+ response = requests.get(img_url, timeout=10)
104
  if response.status_code == 200:
105
  return Image.open(io.BytesIO(response.content))
106
+ return None
107
+ except Exception:
108
+ print("Image capture failed:")
109
+ traceback.print_exc()
110
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ with gr.Blocks(title="Simplified Printer Control") as demo:
114
+ status = gr.Textbox(label="Status")
115
+ bed_temp = gr.Textbox(label="Bed Temperature")
116
+ nozzle_temp = gr.Textbox(label="Nozzle Temperature")
117
+ update_time = gr.Textbox(label="Last Update")
118
+ image_display = gr.Image(label="Captured Image", type="pil")
119
 
120
+ nozzle_input = gr.Slider(
121
+ label="Nozzle Temperature", minimum=180, maximum=250, value=200
 
122
  )
123
+ bed_input = gr.Slider(label="Bed Temperature", minimum=40, maximum=100, value=60)
124
+ speed_input = gr.Slider(label="Print Speed", minimum=20, maximum=150, value=60)
125
+ fan_input = gr.Slider(label="Fan Speed", minimum=0, maximum=100, value=100)
126
 
127
+ refresh = gr.Button("Refresh")
128
+ refresh.click(fn=get_status, outputs=[status, bed_temp, nozzle_temp, update_time])
 
 
 
129
 
130
+ capture_btn = gr.Button("Capture Image")
131
+ capture_btn.click(fn=capture_image, outputs=image_display)
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
+ send_params_btn = gr.Button("Send Parameters")
134
  send_params_btn.click(
135
+ fn=send_print_parameters,
136
+ inputs=[nozzle_input, bed_input, speed_input, fan_input],
137
+ outputs=status,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  )
139
 
140
+ print("App initializing...")
141
+ threading.Thread(target=main_logic, daemon=True).start()
142
+ demo.launch()