Zaber Launcher Tutorials
Zaber Motion Library
Sample Projects
Virtual DeviceDropdown icon
About3D Viewer
AccountDropdown icon
Sign InSign Up
Zaber Motion LibraryGetting Started
How-to Guides
Communication
Controlling multiple devicesFinding the right serial port nameImproving performance of X-USBDC (FTDI)Interaction with Zaber LauncherTCP/IP (network) communication
Library Features
Arbitrary unit conversionsError handlingEventsG-CodeNon-blocking (simultaneous) axis movementSaving and loading stateSending arbitrary commands
Device Features
Device I/OLockstepOscilloscopePosition Velocity Time (PVT)PVT Sequence GenerationSettingsStreamed movementTriggersWarning flags
Product-Specific APIs
MicroscopeProcess controllerVirtual devices
Advanced
Building a Standalone Application with MATLAB CompilerBuilding the C++ Library from Source CodeCustom transportsDevice databaseLoggingMATLAB Migration GuidePackaging Your Program with PyinstallerThread safety
API ReferenceSupportBinary Protocol (Legacy)
© 2026 Zaber Technologies Inc.

Position Velocity Time (PVT)

PVT sequences provide a way to travel a trajectory defined by points with position, velocity and time. Since the points are queued by the device internally, the device can travel the trajectory smoothly without any effects from communication delay. Additionally, PVT sequences can perform other actions such as controlling device's IO. You can use PVT sequences to coordinate the movement of multiple axes as well as just to move a single axis. Please note, however, that you can only perform coordinated movement of multiple axes connected to same controller. You cannot use PVT to coordinate the movement of multiple controllers or integrated devices.

This guide mostly describes the PvtSequence class. Additionally, much of the presented API's behavior originates in the underlying Zaber ASCII Protocol. Refer to the pvt section in the ASCII Protocol Manual for an in depth explanation of the behavior.

Checking if your device supports PVT sequences

You may check if the device supports PVT movement by checking if the pvt.numseqs setting has a non-zero value. The feature is available since Firmware version 7.33.

MATLAB (legacy) specific considerations

This API uses arrays as arguments. Sometimes you may need to create an array of a complex type with size of 1. Such an array must be allocated using the javaArray function. The function takes two arguments: fully qualified name of the type and the size of the array. The example follows.

position = javaArray('zaber.motion.Measurement', 1);
position(1) = Measurement(1, Units.LENGTH_CENTIMETRES);
velocity = javaArray('zaber.motion.Measurement', 1);
velocity(1) = Measurement(0);
pvt.point(position, velocity, Measurement(5, Units.TIME_SECONDS));

In any other case the array can be created in the standard way.

Setup

To start using PVT, get a PVT sequence from the device. In this example we get the first sequence.

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
pvt_sequence = device.pvt.get_sequence(1)
const pvtSequence = device.pvt.getSequence(1);
var pvtSequence = device.Pvt.GetSequence(1);
PvtSequence pvtSequence = device.getPvt().getSequence(1);
pvtSequence = device.getPvt().getSequence(1);
PvtSequence pvtSequence = device.getPvt().getSequence(1);

The instance returned represents a handle for PVT sequence number 1 on the device.

The PVT sequence needs to be set up before it can be used. Once the PVT sequence is set up in either “Live” mode or “Store” mode, the methods of this instance allow you to append points onto the sequence.

Live Mode

When a sequence is in Live mode, points in the sequences's action queue will immediately execute. To set up a PVT sequence in Live mode, call the setupLive method with an arguments containing the axis numbers of the axes that the sequence will target. You can provide a variable number of arguments (axis numbers).

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
pvt_sequence.setup_live(1, 2)
await pvtSequence.setupLive(1, 2);
pvtSequence.SetupLive(1, 2);
pvtSequence.setupLive(1, 2);
pvtSequence.setupLive([1, 2]);
pvtSequence.setupLive(1, 2);

Store Mode

PVT sequences set up in Store mode do not execute the points in their action queue. Instead, they store them to a PVT buffer on the device. Once the points are stored, PVT buffers can be played back by live sequence via the

