Minimum robot

Model

New package minimal_robot_description, directly create a new model file, minimal_robot_description.urdf (note that the model file here adopts a unified robot description format).

<?xml version="1.0"?>
<robot  name="one_DOF_robot">

<!-- Used for fixing robot to Gazebo 'base_link' -->
  <link name="world"/>

  <joint name="glue_robot_to_world" type="fixed">
    <parent link="world"/>
    <child link="link1"/>
  </joint>

 <!-- Base Link -->
  <link name="link1">
    <collision>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
       <box size="0.2 0.2 0.7"/>
      </geometry>
    </collision>

    <visual>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
        <box size="0.2 0.2 1"/>
      </geometry>
    </visual>

    <inertial>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <mass value="1"/>
      <inertia
        ixx="1.0" ixy="0.0" ixz="0.0"
        iyy="1.0" iyz="0.0"
        izz="1.0"/>
    </inertial>
  </link>

<!-- Moveable Link -->
  <link name="link2">
    <collision>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
        <cylinder length="1" radius="0.1"/>
        <!--box size="0.15 0.15 0.8"-->
      </geometry>
    </collision>

    <visual>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
        <cylinder length="1" radius="0.1"/>
      </geometry>
    </visual>

    <inertial>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <mass value="1"/>
      <inertia
        ixx="0.1" ixy="0.0" ixz="0.0"
        iyy="0.1" iyz="0.0"
        izz="0.005"/>
    </inertial>
  </link>

  <joint name="joint1" type="continuous">
    <parent link="link1"/>
    <child link="link2"/>
    <origin xyz="0 0 1" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>
</robot>

Manually load model

Note that execute the following command in the directory of the urdf model file.

rosrun gazebo_ros spawn_model -urdf -file minimal_robot_description.urdf -model one_DOF

launch file loading model

The first subfield of the startup file is to place the robot model on the parameter server, and the second subfield is to reproduce the model from the parameter server on Gazebo. This can ensure that the ROS node and Gazebo introduce the same details of the robot model, so as to ensure the correct interaction between the ROS node and the sensor.

<launch>
  <param name="robot_description" 
   textfile="$(find minimal_robot_description)/minimal_robot_description.urdf"/>

  <!-- Spawn a robot into Gazebo -->
  <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" 
     args="-param robot_description -urdf -model one_DOF_robot" />
</launch>
roslaunch minimal_robot_description minimal_robot_description.launch

Minimum controller for joint servo control

New minimal_ joint_ Cpcontroller package, create a new minimal_joint_cpntroller.cpp file, the program is as follows (note that we generally do not use this method for control, but generally use Gazebo for control).

**#include <ros/ros.h> //ALWAYS need to include this
#include <gazebo_msgs/GetModelState.h>
#include <gazebo_msgs/ApplyJointEffort.h>
#include <gazebo_msgs/GetJointProperties.h>
#include <sensor_msgs/JointState.h>
#include <string.h>
#include <stdio.h>  
#include <std_msgs/Float64.h>
#include <math.h>

//some "magic number" global params:
const double Kp = 10.0; //controller gains
const double Kv = 3;
const double dt = 0.01;

//a simple saturation function; provide saturation threshold, sat_val, and arg to be saturated, val

double sat(double val, double sat_val) {
    if (val > sat_val)
        return (sat_val);
    if (val< -sat_val)
        return (-sat_val);
    return val;

}

double min_periodicity(double theta_val) {
    double periodic_val = theta_val;
    while (periodic_val > M_PI) {
        periodic_val -= 2 * M_PI;
    }
    while (periodic_val< -M_PI) {
        periodic_val += 2 * M_PI;
    }
    return periodic_val;
}

double g_pos_cmd = 0.0; //position command input-- global var

void posCmdCB(const std_msgs::Float64& pos_cmd_msg) {
    ROS_INFO("received value of pos_cmd is: %f", pos_cmd_msg.data);
    g_pos_cmd = pos_cmd_msg.data;
}

bool test_services() {
    bool service_ready = false;
    if (!ros::service::exists("/gazebo/apply_joint_effort", true)) {
        ROS_WARN("waiting for apply_joint_effort service");
        return false;
    }
    if (!ros::service::exists("/gazebo/get_joint_properties", true)) {
        ROS_WARN("waiting for /gazebo/get_joint_properties service");
        return false;
    }
    ROS_INFO("services are ready");
    return true;
}

