As you might know, we are proud owners of a Spot robot. It’s been a blast exploring this new technology and seeing how it can help many of our clients. As we’ve been learning more about Spot, we would like to share a high-level overview of how you can command Spot to perform actions without the controller. We can easily get into it with the help of the Spot SDK and Knowledge Center provided by Boston Dynamics.
Communicating with Spot
First, we must establish a secure connection with Spot in order to command it. Spot offers various methods, for best results we recommend connecting to its 2.4GHz Wifi network. Note that if you do the same, it would be ideal to have multiple Wifi (or LAN) connections on your machine, as you will likely need to maintain a connection to your internet to download assets or ask for help along the way.
Once authenticated, we can now ping its ip successfully.
ping 192.168.80.3
PING 192.168.80.3 (192.168.80.3) 56(84) bytes of data.
64 bytes from 192.168.80.3: icmp_seq=1 ttl=64 time=5.23 ms
64 bytes from 192.168.80.3: icmp_seq=2 ttl=64 time=3.04 ms
In order to command Spot, you will also need a working user and password. We’ll be using super secure placeholders for this article.
Environment Setup
Technically you can run our python code directly on your machine. However, here at Osedea, we like to use Docker and with the help of the Visual Studio Code Remote - Containers extension, it’s a dream!
mkdir hello_spot
cd hello_spot
touch Dockerfile
touch docker-requirements.txt
touch hello_spot.py
First, our Dockerfile
should contain the following:
FROM python:3.7-slim
COPY docker-requirements.txt .
RUN python3 -m pip install -r docker-requirements.txt
COPY . /app/
WORKDIR /app
ENTRYPOINT ["python3", "/app/hello_spot.py"]
And our docker-requirements.txt
will contain the libraries we need to communicate with Spot:
bosdyn-api==3.0.0
bosdyn-client==3.0.0
bosdyn-core==3.0.0
Now, from VSCode "Open Folder in Container" and let the magic happen. Once the container is built, you can confirm you can still connect to Spot by pinging its ip once more:
Commanding Spot
Now that we’ve established a connection with Spot and set up a working environment, we can begin to look at some real code.
import argparse
import sys
import time
import bosdyn.client
import bosdyn.client.lease
import bosdyn.client.util
import bosdyn.geometry
from bosdyn.client.robot_command import (RobotCommandBuilder,
RobotCommandClient, blocking_stand)
def hello_spot(options):
"""A simple example of using the Boston Dynamics API to command a Spot robot."""
# The SDK object is the primary entry point to the Boston Dynamics API.
# create_standard_sdk will initialize an SDK object with typical default
# parameters. The argument passed in is a string identifying the client.
sdk = bosdyn.client.create_standard_sdk('HelloSpotClient')
# A Robot object represents a single robot. Clients using the Boston
# Dynamics API can manage multiple robots, but this tutorial limits
# access to just one. The network address of the robot needs to be
# specified to reach it. This can be done with a DNS name
# (e.g. spot.intranet.example.com) or an IP literal (e.g. 10.0.63.1)
robot = sdk.create_robot(options.hostname)
# Clients need to authenticate to a robot before being able to use it.
robot.authenticate(options.username, options.password)
# Establish time sync with the robot. This kicks off a background thread to establish time sync.
# Time sync is required to issue commands to the robot. After starting time sync thread, block
# until sync is established.
robot.time_sync.wait_for_sync()
# Verify the robot is not estopped and that an external application has registered and holds
# an estop endpoint.
assert not robot.is_estopped(), "Robot is estopped. Please use an external E-Stop client, " \
"such as the estop SDK example, to optionsure E-Stop."
# 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. Note that the lease is returned as the "finally" condition in this
# try-catch-finally block.
lease_client = robot.ensure_client(
bosdyn.client.lease.LeaseClient.default_service_name)
lease = lease_client.acquire()
try:
with bosdyn.client.lease.LeaseKeepAlive(lease_client):
# Now, we are ready to power on the robot. This call will block until the power
# is on. Commands would fail if this did not happen. We can also check that the robot is
# powered at any point.
robot.power_on(timeout_sec=20)
assert robot.is_powered_on(), "Robot power on failed."
# Tell the robot to stand up. The command service is used to issue commands to a robot.
# The set of valid commands for a robot depends on hardware optionsuration. See
# SpotCommandHelper for more detailed examples on command building. The robot
# command service requires timesync between the robot and the client.
command_client = robot.ensure_client(
RobotCommandClient.default_service_name)
blocking_stand(command_client, timeout_sec=10)
time.sleep(3)
# Tell the robot to stand in a twisted position.
#
# The RobotCommandBuilder constructs command messages, which are then
# issued to the robot using "robot_command" on the command client.
#
# In this example, the RobotCommandBuilder generates a stand command
# message with a non-default rotation in the footprint frame. The footprint
# frame is a gravity aligned frame with its origin located at the geometric
# center of the feet. The X axis of the footprint frame points forward along
# the robot's length, the Z axis points up aligned with gravity, and the Y
# axis is the cross-product of the two.
footprint_R_body = bosdyn.geometry.EulerZXY(
yaw=0.4, roll=0.0, pitch=0.0)
cmd = RobotCommandBuilder.synchro_stand_command(
footprint_R_body=footprint_R_body)
command_client.robot_command(cmd)
time.sleep(3)
# Now tell the robot to stand taller, using the same approach of constructing
# a command message with the RobotCommandBuilder and issuing it with
# robot_command.
cmd = RobotCommandBuilder.synchro_stand_command(body_height=0.1)
command_client.robot_command(cmd)
time.sleep(3)
# Power the robot off. By specifying "cut_immediately=False", a safe power off command
# is issued to the robot. This will attempt to sit the robot before powering off.
robot.power_off(cut_immediately=False, timeout_sec=20)
assert not robot.is_powered_on(), "Robot power off failed."
finally:
# If we successfully acquired a lease, return it.
lease_client.return_lease(lease)
def main(argv):
"""Command line interface."""
parser = argparse.ArgumentParser()
bosdyn.client.util.add_common_arguments(parser)
options = parser.parse_args(argv)
try:
hello_spot(options)
return True
except Exception as exc:
logger = bosdyn.client.util.get_logger()
logger.error(f"Hello, Spot! threw an exception: {str(exc)}")
return False
if __name__ == '__main__':
if not main(sys.argv[1:]):
sys.exit(1)
Note that the above snippet has been pulled from the aforementioned SDK, and stripped. We invite you to take a look at a more functional example of hello spot.py.
With this done, running the following command will turn on Spot, have it stand, pose, stand taller and then power off [1].
export USERNAME=osedea
export PASSWORD=secret
export ROBOT_IP=192.168.80.3
python3 hello_spot.py --username USERNAME --password PASSWORD ROBOT_IP
Congratulations 🎉! You’ve in theory successfully commanded your Spot… however, if you don’t have one, drop us a message and perhaps you can meet ours.
[1] Technically in order to do so, you need to have an external estop established, which can easily be done by following this example: Boston-Dynamic/spot-sdk but we’re skilling this for simplicity’s sake!
Photo credit: yakari_pixel