Skip to main content

4 posts tagged with "educational"

View All Tags

· 7 min read
Michael Hart

This post is to show how to set up the Boston Dynamics Spot SDK.

If you prefer a video format, or you want to see the samples in action, check out my YouTube video below:

Boston Dynamics Spot SDK

Boston Dynamics released the Spot robot, and I was able to get my hands on one in the lab. I also released the video below to show the basics of getting it unpacked and moving around.

This post is about showing examples from the Boston Dynamics Spot SDK, including mapping its environment and moving autonomously to key waypoints in the map, or detecting and following a person around. Unfortunately, it's hard to demonstrate the samples working in this post! I'll talk about the setup of the SDK and using it to connect to the robot, then leave it to the video for showing the samples themselves.

Downloading the SDK

The SDK is available from this Github repository, and Boston Dynamics host the documentation for the SDK. Most importantly, we're interested in the Python examples.

Start by cloning the SDK in your chosen terminal using git:

git clone https://github.com/boston-dynamics/spot-sdk

Make sure you have python installed - if not, follow the instructions for your system to install.

The next step is to set up a virtual environment for dependencies. This is entirely optional, but it makes it easier to track Python dependencies if they're all installed like this.

To set it up, make sure pip3 is available:

pip3 --version

Then use it to install virtualenv:

pip3 install virtualenv

Once virtualenv is installed, use it to create a virtual environment and activate it:

cd spot-sdk
virtualenv venv
# On Windows:
venv\Scripts\activate
# On Mac:
source venv/bin/activate

Now we have a clean virtual environment, we can install any dependencies we want into it. For example, we can install dependencies for the hello-spot example:

pip install -r ./python/examples/hello_spot/requirements.txt

This same process can be used for every example that you want to run. You do need an internet connection to do this, so it's a good idea to install dependencies you're likely to need now to save reconnecting later.

Hello Spot

To understand how to connect to the robot, the Hello Spot example is a great place to start. Let's take a look at how it works! The code we're looking at is the hello_spot.py file. In addition, we can look up any concepts in the Boston Dynamics Concepts documentation - the code provides a great overview, with the documentation going into more detail about each part.

The interesting parts are all in the hello_spot function. I'll go through a few lines and describe what they're doing. First, a minor but useful step: setting up logging.

bosdyn.client.util.setup_logging(config.verbose)

This first line is using the Python logging module, as described in the code comments. It's using the verbose argument set in config so you can change how verbose the logging is when you start the script.

The next step is to create the standard SDK. This is the object used to interact with the SDK.

sdk = bosdyn.client.create_standard_sdk('HelloSpotClient')

The SDK object is used to create the robot object, which is a Python object representing the robot that we can interact with. This is where the host name is used to contact the robot, which is one of the command line parameters. Get this wrong, or be connected to the wrong network so that you can't contact the robot, and this stage will fail.

robot = sdk.create_robot(config.hostname)

The next step is to authenticate with the robot using the username and password. The SDK provides a utility method which prompts the user (you) for the username and password to connect to the robot.

bosdyn.client.util.authenticate(robot)

Once authenticated, we need to synchronize time with the robot. This is because the robot has its own idea of the current time and will refuse commands sent with a time difference that's too large. For more information here, see the concepts page on time-sync in the documentation.

robot.time_sync.wait_for_sync()

The example then asserts that the robot is not estopped (i.e. emergency stopped). To be able to drive the robot, someone must be able to press the estop. This can be done using the tablet with the Spot app or the estop example from the SDK. If someone is able to press the estop, and the estop has not already been pressed, then execution can continue.

assert not robot.is_estopped(), 'assertion message'

The comments explain the concept of leasing very well, so I'll include the relevant comments:

# Only one client at a time can operate a robot. Clients acquire a lease to
# indicate that they want to control a robot. Acquiring may fail if another
# client is currently controlling the robot. When the client is done
# controlling the robot, it should return the lease so other clients can
# control it. The LeaseKeepAlive object takes care of acquiring and returning
# the lease for us.
lease_client = robot.ensure_client(bosdyn.client.lease.LeaseClient.default_service_name)
with bosdyn.client.lease.LeaseKeepAlive(lease_client, must_acquire=True, return_at_exit=True):

The rest of the code stays within the with block, meaning that it can access the robot and move it around. From there, it is possible to give the robot movement commands, including being able to stand up.

This is the basic pattern for all robot scripts that involve movement:

  1. Create SDK object
  2. Create robot object from SDK
  3. Authenticate with robot
  4. Synchronize time with robot
  5. Check estop
  6. Obtain robot lease

Message Format and Request/Response

The way that our application sends messages to the Spot robot is by building Protocol Buffers (aka protobuf) messages and sending them using gRPC. For the most part, we don't need to understand this, as the SDK does a good job of hiding the messaging behind SDK methods - but once we get to the more advanced examples, understanding how protobuf and gRPC work can help understand the code.

We can use protobuf to define the structure of a message, then generate the code for serializing and deserializing an instance of that message to bytes. The SDK provides the messages and the pre-generated methods for serializing and deserializing those messages for us.

At the same time, we use gRPC to send and receive messages. You can think of this as similar to a ROS service call, which also has a request/response flow. For the Spot SDK, there are defined services available, which we can interact with by using the relevant Request and Response messages.

