Downloads

Software

Below are the latest versions for our APIs and tools.

Item Downloads Links

1. Requires Java 8 update 60 or higher to be installed. Some Linux distributions may additionally require OpenJFX.

Windows (Win32 / Win64)
Linux (x86-64 / ARM)
macOS (64-bit)


1. .tar.gz file preserves symlinks for macOS/Linux shared libraries
2. Unzip corresponding API archive into this folder before building

C (API)

Windows (Win32 / Win64)
Linux (x86-64 / ARM)
macOS (64-bit)


1. .tar.gz file preserves symlinks for macOS/Linux shared libraries
2. Unzip corresponding API archive into this folder before building

Datasheets

Below is a list of datasheets. Additional drawings and CAD models can be found in the accessory section, or on our public GrabCAD repository.

Part Numbers Description Links

A-2020-05-01
A-2020-05-04
A-2020-05-09

A-2020-08-03
A-2020-08-09

X5/8-Series Actuators

PE-2033-01

I/O Board

X-Series Accessories

We offer a wide variety of accessories in order to help build and configure your robot with ease.

If your application requires an accessory that is not listed below, HEBI can provide custom development at an additional cost.

Additional drawings and CAD models can be accessed from our public GrabCAD repository.

Table 1. X-Series Mechanical Accessories currently available
Part Number Description and Drawings Image

A-2038-02

X5/X8-Series 1.25" Tube Adapter w/ indexing pins (Output)
Assembly Drawing: A-2038-02

A-2038-02

A-2039-02

X5/X8-Series 1.25" Tube Adapter w/ indexing pins (Input)
Assembly Drawing: A-2039-02

A-2039-02

A-2040-01R

X5/X8-Series 90o Bracket Heavy Duty (Right)
Assembly Drawing: A-2040-01R

A-2040-01R

A-2040-01L

X5/X8-Series 90o Bracket Heavy Duty (Left)
Assembly Drawing: A-2040-01L

A-2040-01L

A-2042-01R

X5/X8-Series 90o Bracket Lite (Right)
Assembly Drawing: A-2042-01R

A-2042-01R

A-2042-01L

X5/X8-Series 90o Bracket Lite (Left)
Assembly Drawing: A-2042-01L

A-2042-01L

A-2043-01

X5/X8-Series T-Slot Mount Plate 25mm/1"
Assembly Drawing: A-2043-01

A-2043-01

PM-2200-01

1.25" OD Aluminum Tube w/ indexing holes, 150mm length
Drawing: PM-2200-01

PM-2200-01

PM-2200-02

1.25" OD Aluminum Tube w/ indexing holes, 300mm length
Drawing: PM-2200-02

PM-2200-02

PM-2200-03

1.25" OD Aluminum Tube w/ indexing holes, 500mm length
Drawing: PM-2200-03

PM-2200-03

Table 2. X-Series Electrical Accessories currently available
Part Number Description and Drawings Image

PE-2033

HEBI I/O Board
User Manual

PE-2033

PP-2059-01
PP-2060-01
PP-2061-01

High-Flex Ethernet Path Cable
PP-2059-01: 1' (300mm) Length
PP-2060-01: 2' (600mm) Length
PP-2061-01: 3' (900mm) Length

PP-2060-01

PE-2026-01

X5/8 Power Distribution Board

PE-2026-01

A-2046-12
A-2046-18
A-2046-24

Female/Female Power Cable
A-2046-12: 12" (300mm) Length
A-2046-18: 18" (450mm) Length
A-2046-24: 24" (600mm) Length

A-2046-18

A-2047-12

Female/Flying-Lead Power Cable
12" (300mm) Length

A-2047-12

A-2048-02

Male/Male Power Cable
2" (50mm) Length

A-2048-02

Whenever feasible we try to make all HEBI components compatible with existing standards. While we do offer and sell all of the required parts, below are suggestions for some of the most common items that customers may want get from other sources.

Table 3. X-Series compatible COTS components
Type Comments Links

Power Supply
(X-Series)

24-48 VDC output

The peak power draw could be 75W per actuator, but we found ~200W to be sufficient for 6 dof arms

Note that the power supply’s connector will need to be changed to be compatible with the Molex Mini-Fit Jr. connector

Network Cables

Slim snag-less Ethernet cables

Power Supply
(I/O Board)

7V-24 VDC output

Compatible with Arduino power supplies

Quickstart Guide - X-Series Actuator

Getting Started

actuator and wires

The following instructions are meant to get you up and running quickly if you are new to HEBI X-Series actuators. If you have any questions please:

Download the Scope GUI

Go to docs.hebi.us and get the latest version of the Scope GUI for your computer (Windows, macOS, Linux).

Connect Ethernet

plugging ethernet
DHCP example

Connect a standard ethernet cable to either one of the RJ45 ports on the module. Connect the other end of the cable to a router or switch on your local area network (LAN).

Modules ship by default configured to run on DHCP. This means that to work correctly there needs to be a router on the network configured for DHCP.
It is important to use a wired ethernet connection between your computer and modules for maximum reliability. A wireless connection will work, and is generally still useful for viewing feedback in the Scope GUI. However, there will be some amount of packet loss and increased latency that may cause issues with motor control.

Connect Power

plugging power

Using the provided cabling, or any other compatible Molex Mini-Fit Jr. connectors, plug in the power connection to the module.

Do not hot-plug the modules. Connecting and disconnecting the power while the power is on will cause sparking and may damage the module. Only connect and disconnect the power connector when power is off.

Turn On Power

Turn on the power supply, or connect/activate batteries. The modules run on any DC voltage source between 24V-48V.

An X-Sereis actuator module can draw up to 50W-150W at peak power, but draw less than 2W at idle. Typical continuous power draw for most applications is much lower than the peak power draw, usually 10W-20W. It is important to choose an appropriate power supply for your application. See the Power section of the online documentation for more information.
bootloader led

The Status LED will start blinking between light-blue and yellow while it is obtaining an IP address. Once the module has an IP address it will change to a light-blue fade for a few seconds.

application led

After a few seconds, the LED will change to a green fade. Once the module is in this state it will respond to commands and provide feedback from its internal sensors.

Open the Scope GUI

InfoTab

When Scope opens, it will show the Information Tab, which dispays hardware and configuration information for a module. Selecting a module on the list from left side of the Scope GUI will display its information.

Play Around

MonitoringTab

Feel free to play with the module to get a feel for its capabilities. In the lower section of the Monitoring Tab in Scope thereare sliders for controlling position, velocity, and effort. There are also buttons in the upper section of the Monitoring Tab for plotting feedback from the various sensors in the module.

Wiring up Multiple Modules

two actuators

The X-Series actuators are designed to be quickly wired and chained together to make assembling full robotic systems as easy as possible. When connecting multiple modules:

  • The ethernet connection can be daisy-chained thru the modules. It does not matter which ports are used to daisy-chain.

  • The power connection needs to be split external to the module, using the provided power splitters or using other custom wiring.

Mechanically Mounting Modules

The X-Series actuators have threaded holes for M5 metric screws on their top and bottom sides for mounting. If you are using standard HEBI connection parts, the appropriate hardware is provided.

Be careful to use the appropriate length screws when mounting the actuators. Using screws that are too long may damage the actuator if they are tightened while bottomed out in the threaded holes.

Commands

ActuationDemo

Commands section of the Monitoring Tab has sliders for commanding different aspects of an actuator’s motion.

The details of how an actuator will respond to commands will depend on the Control Strategy and the gains on the module. Out of the box, an actuator will start up in Control Strategy 3, with gains that perform well under light loads. For heavier loads or multi-DOF systems the actuator gains will likely have to be tuned for the specific application. Actuator control and tuning is a complex topic and it is detailed more completely in the online documentation in the Motor Control section.

You can view the both commanded and feedback position, velocity, and effort by clicking the corresponding button in the upper portion of the Monitoring Tab.

Position Control

The top slider in the Commands section controls the output position of the actuator in radians. Dragging the slider will send new position commands.

Velocity Control

The middle slider in the Commands section controls the output velocity of the actuator in radians / second. Dragging the slider will send new velocity commands, causing the actuator output to turn continuously.

Effort (Torque) Control

The bottom slider in the Commands section controls the output effort of the actuator. For the X-Series actuators this is the output torque in Newton-meters. Dragging the slider will send new effort commands.

Torque control can be a somewhat unintuitive at first. A commanded torque in one direction can cause the output of an actuator to spin up to its maximum speed until sees a load that allows it to exert the commanded torque.

Feedback

FeedbackDemo

Position / Velocity / Effort (Torque)

You can view the both commanded and feedback position, velocity, and effort by clicking the corresponding button in the upper portion of the Monitoring Tab.

Gyro / Accelerometer

Every module has an internal 3-axis accelerometer and 3-axis gyro. Together these sensors are often called an internal measurement unit (IMU).

The gyro measures the angular velocity of the module in radians / second. The accelerometer measures a combination of gravity and the module’s linear acceleration in meters / second2.

Current / Voltage

Each module senses its bus voltage, reported in units of volts, It also senses the current draw of the motor at the bus voltage, and the winding current through the windings of motor, both reported in units of amps.

Temperature

Each module reports back an ambient temperature, which is the temperature of the electronics in the module. Acuators also report back a modeled winding temperature, bulk actuator temperature, and motor sensor temperature, all in degrees Celcius.

The winding temperature in particular informs a safety controller where motor power may be limited if it is too high. Watching the plot of winding temperature (the red line in the Scope GUI) is good indication of where you are running withing the actuator’s performance envelope.