call

method, so that the stored points are added to the live sequences's action queue.

Stored sequences may also be set up to trigger if a device reaches a condition, such as when an I/O port equals a value. Read more about triggers here.

Buffers on a device are identified by numbers starting from 1. Since Store sequences cannot be set up to use non-empty buffers, it is recommended to erase the buffer before setting up.

See the following code for an example of setting up a PVT sequence in Store mode:

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
pvt_buffer = device.pvt.get_buffer(1)
pvt_buffer.erase()

# set up PVT to store points to PVT buffer 1 and
# to use the first two axes for unit conversion
pvt_sequence.setup_store(pvt_buffer, 1, 2)

# add PVT points...

# finish writing to the buffer
pvt_sequence.disable()
const pvtBuffer = device.pvt.getBuffer(1);
await pvtBuffer.erase();

// set up PVT to store points to PVT buffer 1 and
// to use the first two axes for unit conversion
await pvtSequence.setupStore(pvtBuffer, 1, 2);

// add PVT points...

// finish writing to the buffer
await pvtSequence.disable();
var pvtBuffer = device.Pvt.GetBuffer(1);
pvtBuffer.Erase();

pvtSequence.SetupStore(pvtBuffer, 1, 2);

// add PVT points...

// finish writing to the buffer
pvtSequence.Disable();
PvtBuffer pvtBuffer = device.getPvt().getBuffer(1);
pvtBuffer.erase();

pvtSequence.setupStore(pvtBuffer, 1, 2);

// add PVT points...

// finish writing to the buffer
pvtSequence.disable();
pvtBuffer = device.getPvt().getBuffer(1);
pvtBuffer.erase();

% set up PVT to store points to PVT buffer 1 and
% to use the first two axes for unit conversion
pvtSequence.setupStore(pvtBuffer, [1, 2]);

% add PVT points...

% finish writing to the buffer
pvtSequence.disable();
PvtBuffer pvtBuffer = device.getPvt().getBuffer(1);
pvtBuffer.erase();

pvtSequence.setupStore(pvtBuffer, 1, 2);

// add PVT points...

// finish writing to the buffer
pvtSequence.disable();

Once you have appended some points to the PVT buffer, you can get its content, or call it from a live sequence. Before calling the buffer ensure that the live sequence is set up on the same set of axes as the original store sequence. If the axes are different, the unit conversions applied when storing the commands may result in incorrect motion. You can ignore this condition if the conversion factors of your axes are the same (e.g. the peripherals are the same product).

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
content = pvt_buffer.get_content()
print(content)
# outputs:
# ['setup store 1 2',
# 'point abs p 0 125984 v 0 89445 t 400.0',
# 'point abs p 18142 298163 v 14531 89445 t 520.0',
# ...

pvt_sequence.setup_live(1, 2)
# the sequence will now execute the stored points
pvt_sequence.call(pvt_buffer)
const content = await pvtBuffer.getContent();
console.log(content);
// outputs:
// ['setup store 1 2',
// 'point abs p 0 125984 v 0 89445 t 400.0',
// 'point abs p 18142 298163 v 14531 89445 t 520.0',
// ...

await pvtSequence.setupLive(1, 2);
// the sequence will now execute the stored points
await pvtSequence.call(pvtBuffer);
var content = pvtBuffer.GetContent();
Console.WriteLine("[{0}]", string.Join(",\n", content));
// outputs:
// [setup store 1 2,
// point abs p 0 125984 v 0 206413 t 400.0,
// point abs p 18142 298163 v 29723 488510 t 520.0,
// ...

pvtSequence.SetupLive(1, 2);
// the sequence will now execute the stored points
pvtSequence.Call(pvtBuffer);
String[] content = pvtBuffer.getContent();
System.out.println(Arrays.toString(content));
// outputs:
// ['setup store 1 2',
// 'point abs p 0 125984 v 0 206413 t 400.0',
// 'point abs p 18142 298163 v 29723 488510 t 520.0',
// ...

