j-woo commited on
Commit
783dcab
·
1 Parent(s): 84fd316

added image capture functionality

Browse files
Files changed (1) hide show
  1. app.py +264 -180
app.py CHANGED
@@ -18,57 +18,64 @@ import requests
18
 
19
  logging.basicConfig(
20
  level=logging.INFO,
21
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
22
- handlers=[
23
- logging.StreamHandler(sys.stdout),
24
- logging.FileHandler('app.log')
25
- ]
26
  )
27
  logger = logging.getLogger("bambu-analysis")
28
 
29
- HOST = os.environ.get("MQTT_HOST", "default_host")
30
- PORT = int(os.environ.get("MQTT_PORT", 1883))
31
- USERNAME = os.environ.get("MQTT_USERNAME", "default_user")
32
- PASSWORD = os.environ.get("MQTT_PASSWORD", "default_pass")
33
  DEFAULT_SERIAL = os.environ.get("DEFAULT_SERIAL", "default_serial")
34
 
35
- print(f"Connecting to MQTT at {HOST}:{PORT} with user {USERNAME}")
36
-
37
- if os.environ.get("host"):
38
- HOST = os.environ.get("host")
39
- if os.environ.get("port"):
40
- PORT = int(os.environ.get("port"))
41
- if os.environ.get("username"):
42
- USERNAME = os.environ.get("username")
43
- if os.environ.get("password"):
44
- PASSWORD = os.environ.get("password")
45
 
46
- logger.info(f"MQTT Configuration: HOST={HOST}, PORT={PORT}, USERNAME={USERNAME}")
 
 
47
 
48
  latest_data = {
49
  "bed_temperature": "N/A",
50
  "nozzle_temperature": "N/A",
51
  "status": "N/A",
52
  "update_time": "Waiting for data...",
 
53
  }
54
 
55
- client = None
 
56
  response_topic = None # Will be set dynamically
57
 
58
- def create_client(host, port, username, password):
59
- global client
60
- client = mqtt.Client()
61
- client.username_pw_set(username, password)
62
- client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS)
63
- client.on_connect = on_connect
64
- client.on_message = on_message
65
- client.connect(host, port)
66
- client.loop_start()
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  def on_connect(client, userdata, flags, rc):
69
  logger.info(f"Connected with result code {rc}")
70
 
71
- def on_message(client, userdata, message):
 
72
  global latest_data
73
  logger.info("Received message")
74
  try:
@@ -76,57 +83,76 @@ def on_message(client, userdata, message):
76
  latest_data["bed_temperature"] = data.get("bed_temperature", "N/A")
77
  latest_data["nozzle_temperature"] = data.get("nozzle_temperature", "N/A")
78
  latest_data["status"] = data.get("status", "N/A")
79
- latest_data["update_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  except Exception as e:
81
  logger.error(f"Error parsing MQTT message: {e}")
82
 
 
83
  def get_data(serial=DEFAULT_SERIAL):
84
- global client, response_topic
85
 
86
- if client is None:
87
- create_client(HOST, PORT, USERNAME, PASSWORD)
88
 
89
  request_topic = f"bambu_a1_mini/request/{serial}"
90
  response_topic = f"bambu_a1_mini/response/{serial}"
91
-
92
  logger.info(f"Subscribing to {response_topic}")
93
- client.subscribe(response_topic)
94
-
95
  logger.info(f"Publishing request to {request_topic}")
96
- client.publish(request_topic, json.dumps("HI"))
97
-
98
  global latest_data
99
  latest_data["bed_temperature"] = "N/A"
100
  timeout = 10
101
  while latest_data["bed_temperature"] == "N/A" and timeout > 0:
102
  time.sleep(1)
103
  timeout -= 1
104
-
105
  return (
106
  latest_data["status"],
107
  latest_data["bed_temperature"],
108
  latest_data["nozzle_temperature"],
109
- latest_data["update_time"]
110
  )
111
 
 
112
  def send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed):
 
 
113
  serial = DEFAULT_SERIAL
114
- logger.info(f"Sending parameters to {serial}: nozzle={nozzle_temp}, bed={bed_temp}, speed={print_speed}, fan={fan_speed}")
 
 
115
  try:
116
  params = {
117
- 'nozzle_temp': nozzle_temp,
118
- 'bed_temp': bed_temp,
119
- 'print_speed': print_speed,
120
- 'fan_speed': fan_speed
121
  }
122
-
123
  request_topic = f"bambu_a1_mini/request/{serial}"
124
-
125
- if client:
126
- client.publish(request_topic, json.dumps({
127
- 'command': 'set_parameters',
128
- 'parameters': params
129
- }))
130
  logger.info("Parameters sent successfully")
131
  return "Parameters sent successfully"
132
  else:
@@ -136,55 +162,87 @@ def send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed):
136
  logger.error(f"Error sending parameters: {e}")
137
  return f"Error sending parameters: {e}"
138
 
 
139
  def get_image_base64(image):
140
  if image is None:
141
  logger.warning("No image to encode")
142
  return None
143
-
144
  try:
145
  if isinstance(image, np.ndarray):
146
  image = Image.fromarray(image)
147
-
148
  buffer = io.BytesIO()
149
  image.save(buffer, format="PNG")
150
- img_str = base64.b64encode(buffer.getvalue()).decode('utf-8')
151
  logger.info(f"Image encoded to base64 (length: {len(img_str)})")
152
  return img_str
153
  except Exception as e:
154
  logger.error(f"Error encoding image: {e}")
155
  return None
156
 
 
157
  def get_test_image(image_name=None):
158
- import os
159
- import random
160
-
161
  test_dir = os.path.join(os.path.dirname(__file__), "test_images")
162
-
163
  if not os.path.exists(test_dir):
164
  logger.error(f"Test images directory not found: {test_dir}")
165
  return None
166
-
167
- image_files = [f for f in os.listdir(test_dir)
168
- if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp'))]
169
-
 
 
 
170
  if not image_files:
171
  logger.error("No test images found")
172
  return None
173
-
174
  if image_name and image_name in image_files:
175
  image_path = os.path.join(test_dir, image_name)
176
  else:
177
  image_path = os.path.join(test_dir, random.choice(image_files))
178
-
179
  logger.info(f"Using test image: {image_path}")
180
-
181
  try:
182
  return Image.open(image_path)
183
  except Exception as e:
184
  logger.error(f"Failed to open test image: {e}")
185
  return None
186
 
 
187
  def capture_image(url=None, use_test_image=False, test_image_name=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
  if use_test_image:
190
  logger.info("Using test image instead of URL")
@@ -193,9 +251,8 @@ def capture_image(url=None, use_test_image=False, test_image_name=None):
193
  return test_img
194
  else:
195
  logger.warning("Failed to get specified test image, trying URL")
196
-
197
 
198
- if url:
199
  try:
200
  logger.info(f"Capturing image from URL: {url}")
201
  response = requests.get(url, timeout=10)
@@ -205,32 +262,41 @@ def capture_image(url=None, use_test_image=False, test_image_name=None):
205
  logger.error(f"Failed to get image from URL: {response.status_code}")
206
  except Exception as e:
207
  logger.error(f"Error capturing image from URL: {e}")
208
-
209
  logger.info("URL capture failed or not provided, using random test image")
210
  return get_test_image()
211
 
 
212
  def health_check():
213
  status = {
214
  "app": "running",
215
  "time": time.strftime("%Y-%m-%d %H:%M:%S"),
216
- "mqtt_connected": client is not None,
217
- "latest_update": latest_data["update_time"]
 
218
  }
219
  logger.info(f"Health check: {status}")
220
  return status
221
 
 
222
  demo = gr.Blocks(title="Bambu A1 Mini Print Control")
223
 
224
  with demo:
225
  gr.Markdown("# Bambu A1 Mini Print Control")
226
-
227
  with gr.Row():
228
  refresh_btn = gr.Button("Refresh Status")
229
-
230
  with gr.Row():
231
- current_status = gr.Textbox(label="Printer Status", value="N/A", interactive=False)
232
- current_bed_temp = gr.Textbox(label="Current Bed Temperature", value="N/A", interactive=False)
233
- current_nozzle_temp = gr.Textbox(label="Current Nozzle Temperature", value="N/A", interactive=False)
 
 
 
 
 
 
234
  last_update = gr.Textbox(label="Last Update", value="N/A", interactive=False)
235
 
236
  with gr.Row():
@@ -238,11 +304,11 @@ with demo:
238
  with gr.Column(scale=2):
239
  captured_image = gr.Image(label="Current Print Image", type="pil")
240
  capture_btn = gr.Button("Capture Image")
241
-
242
  # Right column for queue status and livestream
243
  with gr.Column(scale=1):
244
  gr.Markdown("### YouTube Livestream")
245
- iframe_html = '''
246
  <div style="position: relative; width: 100%; padding-top: 56.25%;">
247
  <iframe
248
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
@@ -254,80 +320,100 @@ with demo:
254
  allowfullscreen>
255
  </iframe>
256
  </div>
257
- '''
258
  gr.HTML(iframe_html)
259
 
260
  with gr.Row():
261
  with gr.Column():
262
- nozzle_temp = gr.Slider(minimum=180, maximum=250, step=1, value=200, label="Nozzle Temperature (°C)")
263
- bed_temp = gr.Slider(minimum=40, maximum=100, step=1, value=60, label="Bed Temperature (°C)")
264
- print_speed = gr.Slider(minimum=20, maximum=150, step=1, value=60, label="Print Speed (mm/s)")
265
- fan_speed = gr.Slider(minimum=0, maximum=100, step=1, value=100, label="Fan Speed (%)")
266
-
 
 
 
 
 
 
 
 
 
 
 
 
267
  send_params_btn = gr.Button("Send Print Parameters")
268
 
269
  refresh_btn.click(
270
  fn=get_data,
271
- outputs=[current_status, current_bed_temp, current_nozzle_temp, last_update]
272
  )
273
-
274
  capture_btn.click(
275
- fn=capture_image,
276
- outputs=[captured_image]
 
277
  )
278
-
279
  send_params_btn.click(
280
  fn=send_print_parameters,
281
  inputs=[nozzle_temp, bed_temp, print_speed, fan_speed],
282
- outputs=[current_status]
283
  )
284
-
285
  def api_get_data():
286
  logger.info("API call: get_data")
287
  return get_data()
288
-
289
  def api_capture_frame(url=None, use_test_image=False, test_image_name=None):
290
- logger.info(f"API call: capture_frame with URL: {url}, use_test_image: {use_test_image}")
291
-
 
 
292
  try:
293
  img = capture_image(url, use_test_image, test_image_name)
294
  if img:
295
- if img.mode == 'RGBA':
296
- img = img.convert('RGB')
297
-
298
  buffered = io.BytesIO()
299
  img.save(buffered, format="JPEG")
300
  img_str = base64.b64encode(buffered.getvalue()).decode()
301
-
302
- return {
303
- "success": True,
304
- "image": img_str
305
- }
306
  else:
307
- return {
308
- "success": False,
309
- "error": "Failed to capture image"
310
- }
311
  except Exception as e:
312
  logger.error(f"Error in capture_frame: {e}")
313
- return {
314
- "success": False,
315
- "error": str(e)
316
- }
317
-
318
- def api_lambda(img_data=None, param_1=200, param_2=60, param_3=60, param_4=100, use_test_image=False, test_image_name=None):
319
- logger.info(f"API call: lambda with params: {param_1}, {param_2}, {param_3}, {param_4}, use_test_image: {use_test_image}, test_image_name: {test_image_name}")
 
 
 
 
 
 
 
320
  try:
321
  img = None
322
-
323
  if use_test_image:
324
  logger.info(f"Lambda using test image: {test_image_name}")
325
  img = get_test_image(test_image_name)
326
-
327
- elif img_data and isinstance(img_data, str) and (img_data.startswith('http://') or img_data.startswith('https://')):
 
 
 
 
328
  logger.info(f"Lambda received image URL: {img_data}")
329
- img = capture_image(img_data)
330
-
331
  elif img_data and isinstance(img_data, str):
332
  try:
333
  logger.info("Lambda received base64 image data")
@@ -335,25 +421,25 @@ with demo:
335
  img = Image.open(io.BytesIO(img_bytes))
336
  except Exception as e:
337
  logger.error(f"Failed to decode base64 image: {e}")
338
-
339
  if img is None:
340
  logger.info("No valid image data received, using default test image")
341
  img = get_test_image()
342
-
343
  if img:
344
  img_array = np.array(img)
345
 
346
- quality_level = 'low'
347
  if 190 <= param_1 <= 210 and param_3 <= 50 and param_4 >= 80:
348
- quality_level = 'high'
349
  elif 185 <= param_1 <= 215 and param_3 <= 70 and param_4 >= 60:
350
- quality_level = 'medium'
351
-
352
- if quality_level == 'high':
353
  missing_rate = 0.02
354
  excess_rate = 0.01
355
  stringing_rate = 0.01
356
- elif quality_level == 'medium':
357
  missing_rate = 0.05
358
  excess_rate = 0.03
359
  stringing_rate = 0.02
@@ -361,32 +447,37 @@ with demo:
361
  missing_rate = 0.10
362
  excess_rate = 0.07
363
  stringing_rate = 0.05
364
-
365
  uniformity_score = 1.0 - (missing_rate + excess_rate + stringing_rate)
366
-
367
- print_quality_score = 1.0 - (missing_rate * 2.0 + excess_rate * 1.5 + stringing_rate * 1.0)
 
 
368
  print_quality_score = max(0, min(1, print_quality_score))
369
-
370
  print_speed_score = param_3 / 150.0
371
  print_speed_score = max(0, min(1, print_speed_score))
372
-
373
  material_efficiency_score = 1.0 - excess_rate * 3.0
374
  material_efficiency_score = max(0, min(1, material_efficiency_score))
375
-
376
  total_performance_score = (
377
- 0.5 * print_quality_score +
378
- 0.3 * print_speed_score +
379
- 0.2 * material_efficiency_score
380
  )
381
-
382
 
383
  img_draw = img.copy()
384
  draw = ImageDraw.Draw(img_draw)
385
- draw.text((10, 10), f"Quality: {quality_level.upper()}", fill=(255, 0, 0))
 
 
386
  draw.text((10, 30), f"Missing: {missing_rate:.2f}", fill=(255, 0, 0))
387
  draw.text((10, 50), f"Excess: {excess_rate:.2f}", fill=(255, 0, 0))
388
- draw.text((10, 70), f"Stringing: {stringing_rate:.2f}", fill=(255, 0, 0))
389
-
 
 
390
  result = {
391
  "success": True,
392
  "missing_rate": missing_rate,
@@ -396,48 +487,46 @@ with demo:
396
  "print_quality_score": print_quality_score,
397
  "print_speed_score": print_speed_score,
398
  "material_efficiency_score": material_efficiency_score,
399
- "total_performance_score": total_performance_score
400
  }
401
-
402
 
403
- if img_draw.mode == 'RGBA':
404
- img_draw = img_draw.convert('RGB')
405
 
406
  buffered = io.BytesIO()
407
  img_draw.save(buffered, format="JPEG")
408
  img_str = base64.b64encode(buffered.getvalue()).decode()
409
  result["image"] = img_str
410
-
411
  return result
412
  else:
413
- return {
414
- "success": False,
415
- "error": "Failed to get image"
416
- }
417
  except Exception as e:
418
  logger.error(f"Error in lambda: {e}")
419
- return {
420
- "error": str(e)
421
- }
422
-
423
- def api_send_print_parameters(nozzle_temp=200, bed_temp=60, print_speed=60, fan_speed=100):
424
- logger.info(f"API call: send_print_parameters with nozzle={nozzle_temp}, bed={bed_temp}, speed={print_speed}, fan={fan_speed}")
 
 
425
  return send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed)
426
 
427
  api_json_output = gr.JSON()
428
  api_text_output = gr.Textbox()
429
-
430
  capture_frame_api = demo.load(
431
  fn=api_capture_frame,
432
  inputs=[
433
  gr.Textbox(label="Image URL"),
434
  gr.Checkbox(label="Use Test Image", value=False),
435
- gr.Textbox(label="Test Image Name", value="")
436
  ],
437
- outputs=api_json_output,
438
- api_name="capture_frame"
439
  )
440
-
441
  lambda_api = demo.load(
442
  fn=api_lambda,
443
  inputs=[
@@ -447,43 +536,38 @@ with demo:
447
  gr.Number(label="Print Speed", value=60),
448
  gr.Number(label="Fan Speed", value=100),
449
  gr.Checkbox(label="Use Test Image", value=False),
450
- gr.Textbox(label="Test Image Name", value="")
451
  ],
452
- outputs=api_json_output,
453
- api_name="lambda"
454
  )
455
-
456
  get_data_api = demo.load(
457
- fn=api_get_data,
458
- inputs=None,
459
- outputs=api_json_output,
460
- api_name="get_data"
461
  )
462
-
463
  send_params_api = demo.load(
464
  fn=api_send_print_parameters,
465
  inputs=[
466
  gr.Number(label="Nozzle Temperature", value=200),
467
  gr.Number(label="Bed Temperature", value=60),
468
  gr.Number(label="Print Speed", value=60),
469
- gr.Number(label="Fan Speed", value=100)
470
  ],
471
- outputs=api_text_output,
472
- api_name="send_print_parameters"
473
  )
474
 
475
  if __name__ == "__main__":
476
  logger.info("Starting Bambu A1 Mini Print Control application")
477
-
478
  try:
479
  logger.info("Initializing MQTT client")
480
- create_client(HOST, PORT, USERNAME, PASSWORD)
 
481
  except Exception as e:
482
  logger.error(f"Failed to initialize MQTT: {e}")
483
-
484
  demo.queue().launch(
485
- show_error=True,
486
- share=False,
487
- server_name="0.0.0.0",
488
- server_port=7860
489
  )
 
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")
29
+ BAMBU_PASSWORD = os.environ.get("BAMBU_PASSWORD", "default_pass")
30
  DEFAULT_SERIAL = os.environ.get("DEFAULT_SERIAL", "default_serial")
31
 
32
+ RPI_HOST = os.environ.get("RPI_HOST", "default_host")
33
+ 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"MQTT Configuration: HOST={BAMBU_HOST}, PORT={BAMBU_PORT}, USERNAME={BAMBU_USERNAME}"
39
+ )
40
 
