Spot SDK blocking robot commands

Alisher A. Khassanov
3 min readJul 13, 2021

Programming Boston Dynamics Spot robot we may use Spot SDK that provides a way to work with the robot’s gRPC API conveniently. In the very first hello_spot example we see a fixed time delay after each robot_command() call.

command_client.robot_command(cmd)
robot.logger.info("Robot standing twisted.")
time.sleep(3)

If we measure the time Python spends in the command_client.robot_command(cmd) function, we will find it returns nearly immediately. It does not wait for a robot to complete a movement specified by cmd- a stand-up, for instance. That’s why we need thistime.sleep(3) following the call. Without it, the script will send another command immediately and the robot will start a new command without completion of the previous one.

At the same time, in the Blocking vs. Asynchronous Spot Python SDK functions section of documentation we see:

The get_id() call is blocking - it will not complete until after the RPC completes.

It may look confusing that get_id() call blocks until we get a result requested (an identifier), but therobot_command() call does not wait for the robot to complete the command (a movement).

How to make a blocking robot command call to stop execution flow until the command is done? How to wait exactly as much as a movement needs to finish (or raise an error by timeout)? How to get rid of the fixed time delays with time.sleep? Let’s find the answer.

TL;DR

Instead of this:

cmd = RobotCommandBuilder.synchro_stand_command()
command_client.robot_command(cmd)
robot.logger.info("Robot standing twisted.")
time.sleep(3)

Do something like that:

cmd = RobotCommandBuilder.synchro_stand_command()
cmd_id = command_client.robot_command_async(cmd)
robot.logger.info("Robot standing twisted.")
start_time = time.time()
end_time = start_time + 5.0 # timeout is 5 seconds
while time.time() < end_time:
cmd_status = (command_client
.robot_command_feedback_async(cmd_id)
.result()
.feedback.synchronized_feedback
.mobility_command_feedback
.stand_feedback
.status
)
if cmd_status == basic_command_pb2.StandCommand.Feedback.STATUS_IS_STANDING:
robot.logger.info("Done.")
break
time.sleep(0.1) # wait 100ms before the next check
else:
robot.logger.info("Timeout!")

Don’t forget to handle all exceptions the right way. There is a proper example of a blocking asynchronous call here: github.com/boston-dynamics/spot-sdk/python/bosdyn-client/src/bosdyn/client/power.py#L377.

Details

Let’s understand the difference in between get_id(), robot_command() and robot_command_async() to understand the answer.

Both get_id() and robot_command() are blocking. They return to execution flow after the robot responds to a request sent by gRPC over the network. The get_id() returns robot ID and robot_command() returns command ID.

Means our command lifecycle as follows:

  1. The client sends the request over the network
  2. The robot receives the request, starts execution, and returns an answer (or an error),
  3. The client receives the response from the robot,
  4. Robot completes command execution changing its status to complete,
  5. If the client queries for command execution status, the robot returns a status says it’s complete.

Synchronous calls like get_id() and robot_command() return to execution flow at step 3.

The robot_command_async() is non-blocking and asynchronous call returns concurent.futures.Future- like object (an instance of FutureWrapper, it’s not awaitable unfortunately). The call does not perform networking communication immediately, but schedules it and returns to the execution flow no waiting for a response from the robot. We can get a response from it later. This means it returns to execution flow after step 1.

That’s the difference! Synchronous and asynchronous here define the fashion functions work with network requests. Synchronous calls block execution flow until they get a response. Asynchronous calls schedule communication with no execution block blocking.

If we need a robot_command() to wait for execution completes, we can use convenience functions like blockind_stand() . This particular function waits for the robot to complete a stand-up movement. You may find more convenience functions in the Spot SDK source code searching for blocking word. These commands block execution flow waiting for a command to complete, not just network communication. In case there is no convenience function implemented for your case, you may implement one queries for status periodically and returns once the command reports an error or a success.

Where can I play with Spot?

I study Boston Dynamics Spot robot applications development at https://spot-sdk.education course. Here you get:

  1. One hour-long online lessons with a flexible schedule (PDT timezone),
  2. Course tasks defining what you should make at the end of each lesson to get a certificate,
  3. Remote access to the robot to run and debug your solutions as well as guidance by an experienced engineer.

The course in the very beginning and you can enroll leaving your email at https://spot-sdk.education.

--

--