pvtSequence.setupLive(1, 2);
// the sequence will now execute the stored points
pvtSequence.call(pvtBuffer);
content = pvtBuffer.getContent();
for i=1:content.size
    fprintf("%s,\n", content(i));
end
% outputs:
% 'setup store 1 2',
% 'point abs p 0 125984 v 0 89445 t 400.0',
% 'point abs p 18142 298163 v 14531 89445 t 520.0',
% ...

pvtSequence.setupLive([1, 2]);
% the sequence will now execute the stored points
pvtSequence.call(pvtBuffer);
std::vector<std::string> content = pvtBuffer.getContent();
for (auto line : content) {
    std::cout << line << std::endl;
}
// outputs:
// setup store 1 2
// point abs p 0 125984 v 0 206413 t 400.0
// point abs p 18142 298163 v 29723 488510 t 520.0
// ...

pvtSequence.setupLive(1, 2);
// the sequence will now execute the stored points
pvtSequence.call(pvtBuffer);

Note that the points printed are in native units and their format is described in the pvt buffer print command documentation in the ASCII Protocol Manual.

Movement

A PVT point can be relative or absolute. If a point is relative, the device treats the current position of the axes as the origin. If a point is absolute, the device treats the position zero of the axes as origin. The time of a point is always specified as relative to the previous one.

Parameters for sequence points make use of Measurement objects for all: position, velocity, and time. Measurement objects are simple objects that contain a value field as well as an optional unit field.

The following example shows how to do a PVT movement.

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
# (x, y, v_x, v_y, time)
path = [
    (0.00, 3.00, 0.0, 1.0, 4.0),
    (6.10, 5.10, 4.0, 0.3, 3.0),
    (9.40, 4.15, 1.0, 0.7, 2.5),
    (4.60, 6.00, 2.0, 0.4, 3.5),
    (7.20, 3.20, 0.0, 0.0, 3.6),
]

for point in path:
    pvt_sequence.point([
        Measurement(point[0], Units.LENGTH_MILLIMETRES), # x
        Measurement(point[1], Units.LENGTH_MILLIMETRES), # y
    ], [
        Measurement(point[2], Units.VELOCITY_MILLIMETRES_PER_SECOND), # v_x
        Measurement(point[3], Units.VELOCITY_MILLIMETRES_PER_SECOND), # v_y
    ], Measurement(point[4], Units.TIME_SECONDS)) # time
const path = [
  { x: 0.00, y: 3.00, vx: 0.0, vy: 1.0, t: 4.0},
  { x: 6.10, y: 5.10, vx: 4.0, vy: 0.3, t: 3.0},
  { x: 9.40, y: 4.15, vx: 1.0, vy: 0.7, t: 2.5},
  { x: 4.60, y: 6.00, vx: 2.0, vy: 0.4, t: 3.5},
  { x: 7.20, y: 3.20, vx: 0.0, vy: 0.0, t: 3.6},
];

for (const point of path) {
  await pvtSequence.point([
    { value: point.x, unit: Length.mm },
    { value: point.y, unit: Length.mm },
  ], [
    { value: point.vx, unit: Velocity.MILLIMETRES_PER_SECOND },
    { value: point.vy, unit: Velocity.MILLIMETRES_PER_SECOND },
  ], { value: point.t, unit: Time.s });
}
var path = new[] {
    new { x= 0.00, y= 3.00, vx= 0.0, vy= 1.0, t= 4.0},
    new { x= 6.10, y= 5.10, vx= 4.0, vy= 0.3, t= 3.0},
    new { x= 9.40, y= 4.15, vx= 1.0, vy= 0.7, t= 2.5},
    new { x= 4.60, y= 6.00, vx= 2.0, vy= 0.4, t= 3.5},
    new { x= 7.20, y= 3.20, vx= 0.0, vy= 0.0, t= 3.6},
};

