Neil-YL commited on
Commit
bdcfebb
·
verified ·
1 Parent(s): 2fb6dc6

temporarily revert to previous version for seeds data collection

Browse files
Files changed (1) hide show
  1. app.py +589 -83
app.py CHANGED
@@ -2,16 +2,27 @@ import gradio as gr
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,120 +34,615 @@ RPI_PORT = int(os.environ.get("RPI_PORT", 1883))
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()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  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
+ )