Linux Basics in ROS 0

In ROS, node is like a person in this game. It’s a part of the system that knows how to do something, like control a robot’s wheel or take a picture. ROS master acts like a switchboard operator, hence nodes communicate through ROS master.

A topic is like the stories, pictures, or sound effects in the game. It’s a way that nodes share what they know with each other. When a node shares (or publishes) information to a topic, any other node that’s listening (or subscribing) to that topic can use that information to do its job.

Services are similar to topics in that they facilitate the passing of messages in between nodes.

When we say source the environment, like typing source /opt/ros/noetic/setup.bash (where noetic is the version of ROS you’re using), you set up environment variable and paths by running the setup.bash file. he first line of a Bash script often begins with #!/bin/bash or #!/usr/bin/env bash. This line is called a shebang, and it tells the system to interpret the script commands with the Bash shell.

What is Catkin Package? ROS software is organized and distributed into packages, which are directories that might contain source code for ROS nodes, libraries, datasets, and more. Each package also contains a file with build instructions – the CMakeLists.txt file – and a package.xml file with information about the package. Packages enable ROS users to organize useful functionality in a convenient and reusable format.

Building the simple_arm package
Write ROS nodes in C++: simple_mover, arm_mover and look_away
how ROS Publishers work in C++? ros::Publisher pub1 = n.advertise(“/topic_name”, queue_size);
$ cd /home/workspace/catkin_ws/src/simple_arm/src/ $ touch simple_mover.cpp; $ cd /home/workspace/catkin_ws/src/simple_arm/src/ $ gedit simple_mover.cpp