foreach (var point in path)
{
    pvtSequence.Point(new[] {
        new Measurement { Value = point.x, Unit = Units.Length_Millimetres },
        new Measurement { Value = point.y, Unit = Units.Length_Millimetres },
    }, new[] {
        new Measurement { Value = point.vx, Unit = Units.Velocity_MillimetresPerSecond },
        new Measurement { Value = point.vy, Unit = Units.Velocity_MillimetresPerSecond },
    }, new Measurement { Value = point.t, Unit = Units.Time_Seconds });
}
// {x, y, v_x, v_y, time}
double[][] path = {
    {0.00, 3.00, 0.0, 1.0, 4.0},
    {6.10, 5.10, 4.0, 0.3, 3.0},
    {9.40, 4.15, 1.0, 0.7, 2.5},
    {4.60, 6.00, 2.0, 0.4, 3.5},
    {7.20, 3.20, 0.0, 0.0, 3.6},
};

for (double[] point : path) {
    pvtSequence.point(new Measurement[] {
            new Measurement(point[0], Units.LENGTH_MILLIMETRES),
            new Measurement(point[1], Units.LENGTH_MILLIMETRES)
        }, new Measurement[] {
            new Measurement(point[2], Units.VELOCITY_MILLIMETRES_PER_SECOND),
            new Measurement(point[3], Units.VELOCITY_MILLIMETRES_PER_SECOND)
        }, new Measurement(point[4], Units.TIME_SECONDS));
}
% x y v_x v_y time
path = [
    0.00, 3.00, 0.0, 1.0, 4.0;
    6.10, 5.10, 4.0, 0.3, 3.0;
    9.40, 4.15, 1.0, 0.7, 2.5;
    4.60, 6.00, 2.0, 0.4, 3.5;
    7.20, 3.20, 0.0, 0.0, 3.6;
];

for row=1:size(path,1)
    pvtSequence.point([
        Measurement(path(row, 1), Units.LENGTH_MILLIMETRES)
        Measurement(path(row, 2), Units.LENGTH_MILLIMETRES)
    ], [
        Measurement(path(row, 3), Units.VELOCITY_MILLIMETRES_PER_SECOND)
        Measurement(path(row, 4), Units.VELOCITY_MILLIMETRES_PER_SECOND)
    ], Measurement(path(row, 5), Units.TIME_SECONDS));
end
// {x, y, v_x, v_y, time}
double path[5][5] = {
    {0.00, 3.00, 0.0, 1.0, 4.0},
    {6.10, 5.10, 4.0, 0.3, 3.0},
    {9.40, 4.15, 1.0, 0.7, 2.5},
    {4.60, 6.00, 2.0, 0.4, 3.5},
    {7.20, 3.20, 0.0, 0.0, 3.6},
};

for (double* point : path) {
    pvtSequence.point({
            Measurement(point[0], Units::LENGTH_MILLIMETRES),
            Measurement(point[1], Units::LENGTH_MILLIMETRES)
        }, {
            Measurement(point[2], Units::VELOCITY_MILLIMETRES_PER_SECOND),
            Measurement(point[3], Units::VELOCITY_MILLIMETRES_PER_SECOND)
        }, Measurement(point[4], Units::TIME_SECONDS));
}

It's crucial that your sequence ends with a point that has 0 velocities of all axes. Otherwise, the device runs out of points stopping unexpectedly, and the library raises PvtExecutionException exception.

Velocity calculation

Instead of providing velocities of the points, the library can calculate them from the positions of the surrounding points. This is useful when you just want to smoothly traverse a number of points with an average speed but without concern for specific velocities. The library uses Finite Difference calculation that only relies on the position of the immediate neighbor points. See the example below on how to utilize the feature.

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
# (x, y, v_x, v_y, time)
path = [
    (0.00, 3.00, 0.0, None, 4.0),
    (6.10, 5.10, None, None, 3.0),
    (9.40, 4.15, None, None, 2.5),
    (4.60, 6.00, None, None, 3.5),
    (7.20, 3.20, 0.0, 0.0, 3.6),
]