int main(int argc, char **argv) {
    //initializations:
    ros::init(argc, argv, "minimal_joint_controller");
    ros::NodeHandle nh;
    ros::Duration half_sec(0.5);

    // make sure services are available before attempting to proceed, else node will crash
    while (!test_services()) {
        ros::spinOnce();
        half_sec.sleep();
    }

    ros::ServiceClient set_trq_client =
            nh.serviceClient<gazebo_msgs::ApplyJointEffort>("/gazebo/apply_joint_effort");
    ros::ServiceClient get_jnt_state_client =
            nh.serviceClient<gazebo_msgs::GetJointProperties>("/gazebo/get_joint_properties");

    gazebo_msgs::ApplyJointEffort effort_cmd_srv_msg;
    gazebo_msgs::GetJointProperties get_joint_state_srv_msg;

    ros::Publisher trq_publisher = nh.advertise<std_msgs::Float64>("jnt_trq", 1);
    ros::Publisher vel_publisher = nh.advertise<std_msgs::Float64>("jnt_vel", 1);
    ros::Publisher pos_publisher = nh.advertise<std_msgs::Float64>("jnt_pos", 1);
    ros::Publisher joint_state_publisher = nh.advertise<sensor_msgs::JointState>("joint_states", 1);
    ros::Subscriber pos_cmd_subscriber = nh.subscribe("pos_cmd", 1, posCmdCB);

    std_msgs::Float64 trq_msg, q1_msg, q1dot_msg;
    double q1, q1dot, q1_err, trq_cmd;
    sensor_msgs::JointState joint_state_msg;
    ros::Duration duration(dt);
    ros::Rate rate_timer(1 / dt);

    effort_cmd_srv_msg.request.joint_name = "joint1";
    effort_cmd_srv_msg.request.effort = 0.0;
    effort_cmd_srv_msg.request.duration = duration;
    get_joint_state_srv_msg.request.joint_name = "joint1";

    // set up the joint_state_msg fields to define a single joint,
    // called joint1, and initial position and vel values of 0
    joint_state_msg.header.stamp = ros::Time::now();
    joint_state_msg.name.push_back("joint1");
    joint_state_msg.position.push_back(0.0);
    joint_state_msg.velocity.push_back(0.0);
    
    //here is the main controller loop:
    while (ros::ok()) { 
        get_jnt_state_client.call(get_joint_state_srv_msg);
        q1 = get_joint_state_srv_msg.response.position[0];
        q1_msg.data = q1;
        pos_publisher.publish(q1_msg); //republish his val on topic jnt_pos

        q1dot = get_joint_state_srv_msg.response.rate[0];
        q1dot_msg.data = q1dot;
        vel_publisher.publish(q1dot_msg);

        joint_state_msg.header.stamp = ros::Time::now();
        joint_state_msg.position[0] = q1;
        joint_state_msg.velocity[0] = q1dot;
        joint_state_publisher.publish(joint_state_msg);

        q1_err = min_periodicity(g_pos_cmd - q1); //jnt angle err; watch for periodicity

        trq_cmd = Kp * (q1_err) - Kv*q1dot;
        trq_msg.data = trq_cmd;
        trq_publisher.publish(trq_msg);

        effort_cmd_srv_msg.request.effort = trq_cmd; // send torque command to Gazebo
        set_trq_client.call(effort_cmd_srv_msg);
        //make sure service call was successful
        bool result = effort_cmd_srv_msg.response.success;
        if (!result)
            ROS_WARN("service call to apply_joint_effort failed!");
        ros::spinOnce();
        rate_timer.sleep();
    }
}

The specific control steps of the controller node are as follows: first, obtain the current joint position and speed from Gazebo, and enter it in the topic joint_ Publish on states; Compare the joint sensor value with the command joint angle; Calculate the moment response; By applying_ joint_ The force service sends this force to Gazebo.

rosrun minimal_joint_controller joint_controller
rostopic pub pos_cmd std_msgs/Float64 1.0


You can see that the joint rotates according to the expected joint angle.

The Gazebo plug-in performs joint servo control

First, download the plug-in. My version is Ubuntu 16 04, so it's kinetic.

sudo apt-get install ros-kinetic-controller-interface ros-kinetic-gazebo-ros-control ros-kinetic-joint-state-control ros-kinetic-effort-controllers

