Tools / APIs

To control and use HEBI hardware we provide Tools and APIs that run on a wide variety of operating systems. Tools are GUIs that provide a user-friendly interface for performing basic tasks and configuration. APIs allow you to write programs in different languages that interface with HEBI hardware. All of these tools and APIs can be found in the Downloads section.

Tool Description OS Support

A GUI for doing basic tasks on modules like sending basic commands, setting name/family, changing gains, and updating firmware.

Windows (Win64)
Linux (x86-64)
macOS (64-bit)

API Description OS Support

A programming language developed by MathWorks commonly used in research and education.

Windows (Win64)
Linux (x86-64)
macOS (64-bit)


The Python API is in an alpha testing stage right now; it is available at if you are interested in being an early tester.

Windows (Win32 / Win64)
Linux (i686 / x86-64 / armhf / aarch64)
macOS (64-bit)


The ROS API is documented and available for download at

Linux (i686 / x86-64 / armhf / aarch64)


Windows (Win32 / Win64)
Linux (i686 / x86-64 / armhf / aarch64)
macOS (64-bit)


The C# API is in a final beta testing stage, and will be released on the NuGet package management system soon.

Windows (Win32 / Win64)


A low-level API that is generally not intended for direct use.

Windows (Win32 / Win64)
Linux (i686 / x86-64 / armhf / aarch64)
macOS (64-bit)

Scope GUI

scope icon 512

Scope is a cross-platform graphical user interface (GUI) tool that provides configuration, monitoring, and testing capabilities for HEBI actuators and modules. We recommend using it at all stages of your development. Before writing a new program Scope is useful in order to make sure that all devices are connected properly and that the system as a whole is working as expected. While developing and debugging a new application Scope is extremely useful for viewing the feedback of modules.

To run Scope, download the latest Release, plug in all network cables, and start the application by double-clicking.

initial screen

Scope will start with a screen that looks similar to the screenshot above. The numbered items in the screenhot are, repectively:

  1. The Device Panel that shows all devices visible on the network interface.

  2. The Main Panel shows information of the currently active tab.

  3. Tabs for the various panels:

    • Information shows general device information such as hardware revisions and Ethernet settings.

    • Monitoring provides plots of device feedback as well as manual inputs for position, velocity, and effort.

    • Gains sets control strategies, gains, and safety limits.

    • Device provides access to hardware features such as zeroing sensors.

  4. A toggle for the Device Panel between showing settable human readable names, or the corresponding hardware types and serial numbers.

  5. A checkbox and slider for controlling the rate of background lookup requests.

  6. A button to check for firmware updates for any of the devices that are currently in the device tab.

  7. A drop-down for selecting the active network interface. For example, if your computer is on a separate wired and wireless network, you can select which network to show in the in the Device Panel. You can show all connected networks by selecting 'ALL'. If a network connection is connected or disconnected you can update the drop-down items by clickign the 'Refresh' button.

Device Panel

The device panel shows all devices visible on the network. The display can be toggled between showing settable human readable names, or the corresponding hardware types and serial numbers.

The colors indicate the current status:

  • Blue text: device is reachable and in Bootloader mode

  • Black text: device is reachable and in Application mode

  • Grey text: device has not been reachable for at least 5 seconds

A right-click on one or multiple selected devices brings up a context menu with various useful actions such as sorting the device list, changing the name used for identification, rebooting, or setting the LED color to help determine a module’s location on a robot.

device set led
Note that some device parameters that get changed frequently (e.g. gains) are stored in memory and will reset when the actuator restarts. The persist button in the context menu forces the currently active parameters to be stored across reboots.

If one or more devices are not shown in the device panel, try the following:

  • Update the network interfaces by clicking the 'Refresh' button in the top bar.

  • Confirm that the background lookup is enabled in the top bar.

  • Confirm that each device has successfully booted up by looking at the LED Status Codes

  • Confirm that the computer that Scope is running on is on the same network

Information Tab

The Information tab shows general device information such as hardware revisions and Ethernet settings.

tab information labels
  1. The Hardware and Electronics sections show the details of the mechanical electrical components of the module. These items are permanent and will never change.

  2. The Firmware section shows the type of firmware (which will typically match the 'Electronics' type), as well as the revision number of firmware the module is running. The revision number will change as you update to the latest firmware.

  3. The Ethernet section that shows the MAC Address of the module, which is permanent, as well as network parameters that will depend on the details of your network configuration.

Monitoring Tab

The Monitoring Tab provides a way to plot device feedback and provide basic inputs for position, velocity, and effort.

tab monitoring

The Monitoring Tab has the following main features:

  1. Plotting buttons for viewing online feedback from a module. If you multi-select modules in the Device Tab, a plot will open for each module. The y-axis of the plots is scaled automatically. A legend for the plot is at the bottom of the plot window.

  2. DPI scaling selectors for better viewing on high-DPI displays. This setting only effects new plots that are opened.

  3. Time Window selection for controlling the rolling-window resolution of the x-axis of plots. If you select 'Auto' the x-axis will scale automatically based on the plotting frequency. This setting only effects new plots that are opened.

  4. A checkbox and slider to control Frequency that feedback is requested and plotted from a module. This setting effects all open plots.

  5. A checkbox and slider to send Commands to the selected modules in the Device Tab.

  6. A button for setting manual command Targets to a module.

For example, to check the position feedback from a module, select an appropriate device on the left panel, and then click Position plot to view the module’s reported position. Commands can be generated by enabling the Position checkbox and moving the corresponding slider. Alternatively, you can command an exact value using the corresponding target button.

If you multi-select modules in the Device Tab, a plot will open for each module. Multiple commands can be active simultaneously.

Commands are sent with a command lifetime disabled. This means that commands on a module will stay active until another command is sent. If you close Scope, commands will still remain active on a module. Unchecking the command slider will clear a given commmand. The slider inputs will not work if someone is actively commanding an actuator with command lifetime enabled.

plot position

The dashed line represents the commanded position, and the solid line the feedback position.

Note that Scope is a completely standalone process with a separate network connection that is independent from any user code. When debugging user applications we recommend running a Scope plot in the background in order to get live feedback on the behavior of an algorithm. For example, math errors often show up as large spikes or full drop-outs in the commanded line.

If you want to get an idea of the performance of the underlying netowrk the Latency and Packet loss plots are useful. The image below shows a typical latency plot for a Windows 8 computer requesting feedback at 100Hz. The green dots represent the dt between received packets and provides information about the OS scheduler. The black dots represent the round-trip time of a packet going to and from a device.

plot latency plot packet loss

The packet loss plot should ideally show a solid line at zero. If there is frequent packet loss on a wired network (Wi-Fi is expected to have some amount of packet loss and jitter) or substantial spikes in latency there may an issue. For example:

  • Bad network connection, e.g., a network cable damaged or not plugged in correctly

  • The network is saturated, e.g., someone is streaming Netflix through the same network

  • The device is overloaded, e.g., someone is sending packets faster than a device can respond (e.g. open-loop commands from the API without pause or something else that limits the request rate)

  • The host computer is overloaded, e.g., there are too many applications running in the background

Plots can be paused and zoomed by left clicking-and-dragging. Right-clicking a plot will resume updates and auto-scaling.

Left-clicking a plot will freeze it, although data may be cleared as feedback updates in the background. To freeze plotting indefinitely, uncheck the box under 'Frequency'.

Gains Tab

The gains tab provides a convenient way to change control strategies (1), set safety limits (2), and to set gains (3/4/5). Tips and guidelines on tuning gains can be found in the Motion Control section.

tab gains

Gains can be set while a device is actively being commanded as long as you are not also setting the control strategy (the dropdown box is blank). For example, you can generate commanded positions, velcoties, and efforts for an actuator programmatically (e.g. in MATLAB or C++) and use Scope to change the gains and plot the tracking performance online.

Gains are non-persisting parameters. This means that they will be reset to the last saved (persisted) values on reboot. To ensure that the current values are restored at next boot, you can press "Send & Persist" (7) or right-click persist for the selected modules on the Device Panel.