for point in path:
    pvt_sequence.point([
        Measurement(point[0], "mm"), # x
        Measurement(point[1], "mm"), # y
    ], [
        Measurement(point[2], "mm/s") if point[2] is not None else None, # v_x
        Measurement(point[3], "mm/s") if point[3] is not None else None, # v_y
    ], Measurement(point[4], "s")) # time
const path = [
  { x: 0.00, y: 3.00, vx: 0.0, vy: null, t: 4.0},
  { x: 6.10, y: 5.10, vx: null, vy: null, t: 3.0},
  { x: 9.40, y: 4.15, vx: null, vy: null, t: 2.5},
  { x: 4.60, y: 6.00, vx: null, vy: null, t: 3.5},
  { x: 7.20, y: 3.20, vx: 0.0, vy: 0.0, t: 3.6},
];

for (const point of path) {
  await pvtSequence.point([
    { value: point.x, unit: Length.mm },
    { value: point.y, unit: Length.mm },
  ], [
    point.vx && { value: point.vx, unit: Velocity.MILLIMETRES_PER_SECOND },
    point.vy && { value: point.vy, unit: Velocity.MILLIMETRES_PER_SECOND },
  ], { value: point.t, unit: Time.s });
}
struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double? VX { get; set; }
    public double? VY { get; set; }
    public double T { get; set; }
}
var path = new Point[] {
    new Point { X= 0.00, Y= 3.00, VX= 0.0, T= 4.0},
    new Point { X= 6.10, Y= 5.10, T= 3.0},
    new Point { X= 9.40, Y= 4.15, T= 2.5},
    new Point { X= 4.60, Y= 6.00, T= 3.5},
    new Point { X= 7.20, Y= 3.20, VX= 0.0, VY= 0.0, T= 3.6},
};

foreach (var point in path)
{
    pvtSequence.Point(new[] {
        new Measurement { Value = point.X, Unit = Units.Length_Millimetres },
        new Measurement { Value = point.Y, Unit = Units.Length_Millimetres },
    }, new[] {
        point.VX != null ? new Measurement {
            Value = point.VX.Value,
            Unit = Units.Velocity_MillimetresPerSecond,
        } : null,
        point.VY != null ? new Measurement {
            Value = point.VY.Value,
            Unit = Units.Velocity_MillimetresPerSecond,
        } : null,
    }, new Measurement { Value = point.T, Unit = Units.Time_Seconds });
}
// {x, y, v_x, v_y, time}
double[][] path = {
    {0.00, 3.00, 0.0, Double.NaN, 4.0},
    {6.10, 5.10, Double.NaN, Double.NaN, 3.0},
    {9.40, 4.15, Double.NaN, Double.NaN, 2.5},
    {4.60, 6.00, Double.NaN, Double.NaN, 3.5},
    {7.20, 3.20, 0.0, 0.0, 3.6},
};

for (double[] point : path) {
    pvtSequence.point(new Measurement[] {
            new Measurement(point[0], Units.LENGTH_MILLIMETRES),
            new Measurement(point[1], Units.LENGTH_MILLIMETRES)
        }, new Measurement[] {
            !Double.isNaN(point[2]) ? new Measurement(point[2], Units.VELOCITY_MILLIMETRES_PER_SECOND) : null,
            !Double.isNaN(point[3]) ? new Measurement(point[3], Units.VELOCITY_MILLIMETRES_PER_SECOND) : null
        }, new Measurement(point[4], Units.TIME_SECONDS));
}
% x y time
path = [
    2.00, 3.00, 4.0;
    6.10, 5.10, 3.0;
    9.40, 4.15, 2.5;
    4.60, 6.00, 3.5;
];

for row=1:size(path,1)
    pvtSequence.point([
        Measurement(path(row, 1), Units.LENGTH_MILLIMETRES)
        Measurement(path(row, 2), Units.LENGTH_MILLIMETRES)
    ], [], Measurement(path(row, 3), Units.TIME_SECONDS));
end

% endpoint
pvtSequence.point([
    Measurement(0) Measurement(0)
], [
    Measurement(0) Measurement(0)
], Measurement(5, Units.TIME_SECONDS));

% when you need to specify the velocity per axis
partialVelocity = javaArray('zaber.motion.Measurement', 2); % number of axes
partialVelocity(1) = Measurement(1.2, Units.VELOCITY_MILLIMETRES_PER_SECOND)
// {x, y, v_x, v_y, time}
std::optional<double> path[5][5] = {
    {0.00, 3.00, 0.0, std::nullopt, 4.0},
    {6.10, 5.10, std::nullopt, std::nullopt, 3.0},
    {9.40, 4.15, std::nullopt, std::nullopt, 2.5},
    {4.60, 6.00, std::nullopt, std::nullopt, 3.5},
    {7.20, 3.20, 0.0, 0.0, 3.6},
};

for (std::optional<double>* point : path) {
    pvtSequence.point({
            Measurement(point[0].value(), Units::LENGTH_MILLIMETRES),
            Measurement(point[1].value(), Units::LENGTH_MILLIMETRES)
        }, {
            (point[2] ? std::make_optional<Measurement>(point[2].value(), Units::VELOCITY_MILLIMETRES_PER_SECOND) : std::nullopt),
            (point[3] ? std::make_optional<Measurement>(point[3].value(), Units::VELOCITY_MILLIMETRES_PER_SECOND) : std::nullopt),
        }, Measurement(point[4].value(), Units::TIME_SECONDS));
}

You can either provide an empty array to calculate all velocities or std::nullopt instead of

Measurement

to calculate velocity of a specific axis.

You can either provide an empty array to calculate all velocities or create partially empty array using javaArray to calculate velocity of a specific axis.

The velocity calculation comes with some limitations. Notably, it cannot calculate the velocity from a combination of absolute and relative points. We recommend sticking to only one or another in your programs. Additionally, the calculation requires that the immediate points before and after the calling of a PVT buffer have defined velocities. The first point of a stored sequence must have a defined velocity as well. Lastly, the endpoint of the sequence must have 0 velocities of all axes.

Is Busy

A PVT sequence is busy if it is currently executing points in its action queue. You can check if a live sequence is busy using the isBusy method.

The waitUntilIdle method waits until the sequence becomes idle.

Here is an example of their usage:

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
if pvt_sequence.is_busy():
    print("Sequence is busy.")

pvt_sequence.wait_until_idle()
if (await pvtSequence.isBusy()) {
  console.log('Sequence is busy.');
}
await pvtSequence.waitUntilIdle();
if (pvtSequence.IsBusy())
{
    Console.WriteLine("Sequence is busy.");
}
pvtSequence.WaitUntilIdle();
if pvtSequence.isBusy()
    fprintf("Sequence is busy\n");
end
pvtSequence.waitUntilIdle();
if (pvtSequence.isBusy())
{
    System.out.println("Sequence is busy.");
}
pvtSequence.waitUntilIdle();
if (pvtSequence.isBusy())
{
    std::cout << "Sequence is busy." << std::endl;
}
pvtSequence.waitUntilIdle();

Disabling

To disable a sequence that has been setup call the disable method. Any other movement (e.g. home, stop) will disable the sequence as well.

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
pvt_sequence.disable()
await pvtSequence.disable();
pvtSequence.Disable();
pvtSequence.disable();
pvtSequence.disable();
pvtSequence.disable();

I/O

