Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Kinematics #187

Merged
merged 33 commits into from
Feb 2, 2024
Merged

Improved Kinematics #187

merged 33 commits into from
Feb 2, 2024

Conversation

liamgilligan
Copy link
Contributor

This PR changes the Kinematics node in four major ways:

1. Improved Kinematics methodology

This PR changes the process by which the robot determines how much thrust each motor produces to a mathematically and physically sound method. It does this by encoding the resultant thrust and torque each motor will produce into a matrix and generating its Moore-Penrose (pseudo)inverse. When this inverse matrix is multiplied by the pilot's desired twist vector, we are given a list of thrusts in newtons that each motor should produce in order to achieve the multiplied twist vector.

Moore-Penrose inverses have multiple important properties in respect to the problem of kinematics:

  1. It finds the minimum Euclidean norm solution. In the case of kinematics, this means that it will produce the most efficient set of thrusts in order to achieve the desired twist; all other sets of thrusts will necessarily have more cancellation of force and torque, resulting in less efficiency.
  2. It is a matrix based solution, which means it can easily and efficiently be GPU accelerated on the deck.

2. Variable Center of Mass

While the Moore-Penrose inverse method described above allows for precise control of the robot, it's main limitation is it requires that the position of the Center of Mass be accurate. One could imagine that as the Center of Mass moves away from its expected location (in practice due to lifting a heavy object) the actual torque produced by each motor will stray further and further from those prescribed by the matrix math (due to a changing radius).

In order to remedy this, the ability for a moving Center of Mass using ROS parameters was added. While this could potentially be controlled automatically in the future, the intended use was for the pilot to be able to trim the Center of Mass of the robot during flight.

While the kinematics allows for arbitrary Center of Mass shifts, it is probably most advantageous to only give the pilot the ability to shift the center of mass along the line connecting the normal Center of Mass position to the Claw, as that is line the Center of Mass will actually shift along when lifting an object (a CoM reset button would also prove useful). Regardless of the implementation, the kinematics allows for it, so this can be tweaked in the future.

3. Thrust Limiting

The Blue Robotics T200 have several limitations, one of which being their thrust limits.

The motors have separate forward and reverse thrust limit, and we must take that into account. The methodology is to find a scaling factor for each motor that would scale it to its maximum thrust in the direction it is going. We then select the minimum of those possible scalars, as any of the other scaling factors will scale some of the other motors past their maximums.

4. Current Limiting

The robot has two main current limits which must be taken into account:

  1. Total current draw must not exceed 70A
  2. The current draw on each ESC must not exceed 40A

To do this we employ an identical method as we do for thrust limiting. We calculate the maximum scaling factors such that all the motors together draw 70A, the motors on the first ESC draw 40A, and the motors on the second ESC also draw 40A. We then select the minimum of those scaling factors.

Of course, to calculate any of those scaling factors is slightly more complicated. In the case of the thrust limiting, it is very easy as thrust is proportional to thrust (the proof of this is left to the reader as an exercise). However, there is a nonlinear relationship between current draw and thrust. We model that relationship with a 5th degree polynomial.

So for a given list of motor thrusts, we can do some algebraic manipulation to receive another 5th degree polynomial function, except this time it is a function of the desired scaling factor. We can then subtract the current limit we are trying to reach, and find the root of that function. We are going to receive multiple roots for this, so we select the smallest positive, real root.

Conclusions

These changes will make the kinematics far more accurate, maximize our thrust envelope, and can account for parametric uncertainties - all while being relatively computationally inexpensive.

However, there will undoubtedly be changes and tweaks needed. Here is a short list of things which will be necessary to change in the future:

  1. Current draw percentage cap: As stated in 7135693, we should likely limit how much current we draw to only be some percentage of the maximum current to account for any inaccuracies. The exact percentage will have to depend on how accurately we're able to model current as a function of thrust during testing.
  2. Updated thrust to current data: Similar to the last step, we are going to need to run tests on our own setup to measure current draw more accurately.

There are certainly other issues which will arise, however the code in this branch works around the limitations that are known to the developer. Any changes that must be made likely won't be too difficult to fix (famous last words?). Please feel free to ask any questions you might have regarding the code or the methodology.

The list in the motor message (data) needed to be initialized to all zeroes, and each value from the result of the matrix multiplication must be assigned to that list. This is because numpy's matmul returns an ndarray and not a list, and therefore cannot be directly copied.

TODO: Use the  function on the resultant ndarray to do direct assignment.
Data came from the Blue Robotics T200 Motor Technical Details, link will be in commit comment. Data will be updated with more accurate numbers after testing.
The thrust limit scalar determines maximum thrust in direction, maximum pilot input controls percentage of that maximum.

TODO: Implement current scaling and select the smaller of the scalars between current and thrust limits to determine thrust maximum
…eter

When the center_of_mass_offset parameter is updated, the motor_config matrix will regenerate and a new inverse_config matrix will be generated from that.
Note: In production, we will have to gather our own thrust->current data, since the provided Blue Robotics data won't accurately reflect our setup. Also, we should not consider the hard limits to be our actual limit, and instead use some percentage of that maximum, such as 80% or 90%
List comprehensions are generally faster and more readable, see Gudio blog post and diff, respectively.

Also removed unnecessary initilization of the motor_msg data array.
@liamgilligan liamgilligan changed the title Liamgilligan kinematicsnode Improved Kinematics Jan 2, 2024
@mike-matera
Copy link
Member

When I try to run the nodes I get this:

