SissiFeng commited on
Commit
eb09393
·
verified ·
1 Parent(s): afe17aa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -309
app.py CHANGED
@@ -47,9 +47,8 @@ latest_data = {
47
  "status": "N/A",
48
  "update_time": "Waiting for data...",
49
  "image_url": "N/A",
50
- "auto_capture_requested": False,
51
- "last_capture_job": "None",
52
- "last_capture_time": "None"
53
  }
54
 
55
  bambu_client = None
@@ -104,28 +103,41 @@ def rpi_on_message(client, userdata, msg):
104
  payload = msg.payload.decode("utf-8")
105
  logger.info(f"Received message from RPI: {payload}")
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  try:
108
- data = json.loads(payload)
109
 
110
- # 处理状态更新
111
- if "status" in data:
112
- latest_data["status"] = data["status"]
113
- latest_data["update_time"] = data.get("update_time", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
114
-
115
- if "nozzle_temperature" in data:
116
- latest_data["nozzle_temperature"] = data["nozzle_temperature"]
117
-
118
- if "bed_temperature" in data:
119
- latest_data["bed_temperature"] = data["bed_temperature"]
120
-
121
- if "error" in data:
122
- logger.error(f"Error from RPI: {data['error']}")
123
-
124
- # 添加对自动拍照命令的处理
125
- if "command" in data and data["command"] == "capture_image" and data.get("auto_triggered", False):
126
- logger.info("收到自动拍照命令")
127
- # 异步处理拍照请求,避免阻塞MQTT回调
128
- threading.Thread(target=handle_auto_capture, args=(data,), daemon=True).start()
129
 
130
  except json.JSONDecodeError:
131
  logger.error(f"Invalid JSON in message: {payload}")
@@ -203,6 +215,12 @@ def send_print_parameters(nozzle_temp, bed_temp, print_speed, fan_speed):
203
  logger.error(f"Error sending parameters: {e}")
204
  return f"Error sending parameters: {e}"
205
 
 
 
 
 
 
 
206
 
207
  def get_image_base64(image):
208
  if image is None:
@@ -223,115 +241,85 @@ def get_image_base64(image):
223
  return None
224
 
225
 
226
- def get_test_image(image_name=None):
227
- test_dir = os.path.join(os.path.dirname(__file__), "test_images")
228
-
229
- if not os.path.exists(test_dir):
230
- logger.error(f"Test images directory not found: {test_dir}")
231
- return None
232
-
233
- image_files = [
234
- f
235
- for f in os.listdir(test_dir)
236
- if f.lower().endswith((".png", ".jpg", ".jpeg", ".bmp"))
237
- ]
238
-
239
- if not image_files:
240
- logger.error("No test images found")
241
- return None
242
-
243
- if image_name and image_name in image_files:
244
- image_path = os.path.join(test_dir, image_name)
245
- else:
246
- image_path = os.path.join(test_dir, random.choice(image_files))
247
-
248
- logger.info(f"Using test image: {image_path}")
249
-
250
- try:
251
- return Image.open(image_path)
252
- except Exception as e:
253
- logger.error(f"Failed to open test image: {e}")
254
- return None
255
-
256
-
257
- def capture_image():
258
- """捕获图像"""
259
- try:
260
- logger.info("Capturing image...")
261
-
262
- # 尝试使用摄像头捕获图像
263
- try:
264
- cap = cv2.VideoCapture(0) # 使用默认摄像头
265
- ret, frame = cap.read()
266
- cap.release()
267
-
268
- if ret:
269
- # 转换为RGB(OpenCV使用BGR)
270
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
271
- logger.info(f"Image captured successfully: {frame_rgb.shape}")
272
- return frame_rgb
273
- else:
274
- logger.error("Failed to capture image from camera")
275
- except Exception as e:
276
- logger.error(f"Error accessing camera: {e}")
277
-
278
- # 如果摄像头捕获失败,使用测试图像
279
- test_image = get_test_image()
280
- if test_image is not None:
281
- logger.info("Using test image instead")
282
- return np.array(test_image)
283
-
284
- # 如果测试图像也不可用,创建一个错误图像
285
- error_img = np.zeros((480, 640, 3), dtype=np.uint8)
286
- cv2.putText(error_img, "Camera Error", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
287
- return error_img
288
-
289
- except Exception as e:
290
- logger.error(f"Error in capture_image: {e}")
291
- # 返回错误图像
292
- error_img = np.zeros((480, 640, 3), dtype=np.uint8)
293
- cv2.putText(error_img, f"Error: {str(e)}", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
294
- return error_img
295
-
296
-
297
  def handle_auto_capture(message):
298
- """处理自动拍照请求"""
299
  try:
300
- logger.info(f"收到自动拍照请求: {message}")
301
 
302
- # 提取信息
303
  print_job = message.get("print_job", "unknown_job")
304
  timestamp = message.get("timestamp", time.strftime("%Y-%m-%d %H:%M:%S"))
305
 
306
- # 记录到最新数据中
307
  latest_data["auto_capture_requested"] = True
308
  latest_data["last_capture_job"] = print_job
309
  latest_data["last_capture_time"] = timestamp
310
 
311
- # 执行拍照操作
312
- image = capture_image()
313
-
314
- # 保存图像
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  try:
316
- save_dir = os.path.join(os.path.dirname(__file__), "captured_images")
317
- os.makedirs(save_dir, exist_ok=True)
318
-
319
- filename = f"{print_job}_{timestamp.replace(' ', '_').replace(':', '-')}.jpg"
320
- filepath = os.path.join(save_dir, filename)
321
-
322
- if isinstance(image, np.ndarray):
323
- cv2.imwrite(filepath, cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
324
  else:
325
- image.save(filepath)
326
-
327
- logger.info(f"Saved captured image to {filepath}")
328
  except Exception as e:
329
- logger.error(f"Error saving captured image: {e}")
330
-
331
- return image
332
- except Exception as e:
333
- logger.error(f"处理自动拍照请求时出错: {e}")
334
- return None
 
 
 
 
 
 
 
 
335
 
336
 
337
  def health_check():
@@ -343,39 +331,17 @@ def health_check():
343
  "latest_update": latest_data["update_time"],
344
  }
345
  logger.info(f"Health check: {status}")
346
-
347
- # 修改返回值,确保返回6个值
348
- return (
349
- status.get("app", "unknown"),
350
- "N/A", # 作为nozzle_temperature
351
- "N/A", # 作为bed_temperature
352
- status.get("time", "unknown"),
353
- 0, # 作为progress
354
- f"MQTT: Bambu={status.get('bambu_mqtt_connected')}, RPI={status.get('rpi_mqtt_connected')}" # 作为message
355
- )
356
-
357
-
358
- def update_print_status():
359
- """更新打印状态显示"""
360
- global latest_data
361
- return (
362
- latest_data["status"],
363
- latest_data["nozzle_temperature"],
364
- latest_data["bed_temperature"],
365
- latest_data["update_time"],
366
- latest_data.get("progress", 0),
367
- latest_data.get("message", ""),
368
- )
369
 
370
 
371
  demo = gr.Blocks(title="Bambu A1 Mini Print Control")
372
 
373
  with demo:
374
  gr.Markdown("# Bambu A1 Mini Print Control")
375
-
376
  with gr.Row():
377
  refresh_btn = gr.Button("Refresh Status")
378
-
379
  with gr.Row():
380
  current_status = gr.Textbox(
381
  label="Printer Status", value="N/A", interactive=False
@@ -387,7 +353,7 @@ with demo:
387
  label="Current Nozzle Temperature", value="N/A", interactive=False
388
  )
389
  last_update = gr.Textbox(label="Last Update", value="N/A", interactive=False)
390
-
391
  with gr.Row():
392
  # Left column for image and capture button
393
  with gr.Column(scale=2):
@@ -437,7 +403,7 @@ with demo:
437
  fn=get_data,
438
  outputs=[current_status, current_bed_temp, current_nozzle_temp, last_update],
439
  )
440
-
441
  capture_btn.click(
442
  fn=lambda: gr.update(interactive=False), outputs=capture_btn
443
  ).then(fn=capture_image, outputs=[captured_image]).then(
@@ -476,123 +442,6 @@ with demo:
476
  logger.error(f"Error in capture_frame: {e}")
477
  return {"success": False, "error": str(e)}
478
 
479
- def api_lambda(
480
- img_data=None,
481
- param_1=200,
482
- param_2=60,
483
- param_3=60,
484
- param_4=100,
485
- use_test_image=False,
486
- test_image_name=None,
487
- ):
488
- logger.info(
489
- 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}"
490
- )
491
- try:
492
- img = None
493
-
494
- if use_test_image:
495
- logger.info(f"Lambda using test image: {test_image_name}")
496
- img = get_test_image(test_image_name)
497
-
498
- elif (
499
- img_data
500
- and isinstance(img_data, str)
501
- and (img_data.startswith("http://") or img_data.startswith("https://"))
502
- ):
503
- logger.info(f"Lambda received image URL: {img_data}")
504
- img = capture_image(img_data)
505
-
506
- elif img_data and isinstance(img_data, str):
507
- try:
508
- logger.info("Lambda received base64 image data")
509
- img_bytes = base64.b64decode(img_data)
510
- img = Image.open(io.BytesIO(img_bytes))
511
- except Exception as e:
512
- logger.error(f"Failed to decode base64 image: {e}")
513
-
514
- if img is None:
515
- logger.info("No valid image data received, using default test image")
516
- img = get_test_image()
517
-
518
- if img:
519
- img_array = np.array(img)
520
-
521
- quality_level = "low"
522
- if 190 <= param_1 <= 210 and param_3 <= 50 and param_4 >= 80:
523
- quality_level = "high"
524
- elif 185 <= param_1 <= 215 and param_3 <= 70 and param_4 >= 60:
525
- quality_level = "medium"
526
-
527
- if quality_level == "high":
528
- missing_rate = 0.02
529
- excess_rate = 0.01
530
- stringing_rate = 0.01
531
- elif quality_level == "medium":
532
- missing_rate = 0.05
533
- excess_rate = 0.03
534
- stringing_rate = 0.02
535
- else: # low
536
- missing_rate = 0.10
537
- excess_rate = 0.07
538
- stringing_rate = 0.05
539
-
540
- uniformity_score = 1.0 - (missing_rate + excess_rate + stringing_rate)
541
-
542
- print_quality_score = 1.0 - (
543
- missing_rate * 2.0 + excess_rate * 1.5 + stringing_rate * 1.0
544
- )
545
- print_quality_score = max(0, min(1, print_quality_score))
546
-
547
- print_speed_score = param_3 / 150.0
548
- print_speed_score = max(0, min(1, print_speed_score))
549
-
550
- material_efficiency_score = 1.0 - excess_rate * 3.0
551
- material_efficiency_score = max(0, min(1, material_efficiency_score))
552
-
553
- total_performance_score = (
554
- 0.5 * print_quality_score
555
- + 0.3 * print_speed_score
556
- + 0.2 * material_efficiency_score
557
- )
558
-
559
- img_draw = img.copy()
560
- draw = ImageDraw.Draw(img_draw)
561
- draw.text(
562
- (10, 10), f"Quality: {quality_level.upper()}", fill=(255, 0, 0)
563
- )
564
- draw.text((10, 30), f"Missing: {missing_rate:.2f}", fill=(255, 0, 0))
565
- draw.text((10, 50), f"Excess: {excess_rate:.2f}", fill=(255, 0, 0))
566
- draw.text(
567
- (10, 70), f"Stringing: {stringing_rate:.2f}", fill=(255, 0, 0)
568
- )
569
-
570
- result = {
571
- "success": True,
572
- "missing_rate": missing_rate,
573
- "excess_rate": excess_rate,
574
- "stringing_rate": stringing_rate,
575
- "uniformity_score": uniformity_score,
576
- "print_quality_score": print_quality_score,
577
- "print_speed_score": print_speed_score,
578
- "material_efficiency_score": material_efficiency_score,
579
- "total_performance_score": total_performance_score,
580
- }
581
-
582
- if img_draw.mode == "RGBA":
583
- img_draw = img_draw.convert("RGB")
584
-
585
- buffered = io.BytesIO()
586
- img_draw.save(buffered, format="JPEG")
587
- img_str = base64.b64encode(buffered.getvalue()).decode()
588
- result["image"] = img_str
589
-
590
- return result
591
- else:
592
- return {"success": False, "error": "Failed to get image"}
593
- except Exception as e:
594
- logger.error(f"Error in lambda: {e}")
595
- return {"error": str(e)}
596
 
597
  def api_send_print_parameters(
598
  nozzle_temp=200, bed_temp=60, print_speed=60, fan_speed=100
@@ -668,52 +517,6 @@ with demo:
668
  api_name="health_check",
669
  )
670
 
671
- # 在Camera Control标签页中添加自动拍照状态显示
672
- with gr.Tab("Camera Control"):
673
- # 添加自动拍照状态显示
674
- with gr.Row():
675
- auto_capture_status = gr.Textbox(label="Auto Capture Status", value="Waiting")
676
- refresh_status_btn = gr.Button("Refresh Status")
677
-
678
- # 添加手动拍照按钮和显示区域
679
- with gr.Row():
680
- capture_btn = gr.Button("Capture Image")
681
-
682
- with gr.Row():
683
- captured_image = gr.Image(label="Captured Image", type="numpy")
684
-
685
- # 定义更新状态函数
686
- def update_capture_status():
687
- auto_status = "Triggered" if latest_data.get("auto_capture_requested", False) else "Waiting"
688
- last_time = latest_data.get("last_capture_time", "None")
689
- last_job = latest_data.get("last_capture_job", "None")
690
- return f"{auto_status} (Last: {last_job} at {last_time})"
691
-
692
- # 连接刷新按钮
693
- refresh_status_btn.click(
694
- fn=update_capture_status,
695
- inputs=[],
696
- outputs=[auto_capture_status]
697
- )
698
-
699
- # 连接拍照按钮
700
- capture_btn.click(
701
- fn=capture_image,
702
- inputs=[],
703
- outputs=[captured_image]
704
- )
705
-
706
- # 使用JavaScript实现自动刷新
707
- auto_refresh_js = """
708
- function autoRefresh() {
709
- document.querySelector('#refresh-status-btn').click();
710
- setTimeout(autoRefresh, 5000); // 每5秒刷新一次
711
- }
712
- setTimeout(autoRefresh, 5000); // 启动自动刷新
713
- """
714
-
715
- gr.HTML(f"<script>{auto_refresh_js}</script>")
716
-
717
  if __name__ == "__main__":
718
  logger.info("Starting Bambu A1 Mini Print Control application")
719
 
 
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
 
103
  payload = msg.payload.decode("utf-8")
104
  logger.info(f"Received message from RPI: {payload}")
105
 
106
+ data = json.loads(payload)
107
+ status = data.get("status", "Unknown")
108
+
109
+ latest_data["status"] = status
110
+ latest_data["update_time"] = data.get("update_time", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
111
+
112
+ if "nozzle_temperature" in data:
113
+ latest_data["nozzle_temperature"] = data["nozzle_temperature"]
114
+
115
+ if "bed_temperature" in data:
116
+ latest_data["bed_temperature"] = data["bed_temperature"]
117
+
118
+ if "error" in data:
119
+ logger.error(f"Error from RPI: {data['error']}")
120
+
121
+ if status == "Ready":
122
+ latest_data["progress"] = 0
123
+ latest_data["message"] = "Printer ready"
124
+ elif status == "Processing":
125
+ latest_data["progress"] = 25
126
+ latest_data["message"] = "Processing G-code..."
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error processing message from RPI: {e}")
130
+
131
  try:
132
+ result = json.loads(payload)
133
 
134
+ if "status" in result:
135
+ status = result["status"]
136
+ latest_data["status"] = status
137
+
138
+ if "command" in result and result["command"] == "capture_image" and result.get("auto_triggered", False):
139
+ logger.info("receive capture command")
140
+ threading.Thread(target=handle_auto_capture, args=(result,), daemon=True).start()
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  except json.JSONDecodeError:
143
  logger.error(f"Invalid JSON in message: {payload}")
 
215
  logger.error(f"Error sending parameters: {e}")
216
  return f"Error sending parameters: {e}"
217
 
218
+
219
+ latest_data["status"] = "Sending"
220
+ latest_data["progress"] = 10
221
+ latest_data["message"] = "Sending parameters to printer..."
222
+
223
+
224
 
225
  def get_image_base64(image):
226
  if image is None:
 
241
  return None
242
 
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  def handle_auto_capture(message):
 
245
  try:
246
+ logger.info(f"receive capture command: {message}")
247
 
 
248
  print_job = message.get("print_job", "unknown_job")
249
  timestamp = message.get("timestamp", time.strftime("%Y-%m-%d %H:%M:%S"))
250
 
 
251
  latest_data["auto_capture_requested"] = True
252
  latest_data["last_capture_job"] = print_job
253
  latest_data["last_capture_time"] = timestamp
254
 
255
+ return capture_image()
256
+ except Exception as e:
257
+ logger.error(f"error: {e}")
258
+ return None, f"capture failed: {str(e)}"
259
+
260
+
261
+ def capture_image(url=None, use_test_image=False, test_image_name=None):
262
+ global rpi_client, latest_data
263
+
264
+ if rpi_client is None:
265
+ create_client("rpi")
266
+
267
+ serial = DEFAULT_SERIAL
268
+ request_topic = f"bambu_a1_mini/request/{serial}"
269
+ response_topic = f"bambu_a1_mini/response/{serial}"
270
+
271
+ logger.info(f"Subscribing to {response_topic}")
272
+ rpi_client.subscribe(response_topic)
273
+
274
+ rpi_client.publish(
275
+ request_topic,
276
+ json.dumps(
277
+ {
278
+ "command": "capture_image",
279
+ }
280
+ ),
281
+ )
282
+
283
+ latest_data["image_url"] = "N/A"
284
+ timeout = 45
285
+ while latest_data["image_url"] == "N/A" and timeout > 0:
286
+ # print("timeout", timeout)
287
+ time.sleep(1)
288
+ timeout -= 1
289
+
290
+ url = latest_data["image_url"]
291
+
292
+ if use_test_image:
293
+ logger.info("Using test image instead of URL")
294
+ test_img = get_test_image(test_image_name)
295
+ if test_img:
296
+ return test_img
297
+ else:
298
+ logger.warning("Failed to get specified test image, trying 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
+ def update_print_status():
314
+ global latest_data
315
+ return (
316
+ latest_data["status"],
317
+ latest_data["nozzle_temperature"],
318
+ latest_data["bed_temperature"],
319
+ latest_data["update_time"],
320
+ latest_data.get("progress", 0),
321
+ latest_data.get("message", ""),
322
+ )
323
 
324
 
325
  def health_check():
 
331
  "latest_update": latest_data["update_time"],
332
  }
333
  logger.info(f"Health check: {status}")
334
+ return status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
 
337
  demo = gr.Blocks(title="Bambu A1 Mini Print Control")
338
 
339
  with demo:
340
  gr.Markdown("# Bambu A1 Mini Print Control")
341
+
342
  with gr.Row():
343
  refresh_btn = gr.Button("Refresh Status")
344
+
345
  with gr.Row():
346
  current_status = gr.Textbox(
347
  label="Printer Status", value="N/A", interactive=False
 
353
  label="Current Nozzle Temperature", value="N/A", interactive=False
354
  )
355
  last_update = gr.Textbox(label="Last Update", value="N/A", interactive=False)
356
+
357
  with gr.Row():
358
  # Left column for image and capture button
359
  with gr.Column(scale=2):
 
403
  fn=get_data,
404
  outputs=[current_status, current_bed_temp, current_nozzle_temp, last_update],
405
  )
406
+
407
  capture_btn.click(
408
  fn=lambda: gr.update(interactive=False), outputs=capture_btn
409
  ).then(fn=capture_image, outputs=[captured_image]).then(
 
442
  logger.error(f"Error in capture_frame: {e}")
443
  return {"success": False, "error": str(e)}
444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
 
446
  def api_send_print_parameters(
447
  nozzle_temp=200, bed_temp=60, print_speed=60, fan_speed=100
 
517
  api_name="health_check",
518
  )
519
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  if __name__ == "__main__":
521
  logger.info("Starting Bambu A1 Mini Print Control application")
522