Latency / Packet Loss
To quantify network performance, the Scope GUI provides plotting of network latency and packet loss. Under normal conditions on a wired LAN a module will show no packet loss, and a round-trip latency of around 1-2ms.

Next Steps

After becoming familiar with the basic functions of an actuator, there are multiple ways to control the actuators and configure their behavior.

Scope GUI

The Scope GUI provides a wide range of capabilities for configuring, controlling, and testing out modules. It can be a good starting point for learning about actuation and control in general, and it is an important tool for debugging during devlopment.

HEBI API for MATLAB

The HEBI API for MATLAB enables the control of actuators and modules directly from the standard MATLAB working environment. It provides multiple levels of APIs that allow quick prototyping of real robotic systems while taking advantage of MATLAB’s powerful plotting and data analysis tools.

HEBI API for C++

The HEBI API for C++ is a cross-platform API with the same functionality as the MATLAB API. It is the basis of our support for ROS.

1. Module Documentation

1.1. Lookup and Module Groups

Once one or more modules are turned on and plugged into the network they can be discovered by a Lookup thread that periodically sends broadcast messages on the network. The interval is configurable and typically initializes at around 5 Hz.

Discovered modules can be combined into a Group that provides a basic way to send commands and retrieve feedback. Groups also handle higher-level issues such as data synchronization and logging.

Each module has multiple keys that can be used to identify a module on a given network

  • Family (human settable, e.g., "RobotArm_6DoF")

  • Name (human settable, e.g., "base" or "elbow")

  • IP address (network unique)

  • MAC address (global unique)

There are no software limits on the created number of groups, the number of modules within a group, or the number of groups that a module is part of.

1.2. Firmware Modes

Modules contain two separate sets of firmware. When powering up or restarting, a device initially starts in a Bootloader mode that initializes the IP address, offers diagnostic features, and allows for safe firmware upgrades. In bootloader, the status LED will blink blue-yellow until the module has been assigned an IP address. Once it has an IP address the status LED will change to a blue on/off fade.

After a fixed period of time, or on request from the Scope GUI or a command from the API, the module boots into the Application mode, which initializes all local device drivers, accepts commands, and responds to requests to feedback. When the module enters application mode the status LED will change to a slow green fade.

1.3. LED Status Codes

The Status LED is a multi-color indicator that provides information about the current state of a module, detailed in the table below. It is located under the white plastic window on the side of the module near the connector. There is also a smaller white LED to the left of the Status LED that indicates that module is receiving power.

1.3.1. Bootloader Mode

The following LED statuses are displayed during normal operation when a module is first powered on or reset.

LED Pattern Meaning

Orange/Light-Blue Blink

Not yet obtained an IP address.

Note: For a module to receive an IP address it needs to be on a local network with a router running DHCP.

Note: If a module remains in this mode for more than a few minutes without a network connection, and then a network connection is established, the module may not receive an IP address. Restarting the module with an established network connection will solve this issue.

Light-Blue Fade

Obtained an IP address. This state should only last for a few seconds. The Scope GUI or APIs can be used to keep the module in Bootloader Mode, or to manually trigger an immediate start of Application Mode.

Note: If a module has a manually assigned IP address it will start directly into this mode when resetting or powering up.

1.3.2. Application Mode - Common Statuses

The following LED statuses are displayed during normal operation, and common fault or safety modes.

LED Pattern Meaning

Green Slow Fade

Operating normally, not receiving any commands.

Green Fast Fade

Operating normally, acting on received commands.

Yellow Fast Blink

M-stop triggered.

Orange Fast Blink

Module output is at a mininum or maximum position limit. The module will not accept commands until it is back within the limits.

Red Fast Blink

Motor winding temperature is high, and the motor’s power is being limited by an internal safety controller to less than what is currently being commanded.

1.3.3. Application Mode - Abnormal Statuses

The following LED statuses are not encountered during normal operation. If you see any of these on module, please contact us at: support@hebirobotics.com.

LED Pattern Meaning

Green Slow Blink

Operating normally, but effort (torque) sensing has not been calibrated.

Yellow Solid On

Module electronics above safe temperature.

Red Solid On

If encountered immediately after exiting the bootloader, the module does not have application firmware. Use the Scope GUI to install new firmware.

If encountered while the module is running normally, the motor winding temperature is above a critical value and power to the motor has been shut off.

Blue Fast Blink

Current sensing is not calibrated. The module’s motor will drive in this mode.

Blue Slow Blink

Position sensing is not calibrated. The module’s motor will only drive in Direct PWM mode.

No Light

There is a problem with the module.

1.4. Command Lifetime

In order to mitigate the risk of accidents caused by severed communications or a crashing user’s program, we’ve introduced the notion of a command lifetime. This is the maximum duration that a command may be active before it expires. If the hardware does not receive further commands within the specified time frame, all local controllers get deactivated and motor control is turned off.

This is similar to a safety feature that many industrial robots have that requires users to send at least one update every interval (e.g. 5 ms) to keep the robot from turning off and locking up. Since the timeout deadline depends heavily on the application, we chose to implement the inverse approach in which every command specifies its own lifetime.

Additionally, supporting hardware does not accept commands from any other sources during the lifetime of a command. This mitigates the risk of other users accidentally sending conflicting targets from, e.g., a GUI.

While the timeout can be deactivated, we strongly recommend to make use of this feature. Otherwise, the hardware will continue to execute the last sent command indefinitely, which can result in unwanted behavior, especially when commanding velocities and efforts (torques). Alternatively, we recommend using a hardware button that triggers a motion stop ("M-Stop").

Note that the command lifetime is one of the most common sources of confusion for new users. If commands are only sent once, or the program contains a pause that is larger than the timeout, the module will stop very quickly. Typically commands should be sent in a loop.

1.5. Programming Levels

Since modular building blocks may be used to build many different types of systems, the programming requirements depend heavily on the application. While some customers use industrial-type arm configurations and want to command higher level trajectories, other customers may want to control the raw motor output using custom PID loops. In order to enable all of the potential use cases, we provide APIs for every level. Please consult the table below for available resources and APIs.

Table 4. Programming Levels
Level Typical Frequency Example Tasks APIs and Resources

High (System)

< 10 Hz

  • Teach-Repeat

  • Pick and Place

  • SLAM

  • "Go-Left"

  • Kits

  • Examples

  • Consulting

  • ROS Ecosystem (external)

Mid (Robot)

50 Hz - 200 Hz

  • Trajectories (A → B)

  • Forward / Inverse Kinematics

  • Gravity-Compensation

  • HebiTrajectoryGenerator

  • HebiTrajectory

  • HebiKinematics

Low (Joint)

100 Hz - 1KHz

  • Position

  • Velocity

  • Effort (Torque)

  • Digital I/O

  • HebiLookup

  • HebiGroup / HebiModule

Hardware (Motor)

1 KHz

  • PID Loops

  • PWM

  • Gains

  • Control Strategies

    • DIRECT_PWM

1.6. Power

Modules run on DC voltages between 24V-48V. The internal electronics of the modules are designed to properly regulate and automatically scale motor control parameters as the bus voltage changes.

However, if actuators are heavily loaded and are being back-driven or impacting the world with significant amounts of force there may be voltage spikes as energy is sent from a back-driven actuator out into the power system. In these cases we recommend using a shunt regulator, or using a large battery system that can absorb these effects.

1.7. Communications

Modules are connected to host computers via standard Ethernet. In an Ethernet network every client needs to obtain an IP address in order to communicate. There are two ways to get an IP address:

1.7.1. Dynamic Address Assignment

Unless you have a background in networking, we recommend that you use dynamic address assignment. This requires you to plug-in your computer and the modules into a router that supports DHCP. If your network does not already have a router, or the router enforces security policies that do not allow unregistered devices on the network, we recommend that you purchase one. Nowadays every consumer-grade router has DHCP enabled by default, so a quick search on your shop of choice (e.g. Amazon) for "Router" should work. Some examples are listed below

  • Mikrotik RB750Gr2

  • TP-LINK TL-R600VPN

    1. Plug the computer as well as your module(s) into the same router as shown below. If there are not enough ports available, you can daisy-chain modules or extend the network using a Switch.

DHCP example
  1. Power on the modules and look at the LED color. Fading orange/blue means that it is searching for an IP address. Once it turns into a blue fade, the modules has obtained an IP address. Now you should be able to connect.

  2. You can check whether communications have been setup correctly by running the Scope GUI or any of our APIs.

1.7.2. Static Address Assignment

Alternatively, IP addresses can be set statically so that modules don’t need to query for an address every time. This can sometimes be useful when it is difficult to integrate a DHCP server or if the robot needs to boot/initialize faster.

Static IP addresses can be set by right-clicking on a module’s entry in the Scope GUI as highlighted below. Note that this requires an initial dynamic address in order to communicate.

set address selected

This will open the following dialog that allows for setting the IP address and subnet mask. The values default to the device’s current address. For more information on what values to set, please refer to Understanding TCP/IP addressing and subnetting basics.

set address dialog

You can revert to dynamic addressing by calling "clear address" in the same menu as "set address" or by doing a hardware reset using the following procedure:

  1. Press the hardware reset button

  2. Restart the module (e.g. by turning power off and on again). The LED should start fading red

  3. Wait until the LED does a faster-blink red and let go of the reset button

  4. Restart the module again

1.7.3. Connections to Multiple Networks

Control computers may have more than one network interfaces and be connected to more than one network. For example, computers can be on wired Ethernet and WiFi at the same time. In order to find modules on all networks, the default behavior for the HEBI module lookup is to broadcast on all interfaces.

Note that in some tools and APIs the local networks may be determined only once on startup, so changing your network, e.g., plugging your computer into a different router, may require a restart of the tool or API.