41
  latest_data = {
42
  "bed_temperature": "N/A",
43
  "nozzle_temperature": "N/A",
44
  "status": "N/A",
45
  "update_time": "Waiting for data...",
46
+ "image_url": "N/A",
47
  }
48
 
49
+ bambu_client = None
50
+ rpi_client = None
51
  response_topic = None # Will be set dynamically
52
 
53
+
54
+ def create_client(client_type, host, port, username, password):
55
+ global bambu_client, rpi_client
56
+ if client_type == "bambu":
57
+ bambu_client = mqtt.Client()
58
+ bambu_client.username_pw_set(username, password)
59
+ bambu_client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS)
60
+ bambu_client.on_connect = on_connect
61
+ bambu_client.on_message = bambu_on_message
62
+ bambu_client.connect(host, port)
63
+ bambu_client.loop_start()
64
+ elif client_type == "rpi":
65
+ rpi_client = mqtt.Client()
66
+ rpi_client.username_pw_set(username, password)
67
+ rpi_client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLS)
68
+ rpi_client.on_connect = on_connect
69
+ rpi_client.on_message = rpi_on_message
70
+ rpi_client.connect(host, port)
71
+ rpi_client.loop_start()
72
+
73
 
74
  def on_connect(client, userdata, flags, rc):