Or in minimal_ robot_ In the description package, create a new control_config directory, create a new one below_ dof_ ctl_ params. Yaml file

one_DOF_robot:
  # Publish all joint states -----------------------------------
  joint_state_controller:
    type: joint_state_controller/JointStateController
    publish_rate: 50  
  
  # Position Controllers ---------------------------------------
  joint1_position_controller:
    type: effort_controllers/JointPositionController
    joint: joint1
    pid: {p: 10.0, i: 10.0, d: 10.0, i_clamp_min: -10.0, i_clamp_max: 10.0}

New model file_ robot_ description_ w_ jnt_ ctl. urdf.

<?xml version="1.0"?>
<robot  name="one_DOF_robot">

<!-- Used for fixing robot to Gazebo 'base_link' -->
  <link name="world"/>

  <joint name="glue_robot_to_world" type="fixed">
    <parent link="world"/>
    <child link="link1"/>
  </joint>

 <!-- Base Link -->
  <link name="link1">
    <collision>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
       <box size="0.2 0.2 0.7"/>
      </geometry>
    </collision>

    <visual>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
        <box size="0.2 0.2 1"/>
      </geometry>
    </visual>

    <inertial>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <mass value="1"/>
      <inertia
        ixx="1.0" ixy="0.0" ixz="0.0"
        iyy="1.0" iyz="0.0"
        izz="1.0"/>
    </inertial>
  </link>

<!-- Moveable Link -->
  <link name="link2">
    <collision>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
        <cylinder length="1" radius="0.1"/>
        <!--box size="0.15 0.15 0.8"-->
      </geometry>
    </collision>

    <visual>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <geometry>
        <cylinder length="1" radius="0.1"/>
      </geometry>
    </visual>

    <inertial>
      <origin xyz="0 0 0.5" rpy="0 0 0"/>
      <mass value="1"/>
      <inertia
        ixx="0.1" ixy="0.0" ixz="0.0"
        iyy="0.1" iyz="0.0"
        izz="0.005"/>
    </inertial>
  </link>

  <joint name="joint1" type="revolute">
    <parent link="link1"/>
    <child link="link2"/>
    <origin xyz="0 0 1" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit effort="10.0" lower="0.0" upper="2.0" velocity="0.5"/>
    <dynamics damping="1.0"/>
  </joint>

  <transmission name="tran1">
    <type>transmission_interface/SimpleTransmission</type>
    <joint name="joint1">
      <hardwareInterface>EffortJointInterface</hardwareInterface>
    </joint>
    <actuator name="motor1">
      <hardwareInterface>EffortJointInterface</hardwareInterface>
      <mechanicalReduction>1</mechanicalReduction>
    </actuator>
  </transmission>
  <gazebo>
     <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
       <robotNamespace>/one_DOF_robot</robotNamespace>
     </plugin>
  </gazebo>
</robot>

Use the startup file minimal of the Gazebo plug-in_ robot_ w_ jnt_ ctl. Launch (combined with model loading).

<launch>
  <!-- Load joint controller configurations from YAML file to parameter server -->
  <rosparam file="$(find minimal_robot_description)/control_config/one_dof_ctl_params.yaml" command="load"/>
  <param name="robot_description" 
     textfile="$(find minimal_robot_description)/minimal_robot_description_w_jnt_ctl.urdf"/>

  <!-- Spawn a robot into Gazebo -->
  <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" 
     args="-param robot_description -urdf -model one_DOF_robot" />
  
  <!--start up the controller plug-ins via the controller manager -->
  <node name="controller_spawner" pkg="controller_manager" type="spawner" respawn="false"
    output="screen" ns="/one_DOF_robot" args="joint_state_controller joint1_position_controller"/>
    
</launch>

The following is the joint servo control.

//Open a blank world
roslaunch gazebo_ros empty_world.launch
//Load the model and start the plug-in
roslaunch minimal_robot_description minimal_robot_w_jnt_ctl.launch
//Publish joint angle
rostopic pub -r 10 /one_DOF_robot/joint1_position_controller/command std_msgs/Float64 1.0

Run screenshot.


Up to now, I have read the loading methods of the two models and the two joint control methods. Although I can't write the model program and control the joints concretely (it's too difficult and I haven't touched them at all)... I'd better start the later part first. These are not used for the time being

Keywords: ROS

Added by creativeimpact on Tue, 18 Jan 2022 10:10:23 +0200