1.8. Wiring X-Series Actuators

X-Series actuators were designed to simplify traditional wiring pain points and feature a through hole as well as a multi-port switch for daisy chaining. Additionally, we recommend using external power splitters to also daisy chain power wires. They can usually be hidden inside tubes such as shown in the image below.

ext with splitterlegend

no through hole

Wires that come from the previous joint should be inserted directly

through hole

Wires that connect to the next joint should be threaded through the hole

Technically there is a latency cost attached to daisy chaining Ethernet cables through multiple switches. However, the added latency is minimal and should not matter in practice. We’ve built complex systems that daisy chain through more than >20 actuators and have yet to run into problems due to this. For more information and benchmarks please refer to our blog.

2. Motor Control

2.1. Overview

HEBI actuator modules can be controlled using position, velocity, and effort (typically torque). This section discusses the details of how these different inputs can be controlled and tuned for specific applications.

For many systems, the best performance can be acheived by controlling a combination of commanded positions, velocities, and effort.

2.2. Control Strategies

Position, velocity, and effort controllers can be run individually or in any combination simultaneously. To combine these control inputs, PID control loops are used on position, velocity, and effort. These controllers can be cascaded and combined in different preset configurations, termed Control Strategies.

The currently active Control Strategy and its corresponding parameters can be set either from the Scope GUI or by using the APIs.

Changes to the active control strategy on an actuator do not automatically get persisted and thus may change after a reboot. Settings can be persisted by right-clicking the actuator in the Scope GUI or via the persist command in the various APIs.

2.2.1. Off

The motor is turned off. The actautor will not respond to commands of any kind.

2.2.2. Direct PWM (Strategy 1)

The effort command directly sets the motor PWM, from -1 to 1. This controller uses no feedback from the sensors in the actuator. The controller does not compensate for changes in the supplied voltage. All safety controllers to protect the actuator (temperature, velocity, joint limits, etc.) are still applied.

2.2.3. Strategy 2

Position and Velocity PID outputs sum with a feed-forward effort signal to generate an intermediate effort signal. This intermediate effort signal is then passed through an "inner" Effort PID controller which generates motor PWM commands.

Strategy 2

2.2.4. Strategy 3

Position, Velocity, and Effort PID controllers all directly sum to generate motor PWM commands.

Strategy 3

2.2.5. Strategy 4

Position PID output sums with a feed-forward Effort signal to generate an intermediate Effort signal. This intermediate effort signal is then passed through an "inner" Effort PID controller. The PWM output of the inner Effort controller is added to a Velocity PID controller output to generate motor PWM commands.

Strategy 4

2.2.6. Choosing a Control Strategy

Different control strategies have different advantages depending on the application. It can be difficult to say which control strategy is best, since overall performance depends heavily on the tuning of the gains within a given control strategy. Often two different control strategies can be tuned to perform equally well for a given application.

As a rule of thumb, Strategy 3 or Strategy 4 are usually the best choices for beginning the process of gain-tuning. In both of these strategies the velocity contoller feeds directly to motor PWM. Because of this, the velocity PID loop can apply a feedforward PWM to the motor based on the desired velocity and the known actuator parameters. This generally allows Strategy 3 and Strategy 4 to have better velocity control with lower Kp gains than Strategy 2.

Strategy 3 is a good starting point for control if:

  • You are new to the field of contol or actuation. Since all the individual PID contollers sum directly on PWM, gain tuning is a little more intuitive.

  • You are concerned primarily with position and velocity control. The accuracy of the effort (torque) sensing does not directly effect the quality of the control, since the outputs of both the position and velocity PID controllers go directly to motor PWM.

  • You are not relying heavily on effort control compared to position / velocity control. If you are relying on the commanded effort to improve the tracking of some aspect of a system (e.g. motor torques being commanded based on an impedance controller on the end effector of an arm), then the effort controller can sometimes 'fight' the position controller in ways that sometimes are not as intuitive as Strategy 4.

Strategy 4 is a good starting point for control if:

  • You want the position controller to act as much as possible as a linear spring. Since the position PID controller feeds to an inner effort controller (i.e. a torque controller) much more of the friction and drag of actuator’s geartrain is cancelled out, compared to Strategy 3. This can result in the actuator feeling more responsive to interference from the outside world (e.g. an arm bumping into a table or interacting with a person guiding it).

  • You are using significant amounts effort control compared to position / velocity control. If you are relying on the commanded effort to improve the tracking of the actuator (e.g. motor torques being commanded based on an impedance controller on the end effector of an arm), then having the efforts sum before going to the effort PID controller can often result in more intuitive behavior when the position and effort commands 'disagree'.

Strategy 2 is good for control if:

  • You want the velocity controller to act as much as possible as a linear damper (viscous damping). At HEBI, we have generally not favored this control strategy for most applications because the noise in the velocity feedback signal makes for poor control when resulting effort command from the velocity PID controller is fed to the effort controller.

Direct PWM is good for control if:

  • You want to run your own position / velocity / effort control in a way that isn’t satisfied by the built-in controllers on the actuator. It is also useful for system identification of the actuator or debugging purposes, since it elimates all feedback control.

2.3. Controller Parameters (Gains)

Each controller for position, velocity, and effort is a full PID-controller that exposes gains for tuning the proportional, integral, and derivative response of the control loop. Additionally, there are are number of other parameters that can be used to modify the response of the loop (see table below), threshold and low-pass the input and output, limit integral windup, and provide feedforward commands where appropriate.

2.3.1. Description of Parameters

The table below lists the parameters that are common to each of the individual position, velocity, and effort PID-controllers. The API-specific information for each parameter are detailed in the next section.

Note that changes to the gains on an actuator do not automatically get persisted and thus may change after a reboot. Gains can be persisted by right-clicking the actuator in the Scope GUI or via the persist command in the various APIs. Persisting will retain only the gains that are currently active. For example, if you set gains in Strategy 3, then switch to Strategy 4 and change the gains, and then 'Persist', only the changed gains in Strategy 4 will get persisted and the changes to the gains in Strategy 3 will be lost.

Parameter Description Units

Kp

The proportional gain of a standard PID-controller. The role of this term is to act like a linear spring and provide an output proportional to the measured error.

(PWM / unit_error) or (effort / unit_error) depending on the Control Strategy

Ki

The integral gain of a standard PID-controller. The role of this term is to accumulate past errors to help eliminate steady-state error.

(PWM / (unit_error x sec)) or (effort / (unit_error x sec)) depending on the Control Strategy

Kd

The derivative gain of a standard PID-controller. The role of this term is to damp the error based on its velocity.

(PWM / (unit_error / sec)) or (effort / (unit_error / sec)) depending on the Control Strategy

Feedforward (FF)

Feedfoward gain that is multiplied to the input value. If the PID controller is a velocity or effort controller that outputs PWM (all the effort PID controllers and the velocity PID controllers in Strategy 3 or Strategy 4) this gain is normally set to 1. Otherwise the gain should generally be set to 0.

(PWM / unit_error), on controllers where feedfoward is used

Deadzone (Dz)

Area around zero where the output of the PID loop is modified to reduce its output. The deadzone will shift the effect of Kp so that the zero-effort or zero-PWM intercept will be at +/- the deadzone value. Ki is modified so that it does not integrate any additional error inside the deadzone, but any accumulated error will remain. Kd is modified to be 0 inside the deadzone. Feedforward terms will still apply inside the deadzone.

(unit_error)

I Clamp

Maximum value that Ki is allowed to wind up. A value of 0 will disable the integral term. Values of +/-inf can be used to remove any windup limits.

(PWM or effort) depending on the Control Strategy

Punch

A step amount that can be added to the standard proportional gain that can sometimes be useful in overcoming motor stiction.

(PWM or effort) depending on the Control Strategy

Min/Max Target

Threshold values for the input to the PID controller.

(unit_error)

Min/Max Output

Threshold values for the output to the PID controller.

(PWM or effort)

Target LP

A IIR low-pass filter applied to either the input to the PID controller.

(0 to 1), where 1 is no low-passing and smaller values more heavily low-pass the input to the controller.

Output LP

A IIR low-pass filter applied to either the output of the PID controller.

(0 to 1), where 1 is no low-passing and smaller values more heavily low-pass the output from the controller.

D on error

Flag for whether Kd runs on the derivative of the output of the PID controller or the error of the PID controller (the difference of the target and output values).

(true or false)

2.3.2. XML Gains Format

In addition to direct programmatic access to setting controller gains, gains can also be stored in a XML file format that is universal to the HEBI APIs and Scope. This format allows gains to be loaded from a file and sent to a group of modules, or saved from a group of modules to a file.

Saving the gains from a module or group of modules will capture all of the settable parameters, including the control strategy.

The format is series of XML elements whose content is a list of values for each module in the group. For example, this snippet shows position PID gains for a set of 3 modules:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<group_gains>
  <control_strategy>3 3 3</control_strategy>
  <position>
    <kp>1 1.2 3</kp>
    <ki>0 0 1.1</ki>
    <kd>0 0.0001 0.001</kd>
  </position>
</group_gains>

This format supports all available gains for the position, velocity, and effort controllers, as can be seen in our downloadable default gains.

Saving and Loading gains

Scope and the C++ and MATLAB APIs all have tools to read and write this gain format.

Note that in each case below the gains will be set on the module, but not "persisted" to internal memory so that they apply on the next reboot of the module. Remember to 'persist' these gains if desired!

In the Scope GUI, the currently active gains can be saved to a file by using the "Save Gains" button at the botton of the "Gains" tab, and the gains in a file can be written to a module by using the "Load Gains" button.

tab gains buttons