75
  logger.info(f"Connected with result code {rc}")
76
 
77
+
78
+ def bambu_on_message(client, userdata, message):
79
  global latest_data
80
  logger.info("Received message")
81
  try:
 
83
  latest_data["bed_temperature"] = data.get("bed_temperature", "N/A")
84
  latest_data["nozzle_temperature"] = data.get("nozzle_temperature", "N/A")
85
  latest_data["status"] = data.get("status", "N/A")
86
+ latest_data["update_time"] = time.strftime(
87
+ "%Y-%m-%d %H:%M:%S", time.localtime()
88
+ )
89
+ latest_data["image_url"] = data.get("image_url", "N/A")
90
+ except Exception as e:
91
+ logger.error(f"Error parsing MQTT message: {e}")
92
+
93
+
94
+ def rpi_on_message(client, userdata, message):
95
+ global latest_data
96
+ logger.info("Received message")
97
+ try:
98
+ data = json.loads(message.payload)
99
+ latest_data["image_url"] = data.get("image_url", "N/A")
100
  except Exception as e:
101
  logger.error(f"Error parsing MQTT message: {e}")
102
 
103
+
104
  def get_data(serial=DEFAULT_SERIAL):
105
+ global bambu_client, response_topic
106
 
107
+ if bambu_client is None:
108
+ create_client("bambu", BAMBU_HOST, BAMBU_PORT, BAMBU_USERNAME, BAMBU_PASSWORD)
109
 
