Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions Drag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#!/usr/bin/env python
import rospy
from geometry_msgs.msg import WrenchStamped
from nav_msgs.msg import Odometry
# Initialized ros parameters and global values (feel free to tweak, values were empirically gained through trial/error)
rospy.set_param('LINEAR_FORCE', 10)
rospy.set_param("TORQUE", 5)
rospy.set_param('ForceDown', -20)
rospy.set_param('TimeOfForceDown', 10)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting them here, just get them as you do below, with defaults. This code would prevent them from ever being changed, as they're just getting reset when the program runs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may also be wise to consider moving this to a config down the line, and ensuring sensible values are set before starting anything.

Linear_Drag_X = 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider naming things in a consistent way

Variables: snake_case
Functions: snake_case
Classes: CamelCase

Linear_Drag_Y = 0
Linear_Drag_Z = 0
Roll_Drag = 0
Pitch_Drag = 0
Yaw_Drag = 0
Velocity = 0
'''
Velocities between two time intervals will never be truly 'equal' in real world
Must compare the abs of the difference of the two with a small delta value
if the difference is less than this value we can conclude that the numbers are satisfyingly close
also used as a value that is satisfyingly close to zero. Hard-coded (can be changed).
'''
delta = .00001
# Max velocity cannot be initialized to 0 or there will be an initial divide-by-zero error
Max_Velocity = .1
Bouyancy = 0
# Magnitude of applied force for determining drag coeffcients in linear axes
appliedLinearForce = rospy.get_param("LINEAR_FORCE", default=10)