In the MATLAB API, these gains can be saved and loaded using function in the HebiUtils class:

% Saving gains from a group of modules
gains = group.getGains()
gainFile = HebiUtils.saveGains(gains, 'MyGains');

% Loading XML file gains and setting on group of modules
gains = HebiUtils.loadGains(gainFile);
group.send('gains', gains);

In the C++ API, these gains can be saved and loaded by using the following code, assuming you have created a Group (see C++ documentation for more info).

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

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

2.3.3. Default gains

We have provided downloadable recommended default gains for all control strategies of each of our X-Series modules. Note that these gains should still be tuned for best performance depending on the particular application.

The default gains provided below assume firmware v10 or higher! Using these settings on earlier firmware versions will cause poorly scaled Kd, Ki, and Feedforward gains.

If you have gains that you have tuned for a specific application, that was running on firmware before v10 you can update the values with the following re-scaling:
- All Feedforward gains that are non-zero will get changed to 1.0.
- All Ki gains get multiplied by 1000.
- All Kd gains get multiplied by .001.

For the MATLAB API we have provided a helper function for scaling gains to firmware v10 on all the modules of a given family name, as well as a function for setting the default gains onto a group of modules. For this script to work you will need to download all the individual gain XML files from the table below. You will also need at least the 1.0 version of the Matlab API.

For the C++ API there is a code snippet in the API documentation that shows how to iterate through a group of modules and scale the gains.

Module Type Strategy 2 Strategy 3 Strategy 4

X5-1

X5-4

X5-9

X8-3

X8-9

X8-16

2.3.4. Tuning Controller Gains

Performance of an actuator or an overall robot depends heavily on the settings of the controller parameters, or 'tuning the gains'. HEBI actuators ship with a set of default gains that allows the actuator to run well under light loads, but if a significant load or inertia is added to the actuator’s output the gains that are needed for good control will likely change.

For some single-DOF systems there are a variety of standard gain-tuning techniques can possibly be applied. Finding 'good gains' for something as complex as a full robot depends on the details of the desired application. For new multi-DOF systems it is difficult to predict ahead of time how the gains will need to be tuned. However, there are some rules of thumb:

  • Lower is better. If a system can perform well with lower gains, that is often preferable for safety, stability, and efficiency.

  • Rather than command only positions, calculate desired velocities and command those as well. Especially in Strategy 3 and 4, this can dramatically improve tracking of commands without relying as much on a high Kp on position.

  • The gains of the effort (i.e. torque) controller generally do not have to be tuned much from their default values. The gains on the position and velocity controllers are usually where most of the tuning occurs.

  • When commanding velocities, set the the Kd on position to 0, since the Kp on velocity will serve the same purpose.

  • In some cases, low-passing the ouputs of the PID controllers (particularly the torque and velocity controllers) can provide more stable control, at the expense of lag in the output.

  • In the case of a robot arm, the proximal actuators closer the base that bear more load will generally have higher position and velocity gains than the more distal actuators out at the wrist.

  • Use commanded effort, rather than high integral gain on the position controller, to handle disturbances that can be modeled for your robot. This is often things like gravity compensation for an arm.

  • Make use of the PWM plot in the Scope GUI, or the commanded PWM feedback in the APIs, to understand how the controllers are responding and combining during tuning. For example this plot can indicate if there is jitter from aggresive gains on a noisy proportional or derivative signal, or how quickly an intergral term is winding up.

3. Assembly Instructions

3.1. Tubing and Tube Adapters

HEBI tubes and tube adapters are an easy way to create systems such as robotic arms. The indexing holes allow for precise length and twist measurements for your robot’s kinematics (see X5 Link in Kinematics section). Our tubes come in standard lengths of 150mm, 300mm, and 500mm, but are easily cut down to custom lengths. Tubes were chosen for their lightweight and thru bore allowing for easy installation of wires (see Module Documentation).

A 2038 02 outputTube clean A-2038-02: Output Tube Adapter

A 2039 02 inputTube clean A-2039-02: Input Tube Adapter

A 2038 02 explode A-2038-02 Assembly Instructions

A 2039 02 explode A-2039-02 Assembly Instructions

3.2. Right Angle Brackets

HEBI right angle brackets allow you to use X-Series actuators in perpendicular axes, commonly needed for bases or end effectors of robotic arms. These brackets come in both a 'Left' and 'Right' configuration making it easy to setup your system specific to your application’s needs.

3.2.1. Lightweight Bracket

These brackets are 3D printed glass-filed nylon and are best for connecting end effectors to a final "wrist" degree of freedom on robotic arms.

A 2042 01L full A-2042-01L: Lightweight Right Angle Bracket (Left)

A 2042 01R full A-2042-01R: Lightweight Right Angle Bracket (Right)

A 2042 01L explode A-2042-01L Assembly Instructions

A 2042 01R explode A-2042-01R Assembly Instructions

3.2.2. Heavy Duty Bracket

These brackets are black anodized aluminum and are best for bases of robotic arms. You can mount the vertical actuator in an 'inside' or 'outside' configuration as shown below.

A 2040 01L out in A-2040-01L: Heavy Duty Right Angle Bracket (Left)

A 2040 01R out in A-2040-01R: Heavy Duty Right Angle Bracket (Right)

A 2040 01L explode A-2040-01L Assembly Instructions

A 2040 01R explode A-2040-01R Assembly Instructions

3.3. T-Slot Adapter

Need an easy solution for mounting your robotic system? Enjoy building structures out of t-slot aluminum extrusion? The t-slot adapter is for you! This plate helps you attach any X-Series actuator to standard 25mm or 1 inch t-slot aluminum extrusion.

A 2043 01 full A-2043-01: T-Slot Aluminum Adapter Plate

A 2043 01 explode A-2043-01 Assembly Instructions

4. Kinematics

This section is currently under development.

We provide libraries that help with common robotics concerns such as calculating forwards kinematics, Jacobians, and inverse kinematics. The kinematic structure of the robot can be specified using built-in "body types". A body can be either a static component (e.g. link) or a dynamic component (e.g. joint).

4.1. Body Types

Below is an overview of the parameterization of body types that are built-in to all of our APIs and correspond to components on our parts list.

Please refer to the language specific API documentation for more information on using these libraries in your application. While the types and parameters are consistent across APIs, the concrete implementation does depend on the language.

4.2. X5 and X8 Actuators

X5 front X5 and X8 Output X5 top X5 Side View

X5 back X5 and X8 Input X8 top X8 Side View

There are no dedicated X8-Link or X8-Bracket parts. The housings of X5 and X8 actuators have the same input and output interface, so the X8 actuators are compatible with X5 connection parts.

The X5 Link represents a straight extension that is typically used to connect two actuators. It consists of input and output adapters, as well as a hollow tube that cables can be wired through. It is defined by the extension length as well as the twist angle to the next output.

front viewangled forward view top view

Parameter Description

Extension

Defined as the center to center distance in meters between rotating axes of the actuator. It is along the X-axis of the link (red axis in the above image). Depending on the type of tube that is being used there are two common ways for calculating the correct distance:

  • Generic tubes: x + 0.025 where x is the distance between the outer-most tube parts.

  • Indexed tubes: (N+1) * 0.025 where N is the total number of holes on the tube.

For example, a 300mm tube section has 12 holes, so the total distance would be 13 * 0.025 = 0.325m.

Twist

Rotation about the X-axis (red axis in the above image). For example, the images above show a twist of PI radians.

The examples below illustrate the possible variations for the Twist parameter in combination with a 300mm tube (extension=0.325m).

twist zero Twist = 0

twist pi Twist = +/-PI

twist pi over two Twist = +PI/2

twist neg pi over two Twist = -PI/2

4.4. X5 Light Bracket

The X5 Light Bracket corresponds to a light-weight right-angle adapter that is typically used at the wrist of an arm. It has a single parameter that specifies the mounting direction.

light
Parameter Description

Mounting

The direction of the output 'Z' axis (blue) pointing towards the next module, assuming that the 'X' axis (red) is pointing forward and the position is zero.

The two possible configurations are:

  • left

  • right

Note that the left and right configurations are physically different parts, rather than the same part mounted in a different orientation.

The below images show both mounting configurations at an output position of zero.

light left Mounting = "left"

Part Number: A-2042-01L

light right Mounting = "right"

Part Number: A-2042-01R

4.5. X5 Heavy Bracket

The X5 Heavy Bracket type corresponds to a sturdy right-angle adapter that is typically used at the base of a robot arm configuration. It has a single parameter that specifies the mounting direction.

heavy
Parameter Description

Mounting

Due to the modular design of the bracket there are four different configurations. This parameter describes the output direction (same as the light bracket above) as well as the mounting side (closer or further away from the center).

The four possible configurations are

  • left-outside

  • left-inside

  • right-inside

  • right-outside

Note that the left vs right portion of the configurations are physically different parts, and the inside vs outside portion of the configuration describe the two different ways a given bracket can be assembled.

The below images show the four possible mounting configurations at an output position of zero.

heavy left outside Mounting = "left-outside" heavy left inside Mounting = "left-inside"

Part Number: A-2040-01L

heavy right outside Mounting = "right-outside" heavy right inside Mounting = "right-inside"

Part Number: A-2040-01R

5. Scope GUI

The Scope GUI is a cross-platform tool that provides monitoring and testing capabilities for individual devices. We recommend running it before writing programs in order to make sure that all devices are connected properly and that the system as a whole is working as expected.

5.1. Setup

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

Note that Scope only looks for network interfaces during application startup. Therefore, if new network connections are established while Scope is already running (e.g. connect to Wi-Fi), you may need to press the "refresh interfaces" button in order to be able to find devices on the network.