110
  request_topic = f"bambu_a1_mini/request/{serial}"
111
  response_topic = f"bambu_a1_mini/response/{serial}"
112
+
113
  logger.info(f"Subscribing to {response_topic}")
114
+ bambu_client.subscribe(response_topic)
115
+
116
  logger.info(f"Publishing request to {request_topic}")
117
+ bambu_client.publish(request_topic, json.dumps("HI"))
118
+
119
  global latest_data
120
  latest_data["bed_temperature"] = "N/A"
121
  timeout = 10
122
  while latest_data["bed_temperature"] == "N/A" and timeout > 0:
123
  time.sleep(1)
124
  timeout -= 1
125
+
126
  return (
127
  latest_data["status"],
128
  latest_data["bed_temperature"],
129
  latest_data["nozzle_temperature"],
130
+ latest_data["update_time"],
131
  )
132
 
133
+
134
  def send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed):
135
+ global bambu_client
136
+
137
  serial = DEFAULT_SERIAL
138
+ logger.info(
139
+ f"Sending parameters to {serial}: nozzle={nozzle_temp}, bed={bed_temp}, speed={print_speed}, fan={fan_speed}"
140
+ )
141
  try:
142
  params = {
143
+ "nozzle_temp": nozzle_temp,
144
+ "bed_temp": bed_temp,
145
+ "print_speed": print_speed,
146
+ "fan_speed": fan_speed,
147
  }
148
+
149
  request_topic = f"bambu_a1_mini/request/{serial}"
150
+
151
+ if bambu_client:
152
+ bambu_client.publish(
153
+ request_topic,
154
+ json.dumps({"command": "set_parameters", "parameters": params}),
155
+ )
156
  logger.info("Parameters sent successfully")
157
  return "Parameters sent successfully"
158
  else:
 
162
  logger.error(f"Error sending parameters: {e}")
163
  return f"Error sending parameters: {e}"
164
 
165
+
166
  def get_image_base64(image):
167
  if image is None:
168
  logger.warning("No image to encode")
169
  return None
170
+
171
  try:
172
  if isinstance(image, np.ndarray):
173
  image = Image.fromarray(image)
174
+
175
  buffer = io.BytesIO()
176
  image.save(buffer, format="PNG")
177
+ img_str = base64.b64encode(buffer.getvalue()).decode("utf-8")
178
  logger.info(f"Image encoded to base64 (length: {len(img_str)})")
179
  return img_str
180
  except Exception as e:
181
  logger.error(f"Error encoding image: {e}")
182
  return None
183
 
184
+
185
  def get_test_image(image_name=None):
 
 
 
186
  test_dir = os.path.join(os.path.dirname(__file__), "test_images")
187
+
188
  if not os.path.exists(test_dir):
189
  logger.error(f"Test images directory not found: {test_dir}")
190
  return None
191
+
192
+ image_files = [
193
+ f
194
+ for f in os.listdir(test_dir)
195
+ if f.lower().endswith((".png", ".jpg", ".jpeg", ".bmp"))
196
+ ]
197
+
198
  if not image_files:
199
  logger.error("No test images found")
200
  return None
201
+
202
  if image_name and image_name in image_files:
203
  image_path = os.path.join(test_dir, image_name)
204
  else:
205
  image_path = os.path.join(test_dir, random.choice(image_files))
206
+
207
  logger.info(f"Using test image: {image_path}")
208
+
209
  try:
210
  return Image.open(image_path)
211
  except Exception as e:
212
  logger.error(f"Failed to open test image: {e}")
213
  return None
214
 
215
+
216
  def capture_image(url=None, use_test_image=False, test_image_name=None):
217
+ global rpi_client, latest_data
218
+
219
+ if rpi_client is None:
220
+ create_client("rpi", BAMBU_HOST, BAMBU_PORT, BAMBU_USERNAME, BAMBU_PASSWORD)
221
+
222
+ serial = DEFAULT_SERIAL
223
+ request_topic = f"bambu_a1_mini/request/{serial}"
224
+ response_topic = f"bambu_a1_mini/response/{serial}"
225
+
226
+ logger.info(f"Subscribing to {response_topic}")
227
+ rpi_client.subscribe(response_topic)
228
+
229
+ rpi_client.publish(
230
+ request_topic,
231
+ json.dumps(
232
+ {
233
+ "command": "capture_image",
234
+ }
235
+ ),
236
+ )
237
+
238
+ latest_data["image_url"] = "N/A"
239
+ timeout = 20
240
+ while latest_data["image_url"] == "N/A" and timeout > 0:
241
+ print("timeout", timeout)
242
+ time.sleep(1)
243
+ timeout -= 1
244
+
245
+ url = latest_data["image_url"]
246
 
247
  if use_test_image:
248
  logger.info("Using test image instead of URL")
 
251
  return test_img
252
  else:
253
  logger.warning("Failed to get specified test image, trying URL")
 
254
 
255
+ if url != "N/A":
256
  try:
257
  logger.info(f"Capturing image from URL: {url}")
258
  response = requests.get(url, timeout=10)
 
262
  logger.error(f"Failed to get image from URL: {response.status_code}")
263
  except Exception as e:
264
  logger.error(f"Error capturing image from URL: {e}")
265
+
266
  logger.info("URL capture failed or not provided, using random test image")
267
  return get_test_image()
268
 
269
+
270
  def health_check():
271
  status = {
272
  "app": "running",
273
  "time": time.strftime("%Y-%m-%d %H:%M:%S"),
274
+ "bambu_mqtt_connected": bambu_client is not None,
275
+ "rpi_mqtt_connected": rpi_client is not None,
276
+ "latest_update": latest_data["update_time"],
277
  }
278
  logger.info(f"Health check: {status}")
279
  return status
280
 
281
+
282
  demo = gr.Blocks(title="Bambu A1 Mini Print Control")
283
 
284
  with demo:
285
  gr.Markdown("# Bambu A1 Mini Print Control")
286
+
287
  with gr.Row():
288
  refresh_btn = gr.Button("Refresh Status")
289
+
290
  with gr.Row():
291
+ current_status = gr.Textbox(
292
+ label="Printer Status", value="N/A", interactive=False
293
+ )
294
+ current_bed_temp = gr.Textbox(
295
+ label="Current Bed Temperature", value="N/A", interactive=False
296
+ )
297
+ current_nozzle_temp = gr.Textbox(
298
+ label="Current Nozzle Temperature", value="N/A", interactive=False
299
+ )
300
  last_update = gr.Textbox(label="Last Update", value="N/A", interactive=False)
301
 
302
  with gr.Row():
 
304
  with gr.Column(scale=2):
305
  captured_image = gr.Image(label="Current Print Image", type="pil")
306
  capture_btn = gr.Button("Capture Image")
307
+
308
  # Right column for queue status and livestream
309
  with gr.Column(scale=1):
310
  gr.Markdown("### YouTube Livestream")
311
+ iframe_html = """
312
  <div style="position: relative; width: 100%; padding-top: 56.25%;">
313
  <iframe
314
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
 
320
  allowfullscreen>
321
  </iframe>
322
  </div>
323
+ """
324
  gr.HTML(iframe_html)
325
 
