Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9a0896e
Initial commit for cart-pole
christophfroehlich Nov 11, 2023
e7d8711
Initial version for action client
christophfroehlich Nov 13, 2023
7b7153c
Add JTC config
christophfroehlich Nov 13, 2023
47c61d3
Switch to velocity interface
christophfroehlich Nov 13, 2023
84833be
Add example trajectory and plotjuggler file
christophfroehlich Nov 13, 2023
1aa65dc
Fix stuck action client
christophfroehlich Nov 14, 2023
8dd4e0b
swingup with higher update rate
christophfroehlich Nov 14, 2023
a23e7b3
Add initial commit for lqr plugin
christophfroehlich Nov 14, 2023
f9c63ea
Initial implementation of LQR
christophfroehlich Nov 14, 2023
94d48af
Add sidestep
christophfroehlich Nov 15, 2023
9b41bed
Add LQR gains to parameters and increase number states
christophfroehlich Nov 15, 2023
d1938aa
Fix LQR calculation
christophfroehlich Nov 17, 2023
6df3c22
Implement computeControlLawRT_impl
christophfroehlich Nov 19, 2023
ca44ed7
Tune controllers
christophfroehlich Nov 19, 2023
631f355
Add minimal doc
christophfroehlich Nov 20, 2023
0ba88f9
Use class for LQR gain storage
christophfroehlich Nov 20, 2023
319ce14
Add more trajectory examples
christophfroehlich Nov 20, 2023
037c03a
Use RT buffer for gains
christophfroehlich Nov 20, 2023
2c7148f
Parse dt from update_rate of controller-node
christophfroehlich Nov 20, 2023
a36bf9d
Set sample time for gains constant
christophfroehlich Nov 22, 2023
c2ef378
No need for explicit update_rate any more
christophfroehlich Nov 22, 2023
87bbad7
Fix computation of LQR gains
christophfroehlich Dec 5, 2023
b6935a9
Correctly load pid_alternative
christophfroehlich Dec 10, 2023
19fe692
Throw warning if trajectory points is wrong
christophfroehlich Dec 10, 2023
3fd5582
Rename method
christophfroehlich Dec 14, 2023
8f1bc18
Make make_symmetric method
christophfroehlich Dec 14, 2023
1af9f2a
Create riccati step method
christophfroehlich Dec 14, 2023
de7a429
Reorder methods
christophfroehlich Dec 14, 2023
430e955
Use typedefs and make methods const
christophfroehlich Dec 14, 2023
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
82 changes: 82 additions & 0 deletions example_XX/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
cmake_minimum_required(VERSION 3.16)
project(ros2_control_demo_example_xx LANGUAGES CXX)

if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
add_compile_options(-Wall -Wextra)
endif()

# find dependencies
set(THIS_PACKAGE_INCLUDE_DEPENDS
backward_ros
pluginlib
generate_parameter_library
joint_trajectory_controller_plugins
trajectory_msgs
rclcpp
rclpy
)

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
find_package(${Dependency} REQUIRED)
endforeach()

generate_parameter_library(cartpole_lqr_trajectory_plugin_parameters
controller/cartpole_lqr_trajectory_plugin_parameters.yaml
)
add_library(${PROJECT_NAME} SHARED controller/cartpole_lqr_trajectory_plugin.cpp)
add_library(ros2_control_demo_example_xx::cartpole_lqr_trajectory_plugin ALIAS ${PROJECT_NAME})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/controller/include>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>)
ament_target_dependencies(${PROJECT_NAME} PUBLIC ${THIS_PACKAGE_INCLUDE_DEPENDS})
target_link_libraries(${PROJECT_NAME} PUBLIC
cartpole_lqr_trajectory_plugin_parameters
)
pluginlib_export_plugin_description_file(joint_trajectory_controller_plugins plugins.xml)

# Causes the visibility macros to use dllexport rather than dllimport,
# which is appropriate when building the dll but not consuming it.
target_compile_definitions(${PROJECT_NAME} PRIVATE "ROS2_CONTROL_DEMO_EXAMPLE_XX_BUILDING_LIBRARY")