5.2. Device Panel

The device panel on the left side shows all devices visible on the network and indicates their status. The display can be toggled between showing human readable names

device panel

as well as the hardware types and serial numbers

device panel hw
  • 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 any individual device brings up a menu with various common commands such as changing the name used for identification, rebooting, and setting the LED color to determine a module’s location.

device set led

5.3. Information Tab

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

tab information

5.4. Monitoring Tab

The monitoring tab provides a way to plot device feedback as well as test simple inputs for position, velocity, and effort (e.g. torque).

tab monitoring

For example, to check the position feedback, 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 Slider in the "Commands" section. Note that 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 that uses a separate network connection and 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.

We also recommend to take a look at the Latency and Packet loss plots to get an idea of the performance of the underlying network. The image below shows a typical latency plot for a Windows 8 computer requesting feedback at 100Hz. The green circles represent the dt between received packets and provides information about the OS scheduler. The black circles 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) 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 commands as fast as possible without any pauses

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

5.5. Gains Tab

The gains tab provides a convenient way to change control strategies, set safety limits, and to set gains.

tab gains

Note that Gains can be set while a device is actively being commanded. For example, you can generate step inputs programmatically (e.g. in MATLAB or C++) and use Scope to change the gains and monitor the results online.

Gains are reset to the last saved (persisted) values on reboot. To ensure the current values are restored at next boot, right click on the device and select "persist".

5.6. Device Tab

The device tab provides hardware features such as zeroing sensors and updating firmware.

tab device

The Update Application button is used to update the module’s internal firmware. If this becomes necessary, the procedure to update a module is:

  1. Ensure the PC is connected to the internet

  2. Note the Firmware Revision on the Information tab.

  3. Select the module in the left panel; right click to bring up the dialog.

  4. Click the reboot option

  5. Wait for the module to reappear on the list in blue italics, and immediately right click and select stop boot

  6. Press the update application button, and ensure the progress bar completes.

  7. Right click on the module in the left panel, and select the boot option.

  8. Note the Firmware Revision on the “Information” tab, and make sure that the firmware has been updated as desired.

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)

6. MATLAB API

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.

6.1. Installation

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,

addpath

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
end
It is usually a good idea to increase the Java Heap Size in MATLAB to a value significantly larger than its default size (using a slider control panel in Preferences→General→Java Heap Memory). In particular this will allow large .hebilog files to be loaded.

6.2. 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
disp(HebiLookup);

% implicit display (no semicolon)
HebiLookup
NetworkLookup

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
HebiLookup.initOnce();
disp(HebiLookup);

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

6.3. Joint-Level Control

6.3.1. 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-00134'
    '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.

6.3.2. Commands

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.

CommandStruct

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
    pause(0.01);
end
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 );
    group.send(cmd);
    pause(0.001);
end

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;
group.send(cmd);

6.3.3. Feedback

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.

SimpleFeedback
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;
    group.send(cmd);
end

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;
    actuator.send(cmd);

end

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).

6.3.4. Gain Scheduling

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);

6.3.5. 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

6.3.6. 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;
end
  • 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);
end
  • 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
-Xms4g

# Maximum total heap
-Xmx4g

# Young generation heap space
-Xmn3g

6.4. Kinematics and Dynamics

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.

Please consult Wikipedia if any of these terms are unknown or unclear.

6.4.1. 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 base and the last body represents the end-effector.

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. Some body types may require a set of parameters. Parameters that are not applicable to the specified type are ignored and will not throw an error message.

6.4.2. X-Series Body Types

Type Required Parameters Optional Parameters
  • X5-1

  • X5-4

  • X5-9

  • X8-3

  • X8-9

  • PositionLimit [1x2 rad]

  • VelocityLimit [1x2 rad/s]

  • EffortLimit [1x2 Nm]

  • Mass [kg]

  • Extension [m]

  • Twist [rad]

  • Mass [kg]

  • Mounting

    • right

    • left

  • Mass [kg]

  • Mounting

    • right-outside

    • right-inside

    • left-outside

    • left-inside

  • Mass [kg]

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

X-Series Leg (2-DoF)
kin 2dof x series
% X-Series Leg (2-DoF)
kin = HebiKinematics();
kin.addBody('X5-4');
kin.addBody('X5-Link', 'ext', .216 + .0715, 'twist', 0, 'mass', 0.318);
kin.addBody('X5-1');
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 );

6.4.3. S-Series Body Types

Type Required Parameters Optional Parameters
  • S5-3

  • PositionLimit [1x2 rad]

  • VelocityLimit [1x2 rad/s]

  • EffortLimit [1x2 Nm]

  • Mass [kg]

  • S5-Gripper

  • Mass [kg]

  • S5-Link

  • Extension [m]

  • Twist [rad]

  • Mass [kg]

  • S5-ElbowLink

  • Extension1 [m]

  • Twist1 [rad]

  • Extension2 [m]

  • Twist2 [rad]

  • Mass [kg]

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

S-Series Snake Robot (16-DoF)
kin 16dof s series snake robot
% S-Series Snake Robot (16-DoF)
numModules = 16;
kin = HebiKinematics();
for i=1:numModules
    kin.addBody('S5-3');
end
S-Series Arm (4-DoF)
kin 4dof s series
% S-Series arm (4-DoF)
kin = HebiKinematics();
kin.addBody('S5-3');
kin.addBody('S5-3');
kin.addBody('S5-ElbowLink', 'ext1', 0.102, 'twist1', pi/2, 'ext2', 0.013, 'twist2', pi);
kin.addBody('S5-3');
kin.addBody('S5-Link', 'ext', 0.152, 'twist', -pi/2);
kin.addBody('S5-3');
kin.addBody('S5-Gripper');

6.4.4. 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
  • GenericJoint

  • Axis

    • prismatic

      • 'x'

      • 'y'

      • 'z'

    • revolute

      • 'rx'

      • 'ry'

      • 'rz'

  • Input [4x4]

    • default: eye(4)

  • Output [4x4]

    • default: eye(4)

  • CoM [x y z] in [m]

    • default: zeros(1,3)

  • PositionLimit [1x2 rad]

    • default: [-inf inf]

  • VelocityLimit [1x2 rad/s]

    • default: [-inf inf]

  • EffortLimit [1x2 Nm|N]

    • default: [-inf inf]

  • Mass [kg]

    • default: 0

  • GenericLink

  • Output [4x4]

  • CoM [x y z] in [m]

  • Mass [kg]

Generic Joint Example

Below code creates a generic joint that is equivalent to a built-in 'S5-3' joint.

% Static S5-3 Parameters
inputToJoint = [
    1, 0, 0, 0
    0, 1, 0, 0
    0, 0, 1, .0366
    0, 0, 0, 1];
jointToOutput = [
    0, 1, 0, 0
    -1, 0, 0, 0
    0, 0, 1, .0273
    0, 0, 0, 1];
mass = 0.206;
maxPos = pi/2;
maxVel = 3.456;

% Add 'X5-3'-like joint
kin = HebiKinematics();
kin.addBody('GenericJoint', ...
    'input', inputToJoint, ... % 4x4 input -> rotation
    'axis', 'ry', ... % axis of movement, e.g., rotation about y axis
    'output', jointToOutput,  ... % 4x4 rotation -> output
    'mass', mass,  ... % mass in [kg]
    'com', inputToJoint(1:3,4), ... % [x y z] input to center of mass
    'posLim',[-maxPos +maxPos], ... % position limit in [rad]
    'velLim',[-maxVel +maxVel]); % velocity limit in [rad/s]
% 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
    'out', gripperOutput, ... % 4x4 input -> output
    'mass', 0.1 ); % mass in [kg]

6.4.5. 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 of the individual bodies
    frames = kin.getForwardKinematics('output', fbk.position);
    disp(frames(:,:,end)); % display end effector output

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

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

end

6.4.6. Jacobians

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('output', fbk.position);

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

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

6.4.7. 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 EndEffector Target Comments

Xyz

xyz position

  • length 3 vector

  • dimensions with nans get ignored, e.g., [x y nan]

TipAxis

z-axis orientation

  • length 3 vector of target direction

  • cost based on angle of AxisAngle representation

SO3

3-dof orientation

  • 3x3 rotation matrix

  • cost based on off-diagonals

MaxIterations

Maximum allowed iterations of the numerical optimization before returning.

InitialPositions

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

% 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);

6.4.8. Dynamics

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 (distance from the end effector output)
mass = 1; % [kg]
com = [0.1 0 0]; % 0.1 [m] in x
kin.setPayload(mass, 'CoM', com);

% Remove payload
kin.setPayload(0);
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);
    group.send(cmd);
end
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, ...
    cmdAccelerations);

6.5. Trajectories

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.

6.5.1. 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);

6.5.2. 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();
kin.addBody('X5-1');
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...');
    end

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

    % Command position/velocity
    cmd.position = pos;
    cmd.velocity = vel;
    group.send(cmd);

end

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.

6.5.3. Visualizing Trajectories

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

% Setup
kin = HebiKinematics();
kin.addBody('X8-3');
trajGen = HebiTrajectoryGenerator(kin);
trajGen.setAlgorithm('UnconstrainedQp');

% 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

6.6. Logging and Visualization

Data logging, visualization, and analysis tools are a critical but often-neglected 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.

% Log data for 5 seconds
group.startLog();
pause(5);
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);

% CSV
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).

6.6.1. Loading logs after a crash

Technically all logging happens in the background and gets stored on disk in a proprietary streaming format. The stopLog() call converts the proprietary format into another format that MATLAB can work with. This also means that in case MATLAB ever crashes due to unforeseen circumstances, none of the logged data is lost. For such cases we provide the convertGroupLog utility to load raw log files after the fact.

