先明确这些变量的意义:
代码块
bool see_ball= false;
vector<double> ball_field = {1,0,0}; // 球在机器人坐标系下的位置
vector<double> ball_global = {0,0,0}; // 球在全局坐标系下的位置
vector<double> ball_velocity = {0,0,0}; // 球在机器人坐标系下的速度
vector<double> robot_global = {0,0,0}; // 机器人全局坐标系下的位置
vector<double> action_global = {0,0,0}; // 目标点在全局坐标系下的位置
vector<double> vel_cmd = {0,0,0}; // 规划出来,用于发布的速度
std::vector<std::vector<double>> obstacles; // 障碍物信息,每个障碍物包含 x, y, radius,存储多个障碍物
int gait_type = 0; // 步态类型
int kick_type = 0; // 踢球类型
bool kick_flag = false; // 是否处于踢球准备阶段的标志
bool dribble_flag = false; // 是否处于盘球阶段的标志
double ball_field_distance; //球在机器人坐标系下的距离
double ball_field_angle; //球在机器人坐标系下的角度
double robot_angle_error = 0; // 朝向目标点 - 现在的朝向
先看主函数:
1. 节点初始化与 ROS 通信配置 (Line 413 - 434)
- 环境变量区分机器人:通过读取环境变量
ZJUDANCER_ROBOTID,为话题 (Topic) 加上特定的前缀,从而在一台电脑或仿真环境中区分控制不同的机器人个体。 - 有个DWA的实例,但还没用上
- 发布与订阅 (Pub & Sub):
- 订阅视觉信息
VisionInfo:获取机器人全局坐标、球的局部和全局坐标、球速等。 - 订阅行为指令
ActionCommand:接收上层决策层下发的指令(包含目标坐标bodyCmd.x, y, t以及期望的步态类型gait_type)。 - 发布速度指令
cmd_vel(geometry_msgs::Twist):发布计算后的平移和旋转速度控制底盘(全向移动底盘)。 - 发布手柄指令
/joy_msg:通过模拟手柄行为来发送诸如“左脚踢球”、“右脚踢球”、“向左扑救”等特殊动作触发指令。
- 订阅视觉信息
2. 控制参数初始化 (Line 436 - 470)
定义了一系列控制动作时使用的常量,包括:
- 速度与容差参数:最大/最小线速度、角速度,到达目标点的容差。
- 盘球 (Dribble) 参数:定义了找球、绕球调整朝向、前进带球这三个子阶段触发的距离和角度容差。
- 踢球 (Kick) 参数:定义了踢球时的期望身位偏移。
- 扑救 (Goalie) 参数:判定球在左侧还是右侧以决定扑救方向。
3. 主事件循环 (状态机) (Line 485 - 827)
不同的 gait_type 对应不同的行为:
case 0 (站立待命)
- 所有的速度指令清零。
case 1 (走到目标点 / Walk)
- 调用
generate_velocity计算产生控制速度。 - 当小车距离目标点的坐标误差小于 0.3 米且偏航角 (Yaw) 误差小于 0.2 弧度时,将速度清零并把状态切回等待态 (
gait_type = 0)。
- 调用
case 2 (走过去+踢球 / Walk & Kick) 这部分是一个多段式的状态转移逻辑:
- 找球与绕球阶段 (
kick_flag == false): i. 接近球:先判断机器人到球的距离是否大于阈值close_to_ball,如果大于那么先靠近球,调用generate_velocity产生控制速度。 ii. 面向球:靠近后判断机器人与球的角度是否大于阈值dribble_face_ball_tolerance,如果大于则基于纯角速度cmd_vel[2]旋转,面向足球。 iii. 面向球门:在面向球的情况下,判断机器人与球门的角度是否大于阈值dribble_face_goal_tolerance,若大于则机器人会通过横移 (cmd_vel[1]) 配合补偿旋转,实现“围绕球作圆弧平移”动作,从而使机器人、球、球门共线。 iv. 对准后,挂起标志位kick_flag = true。 - 踢球微调准备与击球阶段 (
kick_flag == true): i. 使用之前决定的kick_type获取理想触球点参数 (kick_param),计算球的期望和实际的位置和角度偏差。 ii. 如果存在较小的微观位置偏差和角度偏差,则使用最小速度进行细微位置修正。(这里采用了一个小速度而注释掉了P控制,可能追求稳定性吧) iii. 当容差满足时 (err_x, err_y, yaw_err < tol),发布触球指令:模拟发送joy_msg的不同按键指令,触发下位机或执行系统中不同脚法、不同侧向的踢球动作,踢球结束重置标志位。
- 找球与绕球阶段 (
case 3 (盘球/带球 / Dribble)
- 第一阶段与踢球一样:跑位、对准球、对准目标方向。
- 处于对齐理想路径后,机器人仅仅给定一个恒定的直行前向速度 (
MIN_LINEAR_SPEED * 3),笔直地把球沿目标方向推走。
case 4 (原地转向 / Turn)
- 给定最大/最小角速度范围内的一个按比例衰减角速度系数,让机器人原地旋转到目标航向。完成后变为
case 0状态。
- 给定最大/最小角速度范围内的一个按比例衰减角速度系数,让机器人原地旋转到目标航向。完成后变为
case 5 (门将扑救 / Goalie)
- 先将移动速度设为零(守门员不乱跑)。
- 评估传入的
ball_velocity,如果球处于几乎静止状态,不进行扑救。 - 否则利用当前球场的相对Y坐标 (
last_y = parameters.stp.ball_field[1]),当球靠近左侧时模拟按下joy_msg.a = 1.0(触发左扑动作),当球靠近右侧时按下joy_msg.b = 1.0(触发右扑动作)。
4. 发布结果并休眠循环 (Line 816 - 827)
赋值计算好的结果至 velocity_msg 并向小车底层控制发布 cmd_vel;同时也向后发布可能存在按键操作的 joy_msg。进入下一轮判断周期。
再看调用函数
1. double distanceToBallTargetLine () (Line 106 - 162)
计算机器人到“球-目标点”直线的垂直距离。 A点(球),B点(目标点),P点(机器人),分别调用全局坐标系下的坐标,计算出向量AB(球到目标点)和AP(球到机器人),利用叉乘公式|AB x AP| / |AB|计算出点P到AB的距离,即机器人到“球-目标点”直线的垂直距离。
2. void VisionCallBack(const dmsgs::VisionInfo::ConstPtr &msg) (Line 172 - 181)
视觉信息回调
- 更新机器人全局位姿:其中除以100是单位换算,下面同理,不再赘述
- 计算机器人的朝向误差:通过机器人和目标点在全局坐标系下的坐标计算得出。
- 根据视觉信息更新布尔值
see_ball - 更新球的全局/局部坐标、速度
- 更新球相对机器人的距离/角度
- 重置踢球/盘球标志:若球在机器人坐标系下的距离和角度满足条件,则机器人不进入踢球准备阶段和盘球阶段。
3. void ActionCallBack (Line 172 - 181)
动作指令回调(与上一个函数类似)
- 更新全局坐标系下目标位姿
- 更新步态类型:读取
gait_type的信息 - 重新计算朝向误差
4. void generate_velocity(const std::vector& current_pose, const std::vector& target_pose) (Line 200 - 286)
速度规划函数
- 参数验证并提取:确保位姿信息包含x, y, yaw三个元素。
- 参数设置:源代码中注释十分清楚
- 计算全局坐标系中的误差向量并转换到局部坐标系下
- 计算X和Y方向速度及角速度:需要注意的是,这里面的速度为比例增益
Kp和在X/Y方向上的机器人坐标系重点局部误差e_x/y_local乘积,同理角速度为YAW比例增益Kp_YAW和角度局部误差angle_error之积定义。 - 更新速度命令
整体数据流:
graph TD
A[target_pose] --> C((作差))
B[current_pose] --> C
C --> D[全局坐标系误差
e_x_global, e_y_global] D -->|旋转矩阵投影
当前yaw角| E[局部坐标系误差
e_x_local, e_y_local] E --> F[P控制 + 死区判断 + 限幅] F --> G["vel_cmd = [Vx, Vy, Wz]"]
e_x_global, e_y_global] D -->|旋转矩阵投影
当前yaw角| E[局部坐标系误差
e_x_local, e_y_local] E --> F[P控制 + 死区判断 + 限幅] F --> G["vel_cmd = [Vx, Vy, Wz]"]
5. initJoyMsg (Line 288 - 316)
遥控器消息初始化
joy_msg.lt = 1.0 为什么这么定义似乎是手册上面写的?
6. double kick_cost(int kick_type) (Line 329 - 357)
踢球代价计算,在主函数中调用决定踢球动作
- 先将X,Y,YAW三个方向的代价权重归一化
- 提取该踢球类型的期望参数并计算误差
- 计算代价
7. int decide_kick_type() (Line 359 - 379)
最优踢球类型选择
- 若当前踢球类型代价足够小,不切换
- 遍历所有踢球类型,找代价最小的
- 若最优类型与当前类型代价差距不大,保留当前类型