# INSTALL
install(
DIRECTORY description/launch description/ros2_control description/urdf description/gazebo
DESTINATION share/${PROJECT_NAME}
)
install(
DIRECTORY bringup/launch bringup/config
DESTINATION share/${PROJECT_NAME}
)
install(
TARGETS ${PROJECT_NAME} cartpole_lqr_trajectory_plugin_parameters
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)

# Install Python modules
ament_python_install_package(${PROJECT_NAME})
# Install Python executables
install(PROGRAMS
scripts/trajectory_action_client.py
DESTINATION lib/${PROJECT_NAME}
)

if(BUILD_TESTING)
find_package(ament_cmake_gtest REQUIRED)
endif()

## EXPORTS
ament_export_libraries(
cartpole_lqr_trajectory_plugin
)
ament_export_targets(
export_${PROJECT_NAME}
)
ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS})
ament_package()
5 changes: 5 additions & 0 deletions example_XX/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ros2_control_demo_example_xx

This example demonstrates a robot with passive joints.

Find the documentation in [doc/userdoc.rst](doc/userdoc.rst) or on [control.ros.org](https://control.ros.org/master/doc/ros2_control_demos/example_XX/doc/userdoc.html).
63 changes: 63 additions & 0 deletions example_XX/bringup/config/cart_pole_controllers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
controller_manager:
ros__parameters:
update_rate: 100 # Hz

trajectory_controllers:
type: joint_trajectory_controller/JointTrajectoryController

trajectory_controllers_pid:
type: joint_trajectory_controller/JointTrajectoryController

joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster

trajectory_controllers:
ros__parameters:

joints:
- slider_to_cart
- cart_to_pendulum

command_joints:
- slider_to_cart

command_interfaces:
- velocity

state_interfaces:
- position
- velocity

controller_plugin: ros2_control_demo_example_xx::CartpoleLqrTrajectoryPlugin

gains:
R: 0.1
Q: [5.0, 5.0, 0.1, 1.0]
cart_to_pendulum:
angle_wraparound: true

trajectory_controllers_pid:
ros__parameters:
joints:
- slider_to_cart
- cart_to_pendulum

command_joints:
- slider_to_cart

command_interfaces:
- velocity

state_interfaces:
- position
- velocity

gains:
slider_to_cart:
p: 1.0
i: 0.0
d: 0.0
i_clamp: 0.0
ff_velocity_scale: 1.0
cart_to_pendulum:
angle_wraparound: true
95 changes: 95 additions & 0 deletions example_XX/bringup/config/plotjuggler.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab containers="1" tab_name="Cart Pole">
<Container>
<DockSplitter count="2" sizes="0.502252;0.497748" orientation="-">
<DockSplitter count="2" sizes="0.5;0.5" orientation="|">
<DockArea name="...">
<plot style="Lines" flip_y="false" flip_x="false" mode="TimeSeries">
<range bottom="-16.047744" top="16.047744" right="14.993522" left="0.000000"/>
<limitY/>
<curve color="#1f77b4" name="/joint_states/slider_to_cart/position"/>
<curve color="#d62728" name="/trajectory_controllers/controller_state/reference/positions.0"/>
<curve color="#1fb425" name="/trajectory_controllers/controller_state/error/positions.0"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" flip_y="false" flip_x="false" mode="TimeSeries">
<range bottom="-402.645699" top="300.298271" right="14.993522" left="0.000000"/>
<limitY/>
<curve color="#1f77b4" name="/joint_states/slider_to_cart/velocity"/>
<curve color="#d62728" name="/trajectory_controllers/controller_state/reference/velocities.0"/>
<curve color="#ff00b8" name="/trajectory_controllers/controller_state/output/velocities.0"/>
<curve color="#1fb425" name="/trajectory_controllers/controller_state/error/velocities.0"/>
</plot>
</DockArea>
</DockSplitter>
<DockSplitter count="2" sizes="0.502663;0.497337" orientation="|">
<DockArea name="...">
<plot style="Lines" flip_y="false" flip_x="false" mode="TimeSeries">
<range bottom="-39.005507" top="42.147099" right="14.993522" left="0.000000"/>
<limitY/>
<curve color="#1f77b4" name="/joint_states/cart_to_pendulum/position"/>
<curve color="#d62728" name="/trajectory_controllers/controller_state/reference/positions.1"/>
<curve color="#27d63c" name="/trajectory_controllers/controller_state/error/positions.1"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" flip_y="false" flip_x="false" mode="TimeSeries">
<range bottom="-336.185004" top="336.185004" right="14.993522" left="0.000000"/>
<limitY/>
<curve color="#1f77b4" name="/joint_states/cart_to_pendulum/velocity"/>
<curve color="#d62728" name="/trajectory_controllers/controller_state/reference/velocities.1"/>
<curve color="#1ac938" name="/trajectory_controllers/controller_state/error/velocities.1"/>
</plot>
</DockArea>
</DockSplitter>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="0"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default delimiter="0" time_axis=""/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad ROS2 bags">
<use_header_stamp value="false"/>
<discard_large_arrays value="true"/>
<max_array_size value="100"/>
<boolean_strings_to_number value="true"/>
<remove_suffix_from_strings value="true"/>
</plugin>
<plugin ID="DataLoad ULog"/>
<plugin ID="ROS2 Topic Subscriber">
<use_header_stamp value="false"/>
<discard_large_arrays value="true"/>
<max_array_size value="100"/>
<boolean_strings_to_number value="true"/>
<remove_suffix_from_strings value="true"/>
</plugin>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indices, not Lua indices&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
<plugin ID="ROS2 Topic Re-Publisher"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<previouslyLoaded_Datafiles/>
<previouslyLoaded_Streamer name="ROS2 Topic Subscriber"/>
<!-- - - - - - - - - - - - - - - -->
<customMathEquations/>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
107 changes: 107 additions & 0 deletions example_XX/bringup/launch/cart_pole.launch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2023 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os

from ament_index_python.packages import get_package_share_directory


from launch import LaunchDescription
from launch.actions import ExecuteProcess, IncludeLaunchDescription, RegisterEventHandler
from launch.event_handlers import OnProcessExit
from launch.launch_description_sources import PythonLaunchDescriptionSource

from launch_ros.actions import Node

import xacro


def generate_launch_description():
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[
os.path.join(get_package_share_directory("gazebo_ros"), "launch"),
"/gazebo.launch.py",
]
),
launch_arguments={"pause": "false"}.items(),
)