% 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);

6.6.2. 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();
    end

end

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');
end

6.6.3. Replaying logs for testing and verification

We also added the newGroupFromLog utility which supports replaying log files via the standard group interface. This simplifies unit tests and online algorithm development while working with offline data.

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

We currently do not officially support Simulink. However, we have done experiments and found ways to get it working reasonably well. If you urgently need Simulink support, please contact us directly.

7. C++ API

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

Comments are welcome (direct to support@hebirobotics.com). The final 1.0 API is expected to have minor (if any) API changes. 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. After downloading the examples, extract the API as a subdirectory inside the examples folder and run CMake to get started.

7.1. 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. Coming soon for 1.0; currently supported only for v0.16 If you are using Visual Studio, use the NuGet package manager to add the HEBI C++ API as a dependency.

  2. Use the examples as a template for setting up a CMake 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.

Note: support for Debian and RPM package repositories on Linux is coming soon.

7.2. 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;
std::this_thread::sleep_for(std::chrono::seconds(1));

// Take snapshot and print to the screen
auto entry_list = lookup.getEntryList();
std::cout << std::endl << "Modules found on network (Family|Name):" << std::endl;
for (int i = 0; i < entry_list->size(); ++i)
{
  auto entry = entry_list->getEntry(i);
  std::cout << entry.family_ << " | " << entry.name_ << std::endl;
}

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

Broadcasting on:
  10.10.10.255

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)

  • 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.

7.3. Joint-Level Control

7.3.1. 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.

7.3.2. Commands

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());
efforts->setZero();

// Command all modules in a group with this zero force or effort command
hebi::GroupCommand groupCommand(group->size());
groupCommand.setEffort(efforts);
group->sendCommand(groupCommand);
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, there is no command expiration (identical to setting the lifetime to 0). 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
group->setCommandLifetimeMs(100);
// Command must be sent in loop at a faster rate than the lifetime in order to remain in effect.
while (!stop_loop)
{
  group->sendCommand(groupCommand);
  std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
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"});
group->setCommandLifetimeMs(20);

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;
  groupCommand.setPosition(w_t.array().cos());
  groupCommand.setVelocity(w_t.array().sin());
  group->sendCommand(groupCommand);
  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());
groupCommand[0].io().e().setInt(1);
group->sendCommand(groupCommand);

7.3.3. Feedback

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 rate is zero, so no network traffic is sent until a non-zero value is specifically set.

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
group->setFeedbackFrequencyHz(200);

while (!stop_loop)
{
  if (!group->getNextFeedback(&feedback))
    continue;
  // ... 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
  group->setFeedbackFrequencyHz(200);

  // Method 1: Add pointer to function
  group->addFeedbackHandler(feedback_func_callback);

  // Method 2: Add a lambda function; see $$C++$$ reference for more info
  group->addFeedbackHandler(
      [](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 essentially just calls the sendFeedbackRequest function at a certain rate.
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());
group->getNextFeedback(&group_feedback);

// 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());
group_feedback.getGyro(gyros);
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)
group->setFeedbackFrequencyHz(100); // set to 100 Hz; this rate limits the loop below
group->setCommandLifetimeMs(100); // 100 ms command lifetime for safety
hebi::GroupCommand command(group->size());
hebi::GroupFeedback feedback(group->size());

Eigen::VectorXd position = Eigen::VectorXd::Zero(group->size()); // vector of zeros
const double stiffness = 1.0; // [Nm/rad]

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

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)
group->setFeedbackFrequencyHz(100); // set to 100 Hz; this rate limits the loop below
group->setCommandLifetimeMs(100); // 100 ms command lifetime for safety
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());
    group->sendCommand(command);
  }
}

7.3.4. Best Practices

Performance and Efficiency

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, 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());
posVector.setZero();

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

while(!stop_loop) {
    // wait for the feedback
    if (!group->getNextFeedback(&group_feedback))
      continue;
    // edit "posVector"
    ...
    // set command data
    groupCommand.setPosition(posVector);
    group->sendCommand(groupCommand);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
  • 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
  group_feedback.getPosition(positions);
}

7.4. Gains

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))
  group_info.writeGains("saved_gains.xml");

// Loading gains from a file onto a group of modules:
GroupCommand group_cmd(group->size());
group_cmd.readGains("saved_gains.xml");
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());
group->requestInfo(&groupInfo);

for (int i = 0; i < group->size(); i++) {
    auto& info_kp =
        groupInfo[i].settings().actuator().positionGains().positionKp();
    auto& cmd_kp =
        groupCommand[i].settings().actuator().positionGains().positionKp();
    if (info_kp) {
        cmd_kp.set(info_kp.get() * 2.0);
    }
}

7.5. Kinematics

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

7.5.1. Defining the Robot Configuration

The hebi::kinematics::Kinematics class is used to store how different kinematic bodies (HEBI actuators and connecting links) are attached together. It can also incorporate generic links defined as static homogeneous transforms.

The first body added to the hebi::kinematics::Kinematics 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 create hebi::kinematics::KinematicBody objects; after creation, these can be added to the Kinematics object via the addBody method:

// Create a simple kinematic description of a 3-DOF robot arm
kinematics::Kinematics kin;
kin.addBody(kinematics::KinematicBody::createX5());
kin.addBody(kinematics::KinematicBody::createX5LightBracket(HebiMountingTypeRight));
kin.addBody(kinematics::KinematicBody::createX5());
kin.addBody(kinematics::KinematicBody::createX5Link(0.18, M_PI));
kin.addBody(kinematics::KinematicBody::createX5());
kin.addBody(kinematics::KinematicBody::createX5Link(0.28, 0));

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

7.5.2. Forward Kinematics

The Kinematics::getForwardKinematics (or aliased Kinematics::getFK) method computes the poses of each body in the Kinematics 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 Kinematics 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 Matrix4fVector 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 Kinematics object). This type is simply a std::vector containing Eigen::Matrix4f elements; due to memory alignment considerations Eigen types in std containers must use particular allocators, and so we provide this convenience typedef. (See https://eigen.tuxfamily.org/dox-devel/group__TopicStlContainers.html for more information on this requirement)

// First, build up a kinematics::Kinematics object, as in previous examples.
...
// These are the current joint angles/positions, perhaps from a GroupFeedback object:
Eigen::VectorXd angles = group_feedback.getPosition();
Matrix4fVector transforms;
kin.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 Kinematics::getEndEffector method is a shortcut for just obtaining the transform of the last kinematic body in the Kinematics object:

// Set up hebi::Kinematics object "kin" and Eigen::VectorXd "angles" as above...
...
Eigen::Matrix4f transform;
kin.getEndEffector(HebiFrameTypeOutput, 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.

7.5.3. Jacobians

We can also use the Kinematics 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 Kinematics 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::MatrixXf and MatrixXfVector.

// Set up hebi::Kinematics object "kin" and Eigen::VectorXd "angles" as above...
...

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

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

Full API documentation of these functions is available here.

7.5.4. Inverse Kinematics

The Kinematics 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::Kinematics object "kin" and Eigen::VectorXd "angles" as above...
...

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

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

////////////////////////////////////////////////////////////////////////////
// Demonstration of using a single objective:
////////////////////////////////////////////////////////////////////////////

kin.solveIK(
  initial_joint_angles,
  ik_result_joint_angles,
  kinematics::EndEffectorPositionObjective(target_xyz)
);

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

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

kin.solveIK(
  initial_joint_angles,
  ik_result_joint_angles,
  kinematics::EndEffectorPositionObjective(target_xyz),
  kinematics::JointLimitConstraint(min_positions, max_positions)
);

Full API documentation of these functions is available here.

7.6. Trajectories

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.

7.6.1. 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);

7.6.2. 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);
  cmd.setPosition(pos_cmd);
  cmd.setVelocity(vel_cmd);
  group->sendCommand(cmd);
  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).

8. Changelog

8.1. Scope GUI Changelog

RC 0.6

Revision Changes

1908

  • General

    • Renamed torque to effort to better accommodate non-rotary joints

    • Added ability to automatically update multiple modules

    • Added ability to select individual ip addresses for lookup

    • Replaced auto-sort with manual sort to avoid resetting the selection when new devices join the network

    • Reduced size to better fit on Laptop displays

    • Various cosmetic changes

  • Monitoring

    • Added "hw-type | serial" to plot title

    • Added manual scaling options to better support demos and HighDPI displays

    • Reduced rate that arrow keys move sliders to 20% of the original rate

    • Improved performance of latency chart

    • Changed overload behavior to skip points instead of introducing lag

  • Gains

    • Added ability to save and load gains to and from XML files

  • Device

    • Added fields that allow setting non-zero position/torque reference points

RC 0.5

Revision Changes

1799

  • General

    • Added display toggle between human readable names and hardware type and serial number

    • Added a button to refresh network interfaces to reduce the need for restarts

  • Gains

    • Changed gain validation to allow +/- inf values for min/max target/output and I-Clamp

RC 0.4

Revision Changes Bugfixes

1628

Added selector for M-Stop strategy

1515

  • Fixed an SSL certificate issue related to firmware updates

1439

  • Changed name of Main GUI to "Scope"

  • Added auto-update

  • Added standalone binaries for mac, linux, windows

  • Removed all "unsafe" buttons from the device tab

  • Added scrollbars to all tabs to avoid issues with low resolution screens

RC 0.3

Revision Changes Bugfixes

1327

  • Removed deprecated debug buttons

8.2. MATLAB API Changelog

