thruster_solver.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # thruster_solver node
4 #
5 # Input topics:
6 # net_force: The force the control system wants the robot to exert on the world to move
7 #
8 # Output topics:
9 # thruster_forces: Array containing how hard each thruster will push.
10 #
11 # This node works via optimization. A cost function is proposed that measures how optimal a set of thruster forces is.
12 # This function takes into account exerting the correct total forces and power consumption. This node will also ignore
13 # thrusters that are currently out of the water and solve with the thrusters that are in the water. On each new net_force
14 # message, the robot solves for the optimal thruster forces and publishes them
15 
16 import rospy
17 from geometry_msgs.msg import Twist
18 from std_msgs.msg import Float32MultiArray, Int16MultiArray
19 import numpy as np
20 import yaml
21 from tf.transformations import euler_matrix
22 from tf import TransformListener
23 from scipy.optimize import minimize
24 
25 
26 NEUTRAL_PWM = 1500
27 MIN_PWM = 1230
28 MAX_PWM = 1770
29 
30 def msg_to_numpy(msg):
31  return np.array([msg.x, msg.y, msg.z])
32 
34 
35  def __init__(self):
36  rospy.Subscriber("net_force", Twist, self.force_cb, queue_size=1)
37 
38  self.thruster_pub = rospy.Publisher("thruster_forces", Float32MultiArray, queue_size=5)
39  self.pwm_pub = rospy.Publisher("command/pwm", Int16MultiArray, queue_size=5)
40  self.tf_namespace = rospy.get_param("~robot")
41 
42  # Load thruster info
43  config_path = rospy.get_param("~vehicle_config")
44  with open(config_path, 'r') as stream:
45  config_file = yaml.safe_load(stream)
46 
47  thruster_info = config_file['thrusters']
48  self.thruster_coeffs = np.zeros((len(thruster_info), 6))
49  self.thruster_types = np.zeros(len(thruster_info))
50  com = np.array(config_file["com"])
51  self.max_force = config_file["thruster"]["max_force"]
52  self.pwm_file = config_file["thruster"]
53 
54  for i, thruster in enumerate(thruster_info):
55  pose = np.array(thruster["pose"])
56  rot_mat = euler_matrix(*pose[3:])
57  body_force = np.dot(rot_mat, np.array([1, 0, 0, 0]))[:3]
58  body_torque = np.cross(pose[:3]- com, body_force)
59 
60  self.thruster_coeffs[i, :3] = body_force
61  self.thruster_coeffs[i, 3:] = body_torque
62 
63  self.thruster_types[i] = config_file["thrusters"][i]["type"]
64 
65 
66 
68  self.bounds = []
69  for i in range(len(thruster_info)):
70  self.initial_condition.append(0)
71  self.bounds.append((-self.max_force, self.max_force))
72  self.initial_condition = tuple(self.initial_condition)
73  self.bounds = tuple(self.bounds)
74 
75  self.power_priority = 0.001
77 
78  self.start_time = None
79  self.timer = rospy.Timer(rospy.Duration(0.1), self.check_thrusters)
81  self.WATER_LEVEL = 0
82 
83  def publish_pwm(self, forces):
84  pwm_values = []
85 
86  for i in range(self.thruster_coeffs.shape[0]):
87  pwm = NEUTRAL_PWM
88 
89  if (abs(forces[i]) < self.pwm_file["MIN_THRUST"]):
90  pwm = NEUTRAL_PWM
91 
92  elif (forces[i] > 0 and forces[i] <= self.pwm_file["STARTUP_THRUST"]):
93  if self.thruster_types[i] == 0:
94  pwm = (int)(self.pwm_file["SU_THRUST"]["POS_SLOPE"] * forces[i] + self.pwm_file["SU_THRUST"]["POS_YINT"])
95  else:
96  pwm = (int)(-self.pwm_file["SU_THRUST"]["POS_SLOPE"] * forces[i] + self.pwm_file["SU_THRUST"]["NEG_YINT"])
97 
98  elif (forces[i] > 0 and forces[i] > self.pwm_file["STARTUP_THRUST"]):
99  if self.thruster_types[i] == 0:
100  pwm = (int)(self.pwm_file["THRUST"]["POS_SLOPE"] * forces[i] + self.pwm_file["THRUST"]["POS_YINT"])
101  else:
102  pwm = (int)(-self.pwm_file["THRUST"]["POS_SLOPE"] * forces[i] + self.pwm_file["THRUST"]["NEG_YINT"])
103 
104  elif (forces[i] < 0 and forces[i] >= -self.pwm_file["STARTUP_THRUST"]):
105  if self.thruster_types[i] == 0:
106  pwm = (int)(self.pwm_file["SU_THRUST"]["NEG_SLOPE"] * forces[i] + self.pwm_file["SU_THRUST"]["NEG_YINT"])
107  else:
108  pwm = (int)(-self.pwm_file["SU_THRUST"]["NEG_SLOPE"] * forces[i] + self.pwm_file["SU_THRUST"]["POS_YINT"])
109 
110  elif (forces[i] < 0 and forces[i] < -self.pwm_file["STARTUP_THRUST"]):
111  if self.thruster_types[i] == 0:
112  pwm = (int)(self.pwm_file["THRUST"]["NEG_SLOPE"] * forces[i] + self.pwm_file["THRUST"]["NEG_YINT"])
113  else:
114  pwm = (int)(-self.pwm_file["THRUST"]["NEG_SLOPE"] * forces[i] + self.pwm_file["THRUST"]["POS_YINT"])
115 
116  else:
117  pwm = NEUTRAL_PWM
118 
119  pwm_values.append(pwm)
120 
121  msg = Int16MultiArray()
122  msg.data = pwm_values
123  self.pwm_pub.publish(msg)
124 
125 
126  # Timer callback which disables thrusters that are out of the water
127  def check_thrusters(self, timer_event):
128  try:
129  if self.start_time is None:
130  self.start_time = rospy.get_rostime()
131  self.current_thruster_coeffs = np.copy(self.thruster_coeffs)
132  for i in range(self.thruster_coeffs.shape[0]):
133  trans, _ = self.listener.lookupTransform("world", "%s/thruster_%d" % (self.tf_namespace, i), rospy.Time(0))
134  if trans[2] > self.WATER_LEVEL:
135  self.current_thruster_coeffs[i, :] = 0
136  except Exception as ex:
137  # Supress startup errors
138  if (rospy.get_rostime() - self.start_time).secs > 1:
139  rospy.logerr(str(ex))
140 
141  # Cost function forcing the thruster to output desired net force
142  def force_cost(self, thruster_forces, desired_state):
143  residual = np.dot(self.current_thruster_coeffs.T, thruster_forces) - desired_state
144  return np.sum(residual ** 2)
145 
146  def force_cost_jac(self, thruster_forces, desired_state):
147  residual = np.dot(self.current_thruster_coeffs.T, thruster_forces) - desired_state
148  return np.dot(self.current_thruster_coeffs, 2 * residual)
149 
150  # Cost function forcing thrusters to find a solution that is low-power
151  def power_cost(self, thruster_forces):
152  return np.sum(thruster_forces ** 2)
153 
154  def power_cost_jac(self, thruster_forces):
155  return 2 * thruster_forces
156 
157  # Combination of other cost functions
158  def total_cost(self, thruster_forces, desired_state):
159  total_cost = self.force_cost(thruster_forces, desired_state)
160  # We care about low power a whole lot less thus the lower priority
161  total_cost += self.power_cost(thruster_forces) * self.power_priority
162  return total_cost
163 
164  def total_cost_jac(self, thruster_forces, desired_state):
165  total_cost_jac = self.force_cost_jac(thruster_forces, desired_state)
166  total_cost_jac += self.power_cost_jac(thruster_forces) * self.power_priority
167  return total_cost_jac
168 
169  def force_cb(self, msg):
170  desired_state = np.zeros(6)
171  desired_state[:3] = msg_to_numpy(msg.linear)
172  desired_state[3:] = msg_to_numpy(msg.angular)
173 
174  # Optimize cost function to find optimal thruster forces
175  res = minimize(self.total_cost, self.initial_condition, args=(desired_state), method='SLSQP', \
176  jac=self.total_cost_jac, bounds=self.bounds)
177 
178  # Warn if we did not find valid thruster forces
179  if self.force_cost(res.x, desired_state) > 0.05:
180  rospy.logwarn_throttle(1, "Unable to exert requested force")
181 
182  msg = Float32MultiArray()
183  msg.data = res.x
184 
185  self.publish_pwm(res.x)
186 
187  self.thruster_pub.publish(msg)
188 
189 
190 if __name__ == '__main__':
191  rospy.init_node("thruster_solver")
192  controller = ThrusterSolverNode()
193  rospy.spin()
thruster_solver.ThrusterSolverNode.total_cost
def total_cost(self, thruster_forces, desired_state)
Definition: thruster_solver.py:158
thruster_solver.ThrusterSolverNode.force_cost
def force_cost(self, thruster_forces, desired_state)
Definition: thruster_solver.py:142
thruster_solver.ThrusterSolverNode.total_cost_jac
def total_cost_jac(self, thruster_forces, desired_state)
Definition: thruster_solver.py:164
thruster_solver.ThrusterSolverNode.power_cost
def power_cost(self, thruster_forces)
Definition: thruster_solver.py:151
thruster_solver.ThrusterSolverNode.bounds
bounds
Definition: thruster_solver.py:68
thruster_solver.ThrusterSolverNode
Definition: thruster_solver.py:33
thruster_solver.ThrusterSolverNode.initial_condition
initial_condition
Definition: thruster_solver.py:67
thruster_solver.ThrusterSolverNode.current_thruster_coeffs
current_thruster_coeffs
Definition: thruster_solver.py:76
thruster_solver.ThrusterSolverNode.power_cost_jac
def power_cost_jac(self, thruster_forces)
Definition: thruster_solver.py:154
thruster_solver.ThrusterSolverNode.force_cb
def force_cb(self, msg)
Definition: thruster_solver.py:169
thruster_solver.ThrusterSolverNode.thruster_coeffs
thruster_coeffs
Definition: thruster_solver.py:48
thruster_solver.ThrusterSolverNode.force_cost_jac
def force_cost_jac(self, thruster_forces, desired_state)
Definition: thruster_solver.py:146
thruster_solver.ThrusterSolverNode.__init__
def __init__(self)
Definition: thruster_solver.py:35
thruster_solver.msg_to_numpy
def msg_to_numpy(msg)
Definition: thruster_solver.py:30
thruster_solver.ThrusterSolverNode.max_force
max_force
Definition: thruster_solver.py:51
thruster_solver.ThrusterSolverNode.start_time
start_time
Definition: thruster_solver.py:78
thruster_solver.ThrusterSolverNode.pwm_file
pwm_file
Definition: thruster_solver.py:52
thruster_solver.ThrusterSolverNode.thruster_types
thruster_types
Definition: thruster_solver.py:49
thruster_solver.ThrusterSolverNode.publish_pwm
def publish_pwm(self, forces)
Definition: thruster_solver.py:83
thruster_solver.ThrusterSolverNode.pwm_pub
pwm_pub
Definition: thruster_solver.py:39
thruster_solver.ThrusterSolverNode.tf_namespace
tf_namespace
Definition: thruster_solver.py:40
thruster_solver.ThrusterSolverNode.timer
timer
Definition: thruster_solver.py:79
thruster_solver.ThrusterSolverNode.WATER_LEVEL
WATER_LEVEL
Definition: thruster_solver.py:81
tf::TransformListener
thruster_solver.ThrusterSolverNode.power_priority
power_priority
Definition: thruster_solver.py:75
thruster_solver.ThrusterSolverNode.listener
listener
Definition: thruster_solver.py:80
thruster_solver.ThrusterSolverNode.check_thrusters
def check_thrusters(self, timer_event)
Definition: thruster_solver.py:127
thruster_solver.ThrusterSolverNode.thruster_pub
thruster_pub
Definition: thruster_solver.py:38


riptide_controllers
Author(s): Blaine Miller, Mitchell Sayre
autogenerated on Wed Mar 2 2022 00:50:23