Saving/Loading Gain files

Gains can be saved in a group-compatible gain format by selecting one or more devices and pressing the save button (10). Note that the gains of multiple devices will be stored in the same order as they appear on the device pane. Thus, we recommend sorting the list in order to produce reliable results.

Loading gains (9) reads a gain file and loads the values into the corresponding input fields. Note that only one set of gains can be displayed at a single time.

Send Direct (11) loads the gains of an arbitrary number of modules and immediately sends them to the selected devices, without populating the input fields. The order will again match the order in the device pane.

Resetting Gains

In many cases it is useful to reset or compare the active gains to the default values for an actuator. An easy way to do this is by using "Load Defaults" (8), which downloads the appropriate default gains for the selected hardware type and control strategy. Differences compared to the active gains are highlighted in yellow.

Loading the default gains for a module requires an active internet connection. For cases that do not have access to the internet, we provide downloadable default gains that can be stored and loaded offline.

tab gains defaults

Device Tab

The device tab provides access to hardware features such as zeroing sensors.

tab device

The Position Reference button sets the module’s current position to the selected value. This should not be done while the device is moving

The Zero Effort Reference button sets the module’s current effort (usually torque) reading to the set value. This should be done when there is no load on the module (e.g., set down on the table with the output facing up and nothing connected to the output)

Updating Firmware

HEBI releases updates to the internal device firmware that contain performance improvements, additional features, and bugfixes. Each device has an isolated Bootloader mode, so updates can be done safely without the risk of disabling a module. You can check for updates using the Check for Firmware Updates (6) button in the top menu. Updating firmware requires an internet connection.

We strive to maintain backwards and forwards compatibility in firmware udpates. In the rare cases where we do break backwards compatibility, we will send out email notifications to all customers. If you have any concerns about a firmware update, please check the Firmware Changelog or email us at
updates check

The dialog will show all modules for which updates are available. You can then select all or a subset of modules and click Update Selected. The modules will automatically reboot into Bootloader, download the appropriate application firmware, and reboot.

updates program

The update process should take about 10 seconds. It is safe to cancel or interrupt an update at any time.


MATLAB is a proprietary scripting language and computing environment developed by Mathworks. It is widely used for data analysis and research across many engineering disciplines. The language’s focus on linear algebra and matrix operations makes it a very good language for programming robots.

HEBI provides libraries that enable users to control complex systems in real-time directly from MATLAB. This allows for fast prototyping and testing of algorithms on real hardware, without the need for C/C++ or code generation.

We currently do not officially support Simulink. However, we have done some preliminary work in this area. If you require need Simulink support, please contact us directly at


The HEBI API for MATLAB requires MATLAB R2013b or newer.

First, download the latest Release and extract the contents into a directory of your choice. Then, open MATLAB and add the "hebi" directory to the search path.

The path can be added manually by right-clicking on the directory,


or programmatically by calling the built-in addpath function.

addpath([pwd '/hebi']);

For incorporating the HEBI library into a project, we recommend creating a startup script that can add the necessary files automatically.

function [] = startup()
% startup sets up libraries and should be started once on startup.
currentDir = fileparts(mfilename('fullpath'));
addpath(fullfile(currentDir , 'hebi'));
hebi_load(); % explicitely pre-load library
It is usually a good idea to increase MATLAB’s Java Heap Size using the slider control panel in Preferences→General→Java Heap Memory. In particular this will allow larger .hebilog files to be loaded into memory.

Module Discovery

The first call to HebiLookup initializes a background discovery process that can automatically discover modules using UDP broadcast messages. The default settings should be appropriate for most users, but may also be changed in hebi_config.m.

Displaying HebiLookup provides an overview of all modules that are visible on the network. The Family and Name columns show the user-settable names, and the Serial Number column shows the unique serial number of each device.

% explicit display

% implicit display (no semicolon)

In case you need programmatic access to details (e.g. ip address, mac address, mechanical type, etc.) of all modules, you can use the group interface.

% form a group of all modules
modules = HebiLookup.newGroupFromFamily('*');
infoTable = modules.getInfo();

Note that this can throw an error if modules are 'stale' (haven’t responded in several seconds) in which case stale modules would have to be cleared first.

HebiLookup.clearModuleList(); % reset table
pause(0.5); % allow time to re-build table

If for some reason modules are not listed, please refer to the following troubleshooting table.

Problem Suggestions

A module fails to get an IP address (LED fades orange / blue)

  • Check that the module is turned on and connected to a network with DHCP

A module did successfully get an IP address (e.g. LED fades green), but is not visible in the lookup

  • Check that the module and the computer are physically connected to the same network

  • Check that the lookup frequency is not zero, e.g., HebiLookup.setLookupFrequency(5)

  • Check that your firewall does not block incoming traffic. We have seen this issue on some Linux distributions, e.g., Scientific Linux 6, that ship with very restrictive default settings

  • Check that your network does not block UDP broadcasts. If broadcasts are blocked (e.g. on the public CMU Wi-Fi network), individual IP addresses would need to be set manually via HebiLookup.setLookupAddresses(<ip>).

The lookup does not initialize to the default settings

  • We have encountered combinations of MATLAB’s 'clear' command in combination with loading multiple Java libraries that can cause the initialization code to not run. In such cases you can manually reset to the default values with the snippet below.

% Reset lookup to default parameters and display modules

If you encounter an issue connecting to modules that was not covered in the table above, please contact us directly.

Joint-Level Control

Module Selection

Modules that belong to the same system can be combined into a HebiGroup. Groups represent the basic way to send commands and retrieve feedback. They provide convenient ways to deal with modules, and handle high-level issues such as data synchronization and logging. Modules can be identified by user settable parameters, such as their name or family, or hardware constants, such as their serial number or mac address.

% Creating a group by selecting custom names
family = '2dof';
names = {'base', 'knee'};
group = HebiLookup.newGroupFromNames(family, names);
% Creating a group by selecting constant serial numbers
serials = {
    'X-00148' };
group = HebiLookup.newGroupFromSerialNumbers(serials);
The example names and serial numbers need to be modified to match devices that were found on your network during the discovery step.

A comprehensive list of all available calls for selecting modules can be found in the HebiLookup API.


Position, velocity, and effort (e.g. torque) can be commanded by using a CommandStruct. Each field expects a 1xN vector where N is the number of modules within a group. Leaving a field empty or setting a value within a vector to NaN corresponds to turning the corresponding control loop off.


Commands are only valid for a limited CommandLifeTime, so even non-changing commands need to be re-sent before they expire. This safety measure may seem inconvenient at first, but it actually simplifies development due to the fact that motions automatically stop after halting a script (ctrl-c).

% Send a position command to a group with two actuators
cmd = CommandStruct();
while true
    cmd.position = [0 0];
    group.send(cmd); % immediately sends messages to all grouped modules
Individual commands only set the low-level controller targets for the current instant in time, and do not calculate a trajectory from the current state to the desired target state. If you’d like to calculate a trajectory between two or more points, please take a look at the Trajectory API.

The following example shows an open-loop controller commanding sine waves with different frequencies on two actuators using simultaneous position and velocity control.

% Two actuators executing sine waves of 1Hz and 2Hz
group = HebiLookup.newGroupFromNames('leg', {'knee', 'ankle'});
cmd = CommandStruct();
w = 2 * pi * [1 2];
t0 = tic();
while true
    t = toc(t0);
    cmd.position = sin( w * t );
    cmd.velocity = cos( w * t ) * w;

The IoCommandStruct serves as a similar structure to command the outputs on an I/O board. Note that for I/O boards we found it to be more maintainable to create a mapping and to access the individual pin fields by string as shown below.

% Pin mapping
led_r = 'c6';

% Turn on red LED
group = HebiLookup.newGroupFromNames('IO_BOARD', 'IO 25');
cmd = IoCommandStruct();
cmd.(led_r) = 1;


Each group continuously gathers sensor feedback from all contained modules in a background thread, and synchronizes the data into a single struct. MATLAB can then asynchronously access the received feedback with getNextFeedback. Each call returns the next new (not previously accessed) synchronized feedback. Thus, a single feedback response will never be returned more than once. The default rate of 100Hz can be changed via setFeedbackFrequency or by modifying the hebi_config.m file.

The actual feedback frequency is fundamentally limited by the underlying operating system’s scheduler. For example, code that executes at a rate of 1KHz in Linux may be limited to ~640Hz on Windows 7. For typical rates below 250Hz this tends to be irrelevant. The Importance of Metrics and Operating Systems contains more information about the expected performance for various operating systems.
Feedback from multiple modules is typically synchronized to within 1ms. However, the actual timings depends on the network layout and external network traffic. Please see Analyzing the viability of Ethernet and UDP for robot control for more information.

Our devices tend to return a large number of sensors, so we have added different 'views' in order to enable advanced use cases without confusing new users. The default view returns feedback of the most used types of sensors, but there are alternative views in case you’d like to access additional information such as hardware timestamp data.

% Basic feedback (position, velocity, effort (e.g. torque), IMU data, temperature)
fbk = group.getNextFeedback();

% Advanced feedback including hardware timestamps
fbk = group.getNextFeedbackFull();

% I/O board feedback
ioFbk = group.getNextFeedbackIO();

The below example shows a closed-loop controller that implements a virtual spring that controls effort (torque) to drive the output towards the origin (stiffness = 1 Nm / rad).

% Virtual spring on single module
group = HebiLookup.newGroupFromSerialNumbers('<serial>');
cmd = CommandStruct();
stiffness = 1; % [Nm/rad]
while true
    fbk = group.getNextFeedback();
    cmd.effort = -stiffness * fbk.position;

While groups are completely independent of each other, there may be more than one group at a time, and multiple groups may be used together in any way. For example, the following code combines feedback from one group (gyroscope input) to calculate commands for a second group (velocity output). This is similar to the demo shown in Controlling Robots Using Android and MATLAB.

%% Setup groups
imu = HebiLookup.newGroupFromSerialNumbers('<serial>');
actuator = HebiLookup.newGroupFromSerialNumbers('<serial>');

%% Follow gyro reading using pure velocity
cmd = CommandStruct();
while true

    fbk = imu.getNextFeedback();
    cmd.velocity = fbk.gyroZ;


Splitting a system into multiple groups often allows for a cleaner separation between the various elements, e.g., a 6-DoF arm (group 1) and a gripper (group 2).

Further Commands

The send method additionally supports various other commands such as programmatically setting names, resetting modules, setting position limits, and setting LED colors.

LEDs can for example be used to provide live feedback to people standing next to a robot, or for synchronizing/debugging motions in combination with a high-speed camera.

group.send('led', 'red'); % set all to red
group.send('led', [0 0 1]); % set all to blue [r g b] [0-1]
group.send('led', []); % reset to default mode

Advanced Usage

The API has been developed with low-latency, and low memory usage in mind. For applications that require determinism or high control frequencies, we recommend the following additional steps.

  • Reuse command structs rather than allocate one each time in a loop

% Don't allocate CommandStruct in a loop
cmd = CommandStruct();
while true
    cmd.position = desiredPosition;
  • Reuse feedback structs by passing an existing struct into getNextFeedback

% Create feedback struct once and reuse it
tmpFbk = group.getNextFeedback();
while true
    fbk = group.getNextFeedback(tmpFbk);
  • Use convenience wrappers instead of passing parameters where possible

% Parameterized
fbk = group.getNextFeedback('view', 'full');

% Convenience wrapper (recommended)
fbk = group.getNextFeedbackFull();
  • Decrease the frequency of minor garbage collections by increasing the new space in the java.opts file. Note that this is useful for applications that need to run for extended periods of time (e.g. days/weeks) and is overkill for 99.9% of applications. It may also negatively impact e.g. live plotting.

# Minimum total heap

# Maximum total heap

# Young generation heap space


The GainStruct can be used to change the gains of the control loops executed on each device.

% Double position P gain
gains = group.getGains();
gains.positionKp = gains.positionKp * 2;
group.send('gains', gains);

Gains are not automatically persisted and get reset after reboot. The following code forces a persist such that the gains are maintained during a reboot. Note that persist should not be called in a loop due to significant overhead on the device.

% Persist permanently (don't call this in a loop)
group.send('gains', gains, 'persist', true);

Each call to send results in one outgoing message per module. Thus, all commands that are combined in a single call are guaranteed to arrive at a module at the same time.

% Set gains
gains = GainStruct();
gains.positionKp = 5;

% Set desired targets
cmd = CommandStruct();
cmd.position = 0;

% Send at the same time
group.send(cmd, 'gains', gains);


We have created the HebiKinematics API to assist with common robotics problems such as Forward Kinematics, Inverse Kinematics, Jacobians, as well as utilities for computing joint torques and forces.

Defining the Robot Configuration

The addBody method creates a serial chain of bodies that describe the kinematic relation of a robot. A body can be a rigid link as well as a dynamic element. The first body represents the baseFrame and the last body represents the endEffector.

Details about an existing configuration can be accessed at runtime using getBodyInfo for general body information, and getJointInfo for joint specific information. The Type argument specifies the type of module or body that should be added. The currently implemented types are listed in the sections below. Parameters that are not applicable to the specified type are ignored and will not throw an error message.

Below are several sample configurations. The chain gets defined starting from the base module.

Details on parameterized body types are in the Hardware section.

X-Series Leg (2-DoF)
kin 2dof x series
% X-Series Leg (2-DoF)
kin = HebiKinematics();
kin.addBody('X5-Link', 'ext', .216 + .0715, 'twist', 0, 'mass', 0.318);
kin.addBody('X5-Link', 'ext', .332 + .0715/2, 'twist', 0, 'mass', 0.276);

% Relation between world frame and first body
kin.setBaseFrame(eye(4)); % expects 4x4 transform
X-Series SCARA-ish Kit (3-DoF)
kin 3dof x series SCARA ish
% X-Series SCARA-ish Kit (3-DoF)
kin.addBody('X5-4'); % 1st Module
kin.addBody('X5-LightBracket', 'mount', 'right'); % 90-deg bracket
kin.addBody('X5-9'); % 2nd Module
kin.addBody('X5-Link', 'ext', .131 + .0715, 'twist', pi/2, 'mass', 0.15); % Extension
kin.addBody('X5-4'); % 3rd Module
kin.addBody('X5-Link', 'ext', .109 + .0715, 'twist', 0, 'mass', 0.15); % Extension

% Screwdriver End Effector (X5-1 actuator)
screwdriverOut = [
    1 0 0 0
    0 1 0 0
    0 0 1 0.058
    0 0 0 1 ];

kin.addBody( 'GenericLink', ...
    'com', [0 0 0.015], ...
    'out', screwdriverOut, ...
    'mass', 0.35 );
X-Series Manipulator Kit (5-DoF)
kin 5dof x series manipulator
% X-Series Manipulator Kit (5-DoF)
kin = HebiKinematics();
kin.addBody('X5-4'); % 1st Module
kin.addBody('X5-HeavyBracket', 'mount', 'right-outside'); % 90-deg bracket
kin.addBody('X5-9'); % 2nd Module
kin.addBody('X5-Link', 'extension', 0.379, 'twist', pi); % Extension
kin.addBody('X5-9'); % 3rd Module
kin.addBody('X5-Link', 'extension', 0.252, 'twist', pi); % Extension
kin.addBody('X5-1'); % 4th Module
kin.addBody('X5-LightBracket', 'mount', 'right'); % 90-deg bracket
kin.addBody('X5-1'); % 5th Module

% Generic Gripper
gripperOut = [
    1 0 0 0
    0 1 0 0
    0 0 1 0.025
    0 0 0 1 ];

kin.addBody( 'GenericLink', ...
    'com', [0 0 0.0125], ...
    'out', gripperOut, ...
    'mass', 0.1 );

Generic Body Types

For some cases it may be necessary to add custom machined or 3D printed parts. In order to accomodate such use cases we have added support for completely generic modules.

Type Required Parameters Optional Parameters



  • prismatic

    • x

    • y

    • z

  • revolute

    • rx

    • ry

    • rz

  • PositionLimit [1x2 rad]

    • default: [-inf inf]

  • VelocityLimit [1x2 rad/s]

    • default: [-inf inf]

  • EffortLimit [1x2 Nm|N]

    • default: [-inf inf]


  • OutputTransform [4x4]

  • CoM [x y z] in [m]

  • Mass [kg]

Generic Joint Example

Below code creates a generic joint with the same position and velocity limits as an 'S5-3' joint.

% Static S5-3 Parameters
maxPos = pi/2;
maxVel = 3.456;

% Add 'X5-3'-like joint
kin = HebiKinematics();
kin.addBody('GenericJoint', ...
    'axis', 'ry', ... % axis of movement, e.g., rotation about y axis
    'posLim',[-maxPos +maxPos], ... % position limit in [rad]
    'velLim',[-maxVel +maxVel]); % velocity limit in [rad/s]

Generic links can represent arbitrary static bodies. They are useful for specifying arbitrary end effector tools as well as custom connecting pieces.

% Generic Gripper
gripperOutput = [
    1 0 0 0
    0 1 0 0
    0 0 1 0.025
    0 0 0 1 ];
kin.addBody( 'GenericLink', ...
    'CoM', [0 0 0.0125], ... % [x y z] input to center of mass
    'OutputTransform', gripperOutput, ... % 4x4 input -> output
    'Mass', 0.1 ); % mass in [kg]

Forward Kinematics

The getForwardKinematics method computes the poses of the chain of bodies in the base frame, using specified values for the joint parameters.

Poses are returned as a set of [4 x 4 x numBodies] homogeneous transforms, specified in the world frame of the kinematic configuration. Units of XYZ translation are in [m].

% setup
% group = ...
% kin = ...

% loop
while true

    % read sensor values
    fbk = group.getNextFeedback();

    % calculate transforms from the world frame to the output frames of the individual bodies
    frames = kin.getForwardKinematics('OutputFrame', fbk.position);
    disp(frames(:,:,end)); % display end effector output

    % calculate transforms from the world frame to the centers of mass of the individual bodies
    CoM = kin.getForwardKinematics('CoMFrame', fbk.position);

    % calculate transform to only the end effector
    endEffectorFrame = kin.getForwardKinematicsEndEffector();



The getJacobian method calculates the partial derivatives of the kinematics equation, which relates the joint rates to the linear and angular velocity of each body in the kinematics configuration.

The Jacobian is returned as a [6 x numDoF x numBodies] set of matrices. Rows 1:3 of the Jacobian correspond to linear velocities [m/s] along the X-Y-Z axes in the world frame, while rows 4:6 correspond to rotational velocities [rad/s] about the X-Y-Z axes in the world frame.

The call parameters are the same as for forward kinematics shown above.

% Jacobian for all body outputs
J = kin.getJacobian('OutputFrame', fbk.position);

% Jacobian for all body centers of mass
J = kin.getJacobian('CoMFrame', fbk.position);

% Jacobian for only the end effector output frame
J = kin.getJacobianEndEffector();

Inverse Kinematics

The getInverseKinematics method computes the joint positions associated to a desired end-effector configuration. The end effector is assumed to be the last body in the kinematic chain. There are a variety of optimization criteria that can be combined depending on the application. Available parameters include:

Parameter End Effector Target Comments


xyz position (m)

  • [3 x 1] vector for end-effector position (m)

  • Dimensions with NaN get ignored, e.g., [x y NaN] for doing IK for a planar 2-DOF arm.


z-axis orientation of end effector

  • [3 x 1] unit vector of target direction


3-DoF orientation of end effector


Initial seed for the numerical optimization. Defaults to all zeros.


Maximum allowed iterations of the numerical optimization before returning.

% Inverse kinematics on carthesian coordinates
xyz = [0 0 0];
waypoints = kin.getInverseKinematics('xyz', xyz);

% Inverse kinematics for full 6 dof
xyz = [0 0 0];
so3 = eye(3);
positions = kin.getIK('xyz', xyz, 'so3', so3);


Adjusting Payload

The setPayload method provides a way to dynamically specify a payload that gets used for calculating efforts to compensate for gravitational effects and joint accelerations. Specifying a payload currently has no effect on any other functionality.

% Pick up an object with the specified mass and CoM (xyz distance from the end effector output frame)
mass = 1; % [kg]
com = [0.1 0 0]; % xyz distance in [m]
kin.setPayload(mass, 'CoM', com);

% Remove payload
Gravity Compensation

The getGravCompEfforts method computes efforts (torques and forces) that are required to cancel out the forces on an arm caused by gravity.

% Compensate gravity at current position
fbk = group.getNextFeedback();
gravityVec = -[0 0 1]; % Direction of gravity. Does not need to be normalized.
efforts = kin.getGravCompEfforts(fbk.position, gravityVec);

If you are not sure where gravity is coming from, an easy way to figure out the direction of gravity is to look at the accelerometer values of a fixed base module while the robot sits idle.

% automatically determine direction of gravity
fbk = group.getNextFeedback();
gravityVec = -[fbk.accelX(1) fbk.accelY(1) fbk.accelZ(1)];

% dealing with non identity base frames
baseFrame = kin.getBaseFrame();
gravityVec = baseFrame(1:3,1:3)' * gravityVec;

The following example commands a 'weightless' gravity-compensated mode that allows users to touch a robot arm and move it around freely in space. This is often useful control mode for an operator to teach waypoints for teach-repeat applications.

% Determine the direction of gravity based on the
% built-in IMU (assumes fixed base).
fbk = group.getNextFeedback();
gravityVec = -[fbk.accelX(1) fbk.accelY(1) fbk.accelZ(1)];

% Keep the robot in a weight-free teach mode
cmd = CommandStruct();
while true
    fbk = group.getNextFeedback();
    cmd.effort = kin.getGravCompEfforts(fbk.position, gravityVec);
Dynamics Compensation

The getDynamicCompEfforts method computes the efforts (torques and forces) that are required to accelerate the body masses as determined from the specified positions, velocities, and accelerations.

The 'Position' argument expects a vector of positions of all degrees of freedom, used for computing the Jacobian, where effort = J' * desiredForces.

'TargetPositions', 'TargetVelocities', and 'TargetAccelerations' typically come from some sort of trajectory generation function, such as the TrajectoryGenerator API below.

% Calculate required efforts to compensate for desired accelerations
efforts = kin.getDynamicCompEfforts(...
    fbk.position, ...
    cmdPositions, ...
    cmdVelocities, ...


We provide trajectories via the HebiTrajectoryGenerator API to control a group of actuators in a manner similar to more traditional robot arms. Features include parameterized minimum-jerk motion, and built-in gravity and dynamics compensation using the kinematics API.

Blocking Trajectories

The moveJoint and moveLinear functions are simplified wrapper calls that respectively move joints in joint space, or such that the end effector path resembles a straight line in world coordinates. Both calls block until the motion is completed.

% Setup
trajGen = HebiTrajectoryGenerator(kin);

% Move a group of 3 actuators between two waypoints
start = [0 0 0]; % [rad]
finish = pi/2 * [1 1 1]; % [rad]
trajGen.moveJoint(group, [start; finish]);

The default settings use only position and velocity control. Effort control can be enabled with optional parameters.

% Enable effort control
trajGen.moveJoint(group, [start; finish], ...
    'GravityVec', gravityVec, ...
    'DynamicsComp', true);

Gravity compensation or dynamics compensation should always be used together. Otherwise the target efforts may be too low and interfere with position/velocity control, which depending on the control strategy could make the performance worse.

Using these wrapper calls is equivalent to creating a non-blocking trajectory and calling the blocking executeTrajectory function. If you need access to more advanced trajectory options, you can manually split the calls up as shown below.

% Create non-blocking trajectory (offers more options)
traj = trajGen.newJointMove(positions);

% Execute and return when motion is done (blocking)
trajGen.executeTrajectory(group, traj);

Non-Blocking Trajectories

There are many use cases for which it is important to dynamically react to changes in the environment such as hitting unforeseen obstacles or when following a moving target. This can be done by creating a trajectory (newJointMove or (newLinearMove) and executing it manually.

% Setup single joint
group = HebiLookup.newGroupFromNames('*', 'X-001');
kin = HebiKinematics();
trajGen = HebiTrajectoryGenerator(kin);
trajGen.setAlgorithm('UnconstrainedQp'); % MinJerk trajectories
trajGen.setSpeedFactor(0.5); % Slow down to half of max speed

% Create trajectory through multiple waypoints
nWaypoints = 5;
positions = rand(nWaypoints, kin.getNumDoF());
trajectory = trajGen.newJointMove(positions);

% Execute trajectory in position and velocity
cmd = CommandStruct();
t0 = tic();
t = toc(t0);
while t < traj.getDuration()
    t = toc(t0);

    % React to something (e.g. position error or effort threshold)
    fbk = group.getNextFeedback();
    if fbk.position - fbk.positionCmd > 0.1
        group.send(CommandStruct()); % turn off commands
        error('Reacting to something...');

    % Get target state at current point in time
    [pos, vel, accel] = traj.getState(t);

    % Command position/velocity
    cmd.position = pos;
    cmd.velocity = vel;


More information on various options, such as

  • Algorithm

  • Speed factor

  • Minimum duration

  • Total duration

  • Time constraints

  • Velocity constraints

  • Acceleration constraints

can be found in the API documentation for HebiTrajectoryGenerator and newJointMove.

Visualizing Trajectories

The following code creates a sample trajectory between 6 position waypoints for a single actuator.

% Setup
kin = HebiKinematics();
trajGen = HebiTrajectoryGenerator(kin);

% Create trajectory
positions = [0 pi 3 2.5 1 0];
time = [0 1 2 3 4 5];
trajectory = trajGen.newJointMove(positions, 'time', time);
single joint trajectory

The resulting trajectory can be visualized using standard MATLAB plots as shown below.

% Visualize trajectory
figure(); hold on; grid on;

% Position / Velocity / Acceleration profile
t = 0:0.01:trajectory.getDuration();
[pos, vel, accel] = trajectory.getState(t);
plot(t, pos);
plot(t, vel);
plot(t, accel);

% Superimpose target position waypoints
tWaypoint = trajectory.getWaypointTime();
plot(tWaypoint, trajectory.getState(tWaypoint), 'bo');

% Axes labels
title('Joint Trajectory');
ylabel('Value [rad, rad/s, rad/s^2]');
xlabel('Time [s]');
legend Position Velocity Acceleration


Data logging, visualization, and analysis tools are a critical part of robotics development. We provide powerful logging capabilities that allow the logging of data in excess of 1kHz, from any number of modules, and over extended periods of time (multiple days). Logging is done entirely outside of the main MATLAB thread so that there is no performance impact on real-time control.

The logging format is also common across all HEBI APIs, so logs saved with by one API can read by any of the other APIs.

% Log data for 5 seconds
data = group.stopLog();

Logs are returned as structures where data is organized in vectors and matrices so that the resulting data interacts well with MATLAB’s built-in plotting tools.

% Plot logged data and overlay commanded and feedback position
figure(); hold on;
plot(data.time, data.position);
plot(data.time, data.positionCmd, ':');

Logs can also be automatically converted to a variety of formats including CSV and MAT files.

% MAT file
matFilePath = group.stopLog('FileFormat', 'mat');
data = load(matFilePath);

csvFile = group.stopLog('FileFormat', 'csv', 'view', 'full');
The default stopLog call returns a Java object that contains all the data. Thus, each access needs to convert the underlying Java type to the corresponding MATLAB type. This is typically not an issue, but if you need to access fields many times, we recommend converting the Java object to a MATLAB struct via struct(data).

Loading logs from file

All logging happens in the background and gets stored on disk in a binary streaming format. The default stopLog() call converts the binary format into another format that MATLAB can work with. We also provide the convertGroupLog() utility to load raw log files that were previously saved or even generated using other APIs. This also means that in case MATLAB ever crashes due to unforeseen circumstances, none of the logged data is ever lost.

% load raw log file after a crash
binaryFile = '2015-12-02_17-02-12.994887.hebilog';
matFile = HebiUtils.convertGroupLog(binaryFile, 'format', 'mat', 'view', 'full');
data = load(matFile);

Logging over extended periods of time

The same utility also useful for splitting logs into e.g. 1 hour chunks in a long running process. The conversion would be too expensive to do inside a loop, so instead we can call startLog to finish the previous log and to start a new one. This is comparatively very cheap and may be called inside a tight loop.

% log continously over an extended period of time
binaryFiles = {};
t0 = tic();
while true

    % run control loop
    % ...

    % create a new log every hour
    if toc(t0) > 3600
        binaryFiles{end+1} = group.startLog();
        t0 = tic();


The resulting log files can then be loaded once a task is done, or from a different MATLAB instance while the task is still running.

% convert log files offline
nFiles = length(binaryFiles);
for i = 1:nFiles
    disp(['converting log ' num2str(i) '/' num2str(nFiles)];
    matFile = HebiUtils.convertGroupLog(binaryFile, 'format', 'mat', 'view', 'full');

Replaying logs for testing and verification

We also added the newGroupFromLog utility which supports replaying log files via the standard group interface. After loading the log, each call to getNextFeedback on the group returns the next feedback in sequence (including the logged timestamp), regardless of the rate it is actually being called. This is useful, for example, for online algorithm development while working with offline data.

% Iteratively step through log file
group = HebiUtils.newGroupFromLog(path);
while true
    fbk = group.getNextFeedback();

Python API

The Python API is currently in development. Documentation will be coming soon.


Note: this documentation refers to the C++ 1.0 API, available at:

Also available are earlier versions of the API and Doxygen documentation.

The examples are divided into two folders: basic and advanced. The basic examples are designed as a hands-on tutorial for getting started with the API; we highly recommend downloading and going through these examples as concrete/runnable examples of the concepts discussed in this documentation.

Installation/Project Integration

The C++ API is made up of:

  1. The .cpp and .hpp files that comprise the source code of the C++ wrapper around the underlying C library.

  2. The hebi.h header file that provides the function declarations for the underlying C library.

  3. The actual dynamic/shared object library that must be linked into your program (.dll for Windows, .so for Linux, and .dylib for macOS).

The C++ API is designed to be used with the C++ 11 standard; when building your project, ensure you have set the proper compiler switches to enable this support (e.g., -std=c++11 for gcc).

There are a few basic ways to integrate the API into your program:

  1. If you are using Visual Studio, use the NuGet package manager to add the HEBI C++ API as a dependency. You will have to disable "precompiled header" support for the API to properly compile with your project.

  2. Use the examples as a template for setting up a CMake or Visual Studio project dependent on the C++ API.

  3. Add the source .cpp files to your project sources, ensure your include path contains the .hpp and hebi.h file, and link in the shared object for your platform and architecture.

Module Discovery

The hebi::Lookup class manages a background discovery process that can find modules on the local network using UDP broadcast messages. Generally, you will only need one hebi::Lookup object per application, and it can be disposed of after creating the desired hebi::Group objects (see Module Selection).

To view the modules that the Lookup has discovered, use the hebi::LookupEntry class to programmatically access the contents at a snapshot in time.

// Create Lookup and wait for it to populate
hebi::Lookup lookup;

// Take snapshot and print to the screen
auto entry_list = lookup.getEntryList();
std::cout << "Modules found on network (Family | Name):" << std::endl;
for (auto entry : entry_list)
  std::cout << entry.family_ << " | " << entry.name_ << std::endl;

Depending on the available modules, the output will look similar to:

Broadcasting on:

Modules found on network (Family | Name):
arm-1 | base
arm-1 | shoulder
arm-1 | elbow
platform | left-wheel
platform | right-wheel

If for some reason modules are not listed, please refer to the following troubleshooting table.

Problem Suggestions

A module fails to get an IP address (LED fades orange / blue or orange / green)

  • Check that the module is turned on and connected to a network with DHCP

A module did successfully get an IP address (e.g. LED fades green), but is not visible in the lookup

  • Check that the module and the computer are physically connected to the same network

  • Ensure that the hebi::Lookup object was created after the network interface connected to the network; for simple programs, one can just restart the program after connecting to the network

  • Check that your system firewall does not block incoming traffic. We have seen this issue on some Linux distributions, e.g., Scientific Linux 6, that ship with very restrictive default settings

  • Check that your network does not block UDP broadcasts

If you encounter an issue connecting to modules that was not covered in the table above, please contact us directly.

Joint-Level Control

Module Selection

A Lookup class can create a hebi::Group instance. A group is a collection of modules (often part of the same robotic system), and the group interface represents the basic way to send commands and retrieve feedback. They provide convenient ways to deal with modules and handle high-level issues such as data synchronization and logging. Modules can be identified by user settable parameters, such as name or family, or hardware constants, such as their mac address.

// Create a group from a set of names
std::shared_ptr<Group> group = lookup.getGroupFromNames({"family"}, {"name1", "name2"});
// Can provide a different family for each module
std::vector<std::string> families = {"mobile_base", "mobile_base", "arm", "arm"};
std::vector<std::string> names = {"left_wheel", "right_wheel", "shoulder", "elbow"};
std::shared_ptr<Group> group = lookup.getGroupFromNames(families, names);
// providing "*" as the family selects all modules
std::shared_ptr<Group> group = lookup.getGroupFromFamily("*");

The Lookup class documentation provides a comprehensive set of functions used to create group objects.


Position, velocity, effort, and many other fields can be commanded by using the Command API. A hebi::GroupCommand object is used to store the commands to be sent to the group. We use types from the open-source C++ matrix library Eigen to update many of the fields with vectors of data.

// Create an Eigen vector object filled with zeros
Eigen::VectorXd efforts(group->size());

// Command all modules in a group with this zero force or effort command
hebi::GroupCommand groupCommand(group->size());
Setting the values in the GroupCommand object does not affect/command any modules until the command is sent to the group with the Group::sendCommand function. Also note that commands can be dropped on particularly poor or congested networks, and the Group::sendCommand function does not provide guaranteed delivery or even an acknowledgement; it is designed for relatively high-frequency applications where commands are resent frequently (e.g. 100Hz). For commands that are only sent once (such as setting gains on the modules), we recommend usage of Group::sendCommandWithAcknowledgement to verify the command was received.
The command affects the actuator control loops only if at least one of the position, velocity, and effort command fields is not cleared. In particular, a command sent with all three of these cleared will not affect the control loop setpoints. However, if only a subset of the fields are set (e.g., only position), then commands from the other fields will be cancelled when this message is receieved. If you want to completely cancel out a previous command (especially when the Command Lifetime is not set!) and turn the control loops off, you can set the commanded value to NAN (see std::numeric_limits::quiet_NaN).
Individual commands only set the low-level controller targets for the current instant in time, and do not calculate a trajectory from the current state to the desired target state. In other words, setting a single position command will result in a step response from the actuator. If you’d like a smooth motion between two or more positions, please take a look at the trajectory API.

The individual hebi::Command elements of a GroupCommand object can also be accessed and updated individually. Note that you cannot create an instance of Command - you only retrieve references through a GroupCommand. The hebi::Command object provides a hierarchical message that provides access to all of the available options that can be sent through the API — for example, updating the name of the module in the following example:

hebi::GroupCommand groupCommand(group->size());
hebi::Command& command = groupCommand[0]; // retrieve reference to command for first module
command.settings().name().set("new name");
bool success = group->sendCommandWithAcknowledgement(groupCommand);

The Command class documentation provides complete documentation of the Command class fields.

Command Lifetime

Commands are only valid for a limited command lifetime, so even unchanging setpoints need to be re-sent before they expire. When set, this means that is a process is killed (e.g., with ctrl-c), commanded motions are aborted. This is set on a per-group basis with the hebi::Group::setCommandLifetimeMs() function. By default, the command lifetime is 100ms. Note that this function will return 'false' if attempting to set a value longer than the maximum allowable command lifetime.

// Sets command timeout to 100 milliseconds
// Command must be sent in loop at a faster rate than the lifetime in order to remain in effect.
while (!stop_loop)
Example Use Cases

The following example shows an open-loop controller commanding sine waves with different frequencies on two actuators using simultaneous position and velocity control.

#include <cmath> // provides M_PI
// Two actuators executing sine waves of 1Hz and 2Hz
auto group = lookup.getGroupFromNames({"knee", "ankle"}, {"leg"});

hebi::GroupCommand groupCommand(group->size());
Eigen::VectorXd w
  << 2.0 * M_PI,
     4.0 * M_PI;

// It is good practice not to allocate this every loop iteration.
Eigen::VectorXd w_t(2);
double t = 0.0;
double dt = 0.01; // 10 ms
while (!stop_loop) {
  w_t = w * t;
  std::this_thread::sleep_for(std::chrono::milliseconds(dt * 1000.0));
  t += dt;

Note in addition to actuator commands such as position, velocity, and effort, the Command object can be used to set values for other types of modules. Here, we set the value on an I/O board digital output pin to 'high'.

auto group = lookup.getGroupFromNames({"IO_BOARD"}, {"sensor interface"});
hebi::GroupCommand groupCommand(group->size());


A group is responsible for gathering sensor feedback from all contained modules, and synchronizing these into a single Group Feedback structure. By setting the group’s feedback frequency (using Group::setFeedbackFrequencyHz), a background thread will request and synchronize this feedback at a specified rate. The default feedback rate is 100Hz.

The actual feedback frequency is fundamentally limited by the underlying operating system’s scheduler. For example, code that executes at a rate of 1KHz in Linux may be limited to ~640Hz on Windows 7. For typical rates below 250Hz this tends to be irrelevant. The Importance of Metrics and Operating Systems contains more information about the expected performance for various operating systems.
Feedback from multiple modules is typically synchronized to within 1ms. However, the actual timings depends on the network layout and external network traffic. Please see Analyzing the viability of Ethernet and UDP for robot control for more information.

To access this feedback, there are three options:

Get Next Feedback

The first is to call Group::getNextFeedback. This will return the next new (not previously accessed) synchronized feedback. Thus, a single feedback response will never be returned more than once. If there is no new feedback since the last call to this function, it will block and wait for a new packet (up to a configurable timeout), so this can be used as an easy way to control the rate of a loop. For example:

// Best practice is to allocate this once, not every loop iteration
hebi::GroupFeedback feedback(group->size());

// This effectively limits the loop below to 200Hz

while (!stop_loop)
  if (!group->getNextFeedback(&feedback))
  // ... read/use feedback object contents here.

This use of getNextFeedback parallels the approach taken in the HEBI API for MATLAB.

Feedback Callbacks

The second way to access this feedback is to add a feedback handler that is called each time new synchronized group feedback is available. The C++ 11 std::function will automatically convert function pointers and lambda functions to a function object. The callback will execute on a background thread. Below we show two methods for adding a callback function

// Function feedback callback
static void feedback_func_callback(const hebi::GroupFeedback& const feedback) {
  // ... read/use feedback object contents here

void foo() {
  // Sets frequency to 200 Hz

  // Method 1: Add pointer to function

  // Method 2: Add a lambda function; see C++ reference for more info
      [](const hebi::GroupFeedback& const feedback) -> void {
        // ... read/use feedback object contents here
Manual Feedback Requests

You can also retrieve feedback when the feedback frequency is zero by manually sending a feedback request. This allows finer grained control over the network traffic that is sent.

hebi::GroupFeedback feedback(group->size());
group->sendFeedbackRequest(); // Sends a request to the modules for feedback and immediately returns
group->getNextFeedback(&feedback); // Blocks until feedback is in the background queue or timeout
The setFeedbackFrequencyHz call effectively calls the sendFeedbackRequest function at a certain rate on a background thread.
Feedback Contents

The GroupFeedback object contains position, velocity, and effort actuator feedback, as well as sensor feedback from a number of sensors. Similar to commands, many of the values can be accessed as Eigen vectors or matrices, with one row for each module in the group. Fields can also be accessed via individual hebi::Feedback object references, which allow hierarchical access to all information contained in the feedback message.

// Fill in feedback
hebi::GroupFeedback group_feedback(group->size());

// Retrieve positions:
Eigen::VectorXd positions = group_feedback.getPosition();
std::cout << "Position feedback: " << std::endl << positions << std::endl;

// Update an existing Eigen vector or matrix (eliminates a copy operation)
Eigen::MatrixX3d gyros(group->size());
std::cout << "Gyro feedback: " << std::endl << gyros << std::endl;

// Individually access io pin feedback from first module
auto& io_a = group_feedback[0].io().a();
if (io_a.has(1))
  std::cout << "Pin A1: " << io_a.get(1) << std::endl;
Use Case Example

The below example shows a closed-loop controller that implements a virtual spring that controls torque to drive the output towards the origin (stiffness = 1 Nm / rad).

// Virtual spring
hebi::Lookup lookup;
auto group = lookup.getGroupFromNames({"name"}, {"family"}); // change to your name(s)/family(s)
hebi::GroupCommand command(group->size());
hebi::GroupFeedback feedback(group->size());

const double stiffness = 1.0; // [Nm/rad]

while (!stop_loop) {
    group->getNextFeedback(&feedback); // wait for the feedback
    command.setEffort(-stiffness * feedback.getPosition());

Using a lambda callback, this can be rewritten as:

// Virtual spring
hebi::Lookup lookup;
auto group = lookup.getGroupFromNames({"name"}, {"family"}); // change to your name(s)/family(s)
hebi::GroupCommand command(group->size());

const double stiffness = 1.0; // [Nm/rad]

while (!stop_loop) {
  // Add callback, capturing relevant variables by reference and/or value
  [&group, &command, stiffness](const hebi::GroupFeedback& const feedback) -> void {
    command.setEffort(-stiffness * feedback.getPosition());

Best Practices

Performance and Efficiency

The API has been developed with low latency and high efficiency in mind. For applications that require high control frequencies, we recommend the following additional steps.

  • Reuse Command, Feedback and Eigen objects rather than allocating one each time in a loop

// allocate groupCommand outside tight loop below
hebi::GroupCommand groupCommand(group->size());
hebi::GroupFeedback group_feedback(group->size());
Eigen::VectorXd posVector(group->size());

group->setFeedbackFrequencyHz(100); // set maximum loop speed

while(!stop_loop) {
    // wait for the feedback
    if (!group->getNextFeedback(&group_feedback))
    // edit "posVector"
    // set command data
  • If using vectorized access to group feedback fields, pass in an existing Eigen object rather than returning a new one

hebi::GroupFeedback group_feedback(group->size());
Eigen::VectorXd positions(group->size());
if (group->getNextFeedback(&group_feedback)) {
  // Prefer passing in reference rather than returning copy


Control loop gains can be retrieved and set on each device by features provided by the Command and Info APIs.

The recommended way to set and load gains is to use the XML gain format that works across HEBI APIs and GUI tools. To save gains from or load gains onto a module, call the writeGains and readGains functions on the GroupInfo and GroupCommand objects as follows:

// Saving gains from a group of modules:
GroupInfo group_info(group->size());
if (group->requestInfo(&group_info))

// Loading gains from a file onto a group of modules:
GroupCommand group_cmd(group->size());
bool success = group->sendCommandWithAcknowledgement(group_cmd);

Alternatively, individual gains can be accessed and set programmatically. Upon inspection of the C++ API class hierarchy, you will see that there are nested classes within both the hebi::Command and hebi::Info classes. The example below demonstrates programmatic doubling of the position P gain for each module in the group:

// Double position P gain
hebi::GroupInfo groupInfo(group->size());
hebi::GroupCommand groupCommand(group->size());

for (int i = 0; i < group->size(); i++) {
    auto& info_kp =
    auto& cmd_kp =
    if (info_kp) {
        cmd_kp.set(info_kp.get() * 2.0);

Robot Model / Kinematics

The hebi::RobotModel namespace contains classes and functions to store and work with robot kinematic configurations. These assist with computing forward and inverse kinematics and Jacobians.

Defining the Robot Configuration

The hebi::robot_model::RobotModel class is used to store how different rigid bodies and joints are attached together. There are helper functions to add standard HEBI components to the robot model.

The first body added to the hebi::robot_model::RobotModel object represents the base and the last body represents the end (often an end effector) of the kinematic chain.

Currently the API assumes a serial kinematic chain of bodies. Support for kinematic trees is planned in a future release.

Various factory methods are used to add HEBI components to the robot model; these fall into 3 basic categories: Actuator, Link, and Bracket:

using ActuatorType = robot_model::RobotModel::ActuatorType;
using BracketType = robot_model::RobotModel::BracketType;
using LinkType = robot_model::RobotModel::LinkType;

// Create a simple kinematic description of a 3-DOF robot arm
hebi::robot_model::RobotModel model;
model.addLink(LinkType::X5, 0.18, M_PI);
model.addLink(LinkType::X5, 0.28, 0);

The complete list of these methods is documented here. These methods and their named parameters correspond with the actuator, link, and bracket types given in the Kinematic Body Types section.

In addition to standard HEBI components, you can use the addRigidBody and addJoint functions to add any desired masses and degrees of freedom to your system.

Forward Kinematics

The RobotModel::getForwardKinematics (or aliased RobotModel::getFK) method computes the poses of each body in the RobotModel object, using specified values for the joint parameters. Using the HebiFrameType parameter, one can choose either the center of mass or the output frame for each kinematic body.

The resulting 4x4 homogeneous transforms are specified in the world frame of the RobotModel object. This world frame defaults to the input frame of the base module, although a transformation can be prepended using the setBaseFrame function. Units of translation are in meters.

A Matrix4dVector object is passed in to the getFK function, and is resized as necessary and filled in with the computed transforms (in order of the bodies in the RobotModel object). This type is simply a std::vector containing Eigen::Matrix4d elements; due to memory alignment considerations Eigen types in std containers must use particular allocators, and so we provide this convenience typedef. (See for more information on this requirement)

// First, build up a robot_model::RobotModel object, as in previous examples.
// These are the current joint angles/positions, perhaps from a GroupFeedback object:
Eigen::VectorXd angles = group_feedback.getPosition();
Matrix4dVector transforms;
model.getFK(HebiFrameTypeOutput, angles, transforms);
for (int i = 0; i < transforms.size(); $$++i$$)
  std::cout << "Transform for module " << i << " is " << std::endl
            << transforms[i] << std::endl << std::endl;

The RobotModel::getEndEffector method is a shortcut for just obtaining the output transform of the last frame in the RobotModel object:

// Set up hebi::RobotModel object "model" and Eigen::VectorXd "angles" as above...
Eigen::Matrix4d transform;
model.getEndEffector(angles, transform);
std::cout << "4x4 transform from base to end effector: " << std::endl
          << transforms[i] << std::endl;

Full API documentation of these functions is available here.


We can also use the RobotModel object to obtain the Jacobian, or the partial derivatives of the forward kinematics equation. This relates the joint velocities to the linear and angular velocity of each body in the system.

The Jacobian in returned as a set of matrices, one for each body in the RobotModel object. Each matrix is of size 6 x numDoF. Rows 1 - 3 of this matrix corresponds to this body’s linear velocities [m/s] along the X-Y-Z axes in the world frame, while rows 4 - 6 correspond to rotational velocities [rad/s] about the X-Y-Z axes in the world frame. The columns define the contributions to these linear and rotational velocities that would be a result of the respective degree of freedom instantaneously moving at unit velocity (e.g., a rotational joint being driven at 1 rad/s).

As with the forward kinematics, we provide versions for the end effector (which just returns a since 6 x numDoF matrix) as well as the entire system. Note that the matrices are now of type Eigen::MatrixXd and MatrixXdVector.

// Set up hebi::RobotModel object "model" and Eigen::VectorXd "angles" as above...

// Get Jacobian for all bodies:
MatrixXdVector transforms;
model.getJ(HebiFrameTypeOutput, angles, transforms);

// Get Jacobian for end effector:
Eigen::MatrixXd transform;
kin.getJEndEffector(angles, transform);

Full API documentation of these functions is available here.

Inverse Kinematics

The RobotModel object also provides a method for computing joint angles that solve the inverse kinematics, according to a number of objectives and constraints. Note that the implementation of this function relies on a local optimization, and so is highly dependent on the initial joint angles that are provided as the seed for the search.

Currently, it is not possible to define your own objectives or optimization method. This is planned for future releases.

The desired objectives and constraints should be passed to the solveIK function as a series of arguments; any number are permitted, but there are limitations in which can be used together. In future API releases, the IKResult return structure will contain more information about the optimization result.

Here is an example of using the solveIK function to find joint angles that move the end effector to a given position:

// Set up hebi::RobotModel object "model" and Eigen::VectorXd "angles" as above...

Eigen::Vector3f target_xyz;
target_xyz << 0.4, 0.0, 0.2;
Eigen::VectorXd initial_joint_angles(model->getDoFCount());
Eigen::VectorXd ik_result_joint_angles(model->getDoFCount());

// Fill in 'initial joint angles', perhaps with feedback from the robot.

// Demonstration of using a single objective:


// Demonstration of using multiple objectives (allowed through variadic templates)

// Set joint limit constraints:
Eigen::VectorXd min_positions(model->getDoFCount());
min_positions << -M_PI, -1.0f, -1.0f;
Eigen::VectorXd max_positions(model->getDoFCount());
max_positions << M_PI, 1.0f, 1.0f;

  kinematics::JointLimitConstraint(min_positions, max_positions)

Full API documentation of these functions is available here.

Gravity Compensation

Currently, computation of torques for gravity compensation is not supported directly in the API, but our examples repository provides a utility function and examples for adding this into your own system. This is subject to change and inclusion directly in the API in the future. This example demonstrates sending torques to compensate for the effect of gravity for a 3 DOF arm; a simplified version is reproduced below.

#include "util/grav_comp.hpp" // From examples repo

// Set up hebi::Group "group", hebi::RobotModel object "model" and Eigen::VectorXd "angles" as above...

// Retrieve masses from robot model for efficient/convenient access later.
Eigen::VectorXd masses(robot_model_->getFrameCount(HebiFrameTypeCenterOfMass));

// Define gravity directional vector, relative to the base frame of your robot model
Eigen::Vector3d gravity(0, 0, -1);

Eigen::VectorXd effort;
hebi::GroupFeedback feedback(group->size());
hebi::GroupCommand command(group->size());
while (!stop_loop)
  effort = getGravCompEfforts(


A trajectory is a (smooth) motion through two or more waypoints (a waypoint is a specific joint positions for each module in the robot). A trajectory defines the position, velocity, and acceleration for each module at each point in time, from the start to end of the trajectory.

More traditional robot arms usually are controlled by defining waypoints and some motion parameters (e.g., maximum acceleration), as opposed to the joint-level control our API provides at the lowest level. The trajectory API provides a way to define motions in a similar high-level way as these traditional systems, while still allowing access to control and feedback at a lower level.

Generating Trajectories

The hebi::trajectory::Trajectory class is used to generate and access trajectory motions. To create a trajectory object, you must define the waypoints and time at which the robot should pass through each waypoints. Note that the waypoint velocity and acceleration constraints are optional, but it is often useful to at least define the starting and ending velocity and acceleration to be zero. If these are not given, it is left up to the internal implementation to determine values for these.

Below, we demonstate creation of a trajectory for 2 modules over 5 waypoints, using the unconstrained quadratic programming solver:

// Define nan variable for readability below
const double nan = std::numeric_limits<float>::quiet_NaN();

// Position, velocity, and acceleration waypoints.  Each column is a separate
// waypoint; each row is a different joint.
Eigen::MatrixXd positions(num_joints,5);
Eigen::MatrixXd velocities(num_joints,5);
Eigen::MatrixXd accelerations(num_joints,5);
positions << current_position[0], 0, M_PI_2, 0,         0,
             current_position[1], 0, 0,        -M_PI_2, 0;
velocities << 0, nan, nan, nan, 0,
              0, nan, nan, nan, 0;
accelerations << 0, nan, nan, nan, 0,
                 0, nan, nan, nan, 0;

// The times to reach each waypoint (in seconds)
Eigen::VectorXd time(5);
time << 0, 5, 10, 15, 20;

// Define trajectory
auto trajectory = hebi::trajectory::Trajectory::createUnconstrainedQp(time, positions, &velocities, &accelerations);

Executing Trajectories

To play through the trajectory object, we query the position, velocity, and acceleration at each point in time, and send appropriate commands to the robot:

// Follow the trajectory
hebi::GroupCommand cmd(num_joints);
double period = 0.01;
double duration = trajectory->getDuration();
Eigen::VectorXd pos_cmd(num_joints);
Eigen::VectorXd vel_cmd(num_joints);
for (double t = 0; t < duration; t += period)
  // Pass "nullptr" in to ignore a term.
  trajectory->getState(t, &pos_cmd, &vel_cmd, nullptr);
  std::this_thread::sleep_for(std::chrono::milliseconds((long int) (period * 1000)));

Note that acceleration can not be (meaningfully) directly sent to the actuator, but with knowledge of the dynamics this can be converted to a torque or force to compensate for this acceleration. For improved control, we recommend adding effort commands that are the summation of torques/forces that compensate for gravity and torques/forces that compensate for this acceleration (an example of this is coming soon).


Data logging, visualization, and analysis tools are a critical part of robotics development. Our logging capabilities allow logging of data from groups of modules in excess of 1kHz, and over extended periods of time (multiple days). Logging is done on a background thread so there is no impact on the control logic, and logging easy to enable/disable, even in complex existing code.

The logging format is common across all HEBI APIs, so logs saved with one API can be read by any other API.

Here is a simple example of logging data; the logging occurs at the group’s feedback frequency.

// get hebi::Group "group" from lookup

// Start logging (can also specify log file name as second parameter)
std::string full_log_path = group->startLog("./test_logs/");

// main control logic here

// Stop logging
std::shared_ptr<hebi::LogFile> log_file = group->stopLog();

The log data is stored in a file on disk; the name is returned with the initial "startLog" call, and is also retrievable through the LogFile object returned when stopping the log.

Note that is the "stopLog" call returns an empty shared_ptr object, this indicates that the call failed and the log file was not created; this is usually caused by there being no active log that has been started.

The returned LogFile object (see API reference documentation) allows sequential access to the content in the log file; the below example demonstrates analysing a logfile to find the highest torque experienced by each joint:

size_t num_modules = log_file->size();
GroupFeedback feedback(num_modules);
Eigen::VectorXd max_efforts(num_modules);
// If we are trying to find the max, start with the lowest possible number.

// Get the component-wise max each feedback entry compared to the running max efforts
while (log_file->getNextFeedback(feedback))
  max_efforts = max_efforts.cwiseMax(feedback.getPositions());

Note that you can also manually create a LogFile object from an existing file (from the MATLAB or C++ API) using:

std::shared_ptr<hebi::LogFile> log_file = hebi::LogFile::open("/path/to/file.hebilog");


The C# API currently is in development. Documentation will be coming soon.