├── README.md
├── chapter10
├── gmapping_only.launch
├── scan_to_ogm.cpp
├── tf_echo2.cpp
└── tf_pub.cpp
├── chapter11
├── encoder_odom_publisher.cpp
├── manual_pose_and_goal_pub.cpp
├── rviz_click_to_2d.cpp
└── tf_pub.cpp
├── chapter12
├── simple_diff_drive.cpp
└── simple_drive_controller.cpp
├── chapter13
├── costmap_basic.yaml
├── path_planner.cpp
└── practical_common.h
├── chapter14
└── tick_publisher.cpp
├── chapter15
└── ultrasonic_publisher.cpp
├── chapter16
├── lsm303_l3gd20h_imu_pub.cpp
└── lsm303_l3gd20h_imu_w_mag.cpp
├── chapter19
├── cv_bridge_test.cpp
├── cv_test_node.cpp
├── hsv_colors.cpp
└── image_transport_test.cpp
├── chapter2
├── hello_blink.cpp
├── hello_button.cpp
└── hello_callback.cpp
├── chapter20
└── odom_plus.cpp
├── chapter3
├── hello_roomba.cpp
└── roomba_sleep.cpp
├── chapter4
└── hello_motor.cpp
├── chapter5
├── hello_i2c_lsm303.cpp
└── hello_serial.cpp
└── chapter9
├── go_to_x.cpp
├── go_to_xy.cpp
└── simple_goal_pub.cpp
/README.md:
--------------------------------------------------------------------------------
1 | # practical-robotics-hello-programs
2 | This repository contains code examples that accompany the book Practical Robotics in C++ by Lloyd Brombach.
3 | Visit https://github.com/lbrombach/practical_robot for the entire ROS package for the robot project build from chapter 21.
4 |
--------------------------------------------------------------------------------
/chapter10/gmapping_only.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | //tf broadcast period in seconds. default = .05. 0 to disable
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/chapter10/scan_to_ogm.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *scan_to_ogm_beta.cpp
3 | *
4 | *This is a simple ROS node that accepts synchronized laser scan and pose messages to build an
5 | *occupancy grid map and publish that map message. This is an example program to accompany chapter 10
6 | *of the book Practical Robotics in C++ by Lloyd Brombach and published by Packt Publishing.
7 | *
8 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
9 | *10/24/2019
10 | */
11 |
12 | #include "ros/ros.h"
13 | #include
14 | #include
15 | #include
16 | #include "nav_msgs/OccupancyGrid.h"
17 | #include "geometry_msgs/PoseStamped.h"
18 | #include
19 | #include
20 | #include
21 |
22 | using namespace std;
23 | using namespace sensor_msgs;
24 | using namespace geometry_msgs;
25 | using namespace message_filters;
26 |
27 | //manual offset for laser position. Better to handle with transforms
28 | double transformX = .1;
29 |
30 | //map metadata
31 | const int width = 100;
32 | const int height = 100;
33 | const double resolution = .1;
34 |
35 | const int MIN_INTENSITY = 45;
36 |
37 | ros::Publisher pubMap;
38 | nav_msgs::OccupancyGrid::Ptr mapPtr(new nav_msgs::OccupancyGrid());
39 |
40 |
41 | //helper functions to make sure we iterate from low to high in marking
42 | double getStart(double collision, double currentPose, double border = 0)
43 | {
44 | double start = (currentPose < collision) ? currentPose : collision;
45 | start = (start < 0) ? 0 : start;
46 | return start;
47 | }
48 | double getEnd(double collision, double currentPose, double border)
49 | {
50 | double end = (currentPose < collision) ? collision : currentPose;
51 | end = (end > border) ? border : end;
52 | return end;
53 | }
54 |
55 | //decreases occupancy value of cells that fall between laser and detected obstacle
56 | void mark_free(double xCollision, double yCollision, double scanAngle, const PoseStampedConstPtr &pose)
57 | {
58 | mapPtr->data[mapPtr->info.width * (int)pose->pose.position.y*10 + int(pose->pose.position.x)*10] = 0;
59 | double m = -1;
60 | double b = -1;
61 | int start = 0;
62 | int last = 0;
63 | int ogmIndex = 0;
64 | int y = 0;
65 | // cannot divide by zero, vertical line so mark every cell free until hit
66 | if (pose->pose.position.x == xCollision)
67 | {
68 | for (int y = (int)(getStart(yCollision, pose->pose.position.y, 0) * resolution); y < (int)(getEnd(yCollision, pose->pose.position.y, height - 1) * resolution); y++)
69 | {
70 | // decrement is by four. Make sure we don't go below zero
71 | mapPtr->data[mapPtr->info.width * y + (int)(pose->pose.position.x * resolution)] -= 15;
72 | mapPtr->data[mapPtr->info.width * y + (int)(pose->pose.position.x * resolution)] =
73 | (mapPtr->data[mapPtr->info.width * y + (int)(pose->pose.position.x * resolution)] <0) ?
74 | 0 : mapPtr->data[mapPtr->info.width * y + (int)(pose->pose.position.x * resolution)];
75 | }
76 | }
77 | else
78 | {
79 | /*
80 | /checks y coordinate at every .5 resolution * x val. Decrements cells on that lines closer to "free" until
81 | /we hit border or obstacle cell. Here we start by calulating y=m*x+b slope and b values
82 | */
83 | m = (pose->pose.position.y - yCollision) / (pose->pose.position.x - xCollision);
84 | b = (pose->pose.position.y / resolution * 10) - (m * pose->pose.position.x / resolution * 10);
85 |
86 | //order for iterating. Everything multiplied by 10 so we can iterate at higher resolution than 1x per cell
87 | start = getStart(xCollision, pose->pose.position.x, 0) / resolution * 10;
88 | last = getEnd(xCollision, pose->pose.position.x, width / resolution) / resolution * 10;
89 | //iterate every .5 cell widths. Decrease occupancy value by 4 per scan
90 | for (int x = start; x < last; x += 5)
91 | {
92 | y = m * x + b;
93 | ogmIndex = (mapPtr->info.width * (y / 10)) + (x / 10);
94 | if (y > 0 && y / 10 < mapPtr->info.width)
95 | {
96 | mapPtr->data[ogmIndex] -= 15;
97 | mapPtr->data[ogmIndex] = (mapPtr->data[ogmIndex] < 0) ? 0 : mapPtr->data[ogmIndex];
98 | }
99 | }
100 | }
101 | }
102 |
103 |
104 | //callback function that happens whenwe recieve pose with close enough time stamp to scan
105 | //ranges are in meters. cells are .1 meters so we mulitply hits by 10 to identify cell x and y
106 | void scan_handler(const PoseStampedConstPtr &pose, const LaserScanConstPtr &scan)
107 | {
108 | int xHit = -1;
109 | int yHit = -1;
110 | for (int i = 0; i < scan->ranges.size(); i++)
111 | {
112 | //look for objects detected over every scan index when intesity is high enough
113 | if (scan->intensities[i] > MIN_INTENSITY )
114 | {
115 | double scanAngle = pose->pose.orientation.z + scan->angle_min + (i * scan->angle_increment);
116 | double xDistance = cos(scanAngle) * scan->ranges[i];
117 | double yDistance = sin(scanAngle) * scan->ranges[i];
118 | double xCollision = xDistance + pose->pose.position.x;
119 | double yCollision = yDistance + pose->pose.position.y;
120 | //mark free whatever is between laser and object detected
121 | //x and y map coordinates of detection
122 | xHit = int(xCollision * 10);
123 | yHit = int(yCollision * 10);
124 | //increase occupancy value by 20 whatever cell the object is detected in to a max value of 100
125 | if (xHit >= 0 && xHit < width && yHit >= 0 && yHit < height && mapPtr->data[mapPtr->info.width * yHit + xHit] <= 100&& scan->ranges[i] > scan->range_min && scan->ranges[i] < scan->range_max )
126 | {
127 | mapPtr->data[mapPtr->info.width * yHit + xHit] += 25;
128 | if(mapPtr->data[mapPtr->info.width * yHit + (xHit+1)]data.size())
129 | mapPtr->data[mapPtr->info.width * yHit + (xHit+1)] += 25;
130 |
131 | mapPtr->data[mapPtr->info.width * yHit + xHit] = (mapPtr->data[mapPtr->info.width * yHit + xHit] > 100) ? 100 : mapPtr->data[mapPtr->info.width * yHit + xHit];
132 | }
133 | if(scan->ranges[i] > scan->range_min && scan->ranges[i] < scan->range_max)
134 | {
135 | mark_free(xCollision, yCollision, scanAngle, pose);
136 | }
137 |
138 | }
139 | }
140 | mapPtr->header.stamp = ros::Time::now();
141 | pubMap.publish(mapPtr);
142 | }
143 |
144 | int main(int argc, char **argv)
145 | {
146 |
147 | mapPtr->info.width = width;
148 | mapPtr->info.height = height;
149 | mapPtr->info.resolution = resolution;
150 |
151 | mapPtr->info.origin.position.x = 0;
152 | mapPtr->info.origin.position.y = 0;
153 | //init all cells to unknown
154 | mapPtr->data.resize(mapPtr->info.width * mapPtr->info.height);
155 | for (int i = 0; i < mapPtr->data.size(); i++)
156 | {
157 | mapPtr->data[i] = -1;
158 | }
159 |
160 |
161 | ros::init(argc, argv, "simple_OGM_Pub");
162 | ros::NodeHandle node;
163 | //use filters and approximate sync to run callack when timestamps are close enough
164 | message_filters::Subscriber pose_sub(node, "pose", 1);
165 | message_filters::Subscriber scan_sub(node, "scan", 1);
166 |
167 | typedef sync_policies::ApproximateTime MySyncPolicy;
168 | // ApproximateTime takes a queue size as its constructor argument, hence MySyncPolicy(10)
169 | Synchronizer sync(MySyncPolicy(10), pose_sub, scan_sub);
170 | sync.registerCallback(boost::bind(&scan_handler, _1, _2));
171 |
172 | //advertise publisher
173 | pubMap = node.advertise("map", 0);
174 |
175 | while (ros::ok())
176 | {
177 | ros::spin();
178 | }
179 |
180 | return 0;
181 | }
182 |
--------------------------------------------------------------------------------
/chapter10/tf_echo2.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *tf_echo2.cpp is a simple ROS node that displays odom to base_link
3 | *transform data based on a lookupTransform() call.
4 | *Intent is a simple utility that helps teach converting from quaternion to euler angle.
5 | *This program is an accompaniment to the book Practical Robotics in C++
6 | *written by Lloyd Brombach and published by Packt Publishing
7 | *
8 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
9 | *11/7/2019
10 | */
11 |
12 | #include "ros/ros.h"
13 | #include
14 | #include
15 | #include "std_msgs/Float32.h"
16 |
17 | using namespace std;
18 |
19 | float z = 0;
20 |
21 | int main(int argc, char **argv)
22 | {
23 | //handshake with ros master
24 | ros::init(argc, argv, "tf_echo2");
25 | ros::NodeHandle node;
26 |
27 | //set to 1 hz
28 | ros::Rate loop_rate(1);
29 | while (ros::ok())
30 | {
31 | ros::spinOnce();
32 | //create listener. Must be static.
33 | static tf::TransformListener listener;
34 |
35 | //create transform object
36 | tf::StampedTransform odom_base_tf;
37 |
38 | //test if transform available
39 | if (listener.canTransform("odom", "base_link", ros::Time(0), NULL))
40 | {
41 | //get transform data
42 | listener.lookupTransform("odom", "base_link", ros::Time(0), odom_base_tf);
43 |
44 | //convert quaternion data to Euler angle
45 | tf::Quaternion q(0, 0, odom_base_tf.getRotation().z(), odom_base_tf.getRotation().w());
46 | tf::Matrix3x3 m(q);
47 | double roll, pitch, yaw;
48 | m.getRPY(roll, pitch, yaw);
49 | z = yaw;
50 |
51 | //display transform data
52 | cout << odom_base_tf.getOrigin().x() << ", " << odom_base_tf.getOrigin().y() << " " << z << endl;
53 | }
54 | else
55 | {
56 | cout << "UNABLE TO LOOKUP ODOM -> BASE_LINK TRANSFORM" << endl;
57 | }
58 | loop_rate.sleep();
59 | }
60 |
61 | return 0;
62 | }
63 |
--------------------------------------------------------------------------------
/chapter10/tf_pub.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *tf_pub.cpp is a simple ROS node that subscribes to an odometry message
3 | *and publishes an odom to base_link transform based on that data.
4 | *Unlike some of node and messages we use for examples in Practical Robotics in C++,
5 | *tf_pub uses quaternions and illustrates how to conver euler to quaternion
6 | *This program is an accompaniment to the book Practical Robotics in C++
7 | *written by Lloyd Brombach and published by Packt Publishing
8 | *
9 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
10 | *11/7/2019
11 | */
12 |
13 | #include "ros/ros.h"
14 | #include
15 | #include
16 | #include "std_msgs/Float32.h"
17 |
18 |
19 | using namespace std;
20 |
21 | //callback function that broadcasts odom message data as a transform
22 | //this odom message should contain an euler angle in the orientation z data member
23 | void handle_odom(const nav_msgs::Odometry &odom)
24 | {
25 | //create broadcater object
26 | static tf::TransformBroadcaster br;
27 | //create transform object
28 | tf::Transform odom_base_tf;
29 |
30 | //set transform x, y location with data from odom message
31 | odom_base_tf.setOrigin( tf::Vector3(odom.pose.pose.position.x, odom.pose.pose.position.y, 0.0) );
32 | //create quaternion object from euler data received in odom
33 | tf::Quaternion tf_q(odom.pose.pose.orientation.x, odom.pose.pose.orientation.y,
34 | odom.pose.pose.orientation.z, odom.pose.pose.orientation.w);
35 |
36 | //add quaternion data to transform object
37 | odom_base_tf.setRotation(tf_q);
38 |
39 | //broadcast transform
40 | br.sendTransform(tf::StampedTransform(odom_base_tf, odom.header.stamp, "odom", "base_link"));
41 |
42 | }
43 |
44 |
45 | int main(int argc, char **argv)
46 | {
47 | ros::init(argc, argv, "tf_pub");
48 | ros::NodeHandle node;
49 |
50 | ros::Subscriber subOdom = node.subscribe("encoder/odom_quat", 10, handle_odom);
51 |
52 | ros::Rate loop_rate(30);
53 | while (ros::ok())
54 | {
55 | ros::spinOnce();
56 | loop_rate.sleep();
57 | }
58 |
59 | return 0;
60 | }
61 |
--------------------------------------------------------------------------------
/chapter11/encoder_odom_publisher.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *This is a ROS node that subscribes to encoder count messages and publishes odometry
3 | *on in a simple form where orientation.z is an euler angle, then publishes again
4 | *on a topic that uses the quaternion version of the message. This is written
5 | *to be readable for all levels and accompanies the book Practical Robotics in C++.
6 | *
7 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
8 | *11/7/2019
9 | */
10 |
11 | #include "ros/ros.h"
12 | #include "std_msgs/Int16.h"
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | ros::Publisher odom_pub;
20 | ros::Publisher pub_quat;
21 | nav_msgs::Odometry newOdom;
22 | nav_msgs::Odometry oldOdom;
23 |
24 | const double initialX = 0.0;
25 | const double initialY = 0.0;
26 | const double initialTheta = 0.00000000001;
27 | const double PI = 3.141592;
28 |
29 | const double ticksPerwheelRev = 508.8; //not in use. just a reference for now
30 | const double wheelRadius = .03575; // 55.18;
31 | const double WHEEL_BASE = .224; //223.8375mm actually
32 | const double TICKS_PER_M = 1125*2; //1.1645; //1.365 is on hard floor. carpet avg is 1.1926. overall avg = 1.1645
33 |
34 | double leftDistance = 0;
35 | double rightDistance = 0;
36 | bool initialPoseRecieved = false;
37 |
38 |
39 | using namespace std;
40 |
41 | //accept initial_2d pose message form clicks or manual publisher
42 | //at at least one is required to start this node publishing
43 | void setInitialPose(const geometry_msgs::PoseStamped &rvizClick)
44 | {
45 | oldOdom.pose.pose.position.x = rvizClick.pose.position.x;
46 | oldOdom.pose.pose.position.y = rvizClick.pose.position.y;
47 | oldOdom.pose.pose.orientation.z = rvizClick.pose.orientation.z;
48 | initialPoseRecieved = true;
49 | }
50 |
51 | //calculate distance left wheel has traveled since last cycle
52 | void Calc_Left(const std_msgs::Int16& lCount)
53 | {
54 | static int lastCountL = 0;
55 | if(lCount.data!=0&&lastCountL!=0)
56 | {
57 | int leftTicks = (lCount.data - lastCountL);
58 |
59 | if (leftTicks > 10000)
60 | {
61 | leftTicks=0-(65535 - leftTicks);
62 | }
63 | else if (leftTicks < -10000)
64 | {
65 | leftTicks = 65535-leftTicks;
66 | }
67 | leftDistance = leftTicks/TICKS_PER_M;
68 | }
69 | lastCountL = lCount.data;
70 | }
71 |
72 | //calculate distance right wheel has traveled since last cycle
73 | void Calc_Right(const std_msgs::Int16& rCount)
74 | {
75 | static int lastCountR = 0;
76 | if(rCount.data!=0&&lastCountR!=0)
77 | {
78 | int rightTicks = rCount.data - lastCountR;
79 | if (rightTicks > 10000)
80 | {
81 | rightTicks=(0-(65535 - rightTicks))/TICKS_PER_M;
82 | }
83 | else if (rightTicks < -10000)
84 | {
85 | rightTicks = 65535 - rightTicks;
86 | }
87 | rightDistance = rightTicks/TICKS_PER_M;
88 | }
89 | lastCountR=rCount.data;
90 | }
91 |
92 | //publishes the simpler version of the odom message in full quaternion form
93 | //and includes covariance data. This covariance should be "personalized"
94 | //for every specific robot.
95 | void publish_quat()
96 | {
97 | tf2::Quaternion q;
98 | q.setRPY(0, 0, newOdom.pose.pose.orientation.z);
99 |
100 | nav_msgs::Odometry quatOdom;
101 | quatOdom.header.stamp = newOdom.header.stamp;
102 | quatOdom.header.frame_id = "odom";
103 | quatOdom.child_frame_id = "base_link";
104 | quatOdom.pose.pose.position.x = newOdom.pose.pose.position.x;
105 | quatOdom.pose.pose.position.y = newOdom.pose.pose.position.y;
106 | quatOdom.pose.pose.position.z = newOdom.pose.pose.position.z;
107 | quatOdom.pose.pose.orientation.x = q.x();
108 | quatOdom.pose.pose.orientation.y = q.y();
109 | quatOdom.pose.pose.orientation.z = q.z();
110 | quatOdom.pose.pose.orientation.w = q.w();
111 | quatOdom.twist.twist.linear.x = newOdom.twist.twist.linear.x;
112 | quatOdom.twist.twist.linear.y = newOdom.twist.twist.linear.y;
113 | quatOdom.twist.twist.linear.z = newOdom.twist.twist.linear.z;
114 | quatOdom.twist.twist.angular.x = newOdom.twist.twist.angular.x;
115 | quatOdom.twist.twist.angular.y = newOdom.twist.twist.angular.y;
116 | quatOdom.twist.twist.angular.z = newOdom.twist.twist.angular.z;
117 |
118 | for(int i = 0; i<36; i++)
119 | {
120 | if(i == 0 || i == 7 || i == 14)
121 | {
122 | quatOdom.pose.covariance[i] = .01;
123 | }
124 | else if (i == 21 || i == 28 || i== 35)
125 | {
126 | quatOdom.pose.covariance[i] += .165;
127 | }
128 | else
129 | {
130 | quatOdom.pose.covariance[i] = 0;
131 | }
132 | }
133 |
134 | pub_quat.publish(quatOdom);
135 |
136 | }
137 |
138 | void update_odom()
139 | {
140 | //average distance
141 | double cycleDistance = (rightDistance+leftDistance)/2;
142 | //how many radians robot has turned since last cycle
143 | double cycleAngle = asin((rightDistance-leftDistance)/WHEEL_BASE);
144 |
145 | //average angle during last cycle
146 | double avgAngle = cycleAngle/2 + oldOdom.pose.pose.orientation.z;
147 | if (avgAngle > PI)
148 | {
149 | avgAngle -= 2*PI;
150 | }
151 | else if (avgAngle < -PI)
152 | {
153 | avgAngle += 2*PI;
154 | }
155 |
156 | //calculate new x, y, and theta
157 | newOdom.pose.pose.position.x = oldOdom.pose.pose.position.x + cos(avgAngle)*cycleDistance;
158 | newOdom.pose.pose.position.y = oldOdom.pose.pose.position.y + sin(avgAngle)*cycleDistance;
159 | newOdom.pose.pose.orientation.z = cycleAngle + oldOdom.pose.pose.orientation.z;
160 |
161 | //prevent lockup from a single erroneous cycle
162 | if(isnan(newOdom.pose.pose.position.x) || isnan(newOdom.pose.pose.position.y)
163 | || isnan(newOdom.pose.pose.position.z) )
164 | {
165 | newOdom.pose.pose.position.x = oldOdom.pose.pose.position.x;
166 | newOdom.pose.pose.position.y = oldOdom.pose.pose.position.y;
167 | newOdom.pose.pose.orientation.z = oldOdom.pose.pose.orientation.z;
168 | }
169 |
170 | //keep theta in range proper range
171 | if (newOdom.pose.pose.orientation.z > PI)
172 | {
173 | newOdom.pose.pose.orientation.z -= 2*PI;
174 | }
175 | else if (newOdom.pose.pose.orientation.z < -PI)
176 | {
177 | newOdom.pose.pose.orientation.z += 2*PI;
178 | }
179 |
180 | //calculate velocity
181 | newOdom.header.stamp = ros::Time::now();
182 | newOdom.twist.twist.linear.x = cycleDistance/(newOdom.header.stamp.toSec() - oldOdom.header.stamp.toSec());
183 | newOdom.twist.twist.angular.z = cycleAngle/(newOdom.header.stamp.toSec() - oldOdom.header.stamp.toSec());
184 |
185 | //save odom x, y, and theta for use in next cycle
186 | oldOdom.pose.pose.position.x = newOdom.pose.pose.position.x;
187 | oldOdom.pose.pose.position.y = newOdom.pose.pose.position.y;
188 | oldOdom.pose.pose.orientation.z = newOdom.pose.pose.orientation.z;
189 | oldOdom.header.stamp = newOdom.header.stamp;
190 |
191 | //publish odom message
192 | odom_pub.publish(newOdom);
193 | }
194 |
195 | int main(int argc, char **argv)
196 | {
197 | //set fixed data fields
198 | newOdom.header.frame_id = "odom";
199 | newOdom.pose.pose.position.z = 0;
200 | newOdom.pose.pose.orientation.x = 0;
201 | newOdom.pose.pose.orientation.y = 0;
202 | newOdom.twist.twist.linear.x = 0;
203 | newOdom.twist.twist.linear.y = 0;
204 | newOdom.twist.twist.linear.z = 0;
205 | newOdom.twist.twist.angular.x = 0;
206 | newOdom.twist.twist.angular.y = 0;
207 | newOdom.twist.twist.angular.z = 0;
208 | oldOdom.pose.pose.position.x = initialX;
209 | oldOdom.pose.pose.position.y = initialY;
210 | oldOdom.pose.pose.orientation.z = initialTheta;
211 |
212 | //handshake with ros master and create node object
213 | ros::init(argc, argv, "encoder_odom_publisher");
214 | ros::NodeHandle node;
215 |
216 | //Subscribe to topics
217 | ros::Subscriber subForRightCounts = node.subscribe("rightWheel", 100, Calc_Right,ros::TransportHints().tcpNoDelay());
218 | ros::Subscriber subForLeftCounts = node.subscribe("leftWheel",100, Calc_Left,ros::TransportHints().tcpNoDelay());
219 | ros::Subscriber subInitialPose = node.subscribe("initial_2d", 1, setInitialPose);
220 |
221 | //advertise publisher of simpler odom msg where orientation.z is an euler angle
222 | odom_pub = node.advertise("encoder/odom", 100);
223 |
224 | //advertise publisher of full odom msg where orientation is quaternion
225 | pub_quat = node.advertise("encoder/odom_quat", 100);
226 |
227 |
228 | ros::Rate loop_rate(30);
229 | while(ros::ok())
230 | {
231 | ros::spinOnce();
232 | if(initialPoseRecieved)
233 | {
234 | update_odom();
235 | publish_quat();
236 | }
237 | loop_rate.sleep();
238 | }
239 |
240 | return 0;
241 | }
242 |
--------------------------------------------------------------------------------
/chapter11/manual_pose_and_goal_pub.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *manual_pose_and_goal.cpp
3 | *
4 | *This ros node publishes an initial_2d pose msg or goal_2d pose message as input by the user.
5 | *these msgs are published ignoring the quaternion nature and simply using z for yaw
6 | *the initialpose and move_base_simple/goal are also published as if user had clicked in rviz
7 | *
8 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
9 | *11/4/2019
10 | */
11 | #include "ros/ros.h"
12 | #include "geometry_msgs/PoseStamped.h"
13 | #include "geometry_msgs/PoseWithCovarianceStamped.h"
14 | #include
15 | #include
16 |
17 | using namespace std;
18 | ros::Publisher pub;
19 | ros::Publisher pub1;
20 | ros::Publisher pub2;
21 | ros::Publisher pub3;
22 |
23 |
24 | void pub_msg(float x, float y, float yaw, int choice)
25 | {
26 | geometry_msgs::PoseWithCovarianceStamped pose;
27 | geometry_msgs::PoseStamped goal;
28 | geometry_msgs::PoseStamped rpy;
29 | rpy.pose.position.x = x;
30 | rpy.pose.position.y = y;
31 | rpy.pose.position.z = 0;
32 | rpy.pose.orientation.x = 0;
33 | rpy.pose.orientation.y = 0;
34 | rpy.pose.orientation.z = yaw;
35 | rpy.pose.orientation.w = 0;
36 |
37 | if(choice == 1) //publish a pose
38 | {
39 | pose.header.frame_id = "map";
40 | pose.header.stamp = ros::Time::now();
41 | pose.pose.pose = rpy.pose;
42 | pub2.publish(rpy);
43 | }
44 | else //publish a goal
45 | {
46 | cout<<"publishering GOAL"<("goal_2d", 10);
80 | pub1 = node.advertise("move_base_simple/goal", 10);
81 | pub2 = node.advertise("initial_2d", 10);
82 | pub3 = node.advertise("initialpose", 10);
83 |
84 | while (ros::ok())
85 | {
86 | float x =-1;
87 | float y =-1;
88 | float yaw = -11;
89 | int choice = -1;
90 |
91 | while(choice < 1 || choice > 2
92 | || x < 0 || y < 0 || yaw < -3.141592 || yaw > 3.141592)
93 | {
94 | cout<<"\nEnter positive float value for x : "<>x;
96 | cout<<"\nEnter positive float value for y : "<>y;
98 | cout<<"\nEnter float value in for yaw in radians from -PI to +PI: "<>yaw;
100 |
101 |
102 | cout<<"\nEnter 1 if this is a pose, 2 if it is a goal"<>choice;
104 | if(cin.fail())
105 | {
106 | cin.clear();
107 | cin.ignore(50, '/n');
108 | choice = -1;
109 | }
110 | }
111 |
112 | pub_msg(x, y, yaw, choice);
113 |
114 | }
115 | return 0;
116 | }
117 |
--------------------------------------------------------------------------------
/chapter11/rviz_click_to_2d.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *rviz_click_to_2d.cpp
3 | *
4 | *This ros node subscribes to the clicked pose topics published by rviz
5 | *initialpose and move_base_simple/goal, and republishes the data as simple
6 | *2D poses using PoseStamped messages, after converting from quaternion to euler angles
7 | *Intended to accompany example robot/nodes along with the book Practical Robotics in C++
8 | *
9 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
10 | *11/4/2019
11 | */
12 | #include "ros/ros.h"
13 | #include "geometry_msgs/PoseStamped.h"
14 | #include "geometry_msgs/PoseWithCovarianceStamped.h"
15 | #include
16 | #include
17 | using namespace std;
18 | ros::Publisher pub;
19 | ros::Publisher pub2;
20 |
21 | void handle_goal(const geometry_msgs::PoseStamped &goal)
22 | {
23 | geometry_msgs::PoseStamped rpyGoal;
24 | rpyGoal.header.frame_id = "map";
25 | rpyGoal.header.stamp = goal.header.stamp;
26 | rpyGoal.pose.position.x = goal.pose.position.x;
27 | rpyGoal.pose.position.y = goal.pose.position.y;
28 | rpyGoal.pose.position.z = 0;
29 | tf::Quaternion q(0, 0, goal.pose.orientation.z, goal.pose.orientation.w);
30 | tf::Matrix3x3 m(q);
31 | double roll, pitch, yaw;
32 | m.getRPY(roll, pitch, yaw);
33 | rpyGoal.pose.orientation.x = 0;
34 | rpyGoal.pose.orientation.y = 0;
35 | rpyGoal.pose.orientation.z = yaw;
36 | rpyGoal.pose.orientation.w = 0;
37 | pub.publish(rpyGoal);
38 | }
39 | void handle_initial_pose(const geometry_msgs::PoseWithCovarianceStamped &pose)
40 | {
41 | geometry_msgs::PoseStamped rpyPose;
42 | rpyPose.header.frame_id = "map";
43 | rpyPose.header.stamp = pose.header.stamp;
44 | rpyPose.pose.position.x = pose.pose.pose.position.x;
45 | rpyPose.pose.position.y = pose.pose.pose.position.y;
46 | rpyPose.pose.position.z = 0;
47 | tf::Quaternion q(0, 0, pose.pose.pose.orientation.z, pose.pose.pose.orientation.w);
48 | tf::Matrix3x3 m(q);
49 | double roll, pitch, yaw;
50 | m.getRPY(roll, pitch, yaw);
51 | rpyPose.pose.orientation.x = 0;
52 | rpyPose.pose.orientation.y = 0;
53 | rpyPose.pose.orientation.z = yaw;
54 | rpyPose.pose.orientation.w = 0;
55 | pub2.publish(rpyPose);
56 | }
57 | int main(int argc, char **argv)
58 | {
59 | ros::init(argc, argv, "rviz_click_to_2d");
60 | ros::NodeHandle node;
61 | pub = node.advertise("goal_2d", 0);
62 | pub2 = node.advertise("initial_2d", 0);
63 | ros::Subscriber sub = node.subscribe("move_base_simple/goal", 0, handle_goal);
64 |
65 | ros::Subscriber sub2 = node.subscribe("initialpose", 0, handle_initial_pose);
66 | ros::Rate loop_rate(10);
67 | while (ros::ok())
68 | {
69 | ros::spinOnce();
70 | loop_rate.sleep();
71 | }
72 | return 0;
73 | }
74 |
--------------------------------------------------------------------------------
/chapter11/tf_pub.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *tf_pub.cpp is a simple ROS node that subscribes to an odometry message
3 | *and publishes an odom to base_link transform based on that data.
4 | *Unlike some of node and messages we use for examples in Practical Robotics in C++,
5 | *tf_pub uses quaternions and illustrates how to conver euler to quaternion
6 | *This program is an accompaniment to the book Practical Robotics in C++
7 | *written by Lloyd Brombach and published by Packt Publishing
8 | *
9 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
10 | *11/7/2019
11 | */
12 |
13 | #include "ros/ros.h"
14 | #include
15 | #include
16 | #include "std_msgs/Float32.h"
17 |
18 |
19 | using namespace std;
20 |
21 | //callback function that broadcasts odom message data as a transform
22 | //this odom message should contain an euler angle in the orientation z data member
23 | void handle_odom(const nav_msgs::Odometry &odom)
24 | {
25 | //create broadcater object
26 | static tf::TransformBroadcaster br;
27 | //create transform object
28 | tf::Transform odom_base_tf;
29 |
30 | //set transform x, y location with data from odom message
31 | odom_base_tf.setOrigin( tf::Vector3(odom.pose.pose.position.x, odom.pose.pose.position.y, 0.0) );
32 | //create quaternion object from euler data received in odom
33 | tf::Quaternion tf_q(odom.pose.pose.orientation.x, odom.pose.pose.orientation.y,
34 | odom.pose.pose.orientation.z, odom.pose.pose.orientation.w);
35 |
36 | //add quaternion data to transform object
37 | odom_base_tf.setRotation(tf_q);
38 |
39 | //broadcast transform
40 | br.sendTransform(tf::StampedTransform(odom_base_tf, odom.header.stamp, "odom", "base_link"));
41 |
42 | }
43 |
44 |
45 | int main(int argc, char **argv)
46 | {
47 | ros::init(argc, argv, "tf_pub");
48 | ros::NodeHandle node;
49 |
50 | ros::Subscriber subOdom = node.subscribe("encoder/odom_quat", 10, handle_odom);
51 |
52 | ros::Rate loop_rate(30);
53 | while (ros::ok())
54 | {
55 | ros::spinOnce();
56 | loop_rate.sleep();
57 | }
58 |
59 | return 0;
60 | }
61 |
--------------------------------------------------------------------------------
/chapter12/simple_diff_drive.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *simple_diff_drive.cpp
3 | *
4 | *This is a simple differential drive ROS node that accepts cmd_vel messages
5 | *and outputs PWM and direction pin signals to an L298 dual motor driver.
6 | *This is intended to be simple to read and implement, but lacks the robustness of a PID
7 | *Robot prioritizes angular velocities (turns) and stops forward motion while turning.
8 | *
9 | *Rather than a PID to maintain straight driving, a simpler check of the average drift
10 | *over several cycles is applied to a multiplier to modify the PWM to each wheel
11 | *This DRIFT_MULTIPLIER might need to be tweaked for individual wheel sets
12 | *
13 | *Pin numbers are as example project is wired in the book Practical Robotics in C++.
14 | *
15 | *The constants near the top should be "personalized" for your robot, but should be very close if using
16 | *Roomba wheel modules as I did in the book project.
17 | *
18 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
19 | *11/7/2019
20 | */
21 |
22 |
23 | #include "ros/ros.h"
24 | #include "geometry_msgs/Twist.h"
25 | #include "std_msgs/Int16.h"
26 | #include
27 | #include
28 | #include
29 |
30 |
31 | const int PWM_INCREMENT = 1; //the rate pwm out can change per cycle
32 | const double ticksPerwheelRev = 254*2; //508.8; //not in use yet..just a reference for now
33 | const double wheelRadius = .03575; // 55.18;
34 | const double wheelBase = .224; //223.8375mm actually
35 | const double TICKS_PER_M = 1125*2; //1.1645; //1.365 is on hard floor. carpet avg is 1.1926. overall avg = 1.1645 1125.766 t/m
36 | const int KP = 238;
37 | const int DRIFT_MULTIPLIER = 125;
38 | const int TURN_PWM = 54;
39 | const int MIN_PWM = 52;
40 | const int MAX_PWM = 100;
41 |
42 | //left motor
43 | const int PWM_L = 21;
44 | const int MOTOR_L_FWD = 26;
45 | const int MOTOR_L_REV = 13;
46 | //right motor
47 | const int PWM_R = 12;
48 | const int MOTOR_R_FWD = 20;
49 | const int MOTOR_R_REV = 19;
50 |
51 | double leftVelocity = 0;
52 | double rightVelocity = 0;
53 | double leftPwmReq = 0;
54 | double rightPwmReq = 0;
55 | double lastCmdMsgRcvd = 0; //time of last command recieved
56 |
57 |
58 | int pi =-1;
59 |
60 | using namespace std;
61 |
62 |
63 | void Calc_Left_Vel(const std_msgs::Int16& lCount)
64 | {
65 | static double lastTime = 0;
66 | static int lastCount = 0;
67 | int cycleDistance = (65535 + lCount.data - lastCount) % 65535;
68 | if (cycleDistance > 10000)
69 | {
70 | cycleDistance=0-(65535 - cycleDistance);
71 | }
72 | leftVelocity = cycleDistance/TICKS_PER_M/(ros::Time::now().toSec()-lastTime);
73 | lastCount = lCount.data;
74 | lastTime = ros::Time::now().toSec();
75 | }
76 | void Calc_Right_Vel(const std_msgs::Int16& rCount)
77 | {
78 | static double lastTime = 0;
79 | static int lastCount = 0;
80 | int cycleDistance = (65535 + rCount.data - lastCount) % 65535;
81 | if (cycleDistance > 10000)
82 | {
83 | cycleDistance=0-(65535 - cycleDistance);
84 | }
85 | rightVelocity = cycleDistance/TICKS_PER_M/(ros::Time::now().toSec()-lastTime);
86 | lastCount=rCount.data;
87 | lastTime = ros::Time::now().toSec();
88 | }
89 |
90 |
91 | void Set_Speeds(const geometry_msgs::Twist& cmdVel)
92 | {
93 | lastCmdMsgRcvd = ros::Time::now().toSec();
94 | int b = (cmdVel.linear.x > .025 && cmdVel.linear.x < .052) ? 45 : 40;
95 | leftPwmReq =KP*cmdVel.linear.x+b;
96 | rightPwmReq =KP*cmdVel.linear.x+b;
97 | cout<<"iniitial pwm request (left and right same) = "<< leftPwmReq< .0 )//standard gentle turn
101 | {
102 | leftPwmReq = -TURN_PWM;
103 | rightPwmReq = TURN_PWM;
104 | }
105 | else
106 | {
107 | leftPwmReq = TURN_PWM;
108 | rightPwmReq = -TURN_PWM;
109 | }
110 | if( abs(cmdVel.angular.z>.12))//turn a little faster if angle is greater that .12
111 | {
112 | leftPwmReq *= 1.1;
113 | rightPwmReq *= 1.1;
114 | }
115 |
116 | }
117 | else if(abs(cmdVel.linear.x) > .0478 ) // should equal about pwmval of 50
118 | {
119 | static double prevDiff = 0;
120 | static double prevPrevDiff = 0;
121 | double angularVelDifference = leftVelocity - rightVelocity; //how much faster one wheel is actually turning
122 | double avgAngularDiff = (prevDiff+prevPrevDiff+angularVelDifference)/3; //average several cycles
123 | prevPrevDiff=prevDiff;
124 | prevDiff = angularVelDifference;
125 |
126 | //apply corrective offset to each wheel to try and go straight
127 | leftPwmReq -= (int)(avgAngularDiff*DRIFT_MULTIPLIER);
128 | rightPwmReq += (int)(avgAngularDiff*DRIFT_MULTIPLIER);
129 | }
130 |
131 | //don't PWM values that don't do anything
132 | if(abs(leftPwmReq)< MIN_PWM)
133 | {
134 | leftPwmReq=0;
135 | }
136 |
137 | if(abs(rightPwmReq)0) //left fwd
167 | {
168 | gpio_write(pi, MOTOR_L_REV, 1);
169 | gpio_write(pi, MOTOR_L_FWD, 0);
170 | }
171 | else if(leftPwmReq<0) //left rev
172 | {
173 | gpio_write(pi, MOTOR_L_FWD, 1);
174 | gpio_write(pi, MOTOR_L_REV, 0);
175 | }
176 | else if (leftPwmReq == 0 && leftPwmOut == 0 ) //left stop
177 | {
178 | gpio_write(pi, MOTOR_L_FWD, 1);
179 | gpio_write(pi, MOTOR_L_REV, 1);
180 | }
181 |
182 | if(rightPwmReq>0 )//right fwd
183 | {
184 | gpio_write(pi, MOTOR_R_REV, 1);
185 | gpio_write(pi, MOTOR_R_FWD, 0);
186 | }
187 | else if(rightPwmReq<0) //right rev
188 | {
189 | gpio_write(pi, MOTOR_R_FWD, 1);
190 | gpio_write(pi, MOTOR_R_REV, 0);
191 | }
192 | else if (rightPwmReq == 0 && rightPwmOut == 0)
193 | {
194 | gpio_write(pi, MOTOR_R_FWD, 1);
195 | gpio_write(pi, MOTOR_R_REV, 1);
196 | }
197 |
198 |
199 | //bump up pwm if robot is having trouble starting from stopped
200 | if( leftPwmReq != 0 && leftVelocity == 0)
201 | {
202 | leftPwmReq *= 1.4;
203 | }
204 | if( rightPwmReq != 0 && rightVelocity == 0)
205 | {
206 | rightPwmReq *= 1.4;
207 | }
208 |
209 | //this section increments PWM changes instead of jarring/dangeroud sudden big changes
210 | if (abs(leftPwmReq) > leftPwmOut)
211 | {
212 | leftPwmOut += PWM_INCREMENT;
213 | }
214 | else if (abs(leftPwmReq) < leftPwmOut)
215 | {
216 | leftPwmOut -= PWM_INCREMENT;
217 | }
218 | if (abs(rightPwmReq) > rightPwmOut)
219 | {
220 | rightPwmOut += PWM_INCREMENT;
221 | }
222 | else if(abs(rightPwmReq) < rightPwmOut)
223 | {
224 | rightPwmOut -= PWM_INCREMENT;
225 | }
226 |
227 | //cap output at max defined in constants
228 | leftPwmOut = (leftPwmOut>MAX_PWM) ? MAX_PWM : leftPwmOut;
229 | rightPwmOut = (rightPwmOut>MAX_PWM) ? MAX_PWM : rightPwmOut;
230 |
231 | //limit output to a low of zero
232 | leftPwmOut = (leftPwmOut< 0 ) ? 0 : leftPwmOut;
233 | rightPwmOut = (rightPwmOut< 0) ? 0 : rightPwmOut;
234 |
235 | //write the pwm values tot he pins
236 | set_PWM_dutycycle(pi, PWM_L, leftPwmOut);
237 | set_PWM_dutycycle(pi, PWM_R, rightPwmOut);
238 |
239 | cout<<"PWM OUT LEFT AND RIGHT "<=0)
270 | {
271 | cout<<"daemon interface started ok at "< 1)
295 | {
296 | cout<<"NOT RECIEVING CMD_VEL - STOPPING MOTORS -- time sincel last = "<
21 | #include
22 | #include
23 | #include
24 |
25 | using namespace std;
26 |
27 | ros::Publisher pubVelocity;
28 | nav_msgs::Odometry odom;
29 | geometry_msgs::Twist cmdVel;
30 | geometry_msgs::PoseStamped desired;
31 | const double PI = 3.141592;
32 | const double Ka = .35;
33 | const double Klv = .66;
34 | const double initialX = 5.0;
35 | const double initialY = 5.0;
36 | const double angularTolerance = .1;
37 | const double distanceTolerance = .05;
38 | const double MAX_LINEAR_VEL = 1;
39 | bool waypointActive = false;
40 |
41 |
42 | void update_pose(const nav_msgs::Odometry ¤tOdom)
43 | {
44 | odom.pose.pose.position.x = currentOdom.pose.pose.position.x;
45 | odom.pose.pose.position.y = currentOdom.pose.pose.position.y;
46 | odom.pose.pose.orientation.z = currentOdom.pose.pose.orientation.z;
47 | }
48 |
49 | void update_goal(const geometry_msgs::PoseStamped &desiredPose)
50 | {
51 | cout<<"got new goal!"<("cmd_vel", 1);
149 |
150 | ros::Rate loop_rate(10);
151 | while (ros::ok())
152 | {
153 | ros::spinOnce();
154 | if(desired.pose.position.x != -1)
155 | {
156 | set_velocity();
157 | }
158 | cout << "goal = " << desired.pose.position.x << ", " << desired.pose.position.y << endl
159 | << "current x,y = " << odom.pose.pose.position.x << ", " << odom.pose.pose.position.y << endl
160 | << " Distance error = " << getDistanceError() << endl;
161 | cout << "cmd_vel = " << cmdVel.linear.x <<" , "<
16 | #include
17 | #include
18 | #include
19 |
20 | using namespace std;
21 |
22 | //create our subscriber and publisher.
23 | ros::Subscriber subMap, subPose, subGoal;
24 | ros::Publisher pub;
25 | ros::Publisher pathPub;
26 |
27 | //this is where we'll keep the working _map data
28 | nav_msgs::OccupancyGrid::Ptr _map(new nav_msgs::OccupancyGrid());
29 |
30 | //we'll use these points internally for our lists
31 | struct cell
32 | {
33 | cell() : index(-1), x(-1), y(-1), theta(-1), F(INT32_MAX), G(INT32_MAX), H(INT32_MAX),
34 | prevX(-1), prevY(-1) {}
35 | cell(const cell &incoming);
36 |
37 | int index; //the index in the nav_msgs::OccupancyGrid
38 | int x; //x, y as grid cells are pose in meters/mapResolution (10)
39 | int y;
40 | double theta; //the final waypoint is the goal and requires heading theta
41 | int F; //this cells total cost, will be calculated as G + H
42 | int G; //cost (distancetraveled) from start(current position)
43 | int H; //manhatten distance distance to target
44 | int prevX; //map grid coordinates of previous cell
45 | int prevY;
46 | };
47 | //copy constructor
48 | cell::cell(const cell &incoming)
49 | {
50 | index = incoming.index;
51 | x = incoming.x;
52 | y = incoming.y;
53 | theta = incoming.theta;
54 | F = incoming.F;
55 | G = incoming.G;
56 | H = incoming.H;
57 | prevX = incoming.prevX;
58 | prevY = incoming.prevY;
59 | }
60 |
61 | cell start;
62 | cell goal;
63 | bool goalActive = false;
64 |
65 | //copy the supplied costmap to a new _map we can access freely
66 | void map_handler(const nav_msgs::OccupancyGridPtr &costmap)
67 | {
68 | static bool init_complete = false;
69 | //only do this stuff the first time a map is recieved.
70 | if (init_complete == false)
71 | {
72 | _map->header.frame_id = costmap->header.frame_id;
73 | _map->info.resolution = costmap->info.resolution;
74 | _map->info.width = costmap->info.width;
75 | _map->info.height = costmap->info.height;
76 | _map->info.origin.position.x = costmap->info.origin.position.x;
77 | _map->info.origin.position.y = costmap->info.origin.position.y;
78 | _map->info.origin.orientation.x = costmap->info.origin.orientation.x;
79 | _map->info.origin.orientation.y = costmap->info.origin.orientation.y;
80 | _map->info.origin.orientation.z = costmap->info.origin.orientation.z;
81 | _map->info.origin.orientation.w = costmap->info.origin.orientation.w;
82 | _map->data.resize(costmap->data.size());
83 |
84 | cout << "Map recieved. Initializing _map size "
85 | << _map->info.width << " x " << _map->info.height<<" = "<data.size() <<" at resolution "
86 | << _map->info.resolution<<"\nOrigin: "
87 | << _map->info.origin.position.x<<", "<< _map->info.origin.position.x<info.origin.position.x / _map->info.resolution);
95 | int originY = 1 - (_map->info.origin.position.y / _map->info.resolution);
96 |
97 |
98 | //this part we can do every time to ensure we see updates.
99 | //copy the contents of the costmap into the occupancy grid path_planner uses internally
100 | //starting at 0, 0. Data at x<0 or y<0 in the gmap is lost, so set start position accordingly
101 | for(int row = originY; row < _map->info.height ; row++)
102 | {
103 | for(int col = originX; col < _map->info.width; col++)
104 | {
105 | _map->data[getIndex(col-originY, row-originX, costmap)]
106 | = costmap->data[getIndex(col, row, costmap)];
107 | }
108 | }
109 |
110 | }
111 |
112 | //set our start cell as the current grid cell
113 | bool update_start_cell()
114 | {
115 | static tf::TransformListener listener;
116 | tf::StampedTransform odom_base_tf;
117 |
118 | if(listener.canTransform("odom","base_link", ros::Time(0), NULL))
119 | {
120 | listener.lookupTransform("odom", "base_link", ros::Time(0), odom_base_tf);
121 |
122 | //dont forget that grid cell is pose in meters / map resolution
123 | start.x = odom_base_tf.getOrigin().x()/ map_resolution(_map);
124 | start.y = odom_base_tf.getOrigin().y()/ map_resolution(_map);
125 |
126 | tf::Quaternion q(0, 0, odom_base_tf.getRotation().z(), odom_base_tf.getRotation().w());
127 | tf::Matrix3x3 m(q);
128 | double roll, pitch, yaw;
129 | m.getRPY(roll, pitch, yaw);
130 | start.theta = yaw;
131 | start.index = getIndex(start.x, start.y, _map);
132 | return true;
133 | }
134 | else
135 | {
136 | cout<<"UNABLE TO LOOKUP ODOM -> BASE_LINK TRANSFORM, no path planned"< &list, int toCheck)
156 | {
157 | for (int i = 0; i < list.size(); i++)
158 | {
159 | if (list[i].index == toCheck)
160 | {
161 | return true;
162 | }
163 | }
164 | //if not found in above loop it is not found
165 | return false;
166 | }
167 |
168 | //helper to calculate G - the cost to traverse this particular cell
169 | double getG(int x, int y, int currentX, int currentY, double currentG)
170 | {
171 | //cost is infinite if cell is obstacle
172 | if (is_obstacle(x, y, _map))
173 | {
174 | return INT32_MAX;
175 | }
176 | //if cell is not diagonal, the cost to move is 10
177 | else if (x == currentX || y == currentY)
178 | {
179 | return currentG + 10;
180 | }
181 | //cost is 14.142 if cell is diagonal
182 | else
183 | {
184 | return currentG + 14.142;
185 | }
186 | }
187 |
188 | //helper to calculate H heuristic - the manhatten distance from this cell to goal
189 | double getH(int x, int y)
190 | {
191 | return (abs(goal.x - x) + abs(goal.y - y)) * 10;
192 | }
193 |
194 | //helper to calculate F, but avoid integer rollover
195 | double getF(int g, int h)
196 | {
197 | if (g == INT32_MAX)
198 | {
199 | return g;
200 | }
201 | else
202 | {
203 | return g + h;
204 | }
205 | }
206 |
207 | //deletes all waypoints between start and the furthest waypoint with a straight, unobstructed path
208 | void optimize(vector &path)
209 | {
210 | //is there an obstacle between start and cell we're checking
211 | bool obstacle_on_line = true;
212 |
213 | // path[0] = index of goal cell in path[]
214 | int furthestFreeCell = 0;
215 |
216 | //starting at last goal (path[0]) and checking each waypoint until we find clear straight line to a cell
217 | while (obstacle_on_line == true && path[furthestFreeCell++].index != path.back().index)
218 | {
219 | cout<<"furthest free cell = "< &path)
311 | {
312 | nav_msgs::Path waypoints;
313 | waypoints.header.frame_id = "map";
314 | waypoints.header.stamp = ros::Time::now();
315 | waypoints.poses.resize(path.size());
316 |
317 | for (int i = 0; i < path.size(); i++)
318 | {
319 | //path finds integers of grid cells, we need to publish waypoints
320 | //as doubles, and add .05 meters so we aim for middle of the cells
321 | waypoints.poses[i].header.frame_id = "map";
322 | waypoints.poses[i].header.stamp = ros::Time::now();
323 | waypoints.poses[i].pose.position.x = (double)(path[i].x) / 10 + .05;
324 | waypoints.poses[i].pose.position.y = (double)(path[i].y) / 10 + .05;
325 | waypoints.poses[i].pose.position.z = 0;
326 | waypoints.poses[i].pose.orientation.x = 0;
327 | waypoints.poses[i].pose.orientation.y = 0;
328 | waypoints.poses[i].pose.orientation.z = 0;
329 | waypoints.poses[i].pose.orientation.w = 1;
330 | }
331 | pathPub.publish(waypoints);
332 | }
333 |
334 | //trace the path through the closed list and return the next waypoint to be published
335 | int trace(vector &closed)
336 | {
337 | vector path;
338 | //closed.back() will be our goal, and will be element [0] in path
339 | path.push_back(cell(closed.back()));
340 | bool pathComplete = false;
341 | while (pathComplete == false)
342 | {
343 | bool found = false;
344 | //check the closed list for the parent cell of the last cell in path[]
345 | for (int i = 0; found == false && i < closed.size(); i++)
346 | {
347 | //when we find the parent cell, push it to path and we will lool for its parent cell next
348 | if (closed[i].x == path.back().prevX && closed[i].y == path.back().prevY)
349 | {
350 | path.push_back(cell(closed[i]));
351 | found = true;
352 | }
353 | }
354 | //when last element in path[] is the same as our start, we have the complete path
355 | if (path.back().index == start.index)
356 | {
357 | pathComplete = true;
358 | }
359 | }
360 |
361 | //remove waypoints between start and whicever cell we cen get to going in a straght line
362 | if(path.size() > 2)
363 | {
364 | optimize(path);
365 | }
366 |
367 | //optionally call this function to publish path to visualize on rviz
368 | publishPathforRviz(path);
369 |
370 | //the waypoint at back() is currently our start point. By removing it, the new back() will
371 | //be our first waypoint - the one we need to publish to the drive controller.
372 | if (path.back().index != goal.index)
373 | {
374 | path.pop_back();
375 | }
376 |
377 | //if goal, publish goal heading, else publish the heading we took to get here anyway
378 | if (path.back().index != path.front().index)
379 | {
380 | double deltaX = path.back().x - start.x;
381 | double deltaY = path.back().y - start.y;
382 | path.back().theta = atan2(deltaY, deltaX);
383 | }
384 | publish_waypoint(path.back());
385 | return path.back().index;
386 | }
387 |
388 | int find_path()
389 | {
390 | vector open;
391 | vector closed;
392 | cell current(start);
393 | //special case start G must be initialized to 0
394 | current.G = 0;
395 | current.H = getH(start.x, start.y);
396 | current.F = current.G + current.H;
397 | current.index = (getIndex(current.x, current.y, _map));
398 | open.push_back(cell(current));
399 |
400 | //main loop cycles until .H = 0, which means we have found the goal
401 | while (current.H > 0)
402 | {
403 | //iterate over the cells all around the current cell
404 | for (int x = current.x - 1; x <= current.x + 1; x++)
405 | {
406 | for (int y = current.y - 1; y <= current.y + 1; y++)
407 | {
408 | //don't check elements out of array bounds
409 | if (is_in_bounds(x, y, _map))
410 | {
411 | //when we run out of open elements, the must not be a path
412 | if (open.size() == 0)
413 | {
414 | cout << "NO PATH FOUND" << endl;
415 | goalActive = false;
416 | return -1;
417 | }
418 | //if in the open list, check for lower cost path to here
419 | if (contains(open, getIndex(x, y, _map)) == true)
420 | {
421 | //iterate the list until we find the relevant cell
422 | int i = 0;
423 | while (open[i].index != getIndex(x, y, _map))
424 | {
425 | i++;
426 | }
427 |
428 | int tempG = getG(x, y, current.x, current.y, current.G);
429 | int tempH = getH(x, y);
430 | //if this calculation results in lower F cost, replace cells parents with current cell
431 | if (tempG + tempH < open[i].F && tempG != INT32_MAX)
432 | {
433 | open[i].F = tempG + tempH;
434 | open[i].G = tempG;
435 | open[i].prevX = current.x;
436 | open[i].prevY = current.y;
437 | }
438 | }
439 | //if this one not not in open or closed list, add it
440 | else if (contains(closed, getIndex(x, y, _map)) == false)
441 | {
442 | //create the cell object with current cell data
443 | cell newCell;
444 | newCell.x = x;
445 | newCell.y = y;
446 | newCell.index = getIndex(x, y, _map);
447 | newCell.prevX = current.x;
448 | newCell.prevY = current.y;
449 | //calc G dis froms start
450 | newCell.G = getG(x, y, current.x, current.y, current.G);
451 | //calc H to goal
452 | newCell.H = getH(x, y);
453 | //calc F = g+h
454 | if (getF(newCell.G, newCell.H) < INT32_MAX)
455 | {
456 | newCell.F = getF(newCell.G, newCell.H);
457 | }
458 | //add to closed list if obstacle, else add to open list
459 | if (newCell.F == INT32_MAX)
460 | {
461 | closed.push_back(cell(newCell));
462 | }
463 | else
464 | {
465 | open.push_back(newCell);
466 | }
467 | }
468 | }
469 | }
470 | }
471 |
472 | //when we've checked all neighbors, add current cell to closed list and remove from open
473 | closed.push_back(cell(current));
474 | bool found = false;
475 | for (int i = 0; found == false; i++)
476 | {
477 | if (open[i].index == current.index)
478 | {
479 | open.erase(open.begin() + i);
480 | found = true;
481 | }
482 | }
483 |
484 | //find the cell in open list with lowest f cost
485 | int lowestF = 0;
486 | for (int i = 0; i < open.size(); i++)
487 | {
488 | if (open[i].F < open[lowestF].F)
489 | {
490 | lowestF = i;
491 | }
492 | }
493 |
494 | //now make the current = cell we found with lowest f cost
495 | current.index = open[lowestF].index;
496 | current.x = open[lowestF].x;
497 | current.y = open[lowestF].y;
498 | current.theta = open[lowestF].theta;
499 | current.F = open[lowestF].F;
500 | current.G = open[lowestF].G;
501 | current.H = open[lowestF].H;
502 | current.prevX = open[lowestF].prevX;
503 | current.prevY = open[lowestF].prevY;
504 | }
505 | //at this point, we have found the goal. set goal's parents to last cell found
506 | //then add to goal to closed list.
507 | goal.prevX = closed.back().x;
508 | goal.prevY = closed.back().y;
509 | closed.push_back(cell(goal));
510 |
511 | //trace back through closed list
512 | int nextWaypoint = trace(closed);
513 |
514 | ///////////////////TEMP____remove THIS setting false for moving robot//////////////////////////
515 | // goalActive = false;
516 | return nextWaypoint;
517 | }
518 |
519 | int main(int argc, char **argv)
520 | {
521 | ros::init(argc, argv, "path_planner");
522 | ros::NodeHandle node;
523 |
524 | //start tf::listener early so it has time to fill buffer
525 | update_start_cell();
526 |
527 | //subscribe to _map and goal location
528 | subMap = node.subscribe("costmap", 0, map_handler);
529 | subGoal = node.subscribe("goal_2d", 0, set_goal);
530 |
531 | //advertise publisher
532 | pub = node.advertise("waypoint_2d", 0);
533 | pathPub = node.advertise("path", 0);
534 |
535 | ros::Rate loop_rate(1);
536 | while (ros::ok())
537 | {
538 | if (goalActive == true)
539 | {
540 | if(update_start_cell() == false)
541 | {
542 | start.index = 0;
543 | goal.index = 0;
544 | }
545 | cout<< "start and goal = "
546 | << start.x << ", " << start.y << "......" << goal.x << ", " << goal.y << endl;
547 |
548 | //we have . Stop until we receive a new goal
549 | if (start.index == goal.index)
550 | {
551 | cout << "Arrived, goalActive set false" << endl;
552 | publish_waypoint(goal);
553 | goalActive = false;
554 | }
555 | // keep updating path every loop_rate()
556 | else
557 | {
558 | int nextWaypoint = find_path();
559 | if (nextWaypoint == -1)
560 | {
561 | cout << "NO PATH FOUND" << endl;
562 | goalActive = false;
563 | }
564 | }
565 | }
566 | ros::spinOnce();
567 | loop_rate.sleep();
568 | }
569 |
570 | return 0;
571 | }
572 |
--------------------------------------------------------------------------------
/chapter13/practical_common.h:
--------------------------------------------------------------------------------
1 | #ifndef PRACTICAL_COMMON_H_
2 | #define PRACTICAL_COMMON_H_
3 | #include "nav_msgs/OccupancyGrid.h"
4 | #include
5 | #include
6 |
7 |
8 |
9 | //cells from other sources set above this will be considered 100% occupied
10 | const int OCCUPIED_THRESHOLD = 50;
11 |
12 | // distance from center of robot to furthest exterior point, in meters
13 | const double ROBOT_RADIUS = .2;
14 |
15 |
16 | //3 helper functions to get x, y coordinates from index,
17 | //and index from x, y coordinates, based on index = ogm.info.width * y + x
18 | int getX(int index, const nav_msgs::OccupancyGridPtr &map)
19 | {
20 | return index % map->info.width;
21 | }
22 | int getY(int index, const nav_msgs::OccupancyGridPtr &map)
23 | {
24 | return index / map->info.width;
25 | }
26 | int getIndex(int x, int y, const nav_msgs::OccupancyGridPtr &map)
27 | {
28 | return map->info.width * y + x;
29 | }
30 |
31 |
32 | //helper function to stay in map boundaries and avoid segfaults
33 | bool is_in_bounds(int x, int y, const nav_msgs::OccupancyGridPtr &map)
34 | {
35 | return (x >= 0 && x < map->info.width && y >= 0 && y < map->info.height);
36 | }
37 |
38 | //helper to check if cell is marked unknown
39 | bool is_unknown(int x, int y, const nav_msgs::OccupancyGridPtr &map)
40 | {
41 | return ((int)map->data[getIndex(x, y, map)] == -1);
42 | }
43 |
44 |
45 | //helper to check if cell is to be considered an obstacle - includes cells marked unknown
46 | bool is_obstacle(int x, int y, const nav_msgs::OccupancyGridPtr &map)
47 | {
48 | // std::cout<data[getIndex(x, y, map)]<data[getIndex(x, y, map)] > OCCUPIED_THRESHOLD) || is_unknown(x, y, map);
50 | }
51 |
52 |
53 | //helper to return map resolution
54 | double map_resolution(const nav_msgs::OccupancyGridPtr &map)
55 | {
56 | return map->info.resolution;
57 | }
58 |
59 | //returns slope m from slope intercept formula y=m*x+b from two coordinate pairs
60 | //don't forget all coordinates must be either pose in meters or grid cells numbers
61 | // ( grid cell number = pose(in meters) / map_resolution )
62 | //DO NOT MIX POSE COORDINATES WITH GRID CELL COORDINATES - MAKE ALL THE SAME
63 | double get_m(double x1, double y1, double x2, double y2)
64 | {
65 | //****CAUTION< WILL THROW ERROR IF WE DIVIDE BY ZERO
66 | return (y1 - y2) / (x1 - x2);
67 | }
68 |
69 | // b as is the offset from slope intercept formula y=m*x+b
70 | //for b = y-(m*x)
71 | //DO NOT MIX POSE COORDINATES WITH GRID CELL COORDINATES - MAKE ALL THE SAME
72 | double get_b(double x1, double y1, double x2, double y2)
73 | {
74 | // cannot divide by zer0
75 | if(x1 != x2)
76 | {
77 | return y1 - (get_m(x1, y1, x2, y2) * x1);
78 | }
79 | else return x1; //line is vertical, so b = x1
80 | }
81 |
82 | //returns where y falls on a line between two supplied points, for the given x
83 | //returns Y from slope intercept forumula y=m*x+b, given x
84 | //DO NOT MIX POSE COORDINATES WITH GRID CELL COORDINATES - MAKE ALL THE SAME
85 | //****DOES NOT HANDLE VERTICAL LINES****
86 | double get_y_intercept(double x1, double y1, double x2, double y2, double checkX)
87 | {
88 | double m = get_m(x1, y1, x2, y2);
89 | double b = get_b(x1, y1, x2, y2);
90 | return m * checkX + b;
91 | }
92 |
93 | //returns where y falls on a line between two supplied points, for the given y
94 | //returns x from slope intercept forumula y=m*x+b, given y. for x= (y-b)/m
95 | //DO NOT MIX POSE COORDINATES WITH GRID CELL COORDINATES - MAKE ALL THE SAME
96 | //****DOES NOT HANDLE VERTICAL LINES****
97 | double get_x_intercept(double x1, double y1, double x2, double y2, double checkY)
98 | {
99 |
100 | double m = get_m(x1, y1, x2, y2);
101 | double b = get_b(x1, y1, x2, y2);
102 | return (checkY - b) / m;
103 | }
104 |
105 |
106 | #endif /* PRACTICAL_COMMON_H_ */
107 |
--------------------------------------------------------------------------------
/chapter14/tick_publisher.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *This is a ROS node that monitors a pair of hall effect encoders and publishes
3 | *the tick counts for a left wheel and right wheel in ROS. Whether each
4 | *GPIO event is incremented or decremented is determined by check the direction
5 | *signal going to the motor driver. This is written simply
6 | *to be readable for all levels and accompanies the book Practical Robotics in C++.
7 | *
8 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
9 | *11/7/2019
10 | */
11 |
12 | #include "ros/ros.h"
13 | #include "std_msgs/Int16.h"
14 | #include
15 | #include
16 |
17 | using namespace std;
18 |
19 | //GPIO Pin assignments
20 | const int leftEncoder = 22; //left encoder
21 | const int rightEncoder = 23; //right encoder
22 | const int leftReverse = 13; //monitor as input that goes low when left motor set to reverse
23 | const int rightReverse = 19; //monitor as input that goes low when right motor set to reverse
24 |
25 | //max and min allowable values
26 | const int encoderMin = -32768;
27 | const int encoderMax = 32768;
28 |
29 | std_msgs::Int16 leftCount;
30 | std_msgs::Int16 rightCount;
31 |
32 |
33 | //this is the callback function that runs when a change of state happens on the monitored gpio pin
34 | void left_event(int pi, unsigned int gpio, unsigned int edge, unsigned int tick)
35 | {
36 | if(gpio_read(pi, leftReverse)==0) //decrement if motor commanded to reverse
37 | {
38 | if(leftCount.data==encoderMin) //handle rollunder
39 | {
40 | leftCount.data = encoderMax;
41 | }
42 | else
43 | {
44 | leftCount.data--;
45 | }
46 |
47 | }
48 | else //increment if not commanded to reverse (must be going forward)
49 | {
50 | if(leftCount.data==encoderMax) //handle rollover
51 | {
52 | leftCount.data = encoderMin;
53 | }
54 | else
55 | {
56 | leftCount.data++;
57 | }
58 |
59 | }
60 |
61 | }
62 | //this is the callback function that runs when a change of state happens on the monitored gpio pin
63 | void right_event(int pi, unsigned int gpio, unsigned int edge, unsigned int tick)
64 | {
65 | if(gpio_read(pi, rightReverse)==0)
66 | {
67 | if(rightCount.data==encoderMin)
68 | {
69 | rightCount.data = encoderMax;
70 | }
71 | else
72 | {
73 | rightCount.data--;
74 | }
75 | }
76 | else
77 | {
78 | if(rightCount.data==encoderMax)
79 | {
80 | rightCount.data = encoderMin;
81 | }
82 | else
83 | {
84 | rightCount.data++;
85 | }
86 | }
87 |
88 | }
89 |
90 | int PigpioSetup()
91 | {
92 | char *addrStr = NULL;
93 | char *portStr = NULL;
94 | int pi = pigpio_start(addrStr, portStr);
95 |
96 | //set the mode and pullup to read the encoder like a switch
97 | set_mode(pi, leftEncoder, PI_INPUT);
98 | set_mode(pi, rightEncoder, PI_INPUT);
99 | set_mode(pi, leftReverse, PI_INPUT);
100 | set_mode(pi, rightReverse, PI_INPUT);
101 | set_pull_up_down(pi, leftEncoder, PI_PUD_UP);
102 | set_pull_up_down(pi, rightEncoder, PI_PUD_UP);
103 | set_pull_up_down(pi, leftReverse, PI_PUD_UP);
104 | set_pull_up_down(pi, rightReverse, PI_PUD_UP);
105 |
106 | return pi;
107 | }
108 |
109 | int main(int argc, char **argv)
110 | {
111 | //initialize pipiod interface
112 | int pi = PigpioSetup();
113 | if(pi>=0)
114 | {
115 | cout<<"daemon interface started ok at "<("leftWheel", 1000);
132 | ros::Publisher pubRight = node.advertise("rightWheel", 1000);
133 |
134 |
135 | ros::Rate loop_rate(30);
136 | while(ros::ok())
137 | {
138 | pubLeft.publish(leftCount);
139 | pubRight.publish(rightCount);
140 | ros::spinOnce();
141 |
142 | loop_rate.sleep();
143 | }
144 | //terminate callbacks and pigpiod connectoin to release daemon resources
145 | callback_cancel(cbLeft);
146 | callback_cancel(cbRight);
147 | pigpio_stop(pi);
148 | return 0;
149 | }
150 |
--------------------------------------------------------------------------------
/chapter15/ultrasonic_publisher.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *This is a ROS node that reads an HC-SR04 ultrasonic range sensor
3 | *then publishes a ROS sensor_msgs::Range message. This is written
4 | *to be readable for all levels and accompanies the book Practical Robotics in C++.
5 | *
6 | *Author: Lloyd Brombach (lbrombach2@gmail.com)
7 | *11/7/2019
8 | */
9 |
10 | #include "ros/ros.h"
11 | #include "sensor_msgs/Range.h"
12 | #include
13 | #include
14 |
15 | //assign alias to the gpio pin numbers
16 | const int trigger = 6;
17 | const int echo = 16;
18 |
19 | sensor_msgs::Range range;
20 |
21 | using namespace std;
22 |
23 | //Returns range in centimers
24 | float get_range(int pi)
25 | {
26 | //initiate reading with 10 microsecond pulse to trigger
27 | gpio_write(pi, trigger, 1);
28 | time_sleep(.00001);
29 | gpio_write(pi, trigger, 0);
30 |
31 | //wait for echo pin to go from low to high
32 | while(gpio_read(pi, echo)==0){};
33 |
34 | //get current tick (microseconds since boot) from system.
35 | int start = get_current_tick(pi);
36 |
37 | //wait for echo pin to return to return to low
38 | while(gpio_read(pi, echo)==1){};
39 |
40 | //calculate duration of high pulse, which is equal to round trip time
41 | int echoTime = get_current_tick(pi) - start;
42 |
43 | //speed of sound is 343 m/s, but total echo time is round trip
44 | //half that times echo time (in seconds) is the range
45 | return 171.5 * echoTime / 1000000;
46 | }
47 |
48 | //connects with pigpio daemon, intializes gpio pin modes and states
49 | int PigpioSetup()
50 | {
51 | char *addrStr = NULL;
52 | char *portStr = NULL;
53 | int pi = pigpio_start(addrStr, portStr);
54 |
55 | //set the pin modes and set the trigger output to low
56 | set_mode(pi, echo, PI_INPUT);
57 | set_mode(pi, trigger, PI_OUTPUT);
58 | gpio_write(pi, trigger, 0);
59 |
60 | //let pins stabilize before trying to read
61 | time_sleep(.01);
62 |
63 | return pi;
64 | }
65 |
66 | int main(int argc, char **argv)
67 | {
68 | //normal ROS node setup: Register node with master, advertise publisher
69 | ros::init(argc, argv, "ultrasonic_publisher");
70 | ros::NodeHandle node;
71 | ros::Publisher pub = node.advertise("ultra_range", 0);
72 |
73 | //initialize pipiod interface
74 | int pi = PigpioSetup();
75 | if(pi>=0)
76 | {
77 | cout<<"daemon interface started ok at "<
13 | #include
14 |
15 | const int LSM303_accel=0x19; //accelerometer address
16 | const int LSM303_mag =0x1e; //magnetometer address
17 | const int L3GD20_gyro = 0x6b; //gyroscope address
18 | const int I2Cbus=1; //RPi typical I2C bus. Find yours with "ls /dev/*i2c* "
19 | const float RAD_PER_DEG = 0.0174533;
20 |
21 | int pi = -1;
22 | int ACCEL_HANDLE=-1;
23 | int GYRO_HANDLE=-1;
24 |
25 | //create Imu message object
26 | sensor_msgs::Imu myImu;
27 |
28 | using namespace std;
29 |
30 |
31 |
32 | void gyro_setup()
33 | {
34 | //initiate comms with gyroscope and get handle
35 | GYRO_HANDLE=i2c_open(pi,I2Cbus, L3GD20_gyro,0);
36 | if (GYRO_HANDLE>=0)
37 | {
38 | cout<<"Gyro found. Handle = "<=0)
74 | {
75 | cout<<"Accelerometer found. Handle = "<>4)/1000*9.81;
94 |
95 | int yLSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2A);
96 | int yMSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2B);
97 | myImu.linear_acceleration.y=(float)((int16_t)(yLSB | yMSB<<8)>>4)/1000*9.81;
98 |
99 | int zLSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2C);
100 | int zMSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2D);
101 | myImu.linear_acceleration.z=(float)((int16_t)(zLSB | zMSB<<8)>>4)/1000*9.81;
102 |
103 | myImu.header.stamp = ros::Time::now();
104 | }
105 |
106 |
107 | int PigpioSetup()
108 | {
109 | char *addrStr = NULL;
110 | char *portStr = NULL;
111 | pi = pigpio_start(addrStr, portStr);
112 | return pi;
113 | }
114 |
115 |
116 | int main(int argc, char **argv)
117 | {
118 | ros::init(argc, argv, "imu_publisher");
119 | ros::NodeHandle node;
120 | ros::Publisher pub = node.advertise("imu/data_raw", 0);
121 |
122 | //initialize pipiod interface
123 | int pi = PigpioSetup();
124 | if(pi>=0)
125 | {
126 | cout<<"daemon interface started ok at "<
14 | #include
15 |
16 | const int LSM303_accel=0x19; //accelerometer address
17 | const int LSM303_mag =0x1e;
18 | const int L3GD20_gyro = 0x6b;
19 | const int I2Cbus=1; //RPi typical I2C bus. Find yours with "ls /dev/*i2c* "
20 | const float RAD_PER_DEG = 0.0174533;
21 | const float TESLA_PER_GAUSS = .0001;
22 |
23 | int pi = -1;
24 | int ACCEL_HANDLE=-1;
25 | int GYRO_HANDLE=-1;
26 | int MAG_HANDLE=-1;
27 |
28 | sensor_msgs::Imu myImu;
29 | sensor_msgs::MagneticField mag;
30 |
31 | using namespace std;
32 |
33 |
34 | void mag_setup()
35 | {
36 | //initiate comms with magnetometer and get handle
37 | MAG_HANDLE=i2c_open(pi,I2Cbus, LSM303_mag ,0);
38 | if (MAG_HANDLE>=0)
39 | {
40 | cout<<"MagnetometerLSM303_mag found. Handle = "<=0)
82 | {
83 | cout<<"Gyro found. Handle = "<=0)
120 | {
121 | cout<<"Accelerometer found. Handle = "<>4)/1000*9.81;
140 |
141 | int yLSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2A);
142 | int yMSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2B);
143 | myImu.linear_acceleration.y=(float)((int16_t)(yLSB | yMSB<<8)>>4)/1000*9.81;
144 |
145 | int zLSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2C);
146 | int zMSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2D);
147 | myImu.linear_acceleration.z=(float)((int16_t)(zLSB | zMSB<<8)>>4)/1000*9.81;
148 |
149 | myImu.header.stamp = ros::Time::now();
150 | }
151 |
152 |
153 | int PigpioSetup()
154 | {
155 | char *addrStr = NULL;
156 | char *portStr = NULL;
157 | pi = pigpio_start(addrStr, portStr);
158 | return pi;
159 | }
160 |
161 |
162 | int main(int argc, char **argv)
163 | {
164 | ros::init(argc, argv, "imu_publisher");
165 | ros::NodeHandle node;
166 | ros::Publisher pub = node.advertise("imu/data_raw", 0);
167 | ros::Publisher pub2 = node.advertise("imu/mag", 0);
168 |
169 | //initialize pipiod interface
170 | int pi = PigpioSetup();
171 | if(pi>=0)
172 | {
173 | cout<<"daemon interface started ok at "<
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | using namespace std;
11 | using namespace cv;
12 |
13 | image_transport::Subscriber image_sub;
14 | image_transport::Publisher image_pub;
15 |
16 | void handle_image(const sensor_msgs::ImageConstPtr &img)
17 | {
18 | //convert ros image message to open-cv compatible BGR image
19 | Mat image = cv_bridge::toCvShare(img, sensor_msgs::image_encodings::BGR8)->image;
20 |
21 | //draw a rectangle on image, originating at (150,150) or size 100x100,
22 | //set color to (255,0,0) (blue), and line width to 3.
23 | // cv::rectangle(image, cv::Rect(150, 150, 100, 100), cv::Scalar(255, 0, 0), -1);
24 | cv::Rect rectangle(150, 150, 100, 100);
25 | cv::Scalar color(255, 0, 255);
26 | cv::rectangle(image, rectangle, color, -1);
27 |
28 | //use cv_bridge to convert opencv image to ros image message
29 | //create image message
30 | sensor_msgs::Image::Ptr output_img;
31 | //convert opencv image we drew on to ROS image message
32 | output_img = cv_bridge::CvImage(img->header, "bgr8", image).toImageMsg();
33 | //publish our ros image message
34 | image_pub.publish(output_img);
35 | }
36 |
37 | int main(int argc, char **argv)
38 | {
39 | ros::init(argc, argv, "cv_bridge_test");
40 | ros::NodeHandle nh;
41 | image_transport::ImageTransport it_(nh);
42 |
43 | // Subscribe to input video feed and publish output video feed
44 | image_sub = it_.subscribe("/usb_cam/image_raw", 1, handle_image);
45 | image_pub = it_.advertise("image_output", 1);
46 |
47 | ros::Rate loop_rate(30);
48 | while (ros::ok())
49 | {
50 | ros::spinOnce();
51 | loop_rate.sleep();
52 | }
53 |
54 | return 0;
55 | }
--------------------------------------------------------------------------------
/chapter19/cv_test_node.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | using namespace std;
8 |
9 | int main(int argc, char **argv)
10 | {
11 | ros::init(argc, argv, "open_cv_test");
12 | ros::NodeHandle nh;
13 |
14 | //display Open CV version
15 | cout << "OpenCV version : " << CV_VERSION << endl;
16 |
17 | //create blank 3-color image of size 400x400pixels.
18 | cv::Mat testImage = cv::Mat::zeros(cv::Size(400, 400), CV_8UC3);
19 |
20 | //draw a rectangle on testImage, originating at (150,150) or size 100x100,
21 | //set color to (255,0,0) (blue), and line width to 3.
22 | cv::rectangle(testImage, cv::Rect(150,150,100,100), cv::Scalar(255, 0, 0), 3);
23 |
24 | //display testImage in a new window called test_window
25 | cv::imshow("test_window", testImage);
26 |
27 | //wait for key press tocontinue
28 | cv::waitKey(0);
29 |
30 | return 0;
31 | }
--------------------------------------------------------------------------------
/chapter19/hsv_colors.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "opencv2/imgcodecs.hpp"
5 | #include
6 | #include
7 | #include
8 |
9 | using namespace cv;
10 | using namespace std;
11 |
12 | int main()
13 | {
14 | namedWindow("out");
15 | Mat hsv = Mat(Size(900, 150), CV_8UC3, Scalar(0, 255, 255));
16 | Mat bgr;
17 | int hue = 0;
18 |
19 | for (int col = 0; col < 900; col++)
20 | {
21 | for (int row = 0; row < hsv.size().height; row++)
22 | {
23 | hsv.at(row, col) = Vec3b(hue, 255, 255);
24 | }
25 | if (col % 5 == 0)
26 | {
27 | hue++;
28 | }
29 |
30 | cvtColor(hsv, bgr, CV_HSV2BGR);
31 | imshow("out", bgr);
32 | waitKey(2);
33 | }
34 | for (int col = 50; col < bgr.size().width; col += 50)
35 | {
36 | line(bgr, Point(col, bgr.size().height-30), Point(col, bgr.size().height-50), Scalar(0,0,0), 2);
37 | putText(bgr, to_string(col / 5), Point(col-10, bgr.size().height-15), FONT_HERSHEY_COMPLEX_SMALL, .75, Scalar(0, 0, 0), 2);
38 | }
39 | imshow("out", bgr);
40 | waitKey(0);
41 | return 0;
42 | }
--------------------------------------------------------------------------------
/chapter19/image_transport_test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | //using namespace std;
7 |
8 | image_transport::Subscriber image_sub;
9 | image_transport::Publisher image_pub;
10 |
11 | void handle_image(const sensor_msgs::ImageConstPtr &img)
12 | {
13 | //declare a new image
14 | sensor_msgs::Image output_image;
15 |
16 | //simply copy all the data fields from the recieved image
17 | output_image.header = img->header;
18 | output_image.width = img->width;
19 | output_image.height = img->height;
20 | output_image.encoding = img->encoding;
21 | output_image.is_bigendian = img->is_bigendian;
22 | output_image.step = img->step;
23 | output_image.data = img->data;
24 |
25 | //publish our ros image message
26 | image_pub.publish(output_image);
27 | }
28 |
29 | int main(int argc, char **argv)
30 | {
31 | ros::init(argc, argv, "cv_bridge_test");
32 | ros::NodeHandle nh;
33 | image_transport::ImageTransport it_(nh);
34 |
35 | // Subscribe to input video feed and publish output video feed
36 | image_sub = it_.subscribe("/usb_cam/image_raw", 1, handle_image);
37 | image_pub = it_.advertise("image_output", 1);
38 |
39 | ros::Rate loop_rate(30);
40 | while (ros::ok())
41 | {
42 | ros::spinOnce();
43 | loop_rate.sleep();
44 | }
45 |
46 | return 0;
47 | }
--------------------------------------------------------------------------------
/chapter2/hello_blink.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | hello_blink is a program to learn to control the output of a
3 | Raspberry Pi GPIO pin with the PIGPIO library. This program
4 | is an accompaniment to the book Practical Robotics in C++
5 | written by Lloyd Brombach and published by Packt Publishing
6 | */
7 |
8 | #include
9 | #include
10 |
11 | const int LED = 5;
12 |
13 | using namespace std;
14 |
15 |
16 | int PigpioSetup()
17 | {
18 | char *addrStr = NULL;
19 | char *portStr = NULL;
20 | //handshake with daemon and get pi handle
21 | int pi = pigpio_start(addrStr, portStr);
22 |
23 | //set the pin mode and intialize to low
24 | set_mode(pi, LED, PI_OUTPUT);
25 | gpio_write(pi, LED, 0);
26 |
27 | return pi;
28 | }
29 |
30 | int main()
31 | {
32 | //initialize pipiod interface
33 | int pi = PigpioSetup();
34 | //check that handshake went ok
35 | if(pi>=0)
36 | {
37 | cout<<"daemon interface started ok at "<
9 | #include
10 |
11 | const int LED = 5;
12 | const int BUTTON = 27;
13 |
14 | using namespace std;
15 |
16 |
17 | int PigpioSetup()
18 | {
19 | char *addrStr = NULL;
20 | char *portStr = NULL;
21 | //handshake with daemon and get pi handle
22 | int pi = pigpio_start(addrStr, portStr);
23 |
24 | //set the pin 6 mode and intialize to low
25 | set_mode(pi, LED, PI_OUTPUT);
26 | gpio_write(pi, LED, 0);
27 |
28 | //set pin 27 mode as input
29 | set_mode(pi, BUTTON, PI_INPUT);
30 | set_pull_up_down(pi, BUTTON, PI_PUD_UP);
31 |
32 | return pi;
33 | }
34 |
35 | int main()
36 | {
37 | //initialize pipiod interface
38 | int pi = PigpioSetup();
39 | //check that handshake went ok
40 | if(pi>=0)
41 | {
42 | cout<<"daemon interface started ok at "<
9 | #include
10 |
11 | using namespace std;
12 |
13 | const int BUTTON = 27;
14 |
15 |
16 | //this is the callback function that runs when a change of state happens on the monitored gpio pin
17 | void button_event(int pi, unsigned int gpio, unsigned int edge, unsigned int foo)
18 | {
19 | static int i = 0;
20 | cout<<"Button pressed. Press count = "<=0)
30 | {
31 | cout<<"daemon interface started ok at "<
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | ros::Publisher odom_pub;
24 | ros::Publisher pub_quat;
25 | nav_msgs::Odometry newOdom;
26 | nav_msgs::Odometry oldOdom;
27 |
28 | const double initialX = 0.0;
29 | const double initialY = 0.0;
30 | const double initialTheta = 0.00000000001;
31 | const double PI = 3.141592;
32 |
33 | const double ticksPerwheelRev = 508.8; //not in use. just a reference for now
34 | const double wheelRadius = .03575; // 55.18;
35 | const double WHEEL_BASE = .224; //223.8375mm actually
36 | const double TICKS_PER_M = 1125*2; //1.1645; //1.365 is on hard floor. carpet avg is 1.1926. overall avg = 1.1645
37 |
38 | double leftDistance = 0;
39 | double rightDistance = 0;
40 | bool initialPoseRecieved = false;
41 |
42 | float imuHeading = 0;
43 | float headingOffset = 0;
44 | bool haveNewImuHeading = false;
45 | bool imuHeadingInitialized = false;
46 |
47 | using namespace std;
48 |
49 | //accept initial_2d pose message form clicks or manual publisher
50 | //at at least one is required to start this node publishing
51 | void setInitialPose(const geometry_msgs::PoseStamped &rvizClick)
52 | {
53 | cout<<"Got initial pose. Starting node"< 10000)
74 | {
75 | leftTicks=0-(65535 - leftTicks);
76 | }
77 | else if (leftTicks < -10000)
78 | {
79 | leftTicks = 65535-leftTicks;
80 | }
81 | leftDistance = leftTicks/TICKS_PER_M;
82 | }
83 | lastCountL = lCount.data;
84 | }
85 |
86 | //calculate distance right wheel has traveled since last cycle
87 | void Calc_Right(const std_msgs::Int16& rCount)
88 | {
89 | static int lastCountR = 0;
90 | if(rCount.data!=0&&lastCountR!=0)
91 | {
92 | int rightTicks = rCount.data - lastCountR;
93 | if (rightTicks > 10000)
94 | {
95 | rightTicks=(0-(65535 - rightTicks))/TICKS_PER_M;
96 | }
97 | else if (rightTicks < -10000)
98 | {
99 | rightTicks = 65535 - rightTicks;
100 | }
101 | rightDistance = rightTicks/TICKS_PER_M;
102 | }
103 | lastCountR=rCount.data;
104 | }
105 |
106 | //publishes the simpler version of the odom message in full quaternion form
107 | //and includes covariance data. This covariance should be "personalized"
108 | //for a specific robot before it is used.
109 | void publish_quat()
110 | {
111 | tf2::Quaternion q;
112 | q.setRPY(0, 0, newOdom.pose.pose.orientation.z);
113 |
114 | nav_msgs::Odometry quatOdom;
115 | quatOdom.header.stamp = newOdom.header.stamp;
116 | quatOdom.header.frame_id = "odom";
117 | quatOdom.child_frame_id = "base_link";
118 | quatOdom.pose.pose.position.x = newOdom.pose.pose.position.x;
119 | quatOdom.pose.pose.position.y = newOdom.pose.pose.position.y;
120 | quatOdom.pose.pose.position.z = newOdom.pose.pose.position.z;
121 | quatOdom.pose.pose.orientation.x = q.x();
122 | quatOdom.pose.pose.orientation.y = q.y();
123 | quatOdom.pose.pose.orientation.z = q.z();
124 | quatOdom.pose.pose.orientation.w = q.w();
125 | quatOdom.twist.twist.linear.x = newOdom.twist.twist.linear.x;
126 | quatOdom.twist.twist.linear.y = newOdom.twist.twist.linear.y;
127 | quatOdom.twist.twist.linear.z = newOdom.twist.twist.linear.z;
128 | quatOdom.twist.twist.angular.x = newOdom.twist.twist.angular.x;
129 | quatOdom.twist.twist.angular.y = newOdom.twist.twist.angular.y;
130 | quatOdom.twist.twist.angular.z = newOdom.twist.twist.angular.z;
131 |
132 | for(int i = 0; i<36; i++)
133 | {
134 | if(i == 0 || i == 7 || i == 14)
135 | {
136 | quatOdom.pose.covariance[i] = .01;
137 | }
138 | else if (i == 21 || i == 28 || i== 35)
139 | {
140 | quatOdom.pose.covariance[i] += .165;
141 | }
142 | else
143 | {
144 | quatOdom.pose.covariance[i] = 0;
145 | }
146 | }
147 |
148 | pub_quat.publish(quatOdom);
149 |
150 | }
151 |
152 | //checks if rate of change is realistic or likely error
153 | bool isSafeRateOfChange(float headings[], double times[])
154 | {
155 | double rate[3] = {0};
156 | for(int i = 0; i<3; i++)
157 | {
158 | double timeElapsed = headings[i] - headings[i+1];
159 | if(timeElapsed != 0)
160 | {
161 | //calculate radians per second
162 | rate[i]=abs(headings[i]-headings[i+1]) / timeElapsed;
163 | }
164 | if(rate[i] > .785 || timeElapsed == 0) // .785 = about 45 degrees/sec
165 | {
166 | return false;
167 | }
168 | }
169 | return true;
170 | }
171 |
172 |
173 | void update_heading(const sensor_msgs::Imu &imuMsg)
174 | {
175 | //element zero will be most recent heading recieved from imu
176 | static float headings[4]={0};
177 | static double times[4] = {0};
178 |
179 |
180 | if(imuMsg.orientation_covariance[0] != -1)
181 | {
182 | tf::Quaternion q(imuMsg.orientation.x, imuMsg.orientation.y, imuMsg.orientation.z, imuMsg.orientation.w);
183 | tf::Matrix3x3 m(q);
184 | double roll, pitch, yaw;
185 | m.getRPY(roll, pitch, yaw);
186 |
187 | //saves 4 cycles' worth of yaw and times stamps for next step
188 | for(int i = 3; i>0; i++)
189 | {
190 | headings[i] = headings[i-1];
191 | times[i] = times[i-1];
192 | }
193 | headings[0] = yaw;
194 | times[0] = ros::Time::now().toSec();
195 |
196 | //if this is false, possibly errant reading. Require 3 consecutive stable readings to trust.
197 | if(isSafeRateOfChange(headings, times))
198 | {
199 | if(imuHeadingInitialized == false)
200 | {
201 | headingOffset = oldOdom.pose.pose.orientation.z - yaw;
202 | imuHeadingInitialized = true;
203 | cout<<"heading offset = "< PI)
228 | {
229 | avgAngle -= 2*PI;
230 | }
231 | else if (avgAngle < -PI)
232 | {
233 | avgAngle += 2*PI;
234 | }
235 |
236 | //calculate new x, y, and theta
237 | newOdom.pose.pose.position.x = oldOdom.pose.pose.position.x + cos(avgAngle)*cycleDistance;
238 | newOdom.pose.pose.position.y = oldOdom.pose.pose.position.y + sin(avgAngle)*cycleDistance;
239 | newOdom.pose.pose.orientation.z = cycleAngle + oldOdom.pose.pose.orientation.z;
240 |
241 | //prevent lockup from a single erroneous cycle
242 | if(isnan(newOdom.pose.pose.position.x) || isnan(newOdom.pose.pose.position.y)
243 | || isnan(newOdom.pose.pose.position.z) )
244 | {
245 | newOdom.pose.pose.position.x = oldOdom.pose.pose.position.x;
246 | newOdom.pose.pose.position.y = oldOdom.pose.pose.position.y;
247 | newOdom.pose.pose.orientation.z = oldOdom.pose.pose.orientation.z;
248 | }
249 |
250 | //keep theta in range proper range
251 | if (newOdom.pose.pose.orientation.z > PI)
252 | {
253 | newOdom.pose.pose.orientation.z -= 2*PI;
254 | }
255 | else if (newOdom.pose.pose.orientation.z < -PI)
256 | {
257 | newOdom.pose.pose.orientation.z += 2*PI;
258 | }
259 |
260 | //calculate velocity
261 | newOdom.header.stamp = ros::Time::now();
262 | newOdom.twist.twist.linear.x = cycleDistance/(newOdom.header.stamp.toSec() - oldOdom.header.stamp.toSec());
263 | newOdom.twist.twist.angular.z = cycleAngle/(newOdom.header.stamp.toSec() - oldOdom.header.stamp.toSec());
264 |
265 | //update orientation if we have a smart heading from IMU
266 | if(haveNewImuHeading == true)
267 | {
268 | newOdom.pose.pose.orientation.z = imuHeading+headingOffset;
269 | haveNewImuHeading = false;
270 | for(int i = 0; i<36; i++)
271 | {
272 | if(i == 0 || i == 7 || i == 14)
273 | {
274 | newOdom.pose.covariance[i] = .002;
275 | }
276 | else if (i == 21 || i == 28 || i== 35)
277 | {
278 | newOdom.pose.covariance[i] = .001;
279 | }
280 | else
281 | {
282 | newOdom.pose.covariance[i] = 0;
283 | }
284 | }
285 | }
286 | else
287 | {
288 | for(int i = 0; i<36; i++)
289 | {
290 | if(i == 0 || i == 7 || i == 14)
291 | {
292 | newOdom.pose.covariance[i] = .0021;
293 | }
294 | else if (i == 21 || i == 28 || i== 35)
295 | {
296 | if(cycleAngle != 0 )
297 | newOdom.pose.covariance[i] += .0001;
298 | }
299 | else
300 | {
301 | newOdom.pose.covariance[i] = 0;
302 | }
303 | }
304 | }
305 |
306 | //save odom x, y, and theta for use in next cycle
307 | oldOdom.pose.pose.position.x = newOdom.pose.pose.position.x;
308 | oldOdom.pose.pose.position.y = newOdom.pose.pose.position.y;
309 | oldOdom.pose.pose.orientation.z = newOdom.pose.pose.orientation.z;
310 | oldOdom.header.stamp = newOdom.header.stamp;
311 |
312 | //publish odom message
313 | odom_pub.publish(newOdom);
314 | }
315 |
316 | int main(int argc, char **argv)
317 | {
318 | //set fixed data fields
319 | newOdom.header.frame_id = "odom";
320 | newOdom.pose.pose.position.z = 0;
321 | newOdom.pose.pose.orientation.x = 0;
322 | newOdom.pose.pose.orientation.y = 0;
323 | newOdom.twist.twist.linear.x = 0;
324 | newOdom.twist.twist.linear.y = 0;
325 | newOdom.twist.twist.linear.z = 0;
326 | newOdom.twist.twist.angular.x = 0;
327 | newOdom.twist.twist.angular.y = 0;
328 | newOdom.twist.twist.angular.z = 0;
329 | oldOdom.pose.pose.position.x = initialX;
330 | oldOdom.pose.pose.position.y = initialY;
331 | oldOdom.pose.pose.orientation.z = initialTheta;
332 |
333 | //handshake with ros master and create node object
334 | ros::init(argc, argv, "odom_plus");
335 | ros::NodeHandle node;
336 |
337 | //Subscribe to topics
338 | ros::Subscriber subForRightCounts = node.subscribe("rightWheel", 100, Calc_Right,ros::TransportHints().tcpNoDelay());
339 | ros::Subscriber subForLeftCounts = node.subscribe("leftWheel",100, Calc_Left,ros::TransportHints().tcpNoDelay());
340 | ros::Subscriber subInitialPose = node.subscribe("initial_2d", 1, setInitialPose);
341 | ros::Subscriber subImu = node.subscribe("imu", 100, update_heading);
342 |
343 | //advertise publisher of simpler odom msg where orientation.z is an euler angle
344 | odom_pub = node.advertise("encoder/odom", 100);
345 |
346 | //advertise publisher of full odom msg where orientation is quaternion
347 | pub_quat = node.advertise("encoder/odom_quat", 100);
348 |
349 |
350 | ros::Rate loop_rate(30);
351 | while(ros::ok())
352 | {
353 | ros::spinOnce();
354 | if(initialPoseRecieved)
355 | {
356 | update_odom();
357 | publish_quat();
358 | }
359 | loop_rate.sleep();
360 | }
361 |
362 | return 0;
363 | }
364 |
--------------------------------------------------------------------------------
/chapter3/hello_roomba.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *hello_roomba.cpp is a simple program with a few sample functions for communicating with your Roomba
3 | *using the PIGPIO library. Note that you’ll have to study the PIGPIO documentation and the Roomba OI
4 | *documentation for this to really make sense, these are here to make it easier for you to understand
5 | *how the OI and PIGPIO work together.
6 | *
7 | *From the book Practical Robotics in C++ by Lloyd Brombach 2020
8 | */
9 |
10 | #include "pigpiod_if2.h"
11 |
12 | using namespace std;
13 |
14 | int pi = -1;
15 | int serHandle = -1;
16 |
17 | //used to make sure roomba awake and listening
18 | void wake(){
19 | int R1 = 23; //This is assuming the wake relay is on pin 23
20 | set_mode(pi, R1, PI_OUTPUT);
21 |
22 | gpio_write(pi, R1, 0); //pulse wake relay
23 | time_sleep(.1);
24 | gpio_write(pi, R1, 1);
25 |
26 | serial_write_byte(pi,serHandle,128); //send start command
27 | time_sleep(.15);
28 | serial_write_byte(pi,serHandle,131); //set to safe mode
29 | time_sleep(.15);
30 | }
31 |
32 | //drive in reverse at 80mm/sec
33 | void rev()
34 | {
35 | char driveString[] = {137, 255, 136, 0, 0};
36 | serial_write(pi, serHandle, driveString, 5);
37 | }
38 |
39 | //drive forward at 120mm/sec
40 | void fwd()
41 | {
42 | char driveString[] = {137, 0, 120, 127, 255};
43 | serial_write(pi, serHandle, driveString, 5);
44 | }
45 |
46 | //stops wheel motors
47 | void stop()
48 | {
49 | char driveString[] = {137, 0, 0, 0, 0};
50 | serial_write(pi, serHandle, driveString, 5);
51 | }
52 |
53 | //puts Roomba to sleep and frees program resources
54 | void shutdown()
55 | {
56 | serial_write_byte(pi,serHandle,133);
57 | time_sleep(.1);
58 | serial_close(pi, serHandle);
59 | pigpio_stop(pi);
60 | }
61 |
62 | //main() wakes Roomba, drives it forward for 5 seconds. Pauses
63 | //for one second, reverses for 5 seconds, the shuts everything
64 | //down
65 | int main()
66 | {
67 | char *addrStr = NULL;
68 | char *portStr = NULL;
69 | //handshake with daemon and get pi handle
70 | pi = pigpio_start(addrStr, portStr);
71 |
72 | //open serial port
73 | serHandle = serial_open(pi, "/dev/ttyUSB0",115200,0);
74 |
75 | wake();
76 | fwd();
77 | time_sleep(5);
78 | stop();
79 | time_sleep(1);
80 | rev();
81 | time_sleep(5);
82 | stop();
83 | shutdown();
84 | }
85 |
--------------------------------------------------------------------------------
/chapter3/roomba_sleep.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | *roomba_sleep.cpp is a simple program to help "unfreeze" a roomba during eperimentation with the
3 | *open interface. Sometimes test code will leave Roomba waiting for new data forever, so blasting
4 | *it with the "sleep" command should satisfy the data requirement and eventuall put it to sleep mode.
5 | *
6 | *From the book Practical Robotics in C++ by Lloyd Brombach, 2020
7 | */
8 |
9 |
10 | #include "pigpiod_if2.h"
11 |
12 | using namespace std;
13 |
14 |
15 | //puts Roomba to sleep
16 | {
17 | int pi = -1;
18 | int serHandle = -1;
19 | char *addrStr = NULL;
20 | char *portStr = NULL;
21 | //handshake with daemon and get pi handle
22 | pi = pigpio_start(addrStr, portStr);
23 |
24 | //open serial port
25 | //serHandle = serial_open(pi, "/dev/ttyUSB0",115200,0);
26 | serHandle = serial_open(pi, "/dev/ttyAMA0",115200,0);
27 |
28 | for(int i = 0; i < 100; i++)
29 | {
30 | serial_write_byte(pi, serHandle, 133);
31 | time_sleep(.01);
32 | }
33 |
34 | time_sleep(.1);
35 | serial_close(pi, serHandle);
36 | pigpio_stop(pi);
37 | }
38 |
--------------------------------------------------------------------------------
/chapter4/hello_motor.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | hello_motor is a program to learn to use a L298N dual
3 | H Bridge motor driver with the PIGPIO library for the Raspberry Pi.
4 | This program is an accompaniment to the book Practical Robotics in C++
5 | written by Lloyd Brombach and published by Packt Publishing
6 | */
7 |
8 |
9 | #include
10 | #include
11 |
12 |
13 | //define our GPIO pin assignments
14 | const int PWM_A = 21;
15 | const int MOTOR_A_FWD = 26;
16 | const int MOTOR_A_REV = 13;
17 |
18 | //handshakes with Pigpio Daemon and sets up our pins.
19 | int pigpio_setup()
20 | {
21 | char *addrStr = NULL;
22 | char *portStr = NULL;
23 | const int pi = pigpio_start(addrStr, portStr);
24 |
25 | //next four lines sets up our pins. Remember that high is "off"
26 | //and we must drive in1 or in2 low to start the output to motor
27 | set_mode(pi,PWM_A, PI_OUTPUT);
28 | set_mode(pi,MOTOR_A_FWD, PI_OUTPUT);
29 | set_mode(pi,MOTOR_A_REV, PI_OUTPUT);
30 |
31 | //initializes motor off
32 | gpio_write(pi, MOTOR_A_FWD, 1);
33 | gpio_write(pi, MOTOR_A_REV, 1);
34 | return pi;
35 | }
36 |
37 | using namespace std;
38 |
39 | int main()
40 | {
41 | int pi = pigpio_setup();
42 | if(pi < 0)
43 | {
44 | cout<<"Failed to connect to Pigpio Daemon. Is it running?"<
9 | #include
10 |
11 | using namespace std;
12 |
13 | const int LSM303_accel=0x19; //accelerometer address
14 | const int I2Cbus=1; //RPi typical I2C bus. Find yours with "ls /dev/*i2c* "
15 |
16 |
17 |
18 | int main()
19 | {
20 | //initialize connection with pigpio daemon
21 | char *addrStr = NULL;
22 | char *portStr = NULL;
23 | int pi=pigpio_start(addrStr, portStr);
24 | if(pi>=0)
25 | {
26 | cout<<"daemon interface started ok at "<=0)
37 | {
38 | cout<<"Accelerometer found. Handle = "<>4);
58 |
59 | //repeat above for y, then z axis
60 | int yLSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2A);
61 | int yMSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2B);
62 | float accelY=(float)((int16_t)(yLSB | yMSB<<8)>>4);
63 |
64 | int zLSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2C);
65 | int zMSB = (int)i2c_read_byte_data(pi, ACCEL_HANDLE, 0x2D);
66 | float accelZ=(float)((int16_t)(zLSB | zMSB<<8)>>4);
67 |
68 | //display data
69 | cout< -50 && accelY < 50 && accelY > -50)
73 | {
74 | cout<<"The device is fairly level"<
9 | #include
10 |
11 | using namespace std;
12 |
13 | int main()
14 | {
15 | //initialize connection with pigpio daemon
16 | char *addrStr = NULL;
17 | char *portStr = NULL;
18 | int pi=pigpio_start(addrStr, portStr);
19 |
20 | //open serial port connection
21 | int UARTHandle = serial_open(pi, "/dev/ttyAMA0",115200,0); //for use with GPIO serial pings 14 and 15
22 |
23 | //int UARTHandle = serial_open(pi, "/dev/ttyUSB0",115200,0); //for use with FTDI USB device
24 | cout<<"UARTHandle = " << UARTHandle<< endl;
25 | time_sleep(.1);
26 |
27 | //check serial buffer
28 | cout << "Data available start: " << serial_data_available(pi, UARTHandle)<< " bytes" << endl;
29 |
30 | //write a few test bytes
31 | serial_write_byte(pi,UARTHandle,6);
32 | serial_write_byte(pi,UARTHandle,'f');
33 | serial_write_byte(pi,UARTHandle,'F');
34 |
35 | //give time to transmit
36 | time_sleep(.1);
37 |
38 | //check serial buffer again
39 | cout << "Data available after writing: " << serial_data_available(pi, UARTHandle)<< " bytes" << endl;
40 |
41 | //read and display one byte
42 | cout <<"Byte read = " << serial_read_byte(pi, UARTHandle)<< endl;
43 |
44 | //check serial buffer again
45 | cout << "Data available after reading a byte: " << serial_data_available(pi, UARTHandle)<< " bytes" << endl;
46 |
47 | //read and display last two bytes
48 | char inA = serial_read_byte(pi, UARTHandle);
49 | cout <<"Byte read = " << inA << endl;
50 | char inB = serial_read_byte(pi, UARTHandle);
51 | cout <<"Byte read = " << inB<< endl;
52 |
53 | //close serial device and terminate connection with pigpio daemon
54 | serial_close(pi, UARTHandle);
55 | pigpio_stop(pi);
56 | return 0;
57 | }
--------------------------------------------------------------------------------
/chapter9/go_to_x.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | go_to_x.cpp is a program to learn to write ROS nodes.
3 | This node commands the turtlesim turtle to the X location
4 | manually entered in the const double GOAL below.
5 | Change the goal to any value from 0 to 11 and recompile with catkin_make before running.
6 | This program is an accompaniment to the book Practical Robotics in C++
7 | written by Lloyd Brombach and published by Packt Publishing
8 | */
9 |
10 | #include "ros/ros.h"
11 | #include "geometry_msgs/Twist.h"
12 | #include "geometry_msgs/Pose2D.h"
13 | #include "turtlesim/Pose.h"
14 | #include //for abs()
15 | #include
16 |
17 | using namespace std;
18 |
19 | //declaring variables.
20 | geometry_msgs::Twist cmdVel;
21 | geometry_msgs::Pose2D current;
22 | geometry_msgs::Pose2D desired;
23 |
24 | //change GOAL to any value from 0 to 11
25 | const double GOAL = 1.5;
26 |
27 | //the coefficient (or gain) for our linear velocity calculation
28 | const double Kl = 1;
29 |
30 | //the distance we are willing to accept as “close enough”
31 | const double distanceTolerance = .1;
32 |
33 | void misc_setup()
34 | {
35 | desired.x = GOAL;
36 | cmdVel.linear.x = 0;
37 | cmdVel.linear.y = 0;
38 | cmdVel.linear.z = 0;
39 | cmdVel.angular.x = 0;
40 | cmdVel.angular.y = 0;
41 | cmdVel.linear.z = 0;
42 | }
43 |
44 | // callback function to update the current location
45 | //we will use later. We don’t use the y or theta data members
46 | //in this tutorial, but I included them so you could see how
47 | //to access them in the future.
48 | void update_pose(const turtlesim::PoseConstPtr ¤tPose)
49 | {
50 | current.x = currentPose->x;
51 | current.y = currentPose->y;
52 | current.theta = currentPose->theta;
53 | }
54 |
55 | double getDistanceError()
56 | {
57 | return desired.x - current.x;
58 | }
59 |
60 | //if we aren’t close enough, set the cmd_vel data member
61 | //to be published later. Else set it to zero.
62 | void set_velocity()
63 | {
64 | if (abs(getDistanceError()) > distanceTolerance)
65 | {
66 | cmdVel.linear.x = Kl * getDistanceError();
67 | }
68 | else
69 | {
70 | cout << "I'm HERE!" << endl;
71 | cmdVel.linear.x = 0;
72 | }
73 | }
74 |
75 | int main(int argc, char **argv)
76 | {
77 | misc_setup();
78 |
79 | //register node “go_to_x” with roscore, then get a nodehandle
80 | ros::init(argc, argv, "go_to_x");
81 | ros::NodeHandle node;
82 |
83 | //Subscribe to topic and set callback **See detail in figure 9.10
84 | ros::Subscriber subCurrentPose =
85 | node.subscribe("turtle1/pose", 0, update_pose);
86 |
87 | //Register node as publisher **See detail in figure 9.10
88 | ros::Publisher pubVelocity =
89 | node.advertise("turtle1/cmd_vel", 0);
90 |
91 | //set the frequency that you want the loop below to execute at
92 | ros::Rate loop_rate(10); //10 cycles per second
93 |
94 | //execute this loop until connection is lost with ROS Master
95 | while (ros::ok())
96 | {
97 | //call the callbacks waiting to be called.
98 | ros::spinOnce();
99 |
100 | //call our own function after the callbacks are done
101 | set_velocity();
102 |
103 | //publish the message that we internally called cmdVel
104 | pubVelocity.publish(cmdVel);
105 |
106 | //output for you entertainment
107 | cout << "goal x = " << desired.x << endl
108 | << "current x = " << current.x << endl
109 | << " disError = " << getDistanceError() << endl
110 | << "cmd_vel = " << cmdVel.linear.x<< endl;
111 |
112 | //We set the frequency for 10Hz, this sleeps as long as it
113 | //takes to keep that frequency
114 | loop_rate.sleep();
115 | }
116 |
117 | return 0;
118 | }
--------------------------------------------------------------------------------
/chapter9/go_to_xy.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | go_to_xy.cpp is a program to learn to write ROS nodes.
3 | This node commands the turtlesim turtle to the X location
4 | manually entered in the accompanying program simple_goal_pub.cpp.
5 | This program is an accompaniment to the book Practical Robotics in C++
6 | written by Lloyd Brombach and published by Packt Publishing
7 | */
8 |
9 | #include "ros/ros.h"
10 | #include "geometry_msgs/Twist.h"
11 | #include "geometry_msgs/Pose2D.h"
12 | #include "turtlesim/Pose.h"
13 | #include //for abs()
14 | #include
15 |
16 | using namespace std;
17 |
18 | //declaring variables.
19 | ros::Publisher pubVelocity;
20 | geometry_msgs::Twist cmdVel;
21 | geometry_msgs::Pose2D current;
22 | geometry_msgs::Pose2D desired;
23 | const double PI = 3.141592;
24 |
25 | //the coefficient (or gain) for our angular velocity calculation
26 | const double Ka = .5;
27 |
28 | //the coefficient (or gain) for our linear velocity calculation
29 | const double Klv = .5;
30 |
31 | //the angle we'll call "close enough"
32 | const double angularTolerance = .1;
33 |
34 | //the distance we are willing to accept as “close enough”
35 | const double distanceTolerance = .1;
36 |
37 | //a flag that we use to to start and stop calculating and publishing commands
38 | bool waypointActive = false;
39 |
40 | void misc_setup()
41 | {
42 | desired.x = 5.54; //this is just where turtle starts
43 | desired.y = 5.54; //this is just where turtle starts
44 | cmdVel.linear.x = 0;
45 | cmdVel.linear.y = 0;
46 | cmdVel.linear.z = 0;
47 | cmdVel.angular.x = 0;
48 | cmdVel.angular.y = 0;
49 | cmdVel.linear.z = 0;
50 | }
51 |
52 | // callback function to update the current location
53 | void update_pose(const turtlesim::PoseConstPtr ¤tPose)
54 | {
55 | current.x = currentPose->x;
56 | current.y = currentPose->y;
57 | current.theta = currentPose->theta;
58 | }
59 |
60 | // callback function to update goal when goal message received
61 | void update_goal(const geometry_msgs::Pose2D &desiredPose)
62 | {
63 | desired.x = desiredPose.x;
64 | desired.y = desiredPose.y;
65 | waypointActive = true;
66 | }
67 |
68 |
69 | // calculates straight-line distance to goal
70 | double getDistanceError()
71 | {
72 | return sqrt(pow(desired.x - current.x, 2) + pow(desired.y - current.y, 2));
73 | }
74 |
75 | // calculates error in heading angle
76 | double getAngularError()
77 | {
78 | double deltaX = desired.x - current.x;
79 | double deltaY = desired.y - current.y;
80 | double thetaBearing = atan2(deltaY, deltaX);
81 | double angularError = thetaBearing - current.theta;
82 | angularError = (angularError > PI) ? angularError - (2 * PI) : angularError;
83 | angularError = (angularError < -PI) ? angularError + (2 * PI) : angularError;
84 | return angularError;
85 | }
86 |
87 | void set_velocity()
88 | {
89 | cmdVel.linear.x = 0;
90 | cmdVel.linear.y = 0;
91 | cmdVel.linear.z = 0;
92 | cmdVel.angular.x = 0;
93 | cmdVel.angular.y = 0;
94 | cmdVel.linear.z = 0;
95 |
96 | double angularError = getAngularError();
97 | double distanceError = getDistanceError();
98 |
99 | if (waypointActive == true && abs(distanceError) > .15)
100 | {
101 | if (abs(angularError) < .1)
102 | {
103 | cmdVel.linear.x = Klv * distanceError;
104 | cmdVel.angular.z = 0;
105 | }
106 | else
107 | {
108 | cmdVel.angular.z = Ka * angularError;
109 | cmdVel.linear.x = 0;
110 | }
111 | }
112 | else
113 | {
114 | cout << "I'm HERE!" << endl;
115 | cmdVel.linear.x = 0;
116 | cmdVel.angular.z = 0;
117 | waypointActive = false;
118 | }
119 |
120 | pubVelocity.publish(cmdVel);
121 | }
122 |
123 | int main(int argc, char **argv)
124 | {
125 | misc_setup();
126 |
127 | //register node “go_to_x” with roscore, then get a nodehandle
128 | ros::init(argc, argv, "go_to_x");
129 | ros::NodeHandle node;
130 |
131 | //Subscribe to current pose topic and set callback to "update_pose"
132 | ros::Subscriber subCurrentPose =
133 | node.subscribe("turtle1/pose", 0, update_pose);
134 |
135 | //subscribe to goal location topic "waypoint" and set callback to "update_goal"
136 | ros::Subscriber subDesiredPose = node.subscribe("waypoint", 0, update_goal, ros::TransportHints().tcpNoDelay());
137 | pubVelocity = node.advertise("turtle1/cmd_vel", 1);
138 |
139 | //Register node as publisher of type geometry_msgs::Twist and topic name turtle1/cmd_vel
140 | ros::Publisher pubVelocity =
141 | node.advertise("turtle1/cmd_vel", 0);
142 |
143 | //set the frequency that you want the loop below to execute at
144 | ros::Rate loop_rate(10); //10 cycles per second
145 |
146 | //execute this loop until connection is lost with ROS Master
147 | while (ros::ok())
148 | {
149 | //call the callbacks waiting to be called.
150 | ros::spinOnce();
151 |
152 | //call our own function after the callbacks are done
153 | set_velocity();
154 |
155 | //publish the message that we internally called cmdVel
156 | pubVelocity.publish(cmdVel);
157 |
158 | //output for you entertainment
159 | cout << "goal x = " << desired.x << endl
160 | << "current x = " << current.x << endl
161 | << " disError = " << getDistanceError() << endl
162 | << "cmd_vel = " << cmdVel.linear.x<< endl;
163 |
164 | //We set the frequency for 10Hz, this sleeps as long as it
165 | //takes to keep that frequency
166 | loop_rate.sleep();
167 | }
168 |
169 | return 0;
170 | }
--------------------------------------------------------------------------------
/chapter9/simple_goal_pub.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | simplegoal_pub.cpp is a program to learn to write ROS publisher nodes.
3 | This node publishes the Pose2D data type required by the go_to_xy.cpp program
4 | that drives the turtlesim turtle to the location entered here.
5 | This program is an accompaniment to the book Practical Robotics in C++
6 | written by Lloyd Brombach and published by Packt Publishing
7 | */
8 |
9 | #include "ros/ros.h"
10 | #include "geometry_msgs/Pose2D.h"
11 | #include
12 |
13 | geometry_msgs::Pose2D goal;
14 |
15 | using namespace std;
16 |
17 | void set_goal()
18 | {
19 | cout << "enter goal x: ";
20 | cin >> goal.x;
21 | cout << goal.x << " now y: " << endl;
22 | cin >> goal.y;
23 | cout << endl;
24 | }
25 |
26 | int main(int argc, char **argv)
27 | {
28 | ros::init(argc, argv, "simple_goal_pub");
29 | ros::NodeHandle node;
30 |
31 | ros::Publisher pubGoal = node.advertise("waypoint", 0);
32 |
33 | while (ros::ok())
34 | {
35 |
36 | set_goal();
37 | pubGoal.publish(goal);
38 | cout << "publishing" << endl;
39 |
40 | }
41 |
42 | return 0;
43 | }
--------------------------------------------------------------------------------
| | | | |