Take the arm_joint_move example, which has a method to build an arm movement command:

def make_robot_command(arm_joint_traj):
""" Helper function to create a RobotCommand from an ArmJointTrajectory.
The returned command will be a SynchronizedCommand with an ArmJointMoveCommand
filled out to follow the passed in trajectory. """

joint_move_command = arm_command_pb2.ArmJointMoveCommand.Request(trajectory=arm_joint_traj)
arm_command = arm_command_pb2.ArmCommand.Request(arm_joint_move_command=joint_move_command)
sync_arm = synchronized_command_pb2.SynchronizedCommand.Request(arm_command=arm_command)
arm_sync_robot_cmd = robot_command_pb2.RobotCommand(synchronized_command=sync_arm)
return RobotCommandBuilder.build_synchro_command(arm_sync_robot_cmd)

We can see in this code that we construct an arm_command_pb2.ArmCommand.Request, so we can reasonably expect an arm_command_pb2.ArmCommand.Response in response. We can also see that the SDK provides a RobotCommandBuilder to help us build messages for particular services.

This flow is in many places that don't have specific SDK methods. Anywhere that you see _pb2 in the example code is generated by protobuf. Have fun reading through the samples!

Examples

As written earlier in this post, it's difficult to show the working examples in a blog post! Please do take a look at the video if you want to see the following samples in action:

  1. hello_spot
  2. estop
  3. graph_nav_command_line
  4. graph_nav_view_map
  5. spot_detect_and_follow

Summary

In short, that's the basics of interacting with Spot using Python! From there, it's up to you what you can get the robot to do. In the future, I'm eager to get my robot connected to AWS to show how the cloud can provide value for it - maybe I can control it using a Lambda function in the cloud!

· 11 min read
Michael Hart

This post is to give you my five tops on how to stand out as a Software Engineer. These are tips that will help at any career level, not just when you're starting out.

If you prefer a video format, check out my YouTube video below:

Knowing When to Ask Questions

My first tip is knowing when to ask questions. The phrasing of this title sounds like you need to ask fewer questions, but mostly likely you need to ask more questions. The truth is that when you start on a new team, that team is expecting you to ask a lot of questions. This is especially true when you're just starting out, so my advice to you is this:

  1. When you need to know something that you can't find out online, don't waste your time. Ask another team member straight away.
  2. When you need to know something that you could possibly find online, try setting yourself a time limit before asking for help. Give it 15-30 minutes, try and work through it, then find a team member to take a look with you.

This will give you a good balance between feeling like you're pestering people and taking all their time up, and being able to actually complete your work. The last thing your new team members want for you is to sit there wasting hours or even days on something they could have helped you with in two minutes.

Example - Ask Straight Away

You want to find some documentation for your project. It's very unlikely you could find this information by yourself, so it's best for you to find a tem member to ask for help.

Example - Wait, Then Ask

You've changed something in the code and it won't compile any more. This is something you could probably figure out by yourself given enough time, so set a timer for 15 minutes, then try to work through it. If the timer goes off, and you haven't made any progress, find someone who can help.

Taking Responsibility

My second tip is to take responsibility, and there's two ways to take responsibility that I'm talking about: first, taking responsibility for tasks that you don't normally do as part of your work; second, taking responsibility when you make a mistake.

Volunteering for Tasks

As far as tasks outside your normal work goes, it's very common for your manager or your team to have a task come up that needs to be completed, but doesn't naturally fall to a particular person. Chances are, your team would prefer one person to be responsible for it and drive it to completion. If that's something you could do, but is outside of your normal work area, it's a great idea to consider taking it on. It's a way that you can stand out as an engineer, learn something new, and grow in your career.

Example - Running a Hackathon

I had a teammate who wanted to participate in the Hackathon, but when I encouraged them to try and organise it for themself, they weren't willing to take on that responsibility. Instead, I took on the task: I arranged it, chose the theme for it, and made sure it went ahead. Now, I'm much more prepared to run other Hackathons in the future.

Owning Your Mistakes

The second way that you should take responsibility is when you've made a mistake. Especially when you're starting out, but all throughout your career, you can and will make mistakes. The best thing that you can do is learn from them and try to make sure they don't happen again.

The best response you can give if you get called out in a meeting for something you've done wrong is to avoid getting upset, and to simply say, "yes, I made a mistake there, and here's what I'm going to do to stop it happening again."

That could be something you do differently, or it could be a process that you or your team put in place to make sure that no one will make that mistake again.

Example - Not Paying Attention

I took part in an informational meeting with a lot of distractions in the house. I couldn't pay full attention, I wasn't able to ask questions at the end, and I didn't even realise how distracted I was until both my manager and one of my colleagues commented on it. I realised how disrespectful it was to the presenter at the time. To this day, I make sure that there as few distractions in the house as possible when I'm attending a meeting out of respect for other people's time.

Actively Pursue Advancement

My third tip for you is to actively pursue advancement. By that, I mean you need to go after what you want for your next career step.

In my experience, many people are content to receive tasks from their team, and do their work well and on time, but that's not the way that you can grow your career the fastest. The best thing that you can do is have an honest conversation with your manager about where you want to be. Is there more responsibility that you want to take on? Do you want a raise, or a promotion? These are things that you need to bring attention to if you want to make them happen and you need to make them happen yourself.

