From fcf5649d8c3c03b61f2c9fde3e97e4491df4383a Mon Sep 17 00:00:00 2001 From: ywb <347742090@qq.com> Date: Wed, 27 May 2026 14:49:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agv_app/app.py | 10 ++++ agv_app/static/css/style.css | 68 ++++++++++++++++++++++ agv_app/static/js/running.js | 14 ++++- agv_app/templates/running.html | 48 +++++++++++++++ agv_app/utils/mission_executor.py | 97 ++++++++++++++++++++++--------- 5 files changed, 209 insertions(+), 28 deletions(-) diff --git a/agv_app/app.py b/agv_app/app.py index 7b4b2a3..a9d488c 100644 --- a/agv_app/app.py +++ b/agv_app/app.py @@ -1247,6 +1247,15 @@ def api_mission_start(): """开始执行任务(V3: M×N Grid 蛇形路径)""" data = request.json or {} single_step = bool(data.get("single_step", False)) + # 任务步骤控制开关 + options = { + "arm_init": bool(data.get("arm_init", True)), + "agv_move": bool(data.get("agv_move", True)), + "qr_scan": bool(data.get("qr_scan", True)), + "front_photo": bool(data.get("front_photo", True)), + "back_photo": bool(data.get("back_photo", True)), + } + print(f"[Mission] options: {options}") # 清除可能存在的旧实例,确保可以启动 if hasattr(MissionExecutorV3, "_instance"): @@ -1282,6 +1291,7 @@ def api_mission_start(): qr_configs=gs.qr_config, models=models_list, single_step=single_step, + options=options, ) gs.mission_report = report executor.disconnect_all() diff --git a/agv_app/static/css/style.css b/agv_app/static/css/style.css index 3bbc7f6..1acf317 100644 --- a/agv_app/static/css/style.css +++ b/agv_app/static/css/style.css @@ -920,3 +920,71 @@ a:hover { text-decoration: underline; } gap: 16px; margin-top: 12px; } + +/* ========== 任务步骤控制开关 ========== */ +.toggle-grid { + display: flex; + flex-direction: column; + gap: 12px; +} +.toggle-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: #0a0a0a; + border: 1px solid #1a1a1a; + border-radius: 8px; + transition: border-color 0.2s; +} +.toggle-item:hover { + border-color: #333; +} +.toggle-switch { + position: relative; + display: inline-block; + width: 44px; + height: 24px; + flex-shrink: 0; +} +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} +.toggle-slider { + position: absolute; + cursor: pointer; + top: 0; left: 0; right: 0; bottom: 0; + background-color: #333; + border-radius: 24px; + transition: 0.3s; +} +.toggle-slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: #888; + border-radius: 50%; + transition: 0.3s; +} +.toggle-switch input:checked + .toggle-slider { + background-color: #2e7d32; +} +.toggle-switch input:checked + .toggle-slider:before { + transform: translateX(20px); + background-color: #4caf50; +} +.toggle-label { + font-size: 14px; + font-weight: 600; + color: #e0e0e0; + min-width: 140px; +} +.toggle-hint { + font-size: 12px; + color: #666; +} diff --git a/agv_app/static/js/running.js b/agv_app/static/js/running.js index bf00c54..b762852 100644 --- a/agv_app/static/js/running.js +++ b/agv_app/static/js/running.js @@ -21,6 +21,12 @@ createApp({ errorMsg: '', waitingStep: false, stepLabel: '', + // 任务步骤控制开关 + armInitEnabled: true, + agvMoveEnabled: true, + qrScanEnabled: true, + frontPhotoEnabled: true, + backPhotoEnabled: true, } }, computed: { @@ -114,7 +120,13 @@ createApp({ await fetch(API + '/api/mission/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({}) + body: JSON.stringify({ + arm_init: this.armInitEnabled, + agv_move: this.agvMoveEnabled, + qr_scan: this.qrScanEnabled, + front_photo: this.frontPhotoEnabled, + back_photo: this.backPhotoEnabled, + }) }) this.missionState = 'running' }, diff --git a/agv_app/templates/running.html b/agv_app/templates/running.html index 80c86ef..6511cfc 100644 --- a/agv_app/templates/running.html +++ b/agv_app/templates/running.html @@ -48,6 +48,54 @@ + +
+

🎛️ 任务步骤控制

+

关闭的步骤将在本次任务中跳过

+
+
+ + 🦾 机械臂位置初始化 + 移动到每个点位前恢复初始姿态 +
+
+ + 🚗 AGV移动 + 按之字形路线移动到各点位 +
+
+ + 📷 识别二维码 + 调整机械臂姿态扫描二维码 +
+
+ + 📸 拍正面照 + 按机型正面姿态拍照 +
+
+ + 📸 拍背面照 + 按机型背面姿态拍照 +
+
+
+

📋 任务清单 ([[ tasks.length ]] 台机器)

diff --git a/agv_app/utils/mission_executor.py b/agv_app/utils/mission_executor.py index 3f14fec..9afaffc 100644 --- a/agv_app/utils/mission_executor.py +++ b/agv_app/utils/mission_executor.py @@ -124,10 +124,22 @@ class MissionExecutorV3: qr_configs: list, models: list, single_step: bool = False, + options: dict = None, ) -> dict: """ 执行完整拍摄任务。 + Args: + options: 任务步骤控制开关 + - arm_init: 是否执行机械臂位置初始化 + - agv_move: 是否执行AGV移动 + - qr_scan: 是否执行二维码识别 + - front_photo: 是否执行正面拍照 + - back_photo: 是否执行背面拍照 + """ + """ + 执行完整拍摄任务。 + Args: mission_config: {rows, cols, grid, positions} machines: [{id, row, col, front: {coords}, back: {coords}}] @@ -187,6 +199,17 @@ class MissionExecutorV3: "photos_back": 0, } for (r, c) in path] + # 任务步骤控制开关 + if options is None: + options = {} + opt_arm_init = options.get("arm_init", True) + opt_agv_move = options.get("agv_move", True) + opt_qr_scan = options.get("qr_scan", True) + opt_front_photo = options.get("front_photo", True) + opt_back_photo = options.get("back_photo", True) + self._log(f"⚙️ 任务步骤: arm_init={opt_arm_init}, agv_move={opt_agv_move}, " + f"qr_scan={opt_qr_scan}, front={opt_front_photo}, back={opt_back_photo}") + # 机械臂初始姿态(AGV 移动前恢复) arm_initial_pose = mission_config.get("arm_initial_pose", [0.0] * 6) has_arm_pose = self.arm_client and any(abs(a) > 0.01 for a in arm_initial_pose) @@ -203,10 +226,12 @@ class MissionExecutorV3: rl, cl = r + 1, c + 1 # 恢复机械臂初始姿态 - if has_arm_pose: + if opt_arm_init and has_arm_pose: self._log(" 🦾 恢复机械臂初始姿态") self.arm_client.set_angles(arm_initial_pose, speed=500) time.sleep(2) + elif not opt_arm_init: + self._log(" ⏭️ 跳过机械臂位置初始化") # 更新任务状态 → 正面开始 task = self._get_task(r, c) @@ -226,20 +251,27 @@ class MissionExecutorV3: self._step(f"机器 {rl}-{cl} 正面") # 导航到正面点位 - front_pt = self._find_point(positions, r, c, "front") - if front_pt and self._has_coords(front_pt): - if not self._navigate(front_pt, "正面"): - self._log(f"⚠️ 导航失败,尝试继续") - choice = self._wait_error(f"机器 {rl}-{cl} 正面导航失败") - if choice == "abort": - break + if opt_agv_move: + front_pt = self._find_point(positions, r, c, "front") + if front_pt and self._has_coords(front_pt): + if not self._navigate(front_pt, "正面"): + self._log(f"⚠️ 导航失败,尝试继续") + choice = self._wait_error(f"机器 {rl}-{cl} 正面导航失败") + if choice == "abort": + break + else: + self._log(f"⚠️ 无正面点位坐标") else: - self._log(f"⚠️ 无正面点位坐标") + self._log(" ⏭️ 跳过AGV移动(正面)") # 扫描二维码 - qr_value = self._scan_qr_with_poses(qr_configs) - if self._stop.is_set(): - break + qr_value = None + if opt_qr_scan: + qr_value = self._scan_qr_with_poses(qr_configs) + if self._stop.is_set(): + break + else: + self._log(" ⏭️ 跳过二维码识别") # 查机型 + 更新任务步骤 model_name = self._lookup_model(qr_value) @@ -250,11 +282,14 @@ class MissionExecutorV3: task["step"] = "正面拍照" # 正面拍照 - model = self._find_model(models, model_name) - if model: - self._shoot(model, "front", rl, cl, qr_value or "unknown") + if opt_front_photo: + model = self._find_model(models, model_name) + if model: + self._shoot(model, "front", rl, cl, qr_value or "unknown") + else: + self._log(f" ⚠️ 未找到机型 {model_name}") else: - self._log(f" ⚠️ 未找到机型 {model_name}") + self._log(" ⏭️ 跳过正面拍照") self._progress(machine_idx, 1) @@ -265,19 +300,25 @@ class MissionExecutorV3: self._step(f"机器 {rl}-{cl} 背面") # 导航到背面点位 - back_pt = self._find_point(positions, r + 1, c, "back") - if back_pt and self._has_coords(back_pt): - if not self._navigate(back_pt, "背面"): - self._log(f"⚠️ 导航失败,尝试继续") - choice = self._wait_error(f"机器 {rl}-{cl} 背面导航失败") - if choice == "abort": - break + if opt_agv_move: + back_pt = self._find_point(positions, r + 1, c, "back") + if back_pt and self._has_coords(back_pt): + if not self._navigate(back_pt, "背面"): + self._log(f"⚠️ 导航失败,尝试继续") + choice = self._wait_error(f"机器 {rl}-{cl} 背面导航失败") + if choice == "abort": + break + else: + self._log(f"⚠️ 无背面点位坐标") else: - self._log(f"⚠️ 无背面点位坐标") + self._log(" ⏭️ 跳过AGV移动(背面)") # 背面拍照 - if model: - self._shoot(model, "back", rl, cl, qr_value or "unknown") + if opt_back_photo: + if model: + self._shoot(model, "back", rl, cl, qr_value or "unknown") + else: + self._log(" ⏭️ 跳过背面拍照") # 标记任务完成 if task: @@ -300,10 +341,12 @@ class MissionExecutorV3: machine_idx += 1 # 3. 回到出发点 - if not self._stop.is_set(): + if not self._stop.is_set() and opt_agv_move: self._step("返回出发点") self._log("→ 返回 (0, 0)") self._nav2_go_to_point(0, 0, 0, timeout_sec=60) + elif not self._stop.is_set(): + self._log("⏭️ 跳过返回出发点") elapsed = time.time() - start_time return self._finish(elapsed)