326
  with gr.Row():
327
  with gr.Column():
328
+ nozzle_temp = gr.Slider(
329
+ minimum=180,
330
+ maximum=250,
331
+ step=1,
332
+ value=200,
333
+ label="Nozzle Temperature (°C)",
334
+ )
335
+ bed_temp = gr.Slider(
336
+ minimum=40, maximum=100, step=1, value=60, label="Bed Temperature (°C)"
337
+ )
338
+ print_speed = gr.Slider(
339
+ minimum=20, maximum=150, step=1, value=60, label="Print Speed (mm/s)"
340
+ )
341
+ fan_speed = gr.Slider(
342
+ minimum=0, maximum=100, step=1, value=100, label="Fan Speed (%)"
343
+ )
344
+
345
  send_params_btn = gr.Button("Send Print Parameters")
346
 
347
  refresh_btn.click(
348
  fn=get_data,
349
+ outputs=[current_status, current_bed_temp, current_nozzle_temp, last_update],
350
  )
351
+
352
  capture_btn.click(
353
+ fn=lambda: gr.update(interactive=False), outputs=capture_btn
354
+ ).then(fn=capture_image, outputs=[captured_image]).then(
355
+ fn=lambda: gr.update(interactive=True), outputs=capture_btn
356
  )
357
+
358
  send_params_btn.click(
359
  fn=send_print_parameters,
360
  inputs=[nozzle_temp, bed_temp, print_speed, fan_speed],
361
+ outputs=[current_status],
362
  )
363
+
364
  def api_get_data():
365
  logger.info("API call: get_data")
366
  return get_data()
367
+
368
  def api_capture_frame(url=None, use_test_image=False, test_image_name=None):
369
+ logger.info(
370
+ f"API call: capture_frame with URL: {url}, use_test_image: {use_test_image}"
371
+ )
372
+
373
  try:
374
  img = capture_image(url, use_test_image, test_image_name)
375
  if img:
376
+ if img.mode == "RGBA":
377
+ img = img.convert("RGB")
378
+
379
  buffered = io.BytesIO()
380
  img.save(buffered, format="JPEG")
381
  img_str = base64.b64encode(buffered.getvalue()).decode()
382
+
383
+ return {"success": True, "image": img_str}
 
 
 
384
  else:
385
+ return {"success": False, "error": "Failed to capture image"}
 
 
 
386
  except Exception as e:
387
  logger.error(f"Error in capture_frame: {e}")
388
+ return {"success": False, "error": str(e)}
389
+
390
+ def api_lambda(
391
+ img_data=None,
392
+ param_1=200,
393
+ param_2=60,
394
+ param_3=60,
395
+ param_4=100,
396
+ use_test_image=False,
397
+ test_image_name=None,
398
+ ):
399
+ logger.info(
400
+ f"API call: lambda with params: {param_1}, {param_2}, {param_3}, {param_4}, use_test_image: {use_test_image}, test_image_name: {test_image_name}"
401
+ )
402
  try:
403
  img = None
404
+
405
  if use_test_image:
406
  logger.info(f"Lambda using test image: {test_image_name}")
407
  img = get_test_image(test_image_name)
408
+
409
+ elif (
410
+ img_data
411
+ and isinstance(img_data, str)
412
+ and (img_data.startswith("http://") or img_data.startswith("https://"))
413
+ ):
414
  logger.info(f"Lambda received image URL: {img_data}")
415
+ img = capture_image(img_data)
416
+
417
  elif img_data and isinstance(img_data, str):
418
  try:
419
  logger.info("Lambda received base64 image data")
 
421
  img = Image.open(io.BytesIO(img_bytes))
422
  except Exception as e:
423
  logger.error(f"Failed to decode base64 image: {e}")
424
+
425
  if img is None:
426
  logger.info("No valid image data received, using default test image")
427
  img = get_test_image()
428
+
429
  if img:
430
  img_array = np.array(img)
431
 
432
+ quality_level = "low"
433
  if 190 <= param_1 <= 210 and param_3 <= 50 and param_4 >= 80:
434
+ quality_level = "high"
435
  elif 185 <= param_1 <= 215 and param_3 <= 70 and param_4 >= 60:
436
+ quality_level = "medium"
437
+
438
+ if quality_level == "high":
439
  missing_rate = 0.02
440
  excess_rate = 0.01
441
  stringing_rate = 0.01
442
+ elif quality_level == "medium":
443
  missing_rate = 0.05
444
  excess_rate = 0.03
445
  stringing_rate = 0.02
 
447
  missing_rate = 0.10
448
  excess_rate = 0.07
449
  stringing_rate = 0.05
450
+
451
  uniformity_score = 1.0 - (missing_rate + excess_rate + stringing_rate)