[thrust-4] Traceback (most recent call last):
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/install/seahawk/lib/seahawk/thrust", line 33, in <module>
[thrust-4]     sys.exit(load_entry_point('seahawk', 'console_scripts', 'thrust')())
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 288, in main
[thrust-4]     rclpy.spin(Thrust())
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 222, in spin
[thrust-4]     executor.spin_once()
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 739, in spin_once
[thrust-4]     self._spin_once_impl(timeout_sec)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 736, in _spin_once_impl
[thrust-4]     raise handler.exception()
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/task.py", line 239, in __call__
[thrust-4]     self._handler.send(None)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 437, in handler
[thrust-4]     await call_coroutine(entity, arg)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 362, in _execute_subscription
[thrust-4]     await await_or_execute(sub.callback, msg)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 107, in await_or_execute
[thrust-4]     return callback(*args)
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 275, in _callback
[thrust-4]     current_scalar = self.get_minimum_current_scalar(motor_msg.data)
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 139, in get_minimum_current_scalar
[thrust-4]     total_scalar = self.get_current_scalar_value(mv, self.TOTAL_CURRENT_LIMIT)
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 123, in get_current_scalar_value
[thrust-4]     return min(real_positive)
[thrust-4] ValueError: min() arg is an empty sequence
[ERROR] [thrust-4]: process has died [pid 91178, exit code 1, cmd '/home/maximus/Robotics/cabrillo_rov_2023/install/seahawk/lib/seahawk/thrust --ros-args -r __node:=thrust'].

@liamgilligan
Copy link
Contributor Author

When I try to run the nodes I get this:

[thrust-4] Traceback (most recent call last):
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/install/seahawk/lib/seahawk/thrust", line 33, in <module>
[thrust-4]     sys.exit(load_entry_point('seahawk', 'console_scripts', 'thrust')())
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 288, in main
[thrust-4]     rclpy.spin(Thrust())
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 222, in spin
[thrust-4]     executor.spin_once()
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 739, in spin_once
[thrust-4]     self._spin_once_impl(timeout_sec)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 736, in _spin_once_impl
[thrust-4]     raise handler.exception()
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/task.py", line 239, in __call__
[thrust-4]     self._handler.send(None)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 437, in handler
[thrust-4]     await call_coroutine(entity, arg)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 362, in _execute_subscription
[thrust-4]     await await_or_execute(sub.callback, msg)
[thrust-4]   File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 107, in await_or_execute
[thrust-4]     return callback(*args)
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 275, in _callback
[thrust-4]     current_scalar = self.get_minimum_current_scalar(motor_msg.data)
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 139, in get_minimum_current_scalar
[thrust-4]     total_scalar = self.get_current_scalar_value(mv, self.TOTAL_CURRENT_LIMIT)
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 123, in get_current_scalar_value
[thrust-4]     return min(real_positive)
[thrust-4] ValueError: min() arg is an empty sequence
[ERROR] [thrust-4]: process has died [pid 91178, exit code 1, cmd '/home/maximus/Robotics/cabrillo_rov_2023/install/seahawk/lib/seahawk/thrust --ros-args -r __node:=thrust'].

Fixed in be5a597!

return min([(self.MAX_FWD_THRUST / thrust) if thrust > 0
else ((self.MAX_REV_THRUST / thrust) if thrust < 0
else float('inf'))
for thrust in motor_values])

def _callback(self, twist_msg):
Copy link
Member

Choose a reason for hiding this comment

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

Double underscore for private methords

self.__params[5] * sum(mv),
self.__params[6] * len(mv) - limit]

def get_current_scalar_value(self, mv: list, limit: float) -> float:
Copy link
Member

Choose a reason for hiding this comment

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

Consider adding a link to the paper you wrote on the methodology. Add the pdf to the doc directory

Copy link
Member

Choose a reason for hiding this comment

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

Moved to Issue #194

Copy link
Member

@steph1111 steph1111 left a comment

Choose a reason for hiding this comment

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

The revisions made to the Thrust node in this PR are extremely impressive. These enhancements are a substantial improvement over the previous implementation. The updated methodology which uses a much more mathematically sound way of converting Twist to motor values will significantly improve the drive-ability of the robot. From reviewing this code, reading the paper you wrote, and talking to you over the last few months, it is clear that the underlying mathematics of the kinematics, current limiting, and thrust limiting code are well thought out and thoroughly researched. After viewing the simulation in rviz I am confident that this should be merged

Also very noice list comprehensions :D

Updates CoM offset member by calling self.get_parameter(...).value, and moves the other function calls within the try block
Removed as member was never accessed outside of parameter setting callback. Also replaced try/catch with a check for paramter validity and an early return.
@mike-matera
Copy link
Member

I'm getting this error:

[thrust-4] Traceback (most recent call last):
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/install/seahawk/lib/seahawk/thrust", line 33, in <module>
[thrust-4]     sys.exit(load_entry_point('seahawk', 'console_scripts', 'thrust')())
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 278, in main
[thrust-4]     rclpy.spin(Thrust())
[thrust-4]   File "/home/maximus/Robotics/cabrillo_rov_2023/build/seahawk/seahawk_deck/thrust.py", line 80, in __init__
[thrust-4]     self.motor_config = self.generate_motor_config()
[thrust-4] TypeError: Thrust.generate_motor_config() missing 1 required positional argument: 'center_of_mass_offset'

@mike-matera
Copy link
Member

This worked! Nice job.

@liamgilligan liamgilligan merged commit 1687bb6 into main Feb 2, 2024
@steph1111 steph1111 deleted the liamgilligan-kinematicsnode branch February 10, 2024 06:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants