diff --git a/agv_app/utils/mission_executor.py b/agv_app/utils/mission_executor.py index 052cd06..c1b1d3d 100644 --- a/agv_app/utils/mission_executor.py +++ b/agv_app/utils/mission_executor.py @@ -31,11 +31,12 @@ ROS2_SETUP_CMD = "source /opt/ros/humble/setup.bash && source ~/agv_pro_ros2/ins from config import ARM_CAMERA_CONFIG ARM_CAMERA_SNAPSHOT = ARM_CAMERA_CONFIG["snapshot_url"] PHOTOS_DIR = "/home/elephant/photos" +UPLOAD_URL = "https://ts.zhijian168.com/prod-api/file/uploadImage" # 二维码扫描重试参数 QR_SCAN_TIMEOUT = 5 # 单次扫描超时 QR_POSE_WAIT = 1.5 # 调整姿态后等待时间 -MANUAL_QR_TIMEOUT = 300 # 5分钟超时 +MANUAL_QR_TIMEOUT = 300 # 5分钟超时(现已改为无限等待,stop 时解除) class MissionStatus(str, Enum): @@ -111,6 +112,9 @@ class MissionExecutorV3: self.arm_speed = 500 self.agv_speed = 0.5 + # 照片上传序号计数器(连续递增,从1开始) + self.next_upload_index = 1 + # ==================== 连接 ==================== def connect_all(self) -> Dict[str, bool]: @@ -242,6 +246,9 @@ class MissionExecutorV3: self._log(f"📍 点位蛇形路径: {len(path)} 个点位, {total_machines} 台机器") + # 重置照片上传序号(每次任务开始时重置,从1开始) + self.next_upload_index = 1 + # 任务步骤控制开关 if options is None: options = {} @@ -688,22 +695,21 @@ class MissionExecutorV3: return None def _request_manual_qr(self) -> Optional[str]: - """暂停任务,等待手动输入""" + """暂停任务,等待手动输入(不超时,必须输入才能继续;stop 时解除)""" self.status = MissionStatus.WAITING_QR self.report["status"] = "waiting_qr" self.report["step"] = "等待手动输入二维码" - self._log(" ⌨️ 弹窗等待手动输入二维码...") + self._log(" ⌨️ 弹窗等待手动输入二维码(不可跳过)...") self._qr_event.clear() - if self._qr_event.wait(timeout=MANUAL_QR_TIMEOUT): - self.status = MissionStatus.RUNNING - self.report["status"] = "running" + self._qr_event.wait() # 无限等待,直到 set_manual_qr 或 stop() 触发 + self.status = MissionStatus.RUNNING + self.report["status"] = "running" + if self._qr_value: self._log(f" ✏️ 手动输入: {self._qr_value}") return self._qr_value else: - self.status = MissionStatus.RUNNING - self.report["status"] = "running" - self._log(f" ⚠️ 等待超时({MANUAL_QR_TIMEOUT}s),跳过") + self._log(f" ⚠️ 任务已停止") return None def set_manual_qr(self, value: str): @@ -766,14 +772,19 @@ class MissionExecutorV3: self.arm_client.set_angles(angles, speed=self.arm_speed) self._wait_arm_ready(angles) - # 拍照 - path = self._capture_arm_photo(row, col, side, pi + 1, qr_value) + # 拍照(upload_index 连续递增) + path = self._capture_arm_photo(row, col, side, pi + 1, qr_value, upload_index=self.next_upload_index) + self.next_upload_index += 1 if path: self._log(f" 💾 {os.path.basename(path)}") def _capture_arm_photo(self, row: int, col: int, side: str, - pose_idx: int, qr_value: str) -> Optional[str]: - """从机械臂摄像头拍照存本地""" + pose_idx: int, qr_value: str, + upload_index: int = 0) -> Optional[str]: + """从机械臂摄像头拍照存本地,然后上传到服务器 + + upload_index: 从1开始,先正面后背面,由调用方维护 + """ try: resp = requests.get(ARM_CAMERA_SNAPSHOT, timeout=10) if resp.status_code != 200 or not resp.content: @@ -786,11 +797,43 @@ class MissionExecutorV3: fpath = os.path.join(PHOTOS_DIR, fname) with open(fpath, "wb") as f: f.write(resp.content) + self._log(f" 💾 本地保存: {os.path.basename(fpath)}") + + # 上传到服务器 + if qr_value: + self._upload_photo(fpath, qr_value, upload_index) + else: + self._log(" ⚠️ 无二维码,跳过上传") + return fpath except Exception as e: logger.error(f"拍照异常: {e}") return None + def _upload_photo(self, filepath: str, serial_number: str, index: int) -> bool: + """上传照片到远程服务器 + + Args: + filepath: 本地文件路径 + serial_number: 二维码/序列号 + index: 1=正面, 2=背面 + """ + try: + filename = os.path.basename(filepath) + with open(filepath, "rb") as f: + files = {"file": (filename, f, "image/jpeg")} + data = {"serialNumber": serial_number, "index": str(index)} + resp = requests.post(UPLOAD_URL, files=files, data=data, timeout=30) + if resp.status_code == 200: + self._log(f" ☁️ 上传成功 [{index}]: {filename}") + return True + else: + self._log(f" ⚠️ 上传失败 [{index}] HTTP {resp.status_code}: {resp.text[:200]}") + return False + except Exception as e: + self._log(f" ❌ 上传异常 [{index}]: {e}") + return False + # ==================== 控制 ==================== def _wait_pause(self):