Control a Custom Robot Simulation
In the previous lecture we created a gazebo simulation model of a custom robot. This lecture will continue from that point, by going through the process of adding a controller interface which makes the model movable through ROS.
General Setup
Before you do this setup, make sure you have done the setup from the previous lecture (Build a Custom Robot Simulation) first.
- Inside the ROS package that you created for building your robot simulation model, create a new folder called
config. - Inside the
configfolder create a new file calledcontroller_config.yaml. - In the setup.py file of your package add the following line:
# In order to be able to access the added files during runtime we add we add them to data_files # ADD AFTER: ('share/' + package_name, ['package.xml']), (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))), - Create new file in the
urdffolder of your ROS package calledrobot_control.gazebo.xacro. - Inside the
robot_description.urdf.xacrofile add a reference to the newly createdrobot_control.gazebo.xacro(remember to add this line after the<robot>tag):
<xacro:include filename="$(find PACKAGE_NAME)/urdf/robot_control.gazebo.xacro" />
Basic File Structure
The base structure of the robot_control.gazebo.xacro file should look like this:
<?xml version="1.0"?>
<robot>
<!--Add gazebo specific definitions here-->
</robot>
Gazebo Config
We want to be able to move the joints of the robot using ROS2 and therefore define that we want to use the gz_ros2_control plugin. As a parameter we have to define the location of the .yaml file that contains configurations for the controller.
<gazebo>
<plugin filename="gz_ros2_control-system" name="gz_ros2_control::GazeboSimROS2ControlPlugin">
<parameters>$(find PACKAGE_NAME)/config/FILENAME.yaml</parameters>
</plugin>
</gazebo>
We also have to define what joints will be controllable by ros2_control. The basic structure of this definition looks like this:
<ros2_control name="GazeboSimSystem" type="system">
<hardware>
<plugin>gz_ros2_control/GazeboSimSystem</plugin>
</hardware>
<!--Add joint command/state interface definitions-->
</ros2_control>
For each joint we then add a definition of the command and state interfaces available:
<joint name="JOINT_NAME">
<command_interface name="position"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
Note: You can define multiple command interfaces in case you want to also be able to control the velocity or effort (torque) of the joint.
Controller Config
What type of controller will be used on what joints can be defined in your controller_config.yaml. The different types of controllers that are available by default can be found here. First we have to define for the controller manager what type of controllers we want to use:
controller_manager:
ros__parameters:
update_rate: 100 # H
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster
CONTROLLER_NAME:
type: forward_command_controller/ForwardCommandController
CONTROLLER_NAME:
type: joint_trajectory_controller/JointTrajectoryController
Forward Command Controller
An example definition of a forward command controller which uses type forward_command_controller/ForwardCommandController:
CONTROLLER_NAME:
ros__parameters:
joints:
- JOINT_NAME
- JOINT_NAME
interface_name: position
Note: the interface can also be velocity or effort depending on how the joints should be controlled.
Joint Trajectory Controller
The definition of the joint trajectory controller of type joint_trajectory_controller/JointTrajectoryController is slightly different to the forward command controller:
CONTROLLER_NAME:
ros__parameters:
joints:
- JOINT_NAME
- JOINT_NAME
command_interfaces:
- position
state_interfaces:
- position
- velocity
Launch File adjustments
To startup your controllers during launch, add the following lines to the launch file for each controller definition:
#ADD AFTER: def generate_launch_description():
#BUT BEFORE: return LaunchDescription([
node_joint_broadcaster = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster"],
output="screen",
)
# This is the node allows you to control your robot arm.
node_arm_controller = Node(
package="controller_manager",
executable="spawner",
arguments=["CONTROLLER_NAME"],
output="screen",
)
#ADD AFTER: return LaunchDescription([
node_joint_broadcaster,
node_arm_controller,
Controlling the Robot through MATLAB
To control you robot through matlab you will find the example script robot_arm_pub.m in the ros2_students_25/custom_robot_sim repository. Instructions on how to setup the matlab to be able to communicate through ROS you can find here.
Forward Command Controller
robot_arm_controller_node = ros2node("/robot_arm_matlab_controller", 24);
robot_arm_publisher = ros2publisher(robot_arm_controller_node, "/forward_controller/commands", "std_msgs/Float64MultiArray");
joint_states_subscriber = ros2subscriber(robot_arm_controller_node, "/joint_states", "sensor_msgs/JointState");
zero_pos_msg = ros2message(robot_arm_publisher);
zero_pos_msg.data = [0.0 0.0 0.0 0.0 0.0 0.0];
target_pos_msg = ros2message(robot_arm_publisher);
target_pos_msg.data = [pi/4 pi/4 pi/4 pi/4 pi/4 0.07];
move_to_target = false;
for cnt = 1:200
if mod(cnt, 20) == 0
move_to_target = ~move_to_target;
end
receivedData = receive(joint_states_subscriber, 10);
joint_names = receivedData.name
joint_positions = receivedData.position
joint_velocities = receivedData.velocity
joint_efforts = receivedData.effort
if move_to_target
send(robot_arm_publisher,target_pos_msg);
else
send(robot_arm_publisher,zero_pos_msg);
end
pause(0.1)
end
Joint Trajectory Controller
% Set the ros domain id and create a new ros node
setenv("ROS_DOMAIN_ID","24");
test_node = ros2node("joint_trajectory_client_node");
% Define joint names
joints = { ...
'arm_base_joint', ...
'link_1_joint', ...
'link_2_joint', ...
'link_3_joint', ...
'gripper_base_joint', ...
'gripper_finger_left_joint' };
% Define the action type and name
actionType = "control_msgs/FollowJointTrajectory";
actionName = "/arm_controller/follow_joint_trajectory";
% Create the client
client = ros2actionclient(test_node, actionName, actionType);
disp("Waiting for FollowJointTrajectory action server ...");
waitForServer(client);
disp("Action server available.");
% Define a list of goal positions that will define the trajectory
goalPositions = [ ...
pi/2, pi/2-0.5, 0.4, pi/4, 0.0, -0.05; % home-ish
pi/2, pi/2-0.18, 0.6, pi/4, 0.0, -0.05; % pose 2
pi/2, pi/2-0.18, 0.6, pi/4, 0.0, -0.02; % pose 3
pi/2, pi/2-0.5, 0.4, pi/4, 0.0, -0.02;
-pi/2, pi/2-0.5, 0.6, pi/4, 0.0, -0.02;
-pi/2, pi/2-0.35, 0.7, pi/4, 0.0, -0.02;
-pi/2, pi/2-0.35, 0.7, pi/4, 0.0, -0.05;
0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; % pose 4
% Create a JointTrajectory message object
traj = ros2message("trajectory_msgs/JointTrajectory");
traj.joint_names = joints;
% Add all the previously defined goalPositions to the trajectory as
% JointTrajectoryPoint
for i = 1:size(goalPositions,1)
pt1 = ros2message("trajectory_msgs/JointTrajectoryPoint");
pt1.positions = [goalPositions(i,:)]; % in radians
pt1.velocities = zeros(1,6);
pt1.accelerations = zeros(1,6);
pt1.time_from_start.sec = int32(i);
pt1.time_from_start.nanosec = uint32(0);
pt1.effort = [];
pt1.accelerations = [];
if i == 1
traj.points = [pt1];
else
traj.points = [traj.points, pt1];
end
end
% Define the goal message using the previously defined trajectory.
goalMsg = ros2message(client);
goalMsg.trajectory = traj;
goalMsg.goal_time_tolerance.sec = int32(0);
goalMsg.goal_time_tolerance.nanosec = uint32(500000000); % 0.5 s
% Send goal
callbackOpts = ros2ActionSendGoalOptions(FeedbackFcn=@feedbackCallback,ResultFcn=@resultCallback);
goalHandle = sendGoal(client, goalMsg, callbackOpts);
function feedbackCallback(goalStruct,feedbackMsg)
disp('Feedback:')
disp('Joint Positions:')
disp(feedbackMsg.desired.positions);
disp('Joint Velocities:')
disp(feedbackMsg.desired.velocities);
end
function resultCallback(goalStruct, resultMsg)
disp('The robot finished the joint trajectory!')
end