LeKiwi 工作流程

完整工作循环

┌─────────────────────────────────────────────────────────────────┐
│                         系统启动                                 │
│                    python -m src.main                           │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│  1. 初始化                                                      │
│     - init_app()        加载 config.yaml                        │
│     - init_robot()      创建 LeKiwi 实例                        │
│     - robot.connect()   连接硬件,执行校准(如需要)              │
│     - get_observation() 获取初始关节角度                         │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. 机械臂归位                                                   │
│     - return_to_start_position()                                │
│     - 移动到预设位置 (robot_setup.py:5-12)                       │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
                    ┌───────────────────┐
                    │   主循环 while    │
                    │       True        │
                    └───────────────────┘
                                │
                ┌───────────────┼───────────────┐
                ▼               ▼               ▼
        ┌───────────┐   ┌───────────┐   ┌───────────┐
        │  SEARCH   │   │   PICK    │   │FIND_BUCKET│
        └───────────┘   └───────────┘   └───────────┘
                │               │               │
                │               │               │
                ▼               ▼               ▼
        ┌───────────────────────────────────────────────┐
        │            目标检测 (YOLO)                     │
        │  - yolo_infer()        找球                    │
        │  - get_black_bucket()   找黑桶                   │
        │  - get_red_bucket()    找红桶(备选)            │
        └───────────────────────────────────────────────┘
                                │
                                ▼
        ┌───────────────────────────────────────────────┐
        │            底盘移动控制                         │
        │  - move_controller()        搜索目标            │
        │  - move_controller_for_bucket() 跟随桶          │
        └───────────────────────────────────────────────┘
                                │
                                ▼
        ┌───────────────────────────────────────────────┐
        │            发送控制指令                         │
        │  - robot.send_action({**arm_action, **move})  │
        └───────────────────────────────────────────────┘

状态详解

SEARCH(搜索状态)

职责:寻找球

SEARCH:
    ├─ 检测: yolo_infer(frame) → 找球
    ├─ 控制: move_controller(direction, result)
    │         ├─ 球在左侧 → 旋转向左
    │         ├─ 球在右侧 → 旋转向右
    │         ├─ 球太小   → 前进
    │         ├─ 球太大   → 后退
    │         └─ 球居中且大小合适 → 稳定计数
    └─ 稳定10帧 → 切换到 PICK

PICK(捡球状态)

职责:执行捡球动作序列

PICK:
    ├─ 控制器: p_control_loop(CATCH_ACTION[step])
    ├─ 动作序列执行:
    │   1. move_to(0.0989, 0.125)   # 移动到初始位置
    │   2. shoulder_pan(-12)         # 调整肩部角度
    │   3. gripper(60)               # 张开夹爪
    │   4. wrist_flex(80)           # 弯曲腕部
    │   5. move_to(0.140, 0.1211)   # 移动到球上方
    │   6. move_to(0.140, -0.05)    # 下降到球位置
    │   7. gap()                    # 停顿
    │   8. gripper(-60)             # 闭合夹爪(夹球)
    │   9. gap()                    # 停顿
    │   10. shoulder_pan(12)        # 肩部归位
    │   11. move_to(-0.1, 0.2)      # 举起球
    │   12. wrist_flex(-20)         # 调整腕部
    └─ 序列完成 → 切换到 FIND_BUCKET

FIND_BUCKET(寻找桶状态)

职责:导航到桶的位置

FIND_BUCKET:
    ├─ 检测: get_black_bucket_local(frame)
    │         └─ 找不到 → get_red_bucket_local(frame)
    ├─ 检查: gripper_pos > 25 ?
    │         ├─ 否(球丢失)→ reset_robot() → SEARCH
    │         └─ 是(球还在)→ 继续
    ├─ 控制: move_controller_for_bucket(direction, result)
    │         ├─ 桶在左侧 → 旋转向左
    │         ├─ 桶在右侧 → 旋转向右
    │         ├─ 桶太小   → 前进
    │         ├─ 桶太大   → 后退
    │         └─ 桶居中且大小合适 → 稳定计数
    └─ 稳定10帧 → 切换到 PUT_BALL

PUT_BALL(放球状态)

职责:执行放球动作序列

PUT_BALL:
    ├─ 控制器: p_control_loop(PUT_ACTION[step])
    ├─ 动作序列执行:
    │   1. shoulder_lift(50)      # 抬起机械臂
    │   2. gap()                  # 停顿
    │   3. gripper(60)            # 张开夹爪(放球)
    │   4. gap()                  # 停顿
    │   5. move_to(-0.1, 0.2)     # 收回机械臂
    │   6. gripper(-60)           # 闭合夹爪
    └─ 序列完成 → reset_robot() → SEARCH

捡球动作详解

坐标系统

机械臂工作空间坐标(单位:米):

        y (0.22)
         │
         │
         │
(-0.22)──┼───────────────── x
         │
         │
         │
        y (-0.15)

注意:x 范围 -0.22 ~ 0.22,y 范围 -0.15 ~ 0.22

逆运动学控制

文件: arm_inverse_controller.py

def inverse_kinematics(x, y, l1=0.1159, l2=0.1350):
    """
    计算二维机械臂逆运动学
    x, y: 末端执行器目标坐标
    l1: 上臂长度 0.1159m
    l2: 下臂长度 0.1350m
    返回: (肩部角度, 肘部角度)
    """

P控制参数

MOTION_PARAMS = {
    'max_step': 0.01,    # 最大步长 1cm/帧
    'min_step': 0.001,   # 最小步长 1mm/帧
    'slow_dist': 0.05,   # 5cm 内开始减速
    'dead_zone': 0.0005  # 0.5mm 内认为到达
}

夹爪控制

动作说明
张开+60夹爪打开到 60°
闭合-60夹爪闭合,夹住物体
检测>25夹爪角度大于 25° 认为夹住物体

视觉目标检测

检测区域

摄像头画面中央的目标区域:

┌────────────────────────────────────────┐
│  (0,0)                                 │
│    ┌───────────────────────────────┐   │
│    │                               │   │
│    │     ┌───────────────┐         │   │
│    │     │   TARGET ZONE │         │   │ ← 球/桶需要在这个区域
│    │     │               │         │   │
│    │     └───────────────┘         │   │
│    │                               │   │
│    └───────────────────────────────┘   │
│                            (640, 480)  │
└────────────────────────────────────────┘

检测参数

TARGET_POSITION = max(target_w, target_h)  # 约 160 像素
TARGET_CX = left + target_w // 2            # 约 245 + 75 = 320
TARGET_CY = top + target_h // 2            # 约 160 + 80 = 240

目标大小判断

目标在画面中的大小判断机器人动作
max(w,h) < TARGET_POSITION - 10太远前进
max(w,h) > TARGET_POSITION + 10太近后退
居中且大小合适到位等待稳定

异常处理

球丢失检测

# main.py:76-83
if get_robot_status() == RobotStatus.FIND_BUCKET:
    gripper_pos = current_obs.get('arm_gripper.pos', 5)
    is_gripper_holding = gripper_pos > 25

    if not is_gripper_holding:
        set_robot_status(RobotStatus.SEARCH)
        reset_robot()

当检测到夹爪角度小于 25° 时,认为球已丢失,返回 SEARCH 状态重新寻找。

找不到目标

# move_controller.py:58-66
if not result:  # 没有检测到目标
    if _last_ball_center_x is not None:
        # 向最后看到目标的方向旋转
        action = direction.get_action("rotate_left/right")
    else:
        action = direction.get_action(None)  # 停止

机器人会记住最后看到目标的位置,并朝那个方向旋转继续搜索。

完整流程时序图

时间轴
  │
  ▼
SEARCH ────────────────────────────────────────────────────────
  │                                                           │
  │  [检测到球]                                                │
  │                                                           │
  ▼                                                           │
PICK  │                                                         │
  │                                                           │
  ├─ move_to(0.0989, 0.125)    [机械臂移动]                      │
  ├─ shoulder_pan(-12)          [调整角度]                        │
  ├─ gripper(60)                [张开夹爪]                        │
  ├─ move_to(0.140, 0.1211)     [下降到球上方]                     │
  ├─ move_to(0.140, -0.05)     [接触球]                          │
  ├─ gripper(-60)               [夹住球]                          │
  ├─ shoulder_pan(12)          [抬起]                            │
  ├─ move_to(-0.1, 0.2)        [举起球]                          │
  │                                                           │
  ▼                                                           │
FIND_BUCKET  │                                                     │
  │                                                           │
  │  [找桶导航]                                                  │
  │                                                           │
  ▼                                                           │
PUT_BALL  │                                                       │
  │                                                           │
  ├─ shoulder_lift(50)           [抬起臂]                        │
  ├─ gripper(60)                 [张开放球]                        │
  ├─ move_to(-0.1, 0.2)          [收回]                          │
  ├─ gripper(-60)                [闭合夹爪]                       │
  │                                                           │
  ▼                                                           │
SEARCH ────────────────────────────────────────────────────────