Revision upgrades with the same release version are backwards compatible. Changes that break backwards compatibility are highlighted in bold.

Release 1.1

Revision Changes

2009

  • HebiKinematics

    • General performance improvements across the board

    • Increased default max iterations for IK from 75 to 150

  • HebiTrajectoryGenerator

    • Significantly improved performance of 'UnconstrainedQp' algorithm. It is now feasible to use for large number of waypoints. 'MinJerkMove' can now be considered deprecated.

  • Bugfixes

    • Setting control strategy was changed to not accept non-integer values

    • Fixed a network lookup failure that occurred when certain VPN programs were active

    • Fixed getJacobianEndEffector to be calculated at the output rather than at the center of mass of the end effector

    • Fixed a bug that caused 'ReferenceEffort' to be ignored when it was the only command that got sent.

Release 1.0

Revision Changes

1908

  • General

    • Renamed torque to effort to better accommodate non-rotary joints. Existing code can be upgraded by running search and replace as follows,

      • torque → effort (case-sensitive)

      • Torque → Effort (case-sensitive)

      • TorLim → EffLim (not case-sensitive)

  • HebiUtils

    • Added ability for saving and loading gains to and from xml files

RC 1.0

Revision Changes

1859

  • HebiLookup

    • Added getters for lookup addresses and frequency

  • HebiGroup

    • Added better error message when log conversion fails due to memory constraints

  • HebiKinematics

    • Improved AddBody help documentation

    • Improved TipAxis objective for IK

    • Improved IK to keep results to within one rotation of the initial condition

1820

  • HebiGroup

    • Renamed set() to send()

    • Added 'Inf/infinity' as valid input to min/max output/target and I-clamp gains

    • Added 'motorPosition' to full feedback

    • Fixed a bug that occurred on some machines when reading log files

    • Added support for reading partially written log files

    • Added setting encoder and position reference points

  • HebiTrajectoryGenerator

    • Changed the default algorithm to 'UnconstrainedQp'

  • HebiKinematics

    • Made naming more consistent

    • Added 'X5-LightBracket' and 'X5-HeavyBracket' w/ various mount options to available module types

    • Reverted direction of the gravity vector for 'getGravCompTorques' (breaks backwards compatibility!!!)

    • Made SO3 objective more stable

  • HebiPoseFilter

    • Overhauled API to make it more usable

  • General

    • Made 'hebi_load()' callable to simplify pre-loading libraries

RC 0.7

Revision Changes

1741

  • HebiLookup

    • Added methods to create a group from serial numbers

    • Switched display of mac addresses to serial numbers

  • HebiGroup

    • Added group.stopLog('LogFormat', 'raw') to stop logging without doing any expensive conversion. This is useful for cases where logging needs to be stopped without halting execution

    • Fixed a bug introduced in rev1732 that prevented gtroups of >30 modules from logging

1732

  • HebiGroup

    • Internal changes to the way logging is done (fully backwards compatible)

    • Added info messages, e.g., gains, names, etc., to log files. Support for reading logged info messages will be added at a future date.

  • HebiKinematics

    • Added an objective to IK that tries to keep the joint angles within +/- pi of the initial condition. This fixes an issue where IK resulted in jumps over multiple rotations on slight variations of the initial conditions.

1723

  • HebiGroup

    • Internal improvements related to converting logs

    • Changed 'Hdr' log format to record each module separately

  • HebiKinematics

    • Added new joint types including X8-3 and X8-9

  • HebiPoseFilter (not a supported API)

    • Fixed a bug that caused the filter to initialize with NaN under some rare conditions

1709

  • HebiTrajectory

    • added getWaypointTime() to get access the times when waypoints are hit.

    • removed the restriction that 'time' needs to start at zero for 'UnconstrainedQp' trajectories.

  • HebiKinematics

    • Fixed a bug that prohibited the calculation of forward kinematics for chains without any joints. The empty matrix "[]" is now a valid position argument.

  • HebiLookup

    • made initOnce() public for cases where the loading of additional Java libraries resets the Java classpath without reinitializing the backing MATLAB class.

1702

  • HebiTrajectoryGenerator

    • Removed requirement for defined end positions when a user supplies a fully defined time vector (UnconstrainedQp algorithm only)

1700

  • HebiKinematics

    • Added ability to set a payload for calculating compensatory torques

  • HebiTrajectoryGenerator

    • Added trajectory implementation that supports velocity and acceleration constraints ('unconstrainedQp')

    • Added speedFactor to allow users to slow down trajectories

    • Overhauled TrajectoryGenerator API

RC 0.6

Revision Changes

1628

  • Added Trajectories to the API

    • Note that RC 0.6 is fully backwards compatible with RC 0.5, but we felt that trajectories was a large enough addon to increase the version number.

  • Bugfixes

    • Fixed logging to stop automatically if the disk is full

RC 0.5

Revision Changes

1434

  • changed HdrHistogram conversion to combine all output files into a single file with multiple tags

  • minor performance improvements

  • Bugfixes

    • Fixed a networking issue that caused a "wall of errors" when cables were disconnected on macOS and Linux

1424

  • HebiLookup

    • the display was changed to make it more obvious when modules become stale

  • HebiGroup API

    • getInfo() now returns a table that behaves consistently for any number of modules. Added 'pcRxTime' and removed 'distal/proximal' ids.

    • changed 'Min/Max-PositionLimit' in set() to a single 'PositionLimit' parameter that is consistent withHebiKinematics.addBody()

    • getNextFeedback() now throws an exception on timeout instead of returning empty. Log-replay groups continue to return empty when the end of a log file is reached.

    • added an optional 'Timeout' parameter to getNextFeedback for setting custom timeouts

  • HebiKinematics General

    • improved display to match HebiGroup and HebiLookup

    • added convenience overloads for getJacobianEndEffector and getForwardKinematicsEndEffector. This should simplify auto-complete.

  • HebiKinematics Bodies

    • added required 'twist' parameter to 'X5Link'. The previous default value was 'pi'.

    • 'CoM' was changed to be a 3x1 vector instead of a transform. The vector describes the location of the CoM in the body’s input frame.

    • 'FieldableGripper' is now considered a link and instead of a DoF

    • added getBodyInfo() and getJointInfo()

    • added support for overriding 'mass' on any body

    • added support for overriding position, velocity and torque limits on joints. Position limits get used by IK.

    • changed the default position limits for X5 modules to be +/- Inf

    • deprecated 'X5Joint' and added 'X5-1', 'X5-4' and 'X5-9'

  • HebiKinematics IK

    • added 'nan' inputs to 'xyz' objective to ignore not applicable dimensions

    • added a warning message in case IK doesn’t converge within max number of iterations

RC 0.4

Revision Changes

1371

  • Added support for logical vectors in reset/boot commands

  • Changed logical inputs to only allow 0 or 1 values to avoid ambiguity

1356

  • Changed default naming of logs to use absolute wall-clock time

  • Minor performance improvements

1328

  • Added getBaseFrame() to kinematics

1324

  • Added basic support for I/O Board (commands, feedback, logging struct)

  • Minor performance improvements

1289

  • Added support for high-res position

  • Added support for setting position limits (virtual hard stop)

  • Minor performance improvements

1270

  • Changed getJacobian() output to 6xNxM matrices to be consistent with getForwardKinematics()

  • Changed getAccelCompTorques name to getDynamicCompTorques

RC 0.3

Revision Changes

1263

  • Internal performance improvements

  • Bugfixes

    • Log files now always get closed properly when disposing groups

1241

  • Added getAccelCompTorques to HebiKinematics

1234

  • Added command lifetime safety feature to mitigate risks on unexpected interruptions (requires firmware upgrade to work)

1229

  • Cleaned up auto-complete

  • Changed display of empty groups to be more appropriate

1223

  • Added GenericLink to HebiKinematics

  • Bugfixes

    • Modified group creation such that each module must be unique

1218

  • Improved display for HebiLookup and HebiGroup

1214

  • Extensive API rework

8.3. C API Changelog

After v1.0 release, major version number changes indicate backwards incompatible changes, while minor version number changes are backwards compatible, both in API and ABI. Before v1.0, changes that break backwards compatibility are highlighted in bold.

1.0-rc5

Features Changes/Fixes
  • Added serial number field to info messages.

  • Added sender ID field to feedback messages.

  • Updated "lookup" calls involving mac addresses to pass structures by reference instead of value for improved portability.

  • Fixed bug on certain platforms where GetNextFeedback returned success and and empty feedback if no feedback had been received.

  • Fixed XML gain "control strategy" tag parsing

  • Fixed macOS compilation issues.

  • Fixed basic/01_lookup.c example; previously, when parsing lookup entry list, names/families that were longer than previous values would not be displayed.

1.0-rc1 through 1.0-rc4

Features Changes/Fixes
  • requestFeedback has been removed and replaced with getNextFeedback to match the MATLAB convention; see corresponding C++ documentation for more information.

  • Log files: separate arguments are now included for directory and file, to increase log file location customization. Default file name is created from current time if not specified.

  • Added log file parsing support.

  • Improved/generalized IK interface; added new objectives and constraints.

  • Added support for reading gains from and writing gains to an XML file.

  • Added support for an "imitation group" which acts like a Group, but does not have a physical module backing it.

  • Added access to timestamps in the feedback messages (PC RX/TX and hardware RX/TX)

  • New Kinematics body creation helpers (light/heavy bracket)

  • Lookup name/family ordering switched when using hebiGroupCreateFromNames

  • "Torque" renamed "Effort" throughout API

  • Removed hebiLookupPrintTable function; programmatic access now provided via LookupEntryList functions.

  • Renamed platform specific "lib" directories

  • Refactored CMake files

  • All enums now have "Hebi" prepending (e.g., HebiFrameTypeOutput)

  • 'get' functions for feedback/commands/info now return sane defaults instead of undefined behavior if 'has' not set.

  • Significantly faster (e.g., kinematics code has sped up at least 10X).

  • Condensed include files into one file (hebi.h) to simplify integration

  • Renamed a number of functions for consistency.