452
+
453
+ print_quality_score = 1.0 - (
454
+ missing_rate * 2.0 + excess_rate * 1.5 + stringing_rate * 1.0
455
+ )
456
  print_quality_score = max(0, min(1, print_quality_score))
457
+
458
  print_speed_score = param_3 / 150.0
459
  print_speed_score = max(0, min(1, print_speed_score))
460
+
461
  material_efficiency_score = 1.0 - excess_rate * 3.0
462
  material_efficiency_score = max(0, min(1, material_efficiency_score))
463
+
464
  total_performance_score = (
465
+ 0.5 * print_quality_score
466
+ + 0.3 * print_speed_score
467
+ + 0.2 * material_efficiency_score
468
  )
 
469
 
470
  img_draw = img.copy()
471
  draw = ImageDraw.Draw(img_draw)
472
+ draw.text(
473
+ (10, 10), f"Quality: {quality_level.upper()}", fill=(255, 0, 0)
474
+ )
475
  draw.text((10, 30), f"Missing: {missing_rate:.2f}", fill=(255, 0, 0))
476
  draw.text((10, 50), f"Excess: {excess_rate:.2f}", fill=(255, 0, 0))
477
+ draw.text(
478
+ (10, 70), f"Stringing: {stringing_rate:.2f}", fill=(255, 0, 0)
479
+ )
480
+
481
  result = {
482
  "success": True,
483
  "missing_rate": missing_rate,
 
487
  "print_quality_score": print_quality_score,
488
  "print_speed_score": print_speed_score,
489
  "material_efficiency_score": material_efficiency_score,
490
+ "total_performance_score": total_performance_score,
491
  }
 
492
 
493
+ if img_draw.mode == "RGBA":
494
+ img_draw = img_draw.convert("RGB")
495
 
496
  buffered = io.BytesIO()
497
  img_draw.save(buffered, format="JPEG")
498
  img_str = base64.b64encode(buffered.getvalue()).decode()
499
  result["image"] = img_str
500
+
501
  return result
502
  else:
503
+ return {"success": False, "error": "Failed to get image"}
 
 
 
504
  except Exception as e:
505
  logger.error(f"Error in lambda: {e}")
506
+ return {"error": str(e)}
507
+
508
+ def api_send_print_parameters(
509
+ nozzle_temp=200, bed_temp=60, print_speed=60, fan_speed=100
510
+ ):
511
+ logger.info(
512
+ f"API call: send_print_parameters with nozzle={nozzle_temp}, bed={bed_temp}, speed={print_speed}, fan={fan_speed}"
513
+ )
514
  return send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed)
515
 
516
  api_json_output = gr.JSON()
517
  api_text_output = gr.Textbox()
518
+
519
  capture_frame_api = demo.load(
520
  fn=api_capture_frame,
521
  inputs=[
522
  gr.Textbox(label="Image URL"),
523
  gr.Checkbox(label="Use Test Image", value=False),
524
+ gr.Textbox(label="Test Image Name", value=""),
525
  ],
526
+ outputs=api_json_output,
527
+ api_name="capture_frame",
528
  )
529
+
530
  lambda_api = demo.load(
531
  fn=api_lambda,
532
  inputs=[
 
536
  gr.Number(label="Print Speed", value=60),
537
  gr.Number(label="Fan Speed", value=100),
538
  gr.Checkbox(label="Use Test Image", value=False),
539
+ gr.Textbox(label="Test Image Name", value=""),
540
  ],
541
+ outputs=api_json_output,
542
+ api_name="lambda",
543
  )
544
+
545
  get_data_api = demo.load(
546
+ fn=api_get_data, inputs=None, outputs=api_json_output, api_name="get_data"
 
 
 
547
  )
548
+
549
  send_params_api = demo.load(
550
  fn=api_send_print_parameters,
551
  inputs=[
552
  gr.Number(label="Nozzle Temperature", value=200),
553
  gr.Number(label="Bed Temperature", value=60),
554
  gr.Number(label="Print Speed", value=60),
555
+ gr.Number(label="Fan Speed", value=100),
556
  ],
557
+ outputs=api_text_output,
558
+ api_name="send_print_parameters",
559
  )
560
 
561
  if __name__ == "__main__":
562
  logger.info("Starting Bambu A1 Mini Print Control application")
563
+
564
  try:
565
  logger.info("Initializing MQTT client")
566
+ create_client("bambu", BAMBU_HOST, BAMBU_PORT, BAMBU_USERNAME, BAMBU_PASSWORD)
567
+ create_client("rpi", RPI_HOST, RPI_PORT, RPI_USERNAME, RPI_PASSWORD)
568
  except Exception as e:
569
  logger.error(f"Failed to initialize MQTT: {e}")
570
+
571
  demo.queue().launch(
572
+ show_error=True, share=False, server_name="0.0.0.0", server_port=7860
 
 
 
573
  )