PVT sequences can set the digital or analog output ports of a device as an action at points. See the PvtIo class for a full list of available functions.

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
pvt_sequence.io.set_digital_output(1, DigitalOutputAction.ON)
pvt_sequence.io.set_digital_output_schedule(1, DigitalOutputAction.ON, DigitalOutputAction.OFF, 100)
pvt_sequence.io.set_analog_output(1, 0.42) # in Volts
await pvtSequence.io.setDigitalOutput(1, DigitalOutputAction.ON);
await pvtSequence.io.setDigitalOutputSchedule(1, DigitalOutputAction.ON, DigitalOutputAction.OFF, 100);
await pvtSequence.io.setAnalogOutput(1, 0.42);
pvtSequence.Io.SetDigitalOutput(1, DigitalOutputAction.On);
pvtSequence.Io.setDigitalOutputSchedule(1, DigitalOutputAction.On, DigitalOutputAction.Off, 100);
pvtSequence.Io.SetAnalogOutput(1, 0.42);
pvtSequence.getIo().setDigitalOutput(1, DigitalOutputAction.ON);
pvtSequence.getIo().setDigitalOutputSchedule(1, DigitalOutputAction.ON, DigitalOutputAction.OFF, 100);
pvtSequence.getIo().setAnalogOutput(1, 0.42);
pvtSequence.getIo().setDigitalOutput(1, DigitalOutputAction.ON);
pvtSequence.getIo().setDigitalOutputSchedule(1, DigitalOutputAction.ON, DigitalOutputAction.OFF, 100);
pvtSequence.getIo().setAnalogOutput(1, 0.42);
pvtSequence.getIo().setDigitalOutput(1, DigitalOutputAction::ON);
pvtSequence.getIo().setDigitalOutputSchedule(1, DigitalOutputAction::ON, DigitalOutputAction::OFF, 100);
pvtSequence.getIo().setAnalogOutput(1, 0.42);

Corking

Corking reduces the likelihood of running out of points by first populating the action queue on the device, and then telling the device to execute it. Corked sequence blocks the execution of points in a live sequence's action queue. Execution resumes when the sequence is uncorked, or when the number of queued points reaches queue's limit.

Corking is useful when you are executing a large number of fast actions, such as moving in small incremements or interacting with I/O multiple times. If points are executed faster than they are received, the action queue will empty and the library throws PvtExecutionException exception. In the case corking does not help and the sequence still runs out of points, you can preload the points to a PVT buffer and call the buffer.

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
# block action execution
pvt_sequence.cork()

# ... add some points to the sequence

# unblock action execution
pvt_sequence.uncork()
// block action execution
await pvtSequence.cork();

// ... add some actions to queue

// unblock action execution
await pvtSequence.uncork();
// block action execution
pvtSequence.Cork();

// ... add some actions to queue

// unblock action execution
pvtSequence.Uncork();
// block action execution
pvtSequence.cork();

// ... add some actions to queue

// unblock action execution
pvtSequence.uncork();
% block action execution
pvtSequence.cork();

% ... add some actions to queue

% unblock action execution
pvtSequence.uncork();
// block action execution
pvtSequence.cork();

// ... add some actions to queue

// unblock action execution
pvtSequence.uncork();

Note that the sequence must be idle when issuing the cork command.

See the PVT sequence fifo command documentation in the ASCII Protocol Manual for more details.

Lockstep

The PVT sequences can also include axes in lockstep groups. In the following example the stream is set up on a lockstep group with number 1 and a single physical axis with number 3.

Python
C#
C++
JavaScript
Java
MATLAB (legacy)
pvt_sequence.setup_live_composite(
    PvtAxisDefinition(1, PvtAxisType.LOCKSTEP),
    PvtAxisDefinition(3, PvtAxisType.PHYSICAL)
)
  await pvtSequence.setupLiveComposite(
    { axisNumber: 1, axisType: PvtAxisType.LOCKSTEP },
    { axisNumber: 3, axisType: PvtAxisType.PHYSICAL },
  );
pvtSequence.SetupLiveComposite(
    new PvtAxisDefinition { AxisNumber = 1, AxisType = PvtAxisType.Lockstep },
    new PvtAxisDefinition { AxisNumber = 3, AxisType = PvtAxisType.Physical }
);
pvtSequence.setupLiveComposite(
    new PvtAxisDefinition(1, PvtAxisType.LOCKSTEP),
    new PvtAxisDefinition(3, PvtAxisType.PHYSICAL)
);
pvtSequence.setupLiveComposite([
    PvtAxisDefinition(1, PvtAxisType.LOCKSTEP)
    PvtAxisDefinition(3, PvtAxisType.PHYSICAL)
]);
pvtSequence.setupLiveComposite(
    PvtAxisDefinition(1, PvtAxisType::LOCKSTEP),
    PvtAxisDefinition(3, PvtAxisType::PHYSICAL)
);