```cpp
#include "ros/ros.h"
#include "std_msgs/Float64.h"
int main(int argc, char** argv)
{
    // Initialize the arm_mover node
    ros::init(argc, argv, "arm_mover");
    // Create a handle to the arm_mover node
    ros::NodeHandle n;
    // Create a publisher that can publish a std_msgs::Float64 message on the /simple_arm/joint_1_position_controller/command topic

&nbsp; &nbsp; ros::Publisher joint1_pub = n.advertise<std_msgs::Float64>("/simple_arm/joint_1_position_controller/command", 10);

&nbsp; &nbsp; // Create a publisher that can publish a std_msgs::Float64 message on the /simple_arm/joint_2_position_controller/command topic

&nbsp; &nbsp; ros::Publisher joint2_pub = n.advertise<std_msgs::Float64>("/simple_arm/joint_2_position_controller/command", 10);



&nbsp; &nbsp; // Set loop frequency of 10Hz

&nbsp; &nbsp; ros::Rate loop_rate(10);



&nbsp; &nbsp; int start_time, elapsed;



&nbsp; &nbsp; // Get ROS start time

&nbsp; &nbsp; while (not start_time) {

&nbsp; &nbsp; &nbsp; &nbsp; start_time = ros::Time::now().toSec();

&nbsp; &nbsp; }



&nbsp; &nbsp; while (ros::ok()) {

&nbsp; &nbsp; &nbsp; &nbsp; // Get ROS elapsed time

&nbsp; &nbsp; &nbsp; &nbsp; elapsed = ros::Time::now().toSec() - start_time;



&nbsp; &nbsp; &nbsp; &nbsp; // Set the arm joint angles

&nbsp; &nbsp; &nbsp; &nbsp; std_msgs::Float64 joint1_angle, joint2_angle;

&nbsp; &nbsp; &nbsp; &nbsp; joint1_angle.data = sin(2 &nbsp;*M_PI* &nbsp;0.1 &nbsp;*elapsed)* &nbsp;(M_PI / 2);

&nbsp; &nbsp; &nbsp; &nbsp; joint2_angle.data = sin(2 &nbsp;*M_PI* &nbsp;0.1 &nbsp;*elapsed)* &nbsp;(M_PI / 2);



&nbsp; &nbsp; &nbsp; &nbsp; // Publish the arm joint angles

&nbsp; &nbsp; &nbsp; &nbsp; joint1_pub.publish(joint1_angle);

&nbsp; &nbsp; &nbsp; &nbsp; joint2_pub.publish(joint2_angle);



&nbsp; &nbsp; &nbsp; &nbsp; // Sleep for the time remaining until 10 Hz is reached

&nbsp; &nbsp; &nbsp; &nbsp; loop_rate.sleep();

&nbsp; &nbsp; }



&nbsp; &nbsp; return 0;

}

Before you can run the simple_mover node, you have to compile the C++ script.
below is the arm_mover cpp file

#include "ros/ros.h"

#include "simple_arm/GoToPosition.h"

#include <std_msgs/Float64.h>



// Global joint publisher variables

ros::Publisher joint1_pub, joint2_pub;



// This function checks and clamps the joint angles to a safe zone

std::vector<float> clamp_at_boundaries(float requested_j1, float requested_j2)

{

&nbsp; &nbsp; // Define clamped joint angles and assign them to the requested ones

&nbsp; &nbsp; float clamped_j1 = requested_j1;

&nbsp; &nbsp; float clamped_j2 = requested_j2;



&nbsp; &nbsp; // Get min and max joint parameters, and assigning them to their respective variables

&nbsp; &nbsp; float min_j1, max_j1, min_j2, max_j2;

&nbsp; &nbsp; // Assign a new node handle since we have no access to the main one

&nbsp; &nbsp; ros::NodeHandle n2;

&nbsp; &nbsp; // Get node name

&nbsp; &nbsp; std::string node_name = ros::this_node::getName();

&nbsp; &nbsp; // Get joints min and max parameters

&nbsp; &nbsp; n2.getParam(node_name + "/min_joint_1_angle", min_j1);

&nbsp; &nbsp; n2.getParam(node_name + "/max_joint_1_angle", max_j1);

&nbsp; &nbsp; n2.getParam(node_name + "/min_joint_2_angle", min_j2);

&nbsp; &nbsp; n2.getParam(node_name + "/max_joint_2_angle", max_j2);



&nbsp; &nbsp; // Check if joint 1 falls in the safe zone, otherwise clamp it

&nbsp; &nbsp; if (requested_j1 < min_j1 || requested_j1 > max_j1) {

&nbsp; &nbsp; &nbsp; &nbsp; clamped_j1 = std::min(std::max(requested_j1, min_j1), max_j1);

&nbsp; &nbsp; &nbsp; &nbsp; ROS_WARN("j1 is out of bounds, valid range (%1.2f,%1.2f), clamping to: %1.2f", min_j1, max_j1, clamped_j1);

&nbsp; &nbsp; }

&nbsp; &nbsp; // Check if joint 2 falls in the safe zone, otherwise clamp it

&nbsp; &nbsp; if (requested_j2 < min_j2 || requested_j2 > max_j2) {

&nbsp; &nbsp; &nbsp; &nbsp; clamped_j2 = std::min(std::max(requested_j2, min_j2), max_j2);

&nbsp; &nbsp; &nbsp; &nbsp; ROS_WARN("j2 is out of bounds, valid range (%1.2f,%1.2f), clamping to: %1.2f", min_j2, max_j2, clamped_j2);

&nbsp; &nbsp; }



&nbsp; &nbsp; // Store clamped joint angles in a clamped_data vector

&nbsp; &nbsp; std::vector<float> clamped_data = { clamped_j1, clamped_j2 };



&nbsp; &nbsp; return clamped_data;

}



// This callback function executes whenever a safe_move service is requested

bool handle_safe_move_request(simple_arm::GoToPosition::Request& req,

&nbsp; &nbsp; simple_arm::GoToPosition::Response& res)

{



&nbsp; &nbsp; ROS_INFO("GoToPositionRequest received - j1:%1.2f, j2:%1.2f", (float)req.joint_1, (float)req.joint_2);



&nbsp; &nbsp; // Check if requested joint angles are in the safe zone, otherwise clamp them

&nbsp; &nbsp; std::vector<float> joints_angles = clamp_at_boundaries(req.joint_1, req.joint_2);



&nbsp; &nbsp; // Publish clamped joint angles to the arm

&nbsp; &nbsp; std_msgs::Float64 joint1_angle, joint2_angle;



&nbsp; &nbsp; joint1_angle.data = joints_angles[0];

&nbsp; &nbsp; joint2_angle.data = joints_angles[1];



&nbsp; &nbsp; joint1_pub.publish(joint1_angle);

&nbsp; &nbsp; joint2_pub.publish(joint2_angle);



&nbsp; &nbsp; // Wait 3 seconds for arm to settle

&nbsp; &nbsp; ros::Duration(3).sleep();



&nbsp; &nbsp; // Return a response message

&nbsp; &nbsp; res.msg_feedback = "Joint angles set - j1: " + std::to_string(joints_angles[0]) + " , j2: " + std::to_string(joints_angles[1]);

&nbsp; &nbsp; ROS_INFO_STREAM(res.msg_feedback);



&nbsp; &nbsp; return true;

}



int main(int argc, char** argv)

{

&nbsp; &nbsp; // Initialize the arm_mover node and create a handle to it

&nbsp; &nbsp; ros::init(argc, argv, "arm_mover");

&nbsp; &nbsp; ros::NodeHandle n;



&nbsp; &nbsp; // Define two publishers to publish std_msgs::Float64 messages on joints respective topics

&nbsp; &nbsp; joint1_pub = n.advertise<std_msgs::Float64>("/simple_arm/joint_1_position_controller/command", 10);

&nbsp; &nbsp; joint2_pub = n.advertise<std_msgs::Float64>("/simple_arm/joint_2_position_controller/command", 10);



&nbsp; &nbsp; // Define a safe_move service with a handle_safe_move_request callback function

&nbsp; &nbsp; ros::ServiceServer service = n.advertiseService("/arm_mover/safe_move", handle_safe_move_request);

&nbsp; &nbsp; ROS_INFO("Ready to send joint commands");



&nbsp; &nbsp; // Handle ROS communication events

&nbsp; &nbsp; ros::spin();



&nbsp; &nbsp; return 0;

}

the next step is to build it, launch it, and test it out via the command line!

next, the look_away cpp file

#include "ros/ros.h"

#include "simple_arm/GoToPosition.h"

#include <sensor_msgs/JointState.h>

#include <sensor_msgs/Image.h>



// Define global vector of joints last position, moving state of the arm, and the client that can request services

std::vector<double> joints_last_position{ 0, 0 };

bool moving_state = false;

ros::ServiceClient client;



// This function calls the safe_move service to safely move the arm to the center position

void move_arm_center()

{

&nbsp; &nbsp; ROS_INFO_STREAM("Moving the arm to the center");



&nbsp; &nbsp; // Request centered joint angles [1.57, 1.57]

&nbsp; &nbsp; simple_arm::GoToPosition srv;

&nbsp; &nbsp; srv.request.joint_1 = 1.57;

&nbsp; &nbsp; srv.request.joint_2 = 1.57;



&nbsp; &nbsp; // Call the safe_move service and pass the requested joint angles

&nbsp; &nbsp; if (!client.call(srv))

&nbsp; &nbsp; &nbsp; &nbsp; ROS_ERROR("Failed to call service safe_move");

}



// This callback function continuously executes and reads the arm joint angles position

void joint_states_callback(const sensor_msgs::JointState js)

{

&nbsp; &nbsp; // Get joints current position

&nbsp; &nbsp; std::vector<double> joints_current_position = js.position;



&nbsp; &nbsp; // Define a tolerance threshold to compare double values

&nbsp; &nbsp; double tolerance = 0.0005;



&nbsp; &nbsp; // Check if the arm is moving by comparing its current joints position to its latest

&nbsp; &nbsp; if (fabs(joints_current_position[0] - joints_last_position[0]) < tolerance && fabs(joints_current_position[1] - joints_last_position[1]) < tolerance)

&nbsp; &nbsp; &nbsp; &nbsp; moving_state = false;

&nbsp; &nbsp; else {

&nbsp; &nbsp; &nbsp; &nbsp; moving_state = true;

&nbsp; &nbsp; &nbsp; &nbsp; joints_last_position = joints_current_position;

&nbsp; &nbsp; }

}



// This callback function continuously executes and reads the image data

void look_away_callback(const sensor_msgs::Image img)

{



&nbsp; &nbsp; bool uniform_image = true;



&nbsp; &nbsp; // Loop through each pixel in the image and check if its equal to the first one

&nbsp; &nbsp; for (int i = 0; i < img.height * img.step; i++) {

&nbsp; &nbsp; &nbsp; &nbsp; if (img.data[i] - img.data[0] != 0) {

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uniform_image = false;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;

&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; }



&nbsp; &nbsp; // If the image is uniform and the arm is not moving, move the arm to the center

&nbsp; &nbsp; if (uniform_image == true && moving_state == false)

&nbsp; &nbsp; &nbsp; &nbsp; move_arm_center();

}



int main(int argc, char** argv)

{

&nbsp; &nbsp; // Initialize the look_away node and create a handle to it

&nbsp; &nbsp; ros::init(argc, argv, "look_away");

&nbsp; &nbsp; ros::NodeHandle n;



&nbsp; &nbsp; // Define a client service capable of requesting services from safe_move

&nbsp; &nbsp; client = n.serviceClient<simple_arm::GoToPosition>("/arm_mover/safe_move");



&nbsp; &nbsp; // Subscribe to /simple_arm/joint_states topic to read the arm joints position inside the joint_states_callback function

&nbsp; &nbsp; ros::Subscriber sub1 = n.subscribe("/simple_arm/joint_states", 10, joint_states_callback);



&nbsp; &nbsp; // Subscribe to rgb_camera/image_raw topic to read the image data inside the look_away_callback function

&nbsp; &nbsp; ros::Subscriber sub2 = n.subscribe("rgb_camera/image_raw", 10, look_away_callback);



&nbsp; &nbsp; // Handle ROS communication events

&nbsp; &nbsp; ros::spin();



&nbsp; &nbsp; return 0;

}
```

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.