To understand this better, try and think of it from your manager's point of view - or their manager's point of view. They have teams ot manage, projects to get out on time, customers they need to talk to and keep happy; how much of their attention do you think is solely on you? The answer is probably not that much, which is why you need to bring their attention on to you. You need to make it happen, and that's what this conversation would do.

Talk to your manager, tell them what you need, and ask them for feedback so you understand exactly where you are and what weaknesses you need to work on in order to progress.

Example - Asking for a Raise

My most recent example of this was when I was working on a team and I felt like I was taking on more responsibility than my level required - even acting in a team lead role. I had a conversation with my manager and asked him for a raise. Not only did he respond positively to this, he actually helped me to get promoted instead so that I became the official team lead. It was a benefit to me and a benefit to him because it showed how he was growing his team.

Document Your Wins

My fourth tip is to document your wins, by which I mean writing down what you're doing as you're doing it, including any wins that you have in that process.

You can start this by taking notes every day of what you're doing. Open a note with the date and your responsibilities for the day, then log what you do during the day.

Daily Note Template

I've configured my note-taking software Obsidian to automatically open this every day.

This is a great way to keep a log and look back on how you fixed something in the past, but it will also help you with the next step: periodically updating a document that contains all of your wins.

Daily Notes List

My list of daily notes since starting digital notes.

My recommendation for this is something called a brag doc. I use a modified version of the document suggested by Julia Evans. To use this document effectively, set time aside every 2-4 weeks and update the document with what you've been doing and what has gone well. Use the daily notes you've been taking to help supplement this. By doing it bit by bit, it's a lot easier to keep track of what you've done over a long period of time. You'll also have a great body of evidence if you need to pursue advancement; you can show evidence that you've been working above your level. Bonus points if you can write some sort of data down - numbers are even more convincing than quotes when you're trying to prove something.

The next step after this is to use that brag doc to keep your resume, CV, or LinkedIn profile up to date. This is another point where it's much easier to do it little by little over time so it's always up to date, instead of making one large effort when you need it.

Example - Resource Feedback Spreadsheet

For example, I have a spreadsheet that keeps track of everyone that's reached out to me from the company saying something about the resources that I put online. This is a great way for me to figure out what the best resources are what's been most helpful - plus, if I need to prove that what I've done has been helpful, I have all the evidence right there.

Remember to set that time aside for writing your daily notes and updating your brag doc and LinkedIn profile. Don't dismiss this - keeping my LinkedIn profile up to date was what landed me my job at Amazon in the first place!

Know Your Worth

My fifth and final tip is to know your worth. Getting into software engineering is no easy feat. It takes a lot of training and technical knowledge, so getting where you are is already a battle - not to mention any experience that you can get on top of that.

You've earned the right to be confident. You should be confident in your statements and your decisions while being prepared to learn from your mistakes. Even if you don't feel confident from your amount of experience, I advise you to act like you're confident. Enough time acting like you're confident and you will eventually feel that confidence.

Example - High-Level Meeting

My most recent example of this was taking part in a meeting with leaders several levels above me. I was nervous and didn't want to speak in case I didn't sound like I knew what I was talking about. I spent a lot of the meeting sitting and taking notes, distilling what I had heard down right up until the point where I made some realisations. When I eventually spoke up about them, the leaders listened to me and the conversation took a whole different direction.

Another part of knowing your worth is being aware of what other options you have. Keep an eye on your career field and see what other job opportunities there are, as well as the kind of salary your job normally makes. It's a great idea to know what's out there - either you'll find something that you think is more exciting and is a better opportunity, or you can be satisfied that where you are is the best place for you.

Putting the Tips into Action

So there you have my top five tips on how to stand out as a software engineer.

Some things you can do periodically, like updating your brag doc or your LinkedIn profile. You can start straight away by opening up a daily note, writing the date, and starting to take notes. You can also make an empty brag doc, ready to start filling in your first entries.

Another way you can get started is by arranging a talk with your manager, where you can have an honest conversation about where you want to be and what kind of feedback your manager can provide you. Arrange the meeting, go in knowing what you want, and write down the result of the meeting so you can look back on it in your log.

The last part is interacting with your team. Start acting with more confidence around your team, take responsibility for something that's outside of your comfort zone, and take responsibility for your mistakes as they happen. A few good examples of tasks you can take responsibility for are being the Scrum Master for your team, writing up documentation that is currently missing, or leading a meeting that no one has volunteered for.

Any of these options will help you to get started and to grow in your career. Good luck standing out as a software engineer!

· 21 min read
Michael Hart

This post is about how beginners can make the most out of every tutorial by digging deep into the code to understand it. This is the best foundation you can give yourself for continuing to work on the code and making your own modifications. It follows on from Getting Started as a Robotics Software Engineer!, where I give the advice:

First, look for and use every resource you have available to you. Look online, ask people, work in the field; anything you can to make your journey easier.

Following on from that, I wanted to show how to take a tutorial and use various resources to understand what's happening in the provided code. I'll take the tutorial from ROS about writing a simple publisher/subscriber, and I'll use C++ to build it, as this is less well-known than Python and so a better way to demonstrate self-learning.

If you'd prefer to follow along, I've built a video demonstrating everything in this article, available here:

To understand the tutorial code, we'll be using the following resources:

  1. Version control (git) - to check the differences between versions and understand what's changed
  2. Explaining the code - try to explain as much of the code as you can, either in comments or out loud to someone or something else.
  3. Setting up your IDE - using your editor to help you move around the code and look up parts of it will help you find out what's going on.
  4. Using online resources - tutorials, blogs, videos, and a number of other resources can help explain parts that you don't understand up to this point.
  5. Debugging - using a debugger to attach to running code and stop it to interrogate variables and step through each line.
  6. Testing - using unit tests to check your understanding or set up simple test cases to focus on a particular area of code.

Using Source Control to your advantage

This isn't strictly speaking about understanding your code - what it does do is help you get back to a known state, and see what's changed since then. Let's follow the tutorial until there's a blank project checked out, then commit our progress using Git.

Installing Git

First, we need to install Git and do some configuration. Follow the install instructions, then set up your username and e-mail address:

# Replace with your username and email!
git config --global user.name "Mike Likes Robots"
git config --global user.email "mikelikesrobots@outlook.com"

Committing the template project

Follow the tutorial up until Create a package, then execute the pkg create command:

ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_pubsub

Once this is done, let's stop and commit our progress:

cd cpp_pubsub
# This initializes a git repository
git init
git add --all
git commit -m "Initial commit"

Checking Git Status

Our project is now in source control. If we check the status now, we should see that there are no changes:

$ cd ~/dev_ws/src/cpp_pubsub
$ git status
On branch main
nothing to commit, working tree clean

Great! There are no file changes since the last commit. Try running this command again after changing a file!

Adding New Files

Let's follow through the next few steps - work through the whole of step 2. From here, we can run git status again and see some changes.

$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: CMakeLists.txt
modified: package.xml

Untracked files:
(use "git add <file>..." to include in what will be committed)
src/

no changes added to commit (use "git add" and/or "git commit -a")

Git tracks files, but won't track empty folders, so adding the new source file tells git that the src folder exists. As the whole file is new, we won't take a look, but we can take a look at the package.xml changes to see what has been updated:

# Tell me the difference between this file and the last committed version
$ git diff package.xml
diff --git a/package.xml b/package.xml
index 909dca9..5504dc9 100644
--- a/package.xml
+++ b/package.xml
@@ -12,6 +12,9 @@
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

+ <depend>rclcpp</depend>
+ <depend>std_msgs</depend>
+
<export>
<build_type>ament_cmake</build_type>
</export>

The plus signs tell us that lines have been added. If there were any minus signs, that shows the line has been deleted. So not only can we restore a previous version of a file (or even the whole project), we can see the exact changes that have happened!

Any time we get something working, we should make sure to commit it so we can compare against it later. Let's do that now:

$ git add --all
# Use a meaningful commit message - not just "made changes"
$ git commit -m "Add talker node"

Now we can carry on with the tutorial!

Try to Explain the Code

At this point, you should read through the only source file and see how much of it you understand. But, not only should you understand it - you should be able to explain it. For each line, see if you can write a comment above the line with exactly what is happening. You could also do "rubber duck debugging" - take a rubber duck (or another toy), and tell the duck what the code is doing in detail. This is to force you to slow down and read each line thoroughly instead of skimming over lines you think you understand.

Let's take an example from the code and explain it together.

// Gives access to time-related functions
#include <chrono>
// Functional?
#include <functional>
// Gives access to smart pointers
#include <memory>
// Gives access to string functions like string length, string concatenation
#include <string>

Work through the file and see how much of it you understand just from reading the tutorial. Even in the block above, it's not very clear what #include <functional> does - let's see if we can use our IDE to explain it.

Using the IDE

IDE stands for Integrated Development Environment, which means a set of tools to help development integrated into one place. There are a few examples with different amounts of setup required, such as:

  1. Visual Studio Code (aka VSCode)
  2. CLion (or other JetBrains IDEs)
  3. NeoVim

These just to name a few - there are a great many IDEs out there, and you should experiment to find the one you prefer. The ones I've listed here are some of the most popular in my experience, but I would recommend using VSCode with the C/C++ extensions.

Using an IDE will help us to look up functions and variables more easily, highlight issues in the code, and run everything all in one place - including a debugger. In this case, if I open the project in VSCode, I can right click on <functional> and Go To Definition:

Go to definition of functional

This is hugely complicated! We're not getting the information we need from the IDE here. Let's take another example - hover over the using namespace std::chrono_literals line and wait for the explanation:

IDE help for chrono literals

That's much more help! Now we can see that this line helps define time periods, like the 500ms on line 38. We are getting some red underlines in the same image though - this is the IDE telling us that something is wrong. In this case, the IDE can't find the two underlined files that the code is trying to access, even though the build system can; it just means that the IDE isn't working properly. We can do some configuration to solve this error by pressing Ctrl+Shift+P (on Windows/Linux) and searching for C/C++ configuration:

C/C++ edit configuration menu option

Then scroll down to the Include path section and add /opt/ros/humble/setup.bash:

Add include path for IDE

Success! Our red squiggles have disappeared. That's our IDE configured. Try typing some code into functions and see the autocomplete working!

Using Online Resources