See the Lockstep how-to guide for more information about lockstep groups.

Batch Submission

Instead of making individual calls to the point and various output methods as shown above, you can represent an entire PVT sequence as an array of items, where each item can be a point, a buffer call or an IO action.

There are two types of PVT sequences: Complete and partial. The difference between them is that in complete sequences all the points are expected to have values for every axis and time values. Partial sequences allow the points to have certain combinations of values missing. Most of the PVT functions deal with complete sequences; the partial type is intended for loading CSV files that have missing columns; you must use the generateVelocities , generatePositions or generateVelocitiesAndTimes methods to transform them into complete sequences before you can use them with devices. The non-point item types are the same between complete and partial sequences.

Complete PVT sequences are represented as arrays of type PvtSequenceItem , each of which can hold a value that is one of these types:

  • PvtPoint represents a point with all values defined
  • PvtCallAction represents a call to another buffer
  • PvtSetAnalogOutputAction changes the state of one analog output pin
  • PvtSetAllAnalogOutputsAction changes the states of all analog outputs
  • PvtSetDigitalOutputAction changes the state of one digital output pin
  • PvtSetAllDigitalOutputsAction changes the states of all digital outputs
  • PvtCancelOutputScheduleAction cancels a future scheduled change to one analog or digital output pin
  • PvtCancelAllOutputsScheduleAction cancels future scheduled changes to all analog or digital output pins

Partial PVT sequences are similar, except the type of the array elements is PvtPartialSequenceItem and PvtPoint is replaced with PvtPartialPoint , which allows some of its member data to be unspecified.

The array representation is convenient because it allows treating a complete sequence as a single object, and supports loading and saving sequences to CSV files for backup, manual editing or programmatic generation with your own code or external programs. The following methods are relevant when using the array representation:

  • On pvtBuffer :
    • retrieveSequenceData reads the contents of an existing buffer on a device
  • On pvtSequence :
    • loadSequenceData reads complete sequence data from a CSV file on disk
    • loadPartialSequenceData reads partial seqeunce data from a CSV file
    • saveSequenceData saves complete sequence data to a CSV file
    • submitSequenceData sends sequence data to a buffer or live stream on a device
    • generatePositions converts partial sequence data to complete by filling in missing positions
    • generateVelocities converts partial sequence data to complete by filling in missing velocities
    • generateVelocitiesAndTimes converts partial sequence data to complete by filling in missing velocities and times (useful if you only want to calcuate the positions yourself)
    • convertTimeAbsoluteToRelative and convertTimeRelativeToAbsolute convert the time values of points in a sequence between relative (relative to the previous point) and absolute (relative to the start of the sequence) representations. Most of the above methods expect the relative representation but it is sometimes convenient to calculate time in absolute form or store them that way in CSV files. There are also variations of these two functions for partial sequences.

Communication Performance

If you're connecting your device via a serial port or USB to serial bridge, it can potentially take several seconds to transfer all your PVT data to the device.

With devices that have them, consider using the direct USB connection or a TCP/IP connection for better communication performance.

Debugging Tips

If you are having trouble, these are some common points of confusion using PVT:

  • Times must be specified as relative times (ie. durations since the previous point).
  • The starting point (time=0) is inferred from the current state of the device.
  • Positions can be specified absolutely or relatively.
  • The velocity at the end of the sequence must be zero.

Additionally, it is wise to avoid specifying position or velocity values that are very close to device limits. PVT sequences become increasingly difficult to debug as this happens, as trajectories are more likely to briefly exceed axis limits when control points are close to them.

Further Reading

PVT Sequence Generation Guide

Resources

  • ASCII Protocol Manual - PVT (firmware version 7.xx)
  • API Reference
    • PvtSequence
    • PvtBuffer
    • PvtIo
    • PvtAxisDefinition
    • PvtAxisType
    • PvtMode