Files
smart-inspection/docs/AGV_机械臂_技术说明文档.md
T

38 KiB
Raw Blame History

AGV + 机械臂 移动拍摄平台 — 技术说明文档

版本: V3.0 | 更新时间: 2026-06-17 | 作者: 自动生成


目录

  1. 项目概述
  2. 系统架构
  3. 硬件环境与网络拓扑
  4. 核心模块详解
  5. 通信协议
  6. 完整 API 接口文档
  7. 任务执行流程
  8. 数据配置格式
  9. 部署与运维
  10. 关键决策与约束

1. 项目概述

1.1 业务目标

自动巡检拍摄系统:AGVAutomated Guided Vehicle)搭载大象机器人 630 六轴机械臂 + Orbbec Gemini 深度相机,按 M×N 网格布局自动导航到每台待检机器前,识别机器二维码→匹配机型→按预设姿态拍摄正面/背面照片→上传至后端管理系统。

1.2 核心能力

能力 说明
自主导航 基于 ROS2 Humble + Nav2 导航栈,读取预建地图,精确导航至每个目标坐标
多姿态拍摄 每台机器支持自定义正/背面多姿态(机械臂6关节角度预设)
二维码识别 机械臂摄像头(倒装)+ 双引擎识别(pyzbar + OpenCV QRCodeDetector
蛇形路径 M×N 网格蛇形路径优化,相邻路径点高效串联,避免无效往返
报关单查验 集成外部报关系统,按报关单机器清单逐台核对,自动统计查验进度
照片上传 拍摄后即时上传至 Java 后端文件服务,附带 serialNumber + index
双摄像头 AGV Orbbec 深度相机 + 机械臂 USB 摄像头,物理翻转纠正 + 花屏自动检测
单步执行/错误处理 支持单步调试模式、错误弹窗中断/跳过

1.3 技术栈

层级 技术
后端 Python 3 + Flask 2.x(端口 5000
前端 Vue 3CDN+ 原生 JS + HTML/CSS
机器人控制 ROS2 Humble + nav2_simple_commander
机械臂 RoboFlow 630 → TCP Socketarm_server
导航 Nav2 (Behavior Tree) + AMCL 定位
部署 SSH + expect 脚本远程重启

2. 系统架构

2.1 整体架构图

┌────────────────────────────────────────────────────────────────┐
│                        AGV (Ubuntu 22.04)                       │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   Flask Web 服务 (:5000)                   │  │
│  │                                                              │  │
│  │  ┌──────────┐  ┌──────────┐  ┌───────────────┐           │  │
│  │  │ 控制面板  │  │  设置页   │  │  任务运行页    │           │  │
│  │  │ index    │  │ setting  │  │  running      │           │  │
│  │  └──────────┘  └──────────┘  └───────────────┘           │  │
│  │                                                              │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │               GlobalState (全局状态)                    │  │  │
│  │  │  state / arm_client / agv_controller / qr_scanner    │  │  │
│  │  └──────────────────────────────────────────────────────┘  │  │
│  │                                                              │  │
│  │  ┌─────────────────┐  ┌──────────────────────────────┐    │  │
│  │  │  98 个 API 端点   │  │  MissionExecutorV3 任务核    │    │  │
│  │  │  RESTful JSON   │  │  M×N 网格 + 蛇形路径          │    │  │
│  │  └─────────────────┘  └──────────────────────────────┘    │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌──────────────────────┐  │
│  │ AGVController │  │  ArmClient  │  │   Nav2Navigator      │  │
│  │ (ROS2/cmd_vel)│  │ TCP Socket  │  │ BasicNavigator API │  │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬───────────┘  │
│         │                │                      │               │
│  ┌──────┴──────┐  ┌─────┴──────────┐  ┌───────┴──────────┐  │
│  │ ROS2 Topics │  │ arm_server (:5002)│ │ Nav2 Action Srv │  │
│  │ /cmd_vel    │  │ RoboFlow 630     │  │ /amcl_pose      │  │
│  │ /odom       │  │                  │  │ /navigate_to_pose│  │
│  └─────────────┘  └─────────────────┘  └──────────────────┘  │
└────────────────────────────────────────────────────────────────┘
         │                        │
         ▼                        ▼
┌──────────────┐    ┌──────────────────────────────────┐
│ 机械臂 (Pi)   │    │      外部服务 (Java 后端)          │
│ arm_server.py │    │  zhijian168.com / 192.168.60.159  │
│ :5002 TCP    │    │  customsListPage / customsMachines│
│ :5003 Camera │    │  profile/printer / file/uploadImage│
└──────────────┘    └──────────────────────────────────┘

2.2 核心文件清单

文件 行数 职责
app.py 2132 Flask 主程序,98 个 API 端点,GlobalState 全局状态管理
config.py 114 集中配置(IP、端口、API密钥、环境切换)
utils/mission_executor.py 1198 任务执行器 V3:蛇形路径、导航、QR扫描、拍照、上传
utils/agv_controller_ros2.py 216 AGV 运动控制(ROS2 topic 发布 cmd_vel
utils/arm_client.py 170 机械臂 TCP 客户端(set_angles/jog/power_on
utils/nav2_navigator.py 350 Nav2 导航器(BasicNavigator API + /amcl_pose 位置)
utils/qr_scanner.py 170 二维码扫描(V4L2 + 绿屏修复 + 双引擎识别)
utils/image_uploader.py 76 照片 HTTP 上传(multipart/form-data
templates/index.html - AGV 控制页面(实时控制 + 双摄像头预览)
templates/setting.html - 配置页面(网格/机型/点位/报关单)
templates/running.html - 任务运行页(进度 + QR弹窗 + 查验状态)
static/js/app.js - 控制页交互逻辑
static/js/setting.js - 设置页交互逻辑
static/js/running.js - 运行页交互逻辑 + SSE 实时推送

3. 硬件环境与网络拓扑

3.1 设备清单

设备 角色 IP 地址 SSH 凭证 关键软件
AGV 主控 + 运动平台 192.168.60.80 elephant / Elephant ROS2 Humble, Nav2, Flask
机械臂 Pi 机械臂 + 摄像头 192.168.60.120 pi / elephant arm_server.py, RoboFlow, ffmpeg
Java 测试服务器 报关单/上传后端 192.168.60.159:8080 - Spring Boot
生产服务器 正式环境 ts.zhijian168.com - HTTPS + Nginx

3.2 AGV 硬件映射

设备 Linux 路径 用途
AGV 控制器 /dev/ttyCH341USB0 AGV 底盘串口控制
雷达 /dev/ttyACM0 LiDAR 传感器
Orbbec Gemini /dev/video4 深度相机(彩色流 YUYV 640×480

3.3 网络参数

参数 说明
Flask 监听 0.0.0.0:5000 AGV Web 服务
机械臂 TCP 5002 arm_server 控制端口
机械臂摄像头 5003 arm_server MJPEG 流
ROS_DOMAIN_ID 1 DDS 发现域(Flask/Nav2/AGV 节点统一)
AGV 串口波特率 1000000 底盘通信

4. 核心模块详解

4.1 GlobalState — 全局状态管理

class GlobalState:
    state: str              # "idle" | "setting" | "running" | "paused"
    arm_client: ArmClient   # 机械臂 TCP 客户端实例
    agv_controller: AGVController  # ROS2 AGV 控制器
    qr_scanner: QRScanner   # AGV 摄像头二维码扫描器
    navigator: Nav2Navigator # 导航实例
    mission_config: dict    # {rows, cols, grid[][], positions[{row,col,side,coords,poses}]}
    machines_config: list   # [{id, row, col, front:{coords,poses}, back:{coords,poses}}]
    models_config: list     # [{id, name, poses:[{id,name,photo_type,arm_angles,speed}]}]
    qr_config: list         # [{id, name, joint_angles, qr_value, model_id}]
    inspection: dict        # 查验状态 {customsId, customsName, items:[{inventoryCode,quantify,inspected}]}
    current_customs: dict   # 当前报关单 {id, name, machine_ids}

状态转换图

  IDLE ──connect_all──▶ SETTING ──start_mission──▶ RUNNING
    ▲                      ▲                          │
    │                      │                    ┌─────┼─────┐
    │                      │                    ▼     ▼     ▼
    └──disconnect── PAUSED ◀── error/stop ── COMPLETED

4.2 MissionExecutorV3 — 任务执行器核心

类结构

MissionExecutorV3
├── 连接管理: connect_all() / disconnect_all()
├── 主流程: execute_mission(mission_config, machines, models, options)
│   ├── 蛇形路径: _build_snake_path(rows, cols, grid) → 路径列表
│   ├── 导航: _navigate(point, label) → Nav2Navigator
│   ├── QR 扫描: _scan_qr_with_poses(qr_configs, machine_row)
│   │   ├── _decode_qr_from_arm() → pyzbar/OpenCV
│   │   └── _request_manual_qr(message) → 用户手动输入
│   ├── 机型查询: _lookup_model(qr_value) → 报关单API查询
│   ├── 拍照: _shoot(model, side, row, col, qr_value, machine_row)
│   │   ├── _capture_arm_photo() → 机械臂摄像头
│   │   └── _upload_photo_bytes() → HTTP上传
│   └── 返回原点: _return_to_origin()
├── 控制: pause() / resume() / stop()
├── 单步执行: set_step_choice("confirm"|"retry"|"abort")
└── 错误处理: set_error_choice("skip"|"abort")

蛇形路径算法

假设 2行 × 5列,有机器位置: (0,0)(0,1)(0,2)(0,3)(0,4)(1,0)(1,1)(1,2)(1,3)(1,4)

蛇形路径(按点位行 pr 遍历):
  pr=0 (1排正面): (0,0)→(0,1)→(0,2)→(0,3)→(0,4)   [左→右]
  pr=1 (1排背面 + 2排正面): (1,4)→(1,3)→(1,2)→(1,1)→(1,0)  [右→左]
  pr=2 (2排背面): (2,0)→(2,1)→(2,2)→(2,3)→(2,4)   [左→右]

PR 为奇数时列序反向。

同一点位同时服务上一行背面和下一行正面时,先执行背面,再执行正面。
镜像规则:机器行号为奇数时,所有 J1 关节角度取反(镜像)。仅 J1 取反!

任务步骤控制开关

前端执行任务时可选择性开启/关闭步骤:

开关 字段 默认 说明
机械臂初始化 arm_init true 每个点位移到后恢复默认姿态
AGV 移动 agv_move true 导航到目标坐标
二维码识别 qr_scan true 扫描机器二维码
正面拍照 front_photo true 正面姿态组拍摄
背面拍照 back_photo true 背面姿态组拍摄
AGV 速度 agv_speed 1.0 m/s
机械臂速度 arm_speed 1000 RoboFlow 速度参数

4.3 AGVController — ROS2 运动控制

class AGVController:
    def connect()        # 检查 /odom topic 是否存在
    def is_connected()   # 连接状态
    def move_forward()   # 前进 (linear.x > 0)
    def move_backward()  # 后退 (linear.x < 0)
    def turn_left()      # 左转 (angular.z > 0)
    def turn_right()     # 右转 (angular.z < 0)
    def move_left_lateral()  # 左横移 (linear.y > 0)
    def move_right_lateral() # 右横移 (linear.y < 0)
    def stop()           # 停止 (全 0)
    def get_position()   # 从 /odom 获取位置 [x, y, yaw]
    def get_battery()    # 获取电压

原理: 通过 subprocess 执行 ros2 topic pub /cmd_vel geometry_msgs/msg/Twist 发布速度指令。--once 参数发布一次后退出,底层 AGV 驱动收到后会持续执行直到收到下一条指令(或发送零值停止)。

ROS 环境: source /opt/ros/humble/setup.bash && source ~/agv_pro_ros2/install/setup.bash && export ROS_DOMAIN_ID=1

4.4 ArmClient — 机械臂 TCP 客户端

class ArmClient:
    def connect()            # TCP 连接到 arm_server:5002
    def send_command(cmd)    # 发送文本命令,接收响应
    def get_angles()         # → [J1..J6] 当前关节角度
    def set_angles(angles, speed) # 设置全部 6 关节角度
    def set_angle(joint, angle, speed) # 设置单个关节
    def jog_angle(joint, direction, speed) # 连续调节(-1/0/1
    def get_coords()         # → [x, y, z, rx, ry, rz]
    def power_on() / state_on() / state_off()  # 上电控制
    def state_check() / check_running()  # 状态查询
    def wait_done(timeout)   # 等待命令执行完成
    def task_stop()          # 紧急停止

通信协议: 文本行协议(\n 分隔)。

  • 请求: command_name(param1,param2,...)\n
  • 响应: command_name:resultok

关节范围 (机械臂 630):

关节 范围
J1 ±180°
J2 ±90°
J3 ±90°
J4 ±180°
J5 ±90°
J6 ±180°

4.5 Nav2Navigator — 自主导航

class Nav2Navigator:
    def navigate_to_pose(x, y, yaw, timeout_sec, blocking)
        # 使用 BasicNavigator.goToPose() 发送导航目标
        # 子线程中轮询 isTaskComplete(),超时自动取消
    def navigate_through_poses(poses, timeout_per_pose, blocking)
        # 多路径点连续导航
    def stop()           # 取消当前导航
    def get_status()     # {status, current_position, nav2_available}
    def get_current_position()  # 从 /amcl_pose topic 获取 [x,y,yaw]

工作原理:

  1. 使用 nav2_simple_commander.BasicNavigator(官方 Python API
  2. 在子线程中初始化 rclpy,构造 PoseStamped 消息并调用 goToPose()
  3. 轮询 isTaskComplete() 查看导航是否完成
  4. 超时时调用 cancelTask() 取消
  5. 位置反馈从 /amcl_poseAMCL 定位结果)而非 /odom(里程计)获取,避免累积漂移

返回原点机制: _return_to_origin() 导航到 (0, 0),超时 180 秒,最多重试 3 次。

4.6 QRScanner — 二维码识别

class QRScanner:
    def open()           # 打开摄像头(V4L2device_index=4
    def read_frame()     # 读取一帧(带超时保护)
    def detect_qr(frame) # 双引擎:pyzbar > OpenCV QRCodeDetector
    def scan_once()      # 单次扫描
    def scan_with_retry(max_attempts, interval)  # 多次重试

双引擎策略:

  1. pyzbar(优先): 识别率更高,支持多种条码格式
  2. OpenCV QRCodeDetector(兜底): pyzbar 失败时启用

绿屏/花屏修复: _fix_frame() 方法检测 YUYV 格式未转换导致的绿屏(G 通道全满),自动做 COLOR_YUV2BGR_YUYV 转换。全黑帧直接丢弃。

4.7 ImageUploader — 照片上传

class ImageUploader:
    def upload(image_path, serial_number, photo_index, photo_type)
    def upload_batch(image_paths, serial_number, start_index)

上传协议:

  • 方法: HTTP POSTmultipart/form-data
  • URL: {ZHIJIAN_BASE_URL}{API_PREFIX}/file/uploadImage
    • 正式: https://ts.zhijian168.com/prod-api/file/uploadImage
    • 测试: http://192.168.60.159:8080/file/uploadImage
  • 字段: file (MultipartFile), serialNumber (String), index (Integer)
  • 认证: Authorization: Bearer <JWT Token>
  • 重试: 最多 3 次,间隔 2 秒

5. 通信协议

5.1 Flask ↔ 前端

  • 协议: HTTP RESTful JSON
  • 端口: 5000
  • 格式: {"ok": bool, ...data}

5.2 Flask ↔ AGV (ROS2)

# 发布速度指令
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 1.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}" --once

# 获取位置 (AMCL)
ros2 topic echo /amcl_pose --once

5.3 Flask ↔ 机械臂 (TCP)

请求:  set_angles(-90.33,-90.08,0.16,-90.57,0.09,22.23,1000)\n
响应:  set_angles:ok

请求:  get_angles()\n
响应:  get_angles:[-90.33,-90.08,0.16,-90.57,0.09,22.23]

5.4 Flask ↔ Java 后端

接口 方法 URL 路径 说明
报关单列表 GET /zhijian/integration/customsListPage ?pageNum=&pageSize=
机器列表 GET /zhijian/integration/customsMachines ?customsId=
机型查询 GET /zhijian/profile/printer ?serialNumber=
文件上传 POST /file/uploadImage multipart/form-data

认证: 所有请求携带 Authorization: Bearer <token> 头。


6. 完整 API 接口文档

6.1 系统状态 API

路由 方法 说明 参数
/api/status GET 全局状态(连接/地图/任务统计) -
/api/system/connect POST 一次性连接所有设备 -
/api/system/disconnect POST 断开所有设备 -
/api/device/connect POST 连接单个设备 {"device":"agv|arm|camera|arm_camera"}

6.2 AGV 控制 API

路由 方法 说明 参数
/api/agv/move POST 控制移动 {"direction":"forward|backward|left|right|left_lateral|right_lateral|stop","speed":1.0}
/api/agv/position GET 获取位置+电量 -
/api/agv/stop POST 紧急停止 -
/api/agv/reset POST 撞物体后复位 -

6.3 机械臂控制 API

路由 方法 说明 参数
/api/arm/get_angles GET 获取当前6关节角度 -
/api/arm/set_angles POST 设置全部关节 {"angles":[],"speed":1000}
/api/arm/set_angle POST 设置单个关节 {"joint":"J1","angle":90,"speed":500}
/api/arm/jog POST 连续调节关节 {"joint":"J1","direction":1|-1|0,"speed":500}
/api/arm/get_coords GET 获取末端坐标 -
/api/arm/power_on POST 上电 -
/api/arm/state_on POST 激活 -
/api/arm/state_off POST 去激活 -
/api/arm/state_check GET 检查状态 -

6.4 摄像头 API

路由 方法 说明 参数
/api/camera/preview GET AGV 摄像头 MJPEG 流 -
/api/camera/refresh GET AGV 摄像头单帧 JPEG -
/api/camera/capture GET 拍摄一张照片保存本地 -
/api/camera/arm_refresh GET 机械臂摄像头单帧(翻转+花屏检测) -
/api/camera/arm_preview GET 机械臂摄像头 MJPEG 代理流 -
/api/camera/qr_scan GET AGV 摄像头扫码一次 -
/api/camera/capabilities GET 摄像头能力信息 -

6.5 地图导航 API

路由 方法 说明 参数
/api/map/load POST 加载地图文件 {"map_dir":"...","map_file":"map.yaml"}
/api/map/save POST 保存地图配置 {"map_dir":"...","map_file":"map.yaml"}
/api/map/image GET 获取地图 PNG 图像 -
/api/map/meta GET 获取地图元数据(分辨率/原点/尺寸) -
/api/navigate/to POST 导航到目标坐标 {"x":1.0,"y":2.0,"yaw":0.0}
/api/navigate/stop POST 停止导航 -
/api/navigate/cancel POST 取消导航 -
/api/navigate/status GET 获取导航状态 -
/api/navigate/path POST 预览路径(Nav2 不可用) {"x":1.0,"y":2.0}

6.6 任务执行 API

路由 方法 说明 参数
/api/mission/start POST 开始执行任务 {"single_step":false,"arm_init":true,"agv_move":true,"qr_scan":true,"front_photo":true,"back_photo":true,"agv_speed":1.0,"arm_speed":1000}
/api/mission/stop POST 停止任务 -
/api/mission/pause POST 暂停任务 -
/api/mission/resume POST 恢复任务 -
/api/mission/report GET 获取执行报告 -
/api/mission/state GET 任务实时状态(步骤/进度/查验/QR消息) -
/api/mission/log GET 实时日志 -
/api/mission/manual-qr POST 手动输入二维码(弹窗提交) {"qr":"BG042110276"}
/api/mission/error-skip POST 错误弹窗:跳过 -
/api/mission/error-abort POST 错误弹窗:中断 -
/api/mission/singlestep/confirm POST 单步确认 -
/api/mission/singlestep/retry POST 单步重试 -
/api/mission/singlestep/abort POST 单步中断 -

6.7 任务配置 API

路由 方法 说明 参数
/api/mission/config GET 获取网格配置+空位矩阵 -
/api/mission/config POST 设置网格配置 {"rows":2,"cols":5,"grid":[[],...],"arm_initial_pose":[]}
/api/mission/position GET 获取 AGV 当前位置(设置点位用) -
/api/mission/init_pose POST 将 AMCL 初始位置设为 (0,0,0) -
/api/mission/positions GET 获取所有点位坐标 -
/api/mission/positions POST 保存/更新单点位 {"row":0,"col":0,"side":"front","coords":[],"poses":[]}
/api/mission/machines GET 获取所有机器配置 -
/api/mission/machines POST 批量保存机器配置 {"machines":[...]}
/api/mission/machines/add POST 添加单台机器 {"row":0,"col":0,"front":{},"back":{}}
/api/mission/machines/<id> PUT 更新机器配置
/api/mission/machines/<id> DELETE 删除机器配置
/api/mission/poses/<id>/<side> GET 获取机器指定侧姿态 -
/api/mission/poses/<id>/<side> POST 添加姿态到机器 {"arm_angles":[],"speed":500}
/api/mission/poses/<id>/<side>/<pid> DELETE 删除姿态 -
/api/mission/qr_scan/<id> POST AGV 摄像头扫码关联机器 -
/api/mission/generate_sequence GET 生成蛇形拍摄序列预览 -

6.8 机型配置 API

路由 方法 说明 参数
/api/models/list GET 获取所有机型 -
/api/models/add POST 添加机型 {"name":"机型1","serial_prefix":"BG"}
/api/models/<id> POST 更新机型 -
/api/models/<id> DELETE 删除机型 -
/api/models/poses/add POST 添加姿态到机型 {"model_id":"xxx","name":"正1","photo_type":"front","arm_angles":[]}
/api/models/<id>/poses GET 获取机型姿态列表 -
/api/models/<id>/poses/<pid> PUT 更新姿态 -
/api/models/<id>/poses/<pid> DELETE 删除姿态 -

6.9 二维码配置 API

路由 方法 说明 参数
/api/qr/configs GET 获取所有二维码配置 -
/api/qr/configs POST 添加二维码配置 {"name":"二维码1","joint_angles":[]}
/api/qr/configs/<id> PUT 更新二维码配置 -
/api/qr/configs/<id> DELETE 删除二维码配置 -
/api/qr/configs/<id>/read-angles POST 读取当前臂角度写入配置 -
/api/qr/scan/<id> POST 机械臂摄像头扫码保存 -

6.10 报关单与查验 API

路由 方法 说明 参数
/api/customs/list GET 报关单列表(代理) ?pageNum=1&pageSize=50
/api/customs/machines GET 报关单机器列表(代理) ?customsId=xxx
/api/customs/selected POST 设定当前报关单 {"id":"xxx","name":"xxx","machine_ids":[]}
/api/customs/selected GET 获取当前报关单 -
/api/customs/printer GET 查询机型+更新查验计数 ?serialNumber=xxx
/api/customs/inspection/start POST 开始查验 {"customsId":"xxx"}
/api/customs/inspection GET 获取查验状态 -
/api/customs/inspection/end POST 结束查验 -
/api/customs/inspection/update POST 直接更新计数 {"inventoryCode":"xxx"}

6.11 环境切换 API

路由 方法 说明 参数
/api/config/mode GET 获取当前环境 -
/api/config/mode POST 切换测试/正式环境 {"test_mode":true}

环境差异:

项目 测试环境 正式环境
Base URL http://192.168.60.159:8080 https://ts.zhijian168.com
API 前缀 /prod-api
上传地址 http://192.168.60.159:8080/file/uploadImage https://ts.zhijian168.com/prod-api/file/uploadImage

7. 任务执行流程

7.1 完整生命周期

[1] 前端设置页配置
    ├── 加载地图 → 设置 M×N 网格尺寸(rows/cols
    ├── 标注空位(Machine Toggle 切换每个单元格有/无机器)
    ├── 逐点位标定坐标(AGV 开到机器前→读取位置→保存)
    ├── 配置二维码扫描角度(机械臂对准二维码位置)
    ├── 配置机型姿态组(正/背面,每面多角度)
    └── 连接设备(AGV/机械臂/摄像头)

[2] 报关单查验
    ├── 选择报关单 → 开始查验
    └── 系统按 inventoryCode 聚合统计各机型待查验数量

[3] 启动任务
    ├── POST /api/mission/start(可选单步模式+步骤开关)
    └── MissionExecutorV3.execute_mission() 在新线程中运行

[4] 逐点位蛇形执行
    For each 点位 (pr, c) in 蛇形路径:
    ├── [可选] 恢复机械臂初始姿态
    ├── [可选] 导航到该点位坐标
    │       └── Nav2Navigator.navigate_to_pose() → BasicNavigator.goToPose()
    │
    ├── 背面操作(如果 pr>0 且 (pr-1,c) 有机器)
    │   ├── 切换到 QR 扫描姿态(可选)
    │   ├── 扫描二维码 → 查机型 → [可选] 拍照
    │   └── 上传照片 + 更新查验计数
    │
    └── 正面操作(如果 pr<rows 且 (pr,c) 有机器)
        ├── 切换到 QR 扫描姿态
        ├── _scan_qr_with_poses(qr_configs):
        │   ├── 逐姿态尝试扫描(pyzbar + OpenCV
        │   ├── 失败 → 弹窗 _request_manual_qr()
        │   └── 机型不在报关单 → 弹窗重新输入(不可跳过)
        │
        ├── _lookup_model(qr_value):
        │   ├── 请求 /api/customs/printer?serialNumber=xxx
        │   ├── 超量检查(inspected >= quantify
        │   └── 返回机型名称
        │
        └── _shoot(model, "front"):
            ├── 逐姿态设置关节角度 + 等待就位
            ├── _capture_arm_photo() → 机械臂摄像头拍照
            ├── _upload_photo_bytes() → HTTP上传
            └── 更新查验计数

[5] 任务完成
    ├── _return_to_origin() → 导航回 (0,0)
    └── 生成执行报告

7.2 QR 扫描流程详解

_scan_qr_with_poses(qr_configs, machine_row):
    1. 逐 QR 配置尝试
       ├── set_angles(qr_config.joint_angles) → 机械臂移到扫码位
       ├── _wait_arm_ready() → 等待到位(容差 2°)
       └── _decode_qr_from_arm():
           ├── HTTP GET 机械臂摄像头单帧
           ├── 花屏检测 (_is_corrupted_jpeg)
           ├── pyzbar.decode() → 识别成功
           └── OpenCV QRCodeDetector → 兜底

    2. 如果识别失败:
       ├── 报错日志 + 弹窗 _request_manual_qr()
       └── 强制用户扫描/输入(不可跳过,仅任务停止可退出)

    3. 如果机型不在报关单 (_lookup_model 返回 matched=null):
       ├── 弹窗 _request_manual_qr() 强制重新输入
       └── 循环直到匹配或任务停止

    4. 如果已查验数量 ≥ 报关单数量 (_lookup_model 检测超量):
       ├── 弹窗 _request_manual_qr() 强制重新输入
       └── 循环直到不超量或任务停止

7.3 拍照流程详解

_shoot(model, side, row, col, qr_value, machine_row):
    1. 过滤姿态: 只取 photo_type == side 的姿态
    2. 镜像规则: machine_row % 2 == 1 → J1 = -J1
    3. 逐姿态执行:
       ├── set_angles(pose.arm_angles, speed)
       ├── _wait_arm_ready() → 等待姿态稳定
       ├── _capture_arm_photo():
       │   ├── HTTP GET 机械臂摄像头 JPG
       │   ├── 花屏检测
       │   └── 保存到 /home/elephant/photos/
       └── _upload_photo_bytes():
           ├── POST multipart/form-data
           ├── serialNumber = qr_value
           ├── index = next_upload_index(全局递增,从1开始)
           └── 重试3次
    4. 日志: "拍照完成 (机型=Mxx, 面=正面, 位置=r-c)"

7.4 错误处理

场景 触发条件 处理方式
导航失败 Nav2 超时/返回 failed 错误弹窗(跳过/中断)
QR 识别失败 所有姿态尝试均未识别 手动输入弹窗(不能跳过)
机型不在报关单 printer 返回空 matchedItem 手动输入弹窗(不能跳过)
查验超量 inspected >= quantify 手动输入弹窗(不能跳过)
拍照失败 HTTP 请求/文件损坏 记录日志,继续下一张
上传失败 HTTP 超时/401/非200 重试3次,记录日志
机械臂超时 _wait_arm_ready 15秒超时 记录实际偏差,继续执行

8. 数据配置格式

8.1 任务网格配置 (mission_config.json)

{
  "rows": 2,
  "cols": 5,
  "grid": [[true, true, true, true, true],
           [true, true, true, true, true]],
  "positions": [
    {"row": 0, "col": 0, "side": "front", "coords": [0.54, -1.32, -0.05], "poses": []},
    {"row": 1, "col": 0, "side": "back",  "coords": [0.65, -3.63, -3.06], "poses": []}
  ],
  "arm_initial_pose": [-90.33, -90.08, 0.16, -90.57, 0.09, 22.23]
}
  • grid[r][c] = true 表示该位置有机器
  • positionsrow=pr 表示点位行(非机器行),机器行 mr = pr (正面) 或 mr = pr-1 (背面)
  • coords = [x, y, yaw] 地图坐标和朝向

8.2 机器配置 (machines_config.json)

[{
  "id": "m_0_0",
  "row": 0, "col": 0,
  "front": {
    "coords": [0.54, -1.32, -0.05],
    "poses": [{"id":"pose_xxx","name":"正1","arm_angles":[...],"speed":500}]
  },
  "back": {
    "coords": [0.65, -3.63, -3.06],
    "poses": [{"id":"pose_xxx","name":"背1","arm_angles":[...],"speed":500}]
  }
}]

8.3 机型配置 (models_config.json)

[{
  "id": "m_1778767289",
  "name": "MXM465N",
  "serial_prefix": "BG",
  "poses": [
    {
      "id": "pose_xxx1",
      "name": "正面姿态1",
      "photo_type": "front",
      "arm_angles": [-93.59, -184.34, 50.58, -38.33, -85.15, 20.40],
      "speed": 500
    },
    {
      "id": "pose_xxx2",
      "name": "背面姿态1",
      "photo_type": "back",
      "arm_angles": [15.86, -161.13, 138.0, -162.0, 168.0, 15.65],
      "speed": 500
    }
  ]
}]
  • photo_type: "front" / "back" / "nameplate"
  • arm_angles: [J1, J2, J3, J4, J5, J6] 单位为度

8.4 二维码扫描姿态 (qr_config.json)

[{
  "id": "qr_001",
  "name": "正面扫码位",
  "joint_angles": [-89.80, -2.01, -87.18, -82.50, -93.32, 20.40],
  "qr_value": "",
  "model_id": ""
}]

9. 部署与运维

9.1 环境要求

AGV (主控):

  • Ubuntu 22.04 (ROS2 Humble)
  • uv 管理的 Python 3.10 虚拟环境
  • OpenCV (cv2), Flask, requests, numpy, pyzbar, PyYAML
  • ROS2 Humble + nav2_simple_commander
  • 系统依赖:ffmpeg、libzbar0

机械臂 (Pi):

  • arm_server.pyTCP 服务器端口 5002
  • arm_camera.pyMJPEG 服务器端口 5003
  • RoboFlow(大象机器人 SDK
  • uv 管理的 Python 3.10 虚拟环境

9.2 启动流程

# === 首次部署 / 依赖同步 ===
cd ~/work/smart-inspection
uv sync

# === 机械臂端 (Pi) ===
# 1. 启动 arm_server (TCP 5002) + arm_camera (MJPEG 5003)
sudo systemctl start arm_server

# === AGV 端 ===
# 2. 完整启动 ROS2 导航栈 + Flask
cd ~/work/smart-inspection
./scripts/start_all.sh

9.3 部署命令

# 本地 → AGV 部署:同步仓库根目录后,在 AGV 上执行
cd ~/work/smart-inspection
uv sync --locked

# 部署后验证远程文件
ssh elephant@192.168.60.80 "grep 'def _lookup_model' /home/elephant/work/smart-inspection/agv_app/utils/mission_executor.py"

# 重启 Flask
ssh elephant@192.168.60.80 'bash -s' < scripts/restart_flask.sh

# 清空 Python 缓存(关键!修改后必须清)
ssh elephant@192.168.60.80 "find /home/elephant/work/smart-inspection/agv_app -name '*.pyc' -delete; find /home/elephant/work/smart-inspection/agv_app -name '__pycache__' -type d -exec rm -rf {} +"

9.4 关键运维经验

问题 根因 解决方案
Flask 模板/JS 不生效 模板缓存 重启 Flask 服务
Python 修改不生效 __pycache__ 缓存 删除所有 .pyc 和 pycache
V4L2 摄像头无响应 设备独占 kill 残留进程后重开
ROS2 节点互相不可见 ROS_DOMAIN_ID 不一致 统一设为 1
导航 DDS 发现失败 FastRTPS 共享内存残留 rm -f /dev/shm/fastrtps_*
机械臂摄像头花屏 USB 掉线致 ffmpeg 读失效 fd arm_server 添加 JPEG 校验+自动重连
Flask SIGTERM 被杀 sshpass+nohup 触发 用 expect 脚本重启
照片上传 405 缺少 /prod-api 前缀 config.py 动态拼接 API_PREFIX

10. 关键决策与约束

10.1 架构决策

决策 原因
agv_controller 用 ROS2 CLI 而非 rclpy 避免 rclpy 初始化与 Flask 多线程冲突
Nav2 用 BasicNavigator API 而非 subprocess 原生 API 更可靠(subprocess 的 stdin pipe 有 Humble bug
机械臂用 TCP Socket 而非 pymycobot pymycobot 存在死锁问题
位置源用 /amcl_pose 而非 /odom /odom 累积漂移,/amcl_pose 有地图校正
Vue 用 {% raw %} 包裹 Jinja2 与 Vue 3 {{}} 冲突
单例 MissionExecutorV3 一个任务实例全局可见,方便停止
蛇形路径镜像只取反 J1 用户要求:同一边只需镜像 J1 关节
QR 弹窗不可跳过 业务约束:机型不在报关单/超量必须人工介入
上传序号全局递增 连续编号便于后端核对
环境切换无需重启 运行时动态修改 config 变量 + 代理 URL

10.2 已知约束

约束 影响
摄像头 device_index=4 固定的 Orbbec Gemini 设备号,不可修改
V4L2 设备独占 同时只能一个进程打开 /dev/video4
ROS 时钟漂移 ~5.5min 需检查 AGV 的 RTC/NTP 同步
机械臂精度 ±1.5° _wait_arm_ready 容差 2° 可能过严
AGV 重启自动切正式环境 无持久化配置方案
报关单数据依赖外部 API API 格式不稳定(裸数组 vs 包装对象)

附录 A:目录结构

agv_app/
├── app.py                    # Flask 主程序 (2132行)
├── config.py                 # 集中配置
├── templates/
│   ├── index.html            # AGV 控制页
│   ├── setting.html          # 设置页(网格/机型/报关单)
│   └── running.html          # 任务运行页
├── static/
│   ├── css/style.css         # 样式(深色主题)
│   └── js/
│       ├── app.js            # 控制页逻辑
│       ├── setting.js        # 设置页逻辑
│       ├── running.js        # 运行页逻辑
│       └── vue3.global.prod.js  # Vue 3 CDN
├── data/
│   ├── mission_config.json   # 网格尺寸+点位坐标
│   ├── machines_config.json  # 机器配置(正/背面)
│   ├── models_config.json    # 机型配置(姿态组)
│   ├── qr_config.json        # 二维码扫描姿态
│   └── map_config.json       # 地图配置
├── utils/
│   ├── mission_executor.py   # 任务执行器 V3 (1198行)
│   ├── agv_controller_ros2.py # AGV 运动控制 (216行)
│   ├── arm_client.py         # 机械臂客户端 (170行)
│   ├── nav2_navigator.py     # Nav2 导航器 (350行)
│   ├── qr_scanner.py         # 二维码扫描 (170行)
│   └── image_uploader.py     # 图片上传 (76行)

启动脚本位于仓库顶层 scripts/。LiDAR 时间戳修复脚本部署在 AGV 的 /home/elephant/work/scan_fixer/,由 scripts/start_all.sh 调用。

附录 B:关键依赖

pyproject.toml                   # Python 依赖声明
uv.lock                          # 锁定版本
.python-version                  # Python 3.10
ffmpeg                           # 系统依赖,机械臂视频流
libzbar0                         # 系统依赖,pyzbar 动态库
ROS2 Humble                      # 系统环境,提供 rclpy/nav2_simple_commander/geometry_msgs

文档维护: 本文档随代码同步更新。关键变更请记录到 memory/YYYY-MM-DD.md