PageRenderTime 33ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 1ms

/udom_control/udom_shape_control/src/udom_shape_control/arandas_controller_node.py

https://gitlab.com/jsanch/udom
Python | 314 lines | 198 code | 15 blank | 101 comment | 2 complexity | 121270ab9d57091ffadca082a2729332 MD5 | raw file
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. """
  4. This node computes the poses for a set of robots/points to move from a current
  5. configuration to a target configuration.
  6. **Input(s):**
  7. * `event_in`: The desired event for the node:
  8. `e_start`: starts the component.
  9. `e_stop`: stops the component.
  10. - *type:* `std_msgs/String`
  11. * `target_poses`: Target poses for the controller to reach.
  12. - *type:* `geometry_msgs/PoseArray`
  13. * `current_poses`: Current poses of the robots/points.
  14. - *type:* `geometry_msgs/PoseArray`
  15. **Output(s):**
  16. * `poses_out`: The commanded poses for the robots/points.
  17. - *type:* `geometry_msgs/PoseArray`
  18. * `event_out`: The current event of the node.
  19. `e_running`: when the component is running.
  20. `e_stopped`: when the component is stopped.
  21. - *type:* `std_msgs/String`
  22. **Parameter(s):**
  23. * `loop_rate`: Node cycle rate (in Hz).
  24. * `number_of_robots`: Number of robots to control.
  25. * `gain`: Controller gain.
  26. * `scale`:Desired scale.
  27. * `weights`: Controller weights for each term.
  28. * `saturation`: Saturation threshold for the controller.
  29. """
  30. import numpy as np
  31. import rospy
  32. import std_msgs.msg
  33. import geometry_msgs.msg
  34. import udom_shape_control.arandas_controller_utils as utils
  35. from dynamic_reconfigure.server import Server
  36. from udom_shape_control.cfg import ArandasControllerConfig as ArandasController
  37. class ArandasControllerNode(object):
  38. """
  39. Computes the poses for a set of robots/points to move from a current configuration
  40. to a target configuration.
  41. """
  42. def __init__(self):
  43. """
  44. Instantiates a Aranda's controller node.
  45. :return: A node to move a set of robots/points to a desired configuration.
  46. :rtype: ArandasControllerNode
  47. """
  48. # Params
  49. self.event = None
  50. self.target = None
  51. self.current = None
  52. self.centroid = None
  53. # Number of robots to control.
  54. self.number_of_robots = rospy.get_param('~number_of_robots', 5)
  55. # Controller gain.
  56. self.gain = rospy.get_param('~gain', 1e-2)
  57. # Desired scale.
  58. self.scale = rospy.get_param('~scale', 1.0)
  59. # Saturation threshold for the controller.
  60. self.saturation = rospy.get_param('~saturation', 0.03)
  61. # Controller weights for each term.
  62. self.weights = rospy.get_param('~weights', [0.1, 0.1, 0.1, 0.1, 0.1])
  63. assert len(self.weights) == 5, "weights must be of dimension 5."
  64. # Node cycle rate (in Hz).
  65. rate = rospy.get_param('~loop_rate', 100)
  66. self.loop_rate = rospy.Rate(rate)
  67. # Create controller
  68. self.controller = utils.ArandasController(
  69. self.number_of_robots, self.gain, self.weights, self.scale)
  70. # Publishers
  71. self.event_out = rospy.Publisher("~event_out", std_msgs.msg.String, queue_size=10)
  72. self.command = rospy.Publisher(
  73. "~command", geometry_msgs.msg.PoseArray, queue_size=1, tcp_nodelay=True)
  74. self.target_pub = rospy.Publisher(
  75. "~target_logger", geometry_msgs.msg.PoseArray, queue_size=1, tcp_nodelay=True)
  76. self.current_pub = rospy.Publisher(
  77. "~current_logger", geometry_msgs.msg.PoseArray, queue_size=1, tcp_nodelay=True)
  78. self.destination = rospy.Publisher(
  79. "~destination", geometry_msgs.msg.PoseArray, queue_size=1, tcp_nodelay=True)
  80. self.destination_g = rospy.Publisher(
  81. "~destination_g", geometry_msgs.msg.PoseArray, queue_size=1, tcp_nodelay=True)
  82. # Subscribers
  83. rospy.Subscriber("~event_in", std_msgs.msg.String, self.event_in_cb)
  84. rospy.Subscriber('~target', geometry_msgs.msg.PoseArray, self.target_cb)
  85. rospy.Subscriber('~current', geometry_msgs.msg.PoseArray, self.current_cb)
  86. rospy.Subscriber('~centroid', geometry_msgs.msg.PoseStamped, self.centroid_cb)
  87. # Dynamic Server Reconfiguration
  88. dynamic_reconfig_srv = Server(ArandasController, self.dynamic_reconfig_cb)
  89. def dynamic_reconfig_cb(self, config, level):
  90. """
  91. Reconfigures the parameters of the Aranda's controller.
  92. """
  93. self.gain = config.gain
  94. self.scale = config.scale
  95. self.saturation = config.saturation
  96. self.weights[0] = config.similarity_weight
  97. self.weights[1] = config.affine_weight
  98. self.weights[2] = config.centroid_weight
  99. self.weights[3] = config.scale_weight
  100. self.weights[4] = config.orientation_size_weight
  101. return config
  102. def event_in_cb(self, msg):
  103. """
  104. Obtains the event for the node (e.g. start, stop).
  105. :param msg: Event message for the node.
  106. :type msg: std_msgs.msg.String
  107. """
  108. self.event = msg.data
  109. def target_cb(self, msg):
  110. """
  111. Obtains the target configuration.
  112. :param msg: Target configuration.
  113. :type msg: geometry_msgs.msg.PoseArray
  114. """
  115. self.target = msg
  116. def current_cb(self, msg):
  117. """
  118. Obtains the current configuration.
  119. :param msg: Current configuration.
  120. :type msg: geometry_msgs.msg.PoseArray
  121. """
  122. self.current = msg
  123. def centroid_cb(self, msg):
  124. """
  125. Obtains the desired centroid.
  126. :param msg: Desired centroid.
  127. :type msg: geometry_msgs.msg.PoseStamped
  128. """
  129. self.centroid = msg
  130. def start(self):
  131. """
  132. Starts the node.
  133. """
  134. rospy.loginfo("Ready to start...")
  135. state = 'INIT'
  136. while not rospy.is_shutdown():
  137. if state == 'INIT':
  138. state = self.init_state()
  139. elif state == 'IDLE':
  140. state = self.idle_state()
  141. elif state == 'RUNNING':
  142. state = self.running_state()
  143. rospy.logdebug("State: {0}".format(state))
  144. self.loop_rate.sleep()
  145. def init_state(self):
  146. """
  147. Executes the INIT state of the state machine.
  148. :return: The updated state.
  149. :rtype: str
  150. """
  151. if self.event == 'e_start':
  152. return 'IDLE'
  153. else:
  154. return 'INIT'
  155. def idle_state(self):
  156. """
  157. Executes the IDLE state of the state machine.
  158. :return: The updated state.
  159. :rtype: str
  160. """
  161. if self.event == 'e_stop':
  162. self.event_out.publish('e_stopped')
  163. self.reset_component_data()
  164. return 'INIT'
  165. elif (self.target is not None) and (self.current is not None):
  166. return 'RUNNING'
  167. else:
  168. return 'IDLE'
  169. def running_state(self):
  170. """
  171. Executes the RUNNING state of the state machine.
  172. :return: The updated state.
  173. :rtype: str
  174. """
  175. if self.event == 'e_stop':
  176. self.event_out.publish('e_stopped')
  177. self.reset_component_data()
  178. return 'INIT'
  179. else:
  180. command = self.compute_command()
  181. self.event_out.publish('e_running')
  182. self.command.publish(command)
  183. self.target_pub.publish(self.target)
  184. self.current_pub.publish(self.current)
  185. self.reset_component_data()
  186. return 'IDLE'
  187. def compute_command(self):
  188. """
  189. Computes the required command to move towards a target configuration.
  190. :return: Command to move towards a target configuration.
  191. :rtype: geometry_msgs.msg.PoseArray
  192. """
  193. current = utils.pose_array_to_matrix(self.current)
  194. target = utils.pose_array_to_matrix(self.target)
  195. # Update controller parameters.
  196. self.controller.gain = self.gain
  197. self.controller.scale = self.scale
  198. self.controller.weights = self.weights
  199. self.controller.saturation = self.saturation
  200. if self.centroid is not None:
  201. centroid = np.array([
  202. self.centroid.pose.position.x, self.centroid.pose.position.y,
  203. self.centroid.pose.position.z])
  204. command_matrix = self.controller.compute_command(current, target, centroid)
  205. else:
  206. command_matrix = self.controller.compute_command(current, target)
  207. command = utils.matrix_to_pose_array(command_matrix)
  208. command.header.frame_id = self.current.header.frame_id
  209. command.header.stamp = rospy.Time.now()
  210. destination_matrix = self.controller.compute_destination(current, target)
  211. destination = utils.matrix_to_pose_array(destination_matrix)
  212. destination.header.frame_id = self.current.header.frame_id
  213. destination.header.stamp = rospy.Time.now()
  214. self.destination.publish(destination)
  215. destination_matrix_g = self.controller.compute_destination_g(current, target)
  216. destination_g = utils.matrix_to_pose_array(destination_matrix_g)
  217. destination_g.header.frame_id = self.current.header.frame_id
  218. destination_g.header.stamp = rospy.Time.now()
  219. self.destination_g.publish(destination_g)
  220. return command
  221. def reset_component_data(self):
  222. """
  223. Clears the data of the component.
  224. """
  225. self.event = None
  226. self.current = None
  227. def main():
  228. rospy.init_node("arandas_controller_node", anonymous=True)
  229. arandas_controller_node = ArandasControllerNode()
  230. arandas_controller_node.start()