Source code for general_formation
#!/usr/bin/env python
"""Abstract Class that represents a general formation.
Cylindrical coordinates are used to specify the position of each agent in the formation.Center
of formation is defined as (0, 0, 0)
"""
from math import sin, cos, pi, sqrt, atan
import rospy
from geometry_msgs.msg import Quaternion
from tf.transformations import quaternion_from_euler, quaternion_multiply, euler_from_quaternion
[docs]class FormationClass(object):
"""Basic formation type
Args:
min_dist (:obj:`float`): Minimum distance between agents in formation
"""
def __init__(self, min_dist):
self._n_agents = 0 #: (int) Number of CF in the formation
self._n_agents_landed = 0 #(int) Number of CF landed, not part of current formation
self._min_dist = min_dist
self._agents_goals = {} #: (dict of Position) Target Pose of all the CF
self._center_dist = {} #: (dict of float) Keys: swarm id, Item: Distance from center
self._angle = {} #: (dict of float) Keys: swarm id, Item: Angle(rad) from x axis
self._extra_agents_id = [] #: list of int: Id of landed agents
#: (dict of float) Keys: swarm id, Item: Height from center (<0 -> below swarm center)
self._center_height = {}
self._scale = 0.0 #: (float) scale of the formation
self._min_scale = 0.0
self._max_scale = 5.0
self._min_height = 0.5 #: float: Minimum height of formation goal
# General methods, valid between formations
[docs] def get_agents_goals(self):
"""Return goal of each agent in formation
Returns:
dict of list: Keys: agents id, Items: goal [x, y, z]
"""
goals = {}
for agent_id, agent_goal in self._agents_goals.items():
goals[agent_id] = [agent_goal.x, agent_goal.y, agent_goal.z]
return goals
[docs] def set_scale(self, new_scale):
"""Set scale of the formation
Args:
new_scale (float): New formation scale
"""
self._scale = new_scale
self._scale = self._min_scale if self._scale < self._min_scale else self._scale
self._scale = self._max_scale if self._scale > self._max_scale else self._scale
self.update_formation_scale()
rospy.loginfo("Formation: Formation scale: %.2f" % self._scale)
[docs] def update_agents_positions(self, formation_goal, crazyflies=None):
"""Compute goal of each agent and updates corresponding CF goal.
Agent goal is calculated based on distance and angle of agent id from formation center.
If crazyflies is not None, will update their goal.
Args:
formation_goal (Position): Goal of the formation
crazyflies (dict, optional): Information of each Crazyflie
"""
# Compute position of all CF
for swarm_id in range(self._n_agents):
if rospy.is_shutdown():
break
yaw = formation_goal.yaw
# Could fail if swarm position is being calculated
try:
theta = self._angle[swarm_id] + yaw
except KeyError:
break
x_dist = cos(theta) * self._center_dist[swarm_id]
y_dist = sin(theta) * self._center_dist[swarm_id]
z_dist = self._center_height[swarm_id]
self._agents_goals[swarm_id].x = formation_goal.x + x_dist
self._agents_goals[swarm_id].y = formation_goal.y + y_dist
self._agents_goals[swarm_id].z = formation_goal.z + z_dist
self._agents_goals[swarm_id].yaw = yaw
# Update all CF formation goal based on swarm ID
if crazyflies is not None:
for _, cf_attrs in crazyflies.items():
if rospy.is_shutdown():
break
cf_id = cf_attrs["swarm_id"]
try:
cf_attrs["formation_goal"].x = self._agents_goals[cf_id].x
cf_attrs["formation_goal"].y = self._agents_goals[cf_id].y
cf_attrs["formation_goal"].z = self._agents_goals[cf_id].z
cf_attrs["formation_goal"].yaw = self._agents_goals[cf_id].yaw
except KeyError:
# Pass, keys arn't initialized yet because of new formation
pass
[docs] def find_extra_agents(self):
"""Find id of agents in extra.
Extra agents can happen when there are too many agents for a formation.
i.e: Making a square with 10 agents will result in one extra agent.
"""
self._extra_agents_id = []
for agent_id in range(self._n_agents, self._n_agents + self._n_agents_landed + 1):
self._extra_agents_id.append(agent_id)
# Methods depending on formation
[docs] def set_n_agents(self, n_agents):
"""Make sure there number of CF is supported by formation and sets it
Args:
n (int): Number of CF
"""
if n_agents > 0:
self._n_agents = n_agents
rospy.loginfo("Formation made of %i crazyflies" % self._n_agents)
else:
self._n_agents = 0
rospy.logerr("Unsuported number of CFs")
[docs] def compute_min_scale(self):
"""Find minimum scale to make sure distance between agents is greater than min_dist
"""
pass
[docs] def compute_formation_positions(self):
"""Compute position of each agent from formation center
Position are defined by radius from center (x, y plane), height from center and radius angle
"""
pass
[docs] def update_formation_scale(self):
"""Compute new formation information after the scale is changed.
i.e: Distance/angle between agents
Unique to each formation
"""
pass
[docs]def compute_info_from_center(agent_position):
"""Calculate distance and angle from formation center
Formation center is considered to be at 0, 0, 0
Args:
cf_position (list of float): Position from [0 , 0 ,0][x, y, z]
Returns:
list of float: [distance from center, angle from center, height from center]
"""
x_dist = agent_position[0]
y_dist = agent_position[1]
z_dist = agent_position[2]
center_dist = sqrt(x_dist**2 + y_dist**2)
if x_dist != 0:
theta = atan(y_dist/x_dist)
if x_dist < 0 and y_dist < 0:
theta = theta - pi
elif x_dist < 0:
theta = theta + pi
else:
if y_dist > 0:
theta = pi/2
else:
theta = -pi/2
return center_dist, theta, z_dist
[docs]def calculate_rot(start_orientation, rot):
"""Apply rotation to a quaternion
Args:
start_orientation (Quaternion): Initial orientation
rot (Vector3): Angular speed to apply
Returns:
Quaternion: Result
"""
rot_q = quaternion_from_euler(rot.x, rot.y, rot.z)
orig_q = [start_orientation.x,
start_orientation.y,
start_orientation.z,
start_orientation.w]
res_q = quaternion_multiply(rot_q, orig_q)
res_msg = Quaternion()
res_msg.x = res_q[0]
res_msg.y = res_q[1]
res_msg.z = res_q[2]
res_msg.w = res_q[3]
return res_msg
[docs]def yaw_from_quat(quaternion):
"""Returns yaw from a quaternion
Args:
quaternion (Quaternion)
Returns:
float: Yaw
"""
_, _, yaw = euler_from_quaternion([quaternion.x,
quaternion.y,
quaternion.z,
quaternion.w])
return yaw
[docs]def quat_from_yaw(yaw):
"""Compute a quaternion from yaw
Pitch and roll are considered zero
Args:
yaw (float): Yaw
Returns:
Quaternion
"""
x_quat, y_quat, z_quat, w_quat = quaternion_from_euler(0, 0, yaw)
return Quaternion(x_quat, y_quat, z_quat, w_quat)