@rleyva rleyva Apr 27, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd caution against setting defaults that would cause the sub to do something when no config/values are set (in this case I'm assuming there would be some force/torque/etc. applied for some amount of time?)

# Magnitude of applied torque for determining drag coeffcients in rotational axes
appliedTorque = rospy.get_param("TORQUE", default=5)
appliedForceDown = rospy.get_param('ForceDown', default=-20)
TimeOfAplliedForceDown = rospy.get_param('TimeOfForceDown')


def Find_Bouyancy(choice): # Initial function used to upward force of buoyancy (used in determining drag in Z axis)
global Bouyancy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not great practice to make all your variables global, this would definitely be much more readable using a class

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Down_Force = -.5 # Applies initial downward force in z axis
pub = rospy.Publisher('/wrench', WrenchStamped, queue_size=30)
rospy.init_node('move_sub', anonymous=False)
force_msg = WrenchStamped()
force_msg.wrench.force.z = appliedForceDown
pub.publish(force_msg) # publish wrench with force
rospy.sleep(TimeOfAplliedForceDown) # node sleeps for some amount of time before continuing
force_msg.wrench.force.z = 0 # Applies 0 force which stops downward force
pub.publish(force_msg)
while not (rospy.is_shutdown()):
rospy.Subscriber("/odom", Odometry, get_velocity, choice)
rospy.sleep(1)
if Velocity > 0.0:
while not (rospy.is_shutdown()):
force_msg.wrench.force.z = Down_Force
pub.publish(force_msg)
Down_Force = Down_Force - .001
rospy.sleep(.01)
if Velocity < 0.0:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember that the sub's velocity is estimated from sensors, so it will be noisy. Even if the sub is perfectly still, you'll observe a velocity bouncing around zero.

One might expect this to be compounded by the fact bobbing of the sub in the water

break
break
Bouyancy = abs(Down_Force)


def get_velocity(data, choice): # Function sets velocity in a certain axis depending on char input
global Velocity
# linear
if choice == 'x':
Velocity = data.twist.twist.linear.x
elif choice == 'y':
Velocity = data.twist.twist.linear.y
elif choice == 'z':
Velocity = data.twist.twist.linear.z
# rotational
elif choice == 'rl':
Velocity = data.twist.twist.angular.x
elif choice == 'p':
Velocity = data.twist.twist.angular.y
elif choice == 'yw':
Velocity = data.twist.twist.angular.z


def Calculate_Drag(choice):
'''
Calculates drag based on the initial applied force and the approximate max velocity
the sub achieves in that axis. See for formula:
Axis determined by char argument.
'''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate your use of docstrings. It also really nice to talk about what the functions parameters are and what, if anything the function returns.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it seems like the formula is missing

global Linear_Drag_X, Linear_Drag_Y, Linear_Drag_Z, Pitch_Drag, Roll_Drag, Yaw_Drag, Max_Velocity, Bouyancy
if (choice == 'x'):
Linear_Drag_X = (appliedLinearForce / abs(Max_Velocity))
elif (choice == 'y'):
Linear_Drag_Y = (appliedLinearForce / abs(Max_Velocity))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a from __future__ import division at the top of the file. This can lead to some annoying bugs in the future if an int is passed into this function. If you still want to keep the int dividing behavior in other parts of the file, cast one of the values to a float before dividing.

elif (choice == 'z'):
# Buoyancy affects z axis and must be subtracted from applied for before division.
Linear_Drag_Z = ((appliedLinearForce - Bouyancy) / (abs(Max_Velocity)))
elif (choice == 'rl'):
Roll_Drag = (appliedTorque / abs(Max_Velocity))
elif (choice == 'p'):
Pitch_Drag = (appliedTorque / abs(Max_Velocity))
elif (choice == 'yw'):
Yaw_Drag = (appliedTorque / abs(Max_Velocity))


def Apply_Force(choice):
'''
Function applies force in a given axis and allows sub to achieve
terminal (linear/rotationl) velocity in that direction. Once that
max velocity is found, it is used in the Calculate_Drag() function.
'''
global Max_Velocity, Bouyancy, Linear_Drag_Z
pub = rospy.Publisher('/wrench', WrenchStamped, queue_size=20) # Publisher for applying wrench (force/torque)
rospy.init_node('move_sub', anonymous=False)
force_msg = WrenchStamped()
# Char argument determines axis of applied force/torque
if(choice == 'x'):
force_msg.wrench.force.x = appliedLinearForce
elif(choice == 'y'):
force_msg.wrench.force.y = appliedLinearForce
elif(choice == 'z'):
force_msg.wrench.force.z = -appliedLinearForce
elif(choice == 'yw'):
force_msg.wrench.torque.z = appliedTorque
elif(choice == 'rl'):
force_msg.wrench.torque.x = appliedTorque
elif(choice == 'p'):
force_msg.wrench.torque.y = appliedTorque

pub.publish(force_msg)

while not (rospy.is_shutdown()):
'''
On each iteration of while loop: velocity is gained at two points in time (deltat = 2 seconds)
the velocity is compared by taking the absolute value of the difference of the two velocities
if the abs of the difference of the two velocities is smaller than small delta value,
the two velocities are assumed to be approximately equal. If two velocities taken at different
time intervals are equal, the velocity is assumed to be maximized, reaching terminal velocity.
'''
rospy.Subscriber("/odom", Odometry, get_velocity, choice)
velocity1 = Velocity
rospy.sleep(2)
velocity2 = Velocity
Compare_Velocities = abs(velocity1 - velocity2)
if Compare_Velocities < delta:
Max_Velocity = velocity2
Calculate_Drag(choice) # Once velocity is max, calculate drag using max velocity
break # When the velocities are 'equal', the loop breaks.

# Once drag is calculated, apply wrench with force/torque of zero to slow sub in that axis
if(choice == 'x'):
force_msg.wrench.force.x = 0
elif(choice == 'y'):
force_msg.wrench.force.y = 0
elif(choice == 'z'):
force_msg.wrench.force.z = 0
elif(choice == 'yw'):
force_msg.wrench.torque.z = 0
elif(choice == 'rl'):
force_msg.wrench.torque.x = 0
elif(choice == 'p'):
force_msg.wrench.torque.y = 0

pub.publish(force_msg)

while(Velocity > delta and not rospy.is_shutdown()):
# While loop stops program from proceeding until the sub has basically stopped movement in that direction
rospy.sleep(2)
continue


if __name__ == '__main__':
try:
# Buoyancy is calculated
Find_Bouyancy('z')
# Drag Coeffcients are calculated in each axis
Apply_Force('z')
Apply_Force('y')
Apply_Force('x')
Apply_Force('yw')
Apply_Force('rl')
Apply_Force('p')
# Drag coefficients are written to file, do with them what you wish.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed there is no user feedback for this program. It would be kinda scary/confusing to watch the robot do this with no idea why it's moving or what it's thinking. Encourage you to use rosypy.loginfo upon making these transitions of state. Also recommend printing out to the user the solution in addition to writing it to a file

file_object = open("DragCoefficients", 'w')
file_object.write("Drag Coefficients:")
file_object.write("\nLinear X: " + str(Linear_Drag_X))
file_object.write("\nLinear Y: " + str(Linear_Drag_Y))
file_object.write("\nLinear Z: " + str(Linear_Drag_Z))
file_object.write("\nRoll: " + str(Roll_Drag))
file_object.write("\nPitch: " + str(Pitch_Drag))
file_object.write("\nYaw: " + str(Yaw_Drag))
file_object.close()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be more helpful if this was written to a file in a way that a computer could read, such as yaml. Luckily, this is super easy in python!

import yaml
data = {'x': 500, 'y': 500, ...}
f = open('drag_coefficients.yaml', 'w')
yaml.dump(data, f)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto'ing Kevin's comment, definitely consider dumping the data into a format that's computer readable, a yaml like what Kevin mentions would be great. It would make it much easier to dump things into Python to extract data, graph, etc.

except rospy.ROSInterruptException:
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing on exceptions is a major red flag. The user would not be notified of why the program closes or potentially unpredictable behavior of the robot that an exception could cause. You should always atleast

except rospy.ROSInterruptException as e:
   rospy.logerr(e)