0.16

Features Changes/Fixes
  • Added static keyword to variables and functions in hebi_kinematic_parameters.h

0.15

Features Changes/Fixes
  • Added first version of a kinematics API.

  • Updating logging implementation to include output path option and packet loss statistics.

  • Removed the "module" object based on customer feedback

  • Separated out the libraries into platform specific directories (e.g., lib/winx64) to allow a single download for all platforms.

  • Documentation updates

0.14

Features Changes/Fixes
  • Easier deployment

    • Simplified CMake file

    • Separated library and examples

    • CMake files now demonstrate hierarchical build

  • Added trajectory API to assist in generation of smooth trajectories for robot motion

  • Added API for programmatically retrieving the current contents of the module lookup object

  • Added I/O pin command and feedback support

  • Added "hebi.h" convenience header file to include the entire HEBI C API

  • Added support for macOS

  • Added separate x86 and x64 libraries for Windows

  • Rename several functions to improve naming consistency

    • Functions with Create always require calling a corresponding Release to free resources

    • Functions with Get return temporary variables that do not need to be freed

    • Functions taking an object of type <TypeName> always start with Hebi<TypeName>

  • Changed GetString calls to take in a buffer instead of return a pointer. Previously, return values could point to invalid memory

8.4. CPP API Changelog

The C API wraps the C library, and so the first two numbers of the C API versions are the version number for the corresponding C library.

Changes that break backwards compatibility are highlighted in bold.

RC 0.16.0

Features Changes/Fixes
  • Improved examples and documentation; created basic "tutorial" examples.

  • Updated to v0.16 C API.

  • Added explicit "Eigen" namespace before Vector classes to disambiguate name references during compilation.

RC 0.15.0

Features Changes/Fixes
  • Easier deployment

    • Simplified CMake file

    • Separated library and examples

    • CMake files now demonstrate hierarchical build

  • Added initial version of a kinematics API.

  • Added trajectory API to assist in generation of smooth trajectories for robot motion.

  • Added I/O pin command and feedback support

  • Added API for programmatically retrieving the current contents of the module lookup object

  • Added support for macOS and ARM

  • Added ability to set command lifetime through the C++ API.

  • For commands and feedback, added convenience get and set functions for position, velocity, and torque fields; these function use Eigen double vectors (Eigen::VectorXd).

  • Updated to v0.15 C API.

  • Added path for log directory when logging.

  • Removed the "module" object based on customer feedback.

  • Separated out the libraries into platform specific directories (e.g., lib/winx64) to allow a single download for all platforms.

  • Fixed bug in GetString calls that would sometimes return uninitialized memory.

  • Added a number of "const" versions of getter function to allow better use within const-correct parent structures and classes.

  • Documentation updates.

  • Added dependency on Eigen v3.3.3. Included for convenience with downloaded packages.

  • Added index operator to mac address class.

8.5. Firmware Changelog

X5_8_E

Release Changes Bugfixes

10.888.885

  • Ki and Kd gains now have SI units. In particular time-scaling was changed from millieseconds to seconds.

    • Ki gains will be need to be multiplied by 1000 to have same effect as before.

    • Kd gains should be multiplied by .001 to have the same effect as before.

  • Feedforward gain change: 1.0 is now the appropriate default gain value for velocity and effort loops that output PWM.

  • Deadzone is changed to not accumulate integral error. Error outside of deadzone is now shifted to start at zero instead of being a discontinous jump.

  • Additional feedback sensor: motorPosition

  • Direct PWM control strategy now has settable min/max target PWM values.

  • Improved parameters of onboard gyro zero-update

  • Improved internal safety controllers to better protect the modules when running voltages above 24V

  • Improved motor winding thermal estimation

  • Improved stability of software position safety limits

  • Improved performance under high network communications loads

9.834.830

  • Initial release; equivalent to release 9.811.807 for X5-8 C and D

X5_8_D

Release Changes Bugfixes

10.888.885

  • Ki and Kd gains now have SI units. In particular time-scaling was changed from millieseconds to seconds.

    • Ki gains will be need to be multiplied by 1000 to have same effect as before.

    • Kd gains should be multiplied by .001 to have the same effect as before.

  • Feedforward gain change: 1.0 is now the appropriate default gain value for velocity and effort loops that output PWM.

  • Deadzone is changed to not accumulate integral error. Error outside of deadzone is now shifted to start at zero instead of being a discontinous jump.

  • Additional feedback sensor: motorPosition

  • Direct PWM control strategy now has settable min/max target PWM values.

  • Improved parameters of onboard gyro zero-update

  • Improved internal safety controllers to better protect the modules when running voltages above 24V

  • Improved motor winding thermal estimation

  • Improved stability of software position safety limits

  • Improved performance under high network communications loads

9.811.807

  • Processed network packets more efficiently to improve responsiveness under high load.

8.795.793

  • Improved onboard thermal model by accounting for more secondary electronic effects.

  • Improved control stability of X8 while near velocity limits.

7.785.769

  • Internal calibration procedure updates

6.775.769

  • Internal calibration procedure updates

5.772.769

  • Firmware now supports X8 and X5 modules

  • Added 3-mode M-stop functionality (disabled, 'hold', and 'off' modes)

  • Improved onboard thermal limiting for the motor based on additional experimental data

  • Fixed an issue with the onboard timer that could theoretically lead to occasional very large, inaccurate reported time steps

  • Updated gyro low pass filter

4.0 rev629

  • Fixed an issue in the thermal model that could cause a motor to overheat after a reset under continuous load

3.1 rev617

  • Fixed an issue in the safety controller that could cause a motor to spin faster in one direction

3.0 rev606

  • Initial release (includes voltage scaling and IMU calibration)

X5_8_C

Release Changes Bugfixes

10.888.885

  • Ki and Kd gains now have SI units. In particular time-scaling was changed from millieseconds to seconds.

    • Ki gains will be need to be multiplied by 1000 to have same effect as before.

    • Kd gains should be multiplied by .001 to have the same effect as before.

  • Feedforward gain change: 1.0 is now the appropriate default gain value for velocity and effort loops that output PWM.

  • Deadzone is changed to not accumulate integral error. Error outside of deadzone is now shifted to start at zero instead of being a discontinous jump.

  • Additional feedback sensor: motorPosition

  • Direct PWM control strategy now has settable min/max target PWM values.

  • Improved parameters of onboard gyro zero-update

  • Improved internal safety controllers to better protect the modules when running voltages above 24V

  • Improved motor winding thermal estimation

  • Improved stability of software position safety limits

  • Improved performance under high network communications loads

9.811.807

  • Processed network packets more efficiently to improve responsiveness under high load.

8.795.793

  • Improved onboard thermal model by accounting for more secondary electronic effects.

  • Improved control stability of X8 while near velocity limits.

7.785.769

  • Internal calibration procedure updates

6.775.769

  • Internal calibration procedure updates

5.772.769

  • Firmware now supports X8 and X5 modules

  • Added 3-mode M-stop functionality (disabled, 'hold', and 'off' modes)

  • Improved onboard thermal limiting for the motor based on additional experimental data

  • Fixed an issue with the onboard timer that could theoretically lead to occasional very large, inaccurate reported time steps

  • Updated gyro low pass filter

3.0 rev629

  • Added auto-voltage scaling so that gains behave the same way under any voltage

  • Improved accelerometer bias (requires calibration)

  • Added a filter to continuously estimate and compensate for the gyro bias

  • Fixed an issue in the thermal model that could cause a motor to overheat after a reset under continuous load

  • Fixed an issue in the safety controller that could cause a motor to spin faster in one direction

2.0 rev519

  • Improved factory calibration for position and torque

1.0 rev480

  • Initial release

S5_B

Release Changes Bugfixes

1.740.737

  • Initial release; duplicate of SEA_D release 2 except for electronics updates

S_CAM_A

Release Changes Bugfixes

2.745.737

  • Update thermal model

  • Limit LED brightness to ensure safe steady-state performance

1.743.737

  • Initial release

SEA_D

Release Changes Bugfixes

2.711.704

  • Auto gyro-unbiasing in IMU

  • Addition of settable accelerometer bias

  • Gains auto-scaled when supply voltage changes

  • Automatically persist name/family when these values are changed

  • New calibration routine that includes automatic determination and sanity checking of spring constant to help detect early spring failure

  • Added support for "command lifetime" feature

  • Updated default gains

  • Added user-settable torque offset (via GUI "zero torque" button)

  • Fixed an issue in the thermal model that could cause a motor to overheat after a reset under continuous load

  • Improved motor current estimate and IMU filtering settings

IO_BASIC_B

Release Changes Bugfixes

1.0 rev552

  • Initial release

Bootloader

Release Changes Bugfixes

8.832.830

  • Added support for new modules (internal)

7.810.807

  • Processed network packets more efficiently to improve responsiveness under high load.

6.0.735.733

  • Disabled clearing of internal storage via the reset button

  • Updated version number display

  • Added support for new modules (internal)

5.0 rev599

  • Added support for new modules (internal)

  • Fixed an issue that caused IMU calibration to be cleared after a database erase

4.0 rev548

  • Added support for new modules (internal)

3.0 rev514

  • Added "auto-update"

2.0 rev478

  • Added support for new modules (internal)

1.0 rev345

  • Initial release