ros2_control_demo_example_xx_path = os.path.join(
get_package_share_directory("ros2_control_demo_example_xx")
)

xacro_file = os.path.join(ros2_control_demo_example_xx_path, "urdf", "cart_pole.urdf.xacro")

doc = xacro.parse(open(xacro_file))
xacro.process_doc(doc)
params = {"robot_description": doc.toxml()}

node_robot_state_publisher = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
output="screen",
parameters=[params],
)

spawn_entity = Node(
package="gazebo_ros",
executable="spawn_entity.py",
arguments=["-topic", "robot_description", "-entity", "cartpole"],
output="screen",
)

load_joint_state_controller = ExecuteProcess(
cmd=[
"ros2",
"control",
"load_controller",
"--set-state",
"active",
"joint_state_broadcaster",
],
output="screen",
)

load_joint_trajectory_controller = ExecuteProcess(
cmd=[
"ros2",
"control",
"load_controller",
"--set-state",
"active",
"trajectory_controllers",
],
output="screen",
)

return LaunchDescription(
[
RegisterEventHandler(
event_handler=OnProcessExit(
target_action=spawn_entity,
on_exit=[load_joint_state_controller],
)
),
RegisterEventHandler(
event_handler=OnProcessExit(
target_action=load_joint_state_controller,
on_exit=[load_joint_trajectory_controller],
)
),
gazebo,
node_robot_state_publisher,
spawn_entity,
]
)
Loading