编写攻击模块

Note

该文档主要描述了Bubble内部的攻击模块的实现。

Goal: 通过Bubble控制机器人的攻击模式

先决条件

您具备了ROS的基本知识,掌握了如何编写一个 节点

1 编写代码

通过 bubble_aiming 模块定义的API,实现机器人的自瞄任务。下面的例子以自瞄装甲板模块为例。

1.1 编写数据解析器

数据解析器用于将msg数据转换为对应模块所需的数据结构。

编写数据解析器需首先创建订阅者订阅节点数据,通过create_subscription创建订阅者用于订阅需要的数据。 此处订阅主题为 /cv/armour ,消息的类型为自定义BoundingPolygonBoxes2D消息。具体如何编写消息请参考 添加自定义数据控制机器人

self.armour_sub = self.create_subscription(BoundingPolygonBoxes2D, '/cv/armour', self.armour_callback, 1)

自定义BoundingPolygonBoxes2D消息内包含:

std_msgs/Header header
std_msgs/Header image_header
BoundingPolygonBox2D[] bounding_boxes

其中的BoundingPolygonBox2D消息内包含:

string class_id
geometry_msgs/Polygon pose
uint16 img_width
uint16 img_height
int32 center_dist
string type
string id

在aimingNode.py中添加parse函数来对msg进行解析。此处可以参考parse_armour函数,仅对装甲板所需的数据image_header、pose和type进行解析。 ArmourInfo() 为自定义数据结构,用于存放和处理装甲板信息。具体如何编写自定义数据结构请参考 1.6节自定义数据结构TODO。

def parse_armour(self, data: BoundingPolygonBoxes2D):
     armour_list = []
     strip_list = []

     target_time_stamp = data.image_header.stamp.sec + \
         data.image_header.stamp.nanosec * 1e-9
     for target in data.bounding_boxes:
         if target.class_id == "Armour":
             armour = ArmourInfo()
             armour.set_data(target_time_stamp, target.pose.points, target.type)
             armour_list.append(armour)

         elif target.class_id == "Strip":
             pass
     return armour_list, strip_list

用于对时间戳进行解析,将sec和nanosec合并成浮点数类型的时间戳。

target_time_stamp = data.image_header.stamp.sec + \
   data.image_header.stamp.nanosec * 1e-9

1.2 目标决策器

目标决策器用于对多目标进行决策,可添加不同的决策器来满足不同的战术需求。目标决策器的需保证此刻仅有一个结果。

编写决策器首先需分析战术需求,例如此处为保证快速锁敌优先瞄准更加靠近图像中心的目标。 在明确战术需求后, 开始进行逻辑块的编写。逻辑块代码位于decision.py文件下,根据分析的需求进行compareTargetCenter的编写。

def compareTargetCenter(target_center_list: list, img_size: tuple) -> int:
   img_width, img_height = img_size
   rect_center_array = np.array(target_center_list)
   x_gap = abs(rect_center_array[:, 0]-(img_width/2))
   y_gap = abs(rect_center_array[:, 1]-(img_height/2))
   tar_index = x_gap.argmin()
   return tar_index

最后将逻辑块进行组合实现不同的自瞄逻辑。在aimingProcess.py文件中的decideMulTar函数中编写各个逻辑块之间的关系。

def decideMulTar(self, armour_list: list) -> ArmourInfo:
   rect_center_list = [armour.get_rect_rotation()[0] for armour in armour_list]
   target_index = compareTargetCenter(rect_center_list, self.image_size)
   return armour_list[target_index]

1.3 模式判别器

模式判别器用于判断机器人自身攻击模式,以满足场上出现的各类任务。模式切换有两种方法,手动切换模式和自动识别模式,此处的模式判别指的是自动识别模式这种方法。

编写模式判别器首先分析识别该模式的逻辑。例如小陀螺识别会对多帧数据计算装甲板高度差和宽度差,来判断是否进入小陀螺模式。 根据确定的逻辑在armourDecision.py文件内编写judgeMode函数。 最后对逻辑进行组合形成完整的模式判别器

def updateMode(self) -> str:
   self.alter_armour = False
   mode = self.armourDecision.judgeMode(self.origin_armour_list)
   self.alter_armour = self.armourDecision.judgeArmourAlteration()
   return mode

1.4 对应功能模块

预测 ArmourPredictor 补偿 adjustBallistics

1.5 数据发送器

数据发送器用于将模块内的数据结构转换为msg数据。

编写数据发送器需首先创建发布者发布节点数据,通过create_publisher创建订阅者创建发布者,发布对应的主题。 此处订阅主题为 /decision/gimbal_api ,消息的类型为自定义Gimbal消息。具体如何编写消息请参考 添加自定义数据控制机器人

self.gimbal_pub = self.create_publisher(Gimbal, '/decision/gimbal_api', 10)
自定义Gimbal消息内包含:
std_msgs/Header header
int8 mode
float64 yaw
float64 pitch
float64 roll

在aimingNode.py中添加pub函数来生成msg消息。此处可以参考pub_gimbal_data函数,实例化Gimbal数据,然后对Gimbal的属性进行赋值。

def pub_gimbal_data(self, armour):
   gimbal_msg = Gimbal()
   gimbal_msg.mode = 1
   gimbal_msg.header.stamp = self.get_clock().now().to_msg()
   gimbal_msg.yaw = float(armour.yaw_angle)
   gimbal_msg.pitch = float(armour.pitch_angle)
   gimbal_msg.roll = float(armour.roll_angle)
   self.gimbal_pub.publish(gimbal_msg)

1.6 自定义数据结构

为了便于对数据进行管理,将所需的数据统一放进类中。需要使用该数据时,通过引用对象中的属性即可。

首先分析所需数据是否存在相关性,若数据具有相关性可以将这些数据封装到类中。

对类中包含的属性建立接口,用于对类属性的值进行设定与获取。

class ArmourInfo(TargetInfo):
   def __init__(self) -> None:
      super().__init__("Armour")
      self.stamp = None
      self.box_points = None
      self.bbox_rect_rotation = None
      self.target_type = None
      self.pose = {"x": 0, "y": 0, "z": 0}

   def __str__(self) -> str:
      return str(["pose", self.pose])

   def get_rotation_rpy(self):
      return [self.pose['x'], self.pose['y'], self.pose['z']]

   def set_position(self, rvec, tvec):
      self.pose['x'] = tvec[0]
      self.pose['y'] = tvec[1]
      self.pose['z'] = tvec[2]

设计接口:分别设计’set’设定数据接口和’get’获取数据接口。在set接口对数据进行处理,以生成类属性需要的数据。

def get_rotation_rpy(self):
   return [self.pose['x'], self.pose['y'], self.pose['z']]

def set_position(self, rvec, tvec):
   self.pose['x'] = tvec[0]
   self.pose['y'] = tvec[1]
   self.pose['z'] = tvec[2]

2 编译并运行代码

打开一个终端编译并运行当前节点

colcon build --packages-select YOUR_PKG
. install/setup.bash
ros2 run YOUR_PKG YOUR_EXEC