The IDE explained chrono literals for us - but what about that functional header? This is where we need to leave the IDE and get some extra information. Here are a few examples of resources available online that you can use to supplement your understanding:

  1. Documentation: in particular for ROS2, the ROS2 docs are extensive and have a lot of guides to get you started.
  2. Tutorials: plenty of articles exist to get you started with tutorials. This blog post alone uses two tutorials - Writing a simple publisher and subscriber (C++) and Writing Basic Tests with C++ with GTest. With tutorials, try not to go through the steps until they work - take the time to understand what's going on, and read the explanations.
  3. Open Source Examples: there are plenty of open source projects in general that can help provide examples for how to do something. For example, Husky has a lot of ROS2 code showing how to organize packages, write launch files, write tests, and so on. It can be very difficult to enter someone else's project, so my advice here is to figure out the project structure, then search for the specific thing you're looking for. Understanding a full package with no help takes a long time, even for seasoned developers.
  4. Blogs/Videos/Podcasts: these are great for general knowledge about coding or for searching for a specific thing to learn. I recommend finding blogs, video channels, or podcasts that cover interesting topics with good explanations, then follow those resources for new updates. I like Joel on Software as a general blog, and Articulated Robotics gives great explanations while going through examples.
  5. Forums: sites like Stack Overflow and Reddit can directly answer your questions, or have many previously answered questions that may help with your issue. Don't be afraid to post your own question! Folks on these sites are there to help. One tip if you do post a question is to make a small example of your problem and include it in your post so others can instantly see what the issue is. If you don't provide enough information, or you ask a question with an answer that's easily found through a Google search, you won't get a good reception.
  6. Online courses: sites like Codecademy and LeetCode Explore have courses available to guide you through different languages and concepts, explaining every step along the way. These are great to build a great foundation and understand what you're reading and writing better.
  7. Coding challenges: sites like LeetCode and Project Euler provide sample problems that need you to write code in order to solve them. They're good practice and good fun too! My particular favorite is the Advent of Code, which runs every year during advent, and I try to keep up with until I inevitably run out of time and give up on (normally about 11 days in).
  8. AI: the recent surge of GenAI technologies has an amazing benefit to coders, as it can explain tricky concepts. Any part of the code you don't understand, you can usually just plug it in to ChatGPT and get a thorough explanation. There are also coding companions, like Amazon CodeWhisperer, which suggest lines of code while you're programming so you don't need to look up everything in documentation - which can be a significant speedup!

Find the resources that suit you best, and keep them available when you're trying to understand the code. In this case, we can find the C++ reference guide for the functional header, which explains that our header is likely to give access to the std::bind expression in our code.

See if you can go through every line in the code and explain it thoroughly. Even if you can explain it, it's still worth checking through the next sections on debugging and testing to understand the flow of the code.

Debugging

Broadly speaking, debugging is the process of finding and correcting issues in your code, but there's a special tool called a debugger that is REALLY helpful with this process. A debugger can be used to pause actively running code and let you look inside!

You can use a debugger to stop the code at a particular line, or when a variable has a particular value, or when a line has been "hit" a certain number of times - all from within your IDE. You can also step through, line by line, to see how the variables are changing. Let's try it out in our simple talker.

tip

A common method of debugging is to add print/log statements throughout your code and then run the code. This is effective a lot of the time, but doesn't give you the same amount of control as a full debugger, so it is still well worth learning!

Setting up the Debugger

Normally for a project with a standard build system, it's fairly easy to set up debugging. For our system, as it's using colcon build, there's a couple of extra configuration steps to go through.

First, open up the debug menu. Select "create a launch.json file".

Open debug and create launch file

Select the C++ (GDB/LLDB) option. This will open a blank file with no configuration, like this:

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}

Install GDB

We will be using gdb as a debugger, which means we will need to install it:

sudo apt install gdb

Add Debugger Launch Configuration

With that installed, we can add a new configuration for debugging our talker. Click the Add Configuration button in the bottom right and select C/C++: (gdb) Launch.

Add GDB launch configuration

This will create a template launch file. Now change the program entry to "${workspaceFolder}/install/cpp_pubsub/lib/cpp_pubsub/talker". Once finished, your debug configuration file should look like this:

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/install/cpp_pubsub/lib/cpp_pubsub/talker",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}

]
}

That's the debug configuration done - time to build and debug.

Build with Debug Information

One more thing to bear in mind is that we have to build with debug information enabled - this is how the debugger can tell which line corresponds to which part of the running code. We can do this by using a different build command - instead of colcon build, we will instead use:

colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo

Now we're ready to launch!

Pausing on a Breakpoint

Let's tell our debugger to pause inside the timer callback function by adding a breakpoint. That's the red dot to the left of the line number. Open the publish_member_function.cpp file and click to the left of the line number to add the breakpoint - it will look like this:

Add timer callback breakpoint

Now launch the debugger with the green triangle in the debug menu:

Launch debugger task

The program should launch and hit your breakpoint. There's a lot of information here, so let's keep it simple: stepping through the code and watching the message variable change. Take a look at the debugger window:

Debugger stopped on breakpoint

In the top left, numbered 1, we can see the controls for stepping through the code. Number 2 shows the current value of the message object, which is empty at this point. Number 3 shows the point in the program where the debugger has stopped. Bonus: in the bottom left, there's a watch panel, where you can add specific variables to watch - I've added count_ and message.data for easier viewing.

The controls for stepping through the code allow us to:

  1. Resume - continue execution. Keeps running until another breakpoint is hit.
  2. Step over - step to the next line.
  3. Step into - if there is a function call, step into the function.
  4. Step out - run to the end of the current function and step back out.
  5. Stop - quit the program entirely.

We can also edit the breakpoint to have more control over when it stops. Right click the breakpoint and click Edit Breakpoint to see the options available - I'll leave these out for brevity.

Now we can click the step over button a few times to see the message.data variable update with the string that we're going to publish. We can also resume a few times to see the count_ variable increment every time.

From this, we can stop the code, check what variables are doing, and look at the flow of the code. Any time we don't understand how code links together, or we're not sure what kind of data is passed around, debugging can tell us exactly!

However, if we're looking at code that requires a callback to do anything, it can be less convenient to run multiple different programs at the same time. For example, the other half of this tutorial is to create a listener node that listens for messages from the talker node - what happens if we only run the listener node? Nothing, because it doesn't have any messages to listen to. One way we can debug the subscriber code with the same data every single time is to use testing.

Testing [Advanced]

Testing is an incredibly useful tool in software development. It doesn't just mean verifying the code works correctly at the time of writing - the tests can be run automatically for any future update to ensure that the code doesn't break in unexpected places. In this case, we're looking at understanding the code, so you're not likely to be writing your own complete test suite - instead, we'll set up one test that will let us check how the listener node works without having to run the talker node at all!

note

This is a more advanced way of diving deeper into a file. It's very useful once you can get it working, but can be difficult to set up.

Adding Tests to the Listener

First, to get this working, we need to download the listener file. Follow the tutorial to the end of step 4 - and remember to commit your code once you're sure it works!

With the new node downloaded and working, we're next going to hack our subscriber source file a bit by changing the main function to GTest if a particular symbol is defined. That basically means we can compile the file twice and get two different outcomes depending on how we set up the compiler. Replace the subscriber source file with the following:

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}

private:
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

#ifndef DEBUG_TEST_MODE
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}

#else
#include <gtest/gtest.h>

class RclCppRunner {
public:
RclCppRunner(int* argc, char** argv) {
rclcpp::init(*argc, argv);
}
~RclCppRunner() {
rclcpp::shutdown();
}
};

class TestPublisher : public rclcpp::Node {
public:
TestPublisher() : Node("test_publisher") {
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
}
void publish(const std::string contents) {
auto message = std_msgs::msg::String();
message.data = contents;
publisher_->publish(message);
}
private:
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
};

TEST(package_name, debug_listener)
{
auto listener = std::make_shared<MinimalSubscriber>();
TestPublisher talker;
talker.publish("Test Message 1");
rclcpp::spin_some(listener);
}

int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
RclCppRunner runner(&argc, argv);

return RUN_ALL_TESTS();
}

#endif
tip

A useful resource for learning how the testing works here is from the ROS2 documentation on writing tests. We're not following this because the test is only temporary - we're going to remove it once we understand the code.

Compiling our Test Function

We also need to set up the CMakeLists.txt to compile the file differently so we can run our test. To do that, add the following to the end of the CMakeLists.txt file:

if(BUILD_TESTING)
find_package(ament_cmake_gtest REQUIRED)
ament_add_gtest(${PROJECT_NAME}_tutorial_test src/subscriber_member_function.cpp)
target_include_directories(${PROJECT_NAME}_tutorial_test PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
ament_target_dependencies(${PROJECT_NAME}_tutorial_test
rclcpp
std_msgs
)
target_compile_definitions(${PROJECT_NAME}_tutorial_test
PRIVATE
DEBUG_TEST_MODE
)
endif()

Now we can run the build command again to build our new test file:

colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo

Running the Test

From here, you can run the tests either using:

colcon test
colcon test-result --all

Or my preferred method for seeing the GTest output:

$ cd ~/dev_ws
$ ./build/cpp_pubsub/cpp_pubsub_tutorial_test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from package_name
[ RUN ] package_name.debug_listener
[INFO] [1707940864.492059362] [minimal_subscriber]: I heard: 'Test Message 1'
[ OK ] package_name.debug_listener (31 ms)
[----------] 1 test from package_name (31 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (31 ms total)
[ PASSED ] 1 test.

At this point, our subscriber printed a message saying it heard the Test Message. Great! We can even change the test message to whatever we want by changing the test:

TEST(package_name, debug_listener)
{
auto listener = std::make_shared<MinimalSubscriber>();
TestPublisher talker;
// Change the string in the following line
talker.publish("Test Message 1");
rclcpp::spin_some(listener);
}

Rebuild, and the new message will be used instead.

Launch the Test with a Debugger

Finally, we can set up the debugger to run with our new test. Go back to the debug tab and add a new configuration. This time, the program name will be ${workspaceFolder}/build/cpp_pubsub/cpp_pubsub_tutorial_test, and we will change the name to be something more meaningful as well. The end result is something like:

{
"name": "Launch tutorial test",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/cpp_pubsub/cpp_pubsub_tutorial_test",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},

Now set a breakpoint on line 20 inside the topic_callback function and launch the debugger. It should break on the line and let you check the contents of the message!

Break on log line in subscriber

Now we can write whatever test data we want, or write multiple tests with different data or testing different code paths.

This seems complicated!

I don't disagree. Setting up debugging for compiled languages already gets a bit tricky, especially when the language strictly enforces if something is private or public - this isn't an issue we would be facing for Python! Python is much easier to debug, which is another reason it's a great choice for beginners.

With that said, once the initial setup is done, it becomes much easier to extend and get some really valuable information. It's certainly worth learning.

Summary

In short, my advice is to try to thoroughly understand the code in a tutorial, rather than doing the minimum work to get it working. This will help you in the long run, even if it takes you more time now.

Use git to help you track changes and revert to working versions as you experiment. Thoroughly explain the code as you work through it, preferably out loud to a rubber duck. Use your IDE, debugger, and testing setup to help read and run the code so you can see how it works. Last of all, and perhaps most obviously, use all of the resources you can from online to learn more about the code and the technology around it - take a look at my shortlist to figure out the best option for your use case.

Good luck with your future tutorials!

· 12 min read
Michael Hart

This post is all about my advice on getting started as a Robotics Software Engineer. I want to tell you a little of my journey to get to this point, then what you should do to practise and give yourself the best start possible.

If you prefer a video format, check out my YouTube video below:

Who is this post for?

This post is aimed at absolute beginners. If you've seen Boston Dynamics robots running through assault courses or automated drone deliveries and thought, "this is the kind of stuff that I want to work on my whole career" - this post is for you.

Robotics contains a lot of engineering disciplines. If you're interested in the intelligence behind a robot - the way it messages, figures out where it is and where to go, and how it makes decisions - this post is for you.

With that said, if you're interested in building your own arm, 3D printed parts, or printed circuit boards, this post isn't likely to be much help to you. Feel free to read through anyway!

Atlas jumping with a package

Atlas Gets a Grip | Boston Dynamics by Boston Dynamics

Who am I?

I'm a Software Development Engineer focused on robotics. I have a master's degree in Electrical & Electronic Engineering from Imperial College London in the UK. I have 11 years experience in software engineering, with 7 years of that specialising in robotics.

Throughout my career, I've worked on:

  1. A robot arm to cook steak and fries
  2. A maze-exploring rover
  3. A robot arm to tidy your room by picking up pens, toys, and other loose items
  4. Amazon Scout, a delivery rover

That's to name just a few! Suffice to say, I've worked on a lot of different projects, but I'm not a deep expert in any particular field. What I specialise in now is connecting robots to the cloud and getting value from it. That's why I'm working for Amazon Web Services as a Senior Software Development Engineer specialised in robotics. With that history, I'm a good person for getting you started in the world of robotics - starting with the hardware you need.

What hardware do you need?

Thankfully, not a lot! All you really need to get going is a computer. It doesn't need to be especially powerful while you're learning. If you want to be able to run simulations, a GPU will help, but you don't need one.

For the operating system (OS), you'll have a slightly easier time if it's running Linux or Mac OS, but Windows is very close to being as good because of great steps in recent years building the Windows Subsystem for Linux (WSL2). It's basically a way of running Linux insides your Windows computer. Overall, any OS will work just fine, so don't sweat this part.

That's all you need to get started, because the first step to becoming a software engineer for robotics is the software engineer part. You need to learn how to program.

How should you learn to program?

Pick a Language

First, pick a starting language. I do mean a starting language - a good way to think of programming languages is as tools in a toolbox. Each tool can perform multiple jobs, but there's usually a tool that's better suited for completing a given task. Try to fill out your toolbox instead of learning how to use one tool for every job.

Before you can fill out your toolbox, you need to start with one tool. You want to use your tools for robotics at some point, which helps narrow options down to Python and C++ - those are the most common options in robotics. I would recommend Python, as it's easier to understand in the beginning, and learning to program is hard enough without the difficult concepts that come with C++. There are tons of tutorials online for starting in Python. I would recommend trying a Codecademy course, which you can work through to get the beginning concepts.

Once you grasp the concepts, it's time to practise. The important part to understand here is that programming is a different way of thinking - you need to train your brain. You will have to get used to the new concepts and fully understand them before you can use them without thinking about them.

To practise, you could try Leetcode or Project Euler, but those are puzzles in problem-solving or teach computer science concepts, and they aren't the best tool for learning a language in my eyes. I believe that the best way to learn is to come up with a project idea and build it. It doesn't have to be in robotics. You could build a text-based RPG, like:

$ You are in a forest, what do you do?
1. Go forward
2. Look around
>>>

Or, you could build a text-based pokemon battle simulator, where you pick a move that damages the other pokemon. The important part is that the project is something you're interested it - that's what will motivate you to keep building it and practising.

tip

Try writing some post-it notes of features you want to add to your project and stick them on the wall. Try to complete one post-it note before starting another.

Spend time building your project. Look online for different concepts and how they can fit your project. Use sites like Stack Overflow to help if you get stuck. If you can, find someone with more experience who's able to help guide you through the project. You can ask people you know or look online for help, like joining a discord community. There will be times when you're absolutely stumped as to why your project isn't working, and while you could figure it out yourself eventually, a mentor would not only help you past those issues more quickly but also teach you more about the underlying concept.

In essence, that's all you have to do. Get a computer, pick a language, and build a project in it. Look at tutorials and forums online when you get stuck, and find a mentor if you can. If you do this for a while, you'll train your brain to use programming concepts as naturally as thinking.

Other Tools

On top of Python, a couple of tools that you need to learn:

  1. Version control
  2. Linux Terminal

Version Control

Version control is software that allows you to store different versions of files and easily switch between them. Every change to the file can be "committed" as a new version, allowing you to see the differences between every version of your code. There's a lot more it can do, and it is absolutely invaluable to software developers.

Git is by far the most commonly used version control software. Check out a course on how to use it, then get into the havit of committing code versions whenever you get something working - you'll thank yourself when you break your code and can easily reset it to a working version with the tool.

Linux Terminal

The Linux Terminal is a way of typing commands for the computer to execute. You will be using the Terminal extensively when developing software. You don't need a course to learn it, but when you do have to write commands in the terminal, try not to just copy and paste it without looking at the command - read through it and figure out what it's doing. Pretty soon, you'll be writing your own commands.

tip

Use Ctrl+R to search back through history for a command you've already executed and easily run it again.

Work with Others

You don't just have to learn alone - it is hugely helpful to learn from other people. You could look into making a project with friends or an online community, or better yet, join a robotics competition as part of a team.

If you practise enough with what I've told you here, you'll also be eligible for internships. Look around and see what's available. Try to go for companies with experienced software engineers to learn from, and if you're successful in your application, learn as much as you can from them. Software development as a job is very different from doing it at home, and you will learn an incredible amount from the people around you and the processes that the company uses.

What about robots?

So far, we've not talked about robots very much. Getting a solid ground in programming is very important before going on to the next step. But, once you have the basics, this part is how to get going with programming robots.

Robot Operating System (ROS)

The best starting point is ROS. This is the most popular robotics framework, although far from the only one. It is free, will run on any system, and has a ton of tools available that you can learn from. Also, because it's the most popular, there's a lot of help available when you're struggling. Follow the documentation, install it on your system, and get it passing messages around. Then understand the publish and subscribe system it uses for messages - this is crucial for robotics in general. The resources on this blog can help, or you can think of a project you want to build in ROS. Start small, like getting a robot moving around with an Xbox controller: you'll learn pretty quickly during this process that developing robots is really difficult, so set achievable goals for yourself!

Simulation

If you want to work with robots in simulation, that's great! You can get going with just your computer. You need to understand that it's incredibly difficult to make robots behave the same in simulation as they do in real life, so don't expect it to transfer easily. However, it is better for developing robotics software quickly - it's faster to run and quicker to reset, so it's easier to work with. Because of that, it's a valuable skill to have.

If you're looking for somewhere to start, there are a few options. Gazebo is well-known as a ROS simulation tool, but there are also third party simulation software applications that still support ROS. I would start by looking at either NVIDIA Isaac SIM or O3DE - both are user-friendly applications that would be a great starting point.

NVIDIA Isaac Sim Example

NVIDIA - Narrowing the Sim2Real Gap with NVIDIA Isaac Sim

Embedded

As far as embedded development goes, I consider this optional - but helpful. It's good for understanding how computers work, and you may need it if you want to get closer to the electronics. However, I don't think you need it; most programming is on development kits, like Raspberry Pi and Jetson Nano boards. These are running full Linux operating systems, so you don't need to know embedded to use them. If you do want to learn embedded, consider buying a development kit - I would recommend a NUCLEO board (example here) - and work with it to understand how UART, I2C, and other serial communications work, plus operating its LEDs. If you want more advice, let me know.

NUCLEO Product Page

NUCLEO-F302R8 Product Page on Amazon

Real Robot Hardware

How about real robots? This is a bit of an issue - a lot of the cheaper robots you see don't have good computers running on them. You want to find something with at least a Raspberry Pi or Jetson Nano making it work, and that's getting into the hundreds of dollars. It's possible to go cheaper, like with an $80 kit and a $20 board bought separately - but I wouldn't recommend that for a beginner; it's a lot harder to get working. If you are interested in a kit that you can add your own board to, take a look at the Elegoo Robot Kit on Amazon.

My recommended option here would be the JetBot that I've already been making blogs and videos about. It should run up just under $300, and comes with everything you need to start making robotics applications. There will also be a lot of resources and videos on it to get it going.

JetBot Product Information

WaveShare JetBot Product Information

If your budget is a bit higher and you want something more advanced, you could take a look at Turtlebot, like a Turtlebot Burger. That will cost nearer $700, but also comes with a Lidar, which is great for mapping its environment.

TurtleBot Product Information

Turtlebot 3 Burger RPi4

If your budget is lower than a JetBot, I would recommend either working in simulation or trying to build your own robot. Building your own will be a lot tougher and take a lot longer, but you should learn quite a bit from it too.

Some Final Advice

Before finishing up this post, I wanted to give some more general advice.

First, look for and use every resource you have available to you. Look online, ask people, work in the field; anything you can to make your journey easier.

Second, you should get a mentor. This is related to the first point, but it's so important. Find someone you can respect and learn as much as you can from them. This is really the secret to learning a lot - use others' experience to jump ahead instead of learning it yourself the slow, hard way. Finding the right mentor can be a challenge, and you may need to go through a few people before you get to the most helpful person, so be prepared!

I'm sure there's a lot more advice I could give you, but this is my best advice for beginners. At this stage, you need to learn how to learn - finding resources and taking advantage of them. It is the best possible foundation you can give yourself for the rest of your career.