Software/Zaber Console/Scripting

From ZaberWiki
Jump to navigation Jump to search

The Zaber Console Script Editor can be used to send a list of commands to Zaber devices in order to automate a task. You can also make a script decide which actions to perform and repeat a list of actions several times. This page describes how to write scripts in common Microsoft.NET languages like C#, Visual Basic.NET, and JavaScript.NET. Zaber Console also supports Python scripts, but those are described on another page.

Please note that scripting in Zaber Console is not recommended for new development; please consider using the Zaber Motion Library instead.


Scripting for non-programmers

Even if you're not a programmer, you can use scripts that someone else has already written. Several sample scripts are included with the Zaber Console. More sample scripts can be found below. Scripts are usually not difficult to decipher even for non-programmers. If you can find a script that does something close to what you want, you can often modify it to do exactly what you want, simply by changing the numbers or combining parts of other scripts.

If you can't convince one of these scripts to do exactly what you need, we are pleased to offer custom software development services. Simply let us know what you want to do, no matter how trivial or complex, and we'll be happy to give you an estimate. Contact us for more information.

Running existing scripts

Many sample scripts are included with the Zaber Console. Scripts can be executed either by clicking on the "Run" button next to the desired script in the Scripts tab, or by opening a script with the Script Editor and selecting "Run Script" from the Run menu.

Binary vs. ASCII mode

Scripts either send commands in Binary protocol or ASCII protocol. For example, move absolute can either be Conversation.Request(Command.MoveAbsolute, 10000) (Binary protocol) or Conversation.Request("move abs 10000") (ASCII protocol). The device's default communication protocol depends on the series it belongs to. X-Series devices default to ASCII, T-Series and A-Series devices default to Binary. See more information on each of these protocols in the (ASCII protocol manual and the Binary protocol manual).

Basic script syntax

When viewing and modifying the sample scripts, the following descriptions of some common script syntax should help you understand what's going on.

Conversation.Request(Command.XXXXX,DDDDD) (Binary mode)
Conversation.Request("XXXXX DDDDD") (ASCII mode)
This sends a request to the selected device and waits for a response. XXXXX should be replaced with the name of a command, and DDDDD should be replaced with command data. Some common binary commands are Home, MoveAbsolute, MoveRelative, MoveAtConstantSpeed, Stop, and ReturnSetting. The ASCII equivalents are home, move abs, move rel, move vel, stop, and get.
Conversation.Send(Command.XXXXX,DDDDD) (Binary mode)
Conversation.Send("XXXXX DDDDD") (ASCII mode)
This sends a request to the selected device without waiting for a response. That means that the script execution will proceed immediately without waiting for the device to respond, and when the device does respond, its response will be ignored by the script. XXXXX should be replaced with the name of a command, and DDDDD should be replaced with command data.
You can see a complete list of available commands for any device in the Zaber Console. Select the device from the device list and look at the commands tab. Any command in the list can be used in place of XXXXX above. In binary mode, simply remove the spaces from the command name shown. The rest of the commands appear in the Settings tab. For any read-only setting in binary mode, just add "Return" to the start of the name (e.g., ReturnFirmwareVersion). For the other settings in binary mode, add "Set" to the start of the name to set the value. In ASCII mode, just add "get " or "set " before the command name. You can always look in the log window to see what command is being sent when you click on a button.
Sleep(DDDDD)
This causes the script execution to pause for a number of milliseconds specified by DDDDD.
Output.WriteLine("SSSSSS")
This prints a line of text SSSSSS to the output tab.
#template(simple)
This is a template declaration. Most scripts have a template declaration near the beginning. It specifies additional code that gets added to the script before it gets executed. The "simple" template is the most common. If you are only modifying scripts, you don't need to know anything more than that you should leave this declaration as is.
#template(methods)
This is another template declaration. The "methods" template is necessary for scripts that use helper methods. If you are only modifying scripts, you don't need to know anything more than that you should leave this declaration as is.

Sample Scripts

This tutorial section will walk you through a series of scripts where each script adds a new feature. We include a separate version of each script for C#, Visual Basic, and JavaScript. Follow these steps to run one of the scripts:

  1. Read the beginning of the script to see what language it's written in.
  2. Use the mouse to select the script. Make sure you don't select anything else before or after the script.
  3. Type Ctrl-C to copy the script to the clipboard.
  4. Run the Zaber Console or switch back to it if it's already running.
  5. Most scripts assume the Zaber devices are turned on and the port is already open, so click the Open button if you need to.
  6. Click on the scripts tab.
  7. Click on the Script Editor... button.
    If this is the first time you've opened the script editor, it will prompt you for a folder to hold your scripts. You should create a new folder somewhere and select that. Creating a folder called Zaber Scripts under My Documents is a good choice.
  8. In the Script Editor window, click on the Language menu and click the language that the script is written in.
  9. From the File menu, choose New.
  10. Click in the main field and type Ctrl-V to paste the script.
  11. Some scripts need you to select a device number, click on the drop down to see a list of device numbers.
  12. Click the Run Script button.

If you want to keep the script to run again another time, click the File menu and choose Save Script As.... Then type a file name for the script in your scripts folder. Be sure to end the name with the right extension: ".cs" for C#, ".vb" for Visual Basic.NET, ".js" for JavaScript, and so on. Once you've saved it, it will appear in the list on the Scripts tab of Zaber Console.

A list of commands

The simplest script you can write is just a list of commands with no logic. The script makes a request, waits for the response, and then makes the next request until it gets to the end of the list. The ASCII command scripts use PollUntilIdle() to wait until the device has finished moving.

This script makes the selected device take 5 steps backwards, and then extend back to where it started. Open the port before running this script. The device has to start at least 50000 microsteps away from the home position, so if you receive the RelativePositionInvalid error or BADDATA error, just extend the device before running the script.

// A list of binary commands sample - C# or JavaScript
#template(simple)
Conversation.Request(Command.MoveRelative, -10000);
Conversation.Request(Command.MoveRelative, -10000);
Conversation.Request(Command.MoveRelative, -10000);
Conversation.Request(Command.MoveRelative, -10000);
Conversation.Request(Command.MoveRelative, -10000);
Conversation.Request(Command.MoveRelative, 50000);
' A list of binary commands sample - Visual Basic
#template(simple)
Conversation.Request(Command.MoveRelative, -10000)
Conversation.Request(Command.MoveRelative, -10000)
Conversation.Request(Command.MoveRelative, -10000)
Conversation.Request(Command.MoveRelative, -10000)
Conversation.Request(Command.MoveRelative, -10000)
Conversation.Request(Command.MoveRelative, 50000)
// A list of ASCII commands sample - C# or JavaScript
#template(simple)
Conversation.Request("move rel -10000");
Conversation.PollUntilIdle();
Conversation.Request("move rel -10000");
Conversation.PollUntilIdle();
Conversation.Request("move rel -10000");
Conversation.PollUntilIdle();
Conversation.Request("move rel -10000");
Conversation.PollUntilIdle();
Conversation.Request("move rel -10000");
Conversation.PollUntilIdle();
Conversation.Request("move rel 50000");
Conversation.PollUntilIdle();
' A list of ASCII commands sample - Visual Basic
#template(simple)
Conversation.Request("move rel -10000")
Conversation.PollUntilIdle()
Conversation.Request("move rel -10000")
Conversation.PollUntilIdle()
Conversation.Request("move rel -10000")
Conversation.PollUntilIdle()
Conversation.Request("move rel -10000")
Conversation.PollUntilIdle()
Conversation.Request("move rel -10000")
Conversation.PollUntilIdle()
Conversation.Request("move rel 50000")
Conversation.PollUntilIdle()


Looping

You can repeat commands using a loop structure like for or while. This example shows how to use for to replace the five MoveRelative -10000 commands in the previous sample. We also introduce variables with the moveCount and distance variables.

// Looping sample in binary mode - C# or JavaScript
#template(simple)
var moveCount = 5;
var distance = 10000;
for (var i = 0; i < moveCount; i++)
{
    Conversation.Request(Command.MoveRelative, -distance);
}
Conversation.Request(Command.MoveRelative, moveCount*distance);
' Looping sample in binary mode - Visual Basic
#template(simple)
Dim moveCount As Integer = 5
Dim distance As Integer = 10000
For i As Integer = 1 To moveCount
    Conversation.Request(Command.MoveRelative, -distance)
Next
Conversation.Request(Command.MoveRelative, moveCount*distance)
// Looping sample in ASCII mode - C# or JavaScript
#template(simple)
var moveCount = 5;
var distance = 10000;
for (var i = 0; i < moveCount; i++)
{
    Conversation.Request("move rel", -distance);
    Conversation.PollUntilIdle();
}
Conversation.Request("move rel", moveCount*distance);
Conversation.PollUntilIdle();
' Looping sample in ASCII mode - Visual Basic
#template(simple)
Dim moveCount As Integer = 5
Dim distance As Integer = 10000
For i As Integer = 1 To moveCount
    Conversation.Request("move rel", -distance)
    Conversation.PollUntilIdle()
Next
Conversation.Request("move rel", moveCount*distance)
Conversation.PollUntilIdle()


Input and output

You can display messages to the user and ask them for input using the Input and Output objects. This sample asks the user how many moves to make and how big each move should be.

// Input and output sample in binary mode - C# or JavaScript
#template(simple)

Output.WriteLine("How many moves would you like to make?");
var line = Input.ReadLine();
var moveCount = Convert.ToInt32(line);

Output.WriteLine("How many microsteps would you like in each move?");
line = Input.ReadLine();
var distance = Convert.ToInt32(line);
for (var i = 0; i < moveCount; i++)
{
    Conversation.Request(Command.MoveRelative, -distance);
}
Conversation.Request(Command.MoveRelative, moveCount*distance);
' Input and output sample in binary mode - Visual Basic
#template(simple)

Output.WriteLine("How many moves would you like to make?")
Dim line As String = Input.ReadLine()
Dim moveCount As Integer = Convert.ToInt32(line)

Output.WriteLine("How many microsteps would you like in each move?")
line = Input.ReadLine()
Dim distance As Integer = Convert.ToInt32(line)
For i As Integer = 1 To moveCount
    Conversation.Request(Command.MoveRelative, -distance)
Next
Conversation.Request(Command.MoveRelative, moveCount*distance)
// Input and output sample in ASCII mode - C# or JavaScript
#template(simple)

Output.WriteLine("How many moves would you like to make?");
var line = Input.ReadLine();
var moveCount = Convert.ToInt32(line);

Output.WriteLine("How many microsteps would you like in each move?");
line = Input.ReadLine();
var distance = Convert.ToInt32(line);
for (var i = 0; i < moveCount; i++)
{
    Conversation.Request("move rel", -distance);
    Conversation.PollUntilIdle();
}
Conversation.Request("move rel", moveCount*distance);
Conversation.PollUntilIdle();
' Input and output sample in ASCII mode - Visual Basic
#template(simple)

Output.WriteLine("How many moves would you like to make?")
Dim line As String = Input.ReadLine()
Dim moveCount As Integer = Convert.ToInt32(line)

Output.WriteLine("How many microsteps would you like in each move?")
line = Input.ReadLine()
Dim distance As Integer = Convert.ToInt32(line)
For i As Integer = 1 To moveCount
     Conversation.Request("move rel", -distance)
    Conversation.PollUntilIdle()
Next
Conversation.Request("move rel", moveCount*distance)
Conversation.PollUntilIdle()

Response Data

Most commands return some kind of data, and your scripts can use that data. In this sample, we check that there is room for the movement before starting. This sample also introduces conditional logic using the if statement.


// Response data sample in binary mode - C# or JavaScript
#template(simple)

// First check the current position
var currentPosition = Conversation.Request(Command.ReturnCurrentPosition).Data;
Output.WriteLine("Current position is {0} microsteps.", currentPosition);

Output.WriteLine("How many moves would you like to make?");
var line = Input.ReadLine();
var moveCount = Convert.ToInt32(line);

Output.WriteLine("How many microsteps would you like in each move?");
line = Input.ReadLine();
var distance = Convert.ToInt32(line);
var totalDistance = moveCount * distance;

if (totalDistance > currentPosition)
{
    // complain to user
    Output.WriteLine("Not enough room for that.");

    // skip the rest of the script
    return;
}

for (var i = 0; i < moveCount; i++)
{
    var newPosition =
        Conversation.Request(Command.MoveRelative, -distance).Data;
    Output.WriteLine("New position is {0} microsteps.", newPosition);
}
var finalPosition =
    Conversation.Request(Command.MoveRelative, moveCount*distance).Data;
Output.WriteLine("New position is {0} microsteps.", finalPosition);
' Response data sample in binary mode - Visual Basic
#template(simple)

' First check the current position
Dim currentPosition As Integer = _
    Conversation.Request(Command.ReturnCurrentPosition).Data
Output.WriteLine("Current position is {0} microsteps.", currentPosition)

Output.WriteLine("How many moves would you like to make?")
Dim line As String = Input.ReadLine()
Dim moveCount As Integer = Convert.ToInt32(line)

Output.WriteLine("How many microsteps would you like in each move?")
line = Input.ReadLine()
Dim distance As Integer = Convert.ToInt32(line)
Dim totalDistance As Integer = moveCount * distance

If totalDistance > currentPosition Then
    ' complain to user
    Output.WriteLine("Not enough room for that.")

    ' skip the rest of the script
    Return
End If

For i As Integer = 1 To moveCount
    Dim newPosition As Integer = _
        Conversation.Request(Command.MoveRelative, -distance).Data
    Output.WriteLine("New position is {0}.", newPosition)
Next
Dim finalPosition As Integer = _
    Conversation.Request(Command.MoveRelative, moveCount*distance).Data
Output.WriteLine("New position is {0}.", finalPosition)
// Response data sample in ASCII mode - C# or JavaScript
#template(simple)

// First check the current position
var currentPosition = Conversation.Request("get pos").Data;
Output.WriteLine("Current position is {0} microsteps.", currentPosition);

Output.WriteLine("How many moves would you like to make?");
var line = Input.ReadLine();
var moveCount = Convert.ToInt32(line);

Output.WriteLine("How many microsteps would you like in each move?");
line = Input.ReadLine();
var distance = Convert.ToInt32(line);
var totalDistance = moveCount * distance;

if (totalDistance > currentPosition)
{
    // complain to user
    Output.WriteLine("Not enough room for that.");

    // skip the rest of the script
    return;
}

for (var i = 0; i < moveCount; i++)
{
    Conversation.Request("move rel", -distance);
    Conversation.PollUntilIdle();
}
Conversation.Request("move rel", moveCount*distance);
' Response data sample in ASCII mode - Visual Basic
#template(simple)

' First check the current position
Dim currentPosition As Integer = Conversation.Request("get pos").Data
Output.WriteLine("Current position is {0} microsteps.", currentPosition)

Output.WriteLine("How many moves would you like to make?")
Dim line As String = Input.ReadLine()
Dim moveCount As Integer = Convert.ToInt32(line)

Output.WriteLine("How many microsteps would you like in each move?")
line = Input.ReadLine()
Dim distance As Integer = Convert.ToInt32(line)
Dim totalDistance As Integer = moveCount * distance

If totalDistance > currentPosition Then
    ' complain to user
    Output.WriteLine("Not enough room for that.")

    ' skip the rest of the script
    Return
End If

For i As Integer = 1 To moveCount
     Conversation.Request("move rel", -distance)
    Conversation.PollUntilIdle()
Next
Conversation.Request("move rel", moveCount*distance)
Conversation.PollUntilIdle()

Response Data: Analog/Digital Inputs

Most commands return some kind of data, and your scripts can use that data. When reading a digital input we would want to return an integer (boolean), but we want a floating point when reading an analog input because we may want to know precisely what the voltage reading is. This example shows how to read the response of a digital input as an integer and the response of an analog input as a floating point, and then print them.

Please note, digital and analog inputs are only available on the MCB1, and MCB2 series controllers.

// Response data from digital and analog inputs sample in ASCII - C# or JavaScript

#template(simple)
//read analog input 1 and convert the response to a floating point value
var dataAnalog = float.Parse(Conversation.Request("io get ai 1").TextData);
//output the voltage reading [V]
Output.WriteLine(dataAnalog);

//read digital input 1 as an integer value
var dataDigital = Conversation.Request("io get di 1").Data;
Output.WriteLine(dataDigital);
' Response data from digital and analog inputs sample in ASCII - VisualBasic

#template(simple) 
'read analog input 1 and convert the response to a double value
Dim dataAnalog As Double = Conversation.Request("io get ai 1").TextData
'output the voltage reading [V]
Output.WriteLine(dataAnalog)

'read digital input 1 as an integer value
Dim dataDigital As Integer = Conversation.Request("io get di 1").Data
Output.Writeline(dataDigital)

Helper methods

Once your scripts get this big, you can often make them more readable by taking repeated code and moving it into separate helper functions. In this sample, we create a helper function that displays a message and asks the user to enter a number. We also create a helper function for making a move and displaying the new position.

// Helper methods sample in binary mode - C#
#template(methods)

// main method of script (always needed with methods template)
public override void Run()
{
    // First check the current position
    var currentPosition = Conversation.Request(Command.ReturnCurrentPosition).Data; 
    Output.WriteLine("Current position is {0} microsteps.", currentPosition);

    var moveCount = AskNumber("How many moves would you like to make?");
    var distance = AskNumber("How many microsteps would you like in each move?");
    var totalDistance = moveCount * distance;

    if (totalDistance > currentPosition)
    {
        // complain to user
        Output.WriteLine("Not enough room for that.");

        // skip the rest of the script
        return;
    }

    for (var i = 0; i < moveCount; i++)
    {
        MoveAndDisplay(-distance);
    }
    MoveAndDisplay(moveCount*distance);
}

int AskNumber(String question)
{
    Output.WriteLine(question);
    var line = Input.ReadLine();
    return Convert.ToInt32(line);
}

void MoveAndDisplay(int distance)
{
    var newPosition =
        Conversation.Request(Command.MoveRelative, distance).Data;
    Output.WriteLine("New position is {0} microsteps.", newPosition);
}


// Helper methods sample in binary mode - JavaScript
// In JavaScript you don't need a different template for helper methods
#template(simple)

// First check the current position
var currentPosition = Conversation.Request(Command.ReturnCurrentPosition).Data;
Output.WriteLine("Current position is {0} microsteps.", currentPosition);

var moveCount = AskNumber("How many moves would you like to make?");
var distance = AskNumber("How many microsteps would you like in each move?");
var totalDistance = moveCount * distance;

if (totalDistance > currentPosition)
{
    // complain to user
    Output.WriteLine("Not enough room for that.");

    // skip the rest of the script
    return;
}

for (var i = 0; i < moveCount; i++)
{
    MoveAndDisplay(-distance);
}
MoveAndDisplay(moveCount*distance);

function AskNumber(question)
{
    Output.WriteLine(question);
    var line = Input.ReadLine();
    return Convert.ToInt32(line);
}

function MoveAndDisplay(distance)
{
    var newPosition =
        Conversation.Request(Command.MoveRelative, distance).Data;
    Output.WriteLine("New position is {0} microsteps.", newPosition);
}


' Helper methods sample in binary mode - Visual Basic
#template(methods)

Public Overrides Sub Run()
    ' First check the current position
    Dim currentPosition As Integer = _
        Conversation.Request(Command.ReturnCurrentPosition).Data
    Output.WriteLine("Current position is {0} microsteps.", currentPosition)

    Dim moveCount As Integer = AskNumber("How many moves would you like to make?")
    Dim distance As Integer = AskNumber("How many microsteps would you like in each move?")

    Dim totalDistance As Integer = moveCount * distance

    If totalDistance > currentPosition Then
        ' complain to user
        Output.WriteLine("Not enough room for that.")

        ' skip the rest of the script
        Return
    End If

    For i As Integer = 1 To moveCount
        MoveAndDisplay(-distance)
    Next
    MoveAndDisplay(moveCount*distance)
End Sub

Function AskNumber(question As String) As Integer
    Output.WriteLine(question)
    Dim line As String = Input.ReadLine()
    Return Convert.ToInt32(line)
End Function

Sub MoveAndDisplay(distance As Integer)
    Dim newPosition As Integer = _
        Conversation.Request(Command.MoveRelative, distance).Data
    Output.WriteLine("New position is {0} microsteps.", newPosition)
End Sub


// Helper methods sample in ASCII mode - C#
#template(methods)

// main method of script (always needed with methods template)
public override void Run()
{
    // First check the current position
    var currentPosition = Conversation.Request("get pos").Data; 
    Output.WriteLine("Current position is {0} microsteps.", currentPosition);

    var moveCount = AskNumber("How many moves would you like to make?");
    var distance = AskNumber("How many microsteps would you like in each move?");
    var totalDistance = moveCount * distance;

    if (totalDistance > currentPosition)
    {
        // complain to user
        Output.WriteLine("Not enough room for that.");

        // skip the rest of the script
        return;
    }

    for (var i = 0; i < moveCount; i++)
    {
        MoveAndDisplay(-distance);
    }
    MoveAndDisplay(moveCount*distance);
}

int AskNumber(String question)
{
    Output.WriteLine(question);
    var line = Input.ReadLine();
    return Convert.ToInt32(line);
}

void MoveAndDisplay(int distance)
{
    Conversation.Request("move rel", distance);
    Conversation.PollUntilIdle();
    var newPosition = Conversation.Request("get pos").Data;
    Output.WriteLine("New position is {0} microsteps.", newPosition);
}


// Helper methods sample in ASCII mode - JavaScript
// In JavaScript you don't need a different template for helper methods
#template(simple)

// First check the current position
var currentPosition = Conversation.Request("get pos").Data;
Output.WriteLine("Current position is {0} microsteps.", currentPosition);

var moveCount = AskNumber("How many moves would you like to make?");
var distance = AskNumber("How many microsteps would you like in each move?");
var totalDistance = moveCount * distance;

if (totalDistance > currentPosition)
{
    // complain to user
    Output.WriteLine("Not enough room for that.");

    // skip the rest of the script
    return;
}

for (var i = 0; i < moveCount; i++)
{
    MoveAndDisplay(-distance);
}
MoveAndDisplay(moveCount*distance);

function AskNumber(question)
{
    Output.WriteLine(question);
    var line = Input.ReadLine();
    return Convert.ToInt32(line);
}

function MoveAndDisplay(distance)
{
    Conversation.Request("move rel", distance);
    Conversation.PollUntilIdle();
    var newPosition = Conversation.Request("get pos").Data;
    Output.WriteLine("New position is {0} microsteps.", newPosition);
}


' Helper methods sample in ASCII mode - Visual Basic
#template(methods)

Public Overrides Sub Run()
    ' First check the current position
    Dim currentPosition As Integer = Conversation.Request("get pos").Data
    Output.WriteLine("Current position is {0} microsteps.", currentPosition)

    Dim moveCount As Integer = AskNumber("How many moves would you like to make?")
    Dim distance As Integer = AskNumber("How many microsteps would you like in each move?")

    Dim totalDistance As Integer = moveCount * distance

    If totalDistance > currentPosition Then
        ' complain to user
        Output.WriteLine("Not enough room for that.")

        ' skip the rest of the script
        Return
    End If

    For i As Integer = 1 To moveCount
        MoveAndDisplay(-distance)
    Next
    MoveAndDisplay(moveCount*distance)
End Sub

Function AskNumber(question As String) As Integer
    Output.WriteLine(question)
    Dim line As String = Input.ReadLine()
    Return Convert.ToInt32(line)
End Function

Sub MoveAndDisplay(distance As Integer)
    Conversation.Request("move rel", distance)
    Conversation.PollUntilIdle()
    Dim newPosition As Integer = Conversation.Request("get pos").Data
    Output.WriteLine("New position is {0} microsteps.", newPosition)
End Sub


Scripting for programmers

Programmers should review all the information provided in the non-programmers section above as that information applies to you as well. In addition, this section will cover more advanced scripting details. You might also be interested in the Zaber Console source code, as well as writing your own plug ins for Zaber Console.

Running existing scripts

There are three ways to run a script, only two of which were covered in the "for non-programmers" section above:

  1. Click the "Run" button next to it in the Zaber Console scripts tab
  2. Open the script in the Script Editor and select "Run Script" from the Run menu.
  3. Use the Script Runner command line application from a command prompt or within a batch file.

The first two options are the easiest since they can be done from within the Zaber Console. The third is only necessary if you want to execute multiple scripts in sequence or execute scripts from a batch file without having to open the Zaber Console application. Script Runner is a different application from the Zaber Console and is only included with the Windows installer, not the Click Once version.

Add the Zaber Console directory to your Windows path.

  1. Open System Properties (Win+Pause)
  2. Switch to the Advanced tab
  3. Click Environment Variables
  4. Select PATH in the System variables section
  5. Click Edit
  6. Add a semicolon to the end of the list and then add Zaber Console's path.
    For example, pretend the path is already this: C:\Program Files\MSOffice
    If you installed in the default location, you would change it to this: C:\Program Files\MSOffice;C:\Program Files\Zaber Technologies\Zaber Console 1.2.X
  7. Click OK. Click OK.

Then open a command prompt or create a batch file and use the following command line syntax:

ScriptRunner [Options] script_name to run a specified script where script_name is the filename of a script to run. The file extension tells ScriptRunner what language to use (e.g, .cs files are run using the C# compiler).
ScriptRunner /showports to display a list of available ports
ScriptRunner /help to display the help file
Options:
/p port_name optional port to open (COM1, COM2, etc) prior to script execution
/m mode optional mode to open the serial port in: 'b' for binary or 'a' for ASCII. The default is binary.
/b baud_rate optional open the serial port with the requested baud rate. This defaults to 9600 in binary mode and 115200 in ASCII mode.
/d device_number optional device number to use for the default conversation object
/a axis_number optional axis number to use for the default conversation object with a multi-axis device in ASCII mode
/f folder_name optional search for scripts and templates in the given folder
/log optional flag to log all requests and responses to the console

Command Line Examples:

ScriptRunner sample.cs run the sample.cs script
ScriptRunner /p COM1 sample.cs open COM1, then run the sample.cs script
ScriptRunner /p COM1 /d 1 sample.cs open conversation to device 1 on COM 1, then run the sample.cs script
ScriptRunner /showports displays a list of available com ports
ScriptRunner /help displays a help screen

Example batch file:

@echo off
scriptrunner /port com1 "All - stop.cs"
pause


Note that some scripts may not require opening the COM port or selecting a device prior to running the script. A script itself can open the COM port and select devices. Therefore it is good practice to specify setup requirements in comments at the top of your scripts.

The script runner will look for template files in a folder called Templates. It will also look in parent folders for the Templates folder.

Templates

Most scripts should include a template declaration near the beginning. This indicates which template the compiler should use to wrap around the script before compiling. The script editor comes with two templates: "simple" and "methods".

The simple template is sufficient if your script does not require helper methods. To use the simple template, start your script as follows:

// C# or JavaScript Example
#template(simple)
// Insert your code here
' Visual Basic Example
#template(simple)
' Insert your code here

The methods template is required if your script uses helper methods. To use the methods template, your script must look as follows:

// C# Example
#template(methods)
public override void Run()
{
    // insert your code here
}

// add other methods here
private int CalculateFoo()
{
}
// JavaScript Example
#template(methods)
function Run()
{
    // insert your code here
}

// add other methods here
function CalculateFoo()
{
}
' VB Example
#template(methods)
Public Overrides Sub Run()
    ' insert your code here
End Sub

' add other methods here
Private Function CalculateFoo() As Integer
End Function

If interested, you can click on the "Template" tab in the Script Editor to see what code will be wrapped around your script before compiling.

Advanced Templates

You can create your own templates, just write a normal class file and add a line with this marker: $INSERT-SCRIPT-HERE$. The class must implement the IPlugIn interface. You may want to inherit from the PlugInBase class because it provides default implementations of the IPlugIn methods. Once you've written the class file, save it in the Templates folder. Then any script can use it by putting its name in the #template declaration.

You can also write a script without a template. You just write the entire class in your script file and don't include a #template declaration. As with the template, your class must implement the IPlugIn interface.

Script Syntax

Scripts are compiled for use with the .NET Framework. Therefore, you use exactly the same syntax you would be using if you were writing your own applications in MS Visual Studio. You can also use any .NET language you prefer, though most sample scripts are provided in C#. The language menu in the script editor shows all .NET languages installed on your computer. By default, you should see C#, Visual Basic, JavaScript, and Python. Python works differently from the other languages, and is described on the Python scripting page.

There are several sample scripts included with the Zaber Console. It's a good idea to open each of them in Script Editor to gain a better understanding of script syntax. There are three different ways to communicate with Zaber devices in your scripts; you can issue instructions at the "port" level, the "device" level or the "conversation" level.

Objects and Classes

There are some predefined objects for communicating with the selected device or port at different levels.

Predefined Object Class
PortFacade ZaberPortFacade
PortFacade.Port IZaberPort
Conversation Conversation
Conversation.Device ZaberDevice
Input Console
Output Console

These predefined objects are explained in more detail below:

PortFacade
A predefined object implementing the ZaberPortFacade class for the selected port. Use a ZaberPortFacade to get to all the objects representing the Zaber devices connected to your serial port. You can use the GetConversation(deviceNumber), GetConversation(deviceNumber, axisNumber) or GetDevice(deviceNumber) methods to get the conversation or device object for one of your devices. For more details on all classes and features of the library, read the rest of this section or see the library help file in the Help menu of the Zaber Console script editor.
PortFacade.Port
Use this to talk directly to the selected port as follows:
// C# sample script communicating to the "port" using the 
// IZaberPort.Send method in binary mode.
// you must open a port before running the script
#template(simple)
var myPort = PortFacade.Port;
myPort.Send(1, Command.Home, 0);
Sleep(3000);
myPort.Send(1, Command.MoveAbsolute, 100000); 
// JavaScript sample script communicating to the "port" using the 
// IZaberPort.Send method in binary mode.
// you must open a port before running the script
#template(simple)
var myPort = PortFacade.Port;
myPort.Send(1, Command.Home, 0);
Sleep(3000);
myPort.Send(1, Command.MoveAbsolute, '100000'); 
' VB sample script communicating to the "port" using the IZaberPort.Send 
' method in binary mode
' you must open a port before running the script
#template(simple)
Dim myPort As IZaberPort = PortFacade.Port
myPort.Send(1, Command.Home)
Sleep(3000)
myPort.Send(1, Command.MoveAbsolute, 100000)
// C# or JavaScript sample script communicating to the "port" using the 
// IZaberPort.Send method in ASCII mode.
// you must open a port before running the script
#template(simple)
var myPort = PortFacade.Port;
myPort.Send("/1 home");
Sleep(3000);
myPort.Send("/1 move abs 100000");
' VB sample script communicating to the "port" using the IZaberPort.Send 
' method in ASCII mode
' you must open a port before running the script
#template(simple)
Dim myPort As IZaberPort = PortFacade.Port
myPort.Send("/1 home")
Sleep(3000)
myPort.Send("/1 move abs 100000")
Conversation
A predefined object implementing the Conversation class for the selected device. This allows conversation level communications with the currently selected device in Script Editor or Script Runner. (In Script Runner, the selected device would be specified by the command-line parameters.) Communication at the conversation level is synchronous. On sending a request to a device, the execution of your script will wait until the device responds. The response data is accessible by your script. In binary mode, the movement commands don't respond until the movement is finished, and they return the final position as their data value. In ASCII mode, the movement commands respond immediately, so your script has to use PollUntilIdle() to wait for the movement to finish and then get the position from a separate request.
// C# or JavaScript sample of Conversation object in binary mode
#template(simple)
var myResponse = Conversation.Request(Command.MoveRelative, 20000);
var myPosition = myResponse.Data;
Output.WriteLine("Current position is {0}", myPosition);

myResponse = Conversation.Request(Command.MoveRelative, -10000);
myPosition = myResponse.Data;
Output.WriteLine("Current position is {0}", myPosition);
' VB sample of Conversation object in binary mode
#template(simple)
Dim myResponse As DeviceMessage
Dim myPosition As Integer

myResponse = Conversation.Request(Command.MoveRelative, 20000)
myPosition = myResponse.Data
Output.WriteLine("Current position is {0}", myPosition)

myResponse = Conversation.Request(Command.MoveRelative, -10000)
myPosition = myResponse.Data
Output.WriteLine("Current position is {0}", myPosition)
// C# or JavaScript sample of Conversation object in ASCII mode
#template(simple)
Conversation.Request("move rel", 20000);
Conversation.PollUntilIdle();
var myResponse = Conversation.Request("get pos");
var myPosition = myResponse.Data;
Output.WriteLine("Current position is {0}", myPosition);

Conversation.Request("move rel", -10000);
Conversation.PollUntilIdle();
myResponse = Conversation.Request("get pos");
myPosition = myResponse.Data;
Output.WriteLine("Current position is {0}", myPosition);
' VB sample of Conversation object in ASCII mode
#template(simple)
Dim myResponse As DeviceMessage
Dim myPosition As Integer

Conversation.Request("move rel", 20000)
Conversation.PollUntilIdle()
myResponse = Conversation.Request("get pos")
myPosition = myResponse.Data
Output.WriteLine("Current position is {0}", myPosition)

Conversation.Request("move rel", -10000)
Conversation.PollUntilIdle()
myResponse = Conversation.Request("get pos")
myPosition = myResponse.Data
Output.WriteLine("Current position is {0}", myPosition)
Conversation.Device
A predefined object implementing the ZaberDevice class for the selected device. This allows device level communications with the currently selected device in Script Editor or Script Runner. (In Script Runner, the selected device would be specified by the command-line parameters.) Communication at the device level is asynchronous. On sending a request to a device, the execution of your script will continue immediately without waiting for a response, and the response data is not accessible by the script. This isn't as useful in ASCII mode, because all ASCII commands respond immediately.
// C# sample script communicating to the current device using the Send method
// you must open a port before running the script
#template(simple)
Conversation.Device.Send(Command.Home);
Sleep(3000);
Conversation.Device.Send(Command.MoveAbsolute, 100000);
// JavaScript sample script communicating to the current device using the Send method
// you must open a port before running the script
#template(simple)
Conversation.Device.Send(Command.Home);
Sleep(3000);
Conversation.Device.Send(Command.MoveAbsolute, '100000');
' VB sample script communicating to the current device using the Send method
' you must open a port before running the script
#template(simple)
Conversation.Device.Send(Command.Home)
Sleep(3000)
Conversation.Device.Send(Command.MoveAbsolute, 100000)
Input
predefined object for reading input text from user
Output
predefined object for displaying output text to user

Methods

PortFacade.Port.Send
Sends an instruction to the selected port (does not wait for a response). See the PortFacade.Port example above.
PortFacade.GetDevice
Gets a ZaberDevice object with the requested device number. See the description of ZaberDevice above.
// C# sample script selecting a specific device using the GetDevice method
// you must open a port before running the script
#template(simple)
var myDevice = PortFacade.GetDevice(1);
myDevice.Send(Command.Home, 0);
// JavaScript sample script selecting a specific device using the GetDevice method
// you must open a port before running the script
#template(simple)
var myDevice = PortFacade.GetDevice(1);
myDevice.Send(Command.Home, '0');
' VB sample script selecting a specific device using the GetDevice method
' you must open a port before running the script
#template(simple)
Dim myDevice As ZaberDevice = PortFacade.GetDevice(1)
myDevice.Send(Command.Home)
PortFacade.GetConversation
Gets a Conversation object with the requested device number. See the description of Conversation above.
// C# or JavaScript sample script selecting a specific conversation using the GetConversation method
// you must open a port before running the script
#template(simple)
var myConversation = PortFacade.GetConversation(2);
myConversation.Request(Command.Home);
' VB sample script selecting a specific conversation using the GetConversation method
' you must open a port before running the script
#template(simple)
Dim myConversation As Conversation = PortFacade.GetConversation(1)
myConversation.Request(Command.Home)


PortFacade.Open
You can open a port from within Zaber Console before running your script or you can specify the port as a command line option when using Script Runner, but sometimes it's preferable to open the port directly from within your script (for example if your script is always run on the same COM port).
// C# sample script opening a port
// Does nothing if port is already open
#template(simple)
if (!PortFacade.IsOpen)          // make sure port is not already open
{
   PortFacade.Open("COM1");      // Open COM1
   Sleep(1000);                  // Wait for all devices to reply.
   // your code here
   PortFacade.Close();             // Close COM1
}
' VB sample script opening a port
' Does nothing if port is already open
#template(simple)
If Not PortFacade.IsOpen        ' make sure port is not already open
   PortFacade.Open("COM1")      ' Open COM1
   Sleep(1000)                  ' Wait for all devices to reply.
   ' your code here
   PortFacade.Close()           ' Close COM1
End If
Conversation.Request
Sends a request to a device synchronously (script waits for a response before continuing).
Conversation.Device.Send
Sends a request to a device asynchronously (script continues immediately).
Output.Writeline
Writes a line to the output.
Output.WriteLine("Hello World")
Input.ReadLine
Reads a line from the input.
String line = Input.ReadLine();
Sleep
waits a specified number of milliseconds
Sleep(1000)

Error handling

Communicating over the serial port to Zaber devices sometimes has problems, so you may want to add error handling to your scripts. If you have no error handling, then a communication problem will stop the script and pop up an error message. The devices will finish whatever moves they were executing and then stop. Adding error handling can let your script recover from problems and keep going.

The simplest technique is to avoid communication problems in the first place. The most common cause of problems on T-Series devices is sending commands to two devices at the same time. If they try to respond at the same time, they will occasionally interrupt each other and garble the responses. To avoid this, don't send requests to device zero, and wait for each device to finish its command before sending a request to the next device.

Another simple technique is to write your script so it just restarts when an error occurs. Here's a simple example:

// C# example of simple error handling with a restart strategy
#template(simple)

while ( ! IsCanceled)
{
    var conversation = PortFacade.GetConversation(0);
    try
    {
        while ( ! IsCanceled)
        {
            conversation.Request(Command.MoveAbsolute, 0);
            conversation.Request(Command.MoveRelative, 10000);
            conversation.Request(Command.MoveRelative, -7000);
            conversation.Request(Command.MoveRelative, 5000);
        }
    }
    catch (ConversationException ex)
    {
        Output.WriteLine("Exception: {0}", ex.Message);
    }
}

Note that it starts with an absolute move so that wherever the error occurs in the sequence, it can safely restart.

The next option is to automatically retry any commands that are interrupted by an error. That will usually just make the device send the response again, and the script can continue. The automatic retry feature is smart enough to know which commands are not safe to retry, and it will just report the error for those. One example is the MoveRelative command, so if you want to use the automatic retry feature, all your movements should be absolute. The automatic retry feature is only supported for binary mode, but it's much easier to avoid response collisions in ASCII mode, because all commands respond immediately. Here's a simple example:

// C# example of simple error handling with a retry strategy
#template(simple)

// Don't automatically reopen port if Renumber is detected.
// Sometimes a packet collision looks like a Renumber response.
PortFacade.IsInvalidateEnabled = false;

var conversation = PortFacade.GetConversation(0);
conversation.RetryCount = 10;
while ( ! IsCanceled)
{
    conversation.Request(Command.MoveAbsolute, 0);
    conversation.Request(Command.MoveAbsolute, 10000);
    conversation.Request(Command.MoveAbsolute, 3000);
    conversation.Request(Command.MoveAbsolute, 8000);
}

Note that all the relative moves have been changed to absolute moves.


More Sample Scripts

Polling position during a move

' VB Sample that shows how to send other requests while waiting for a move to complete.
' Sends a move request and then polls the position four times per second.
#template(methods)

Public Overrides Sub Run()
   ' Home the device
   Conversation.Request(Command.Home, 0)

   For i As Integer = 1 to 5
       MoveAndPoll(100000)
       MoveAndPoll(0)
   Next
End Sub

Sub MoveAndPoll(targetPosition as Integer)
   'The topic is used to track the response to our request so we can tell when
   'the move is complete.
   'These two lines are usually hidden inside a call to Conversation.Request()
   'along with a call to topic.Wait(). See the Zaber library help file for all
   'the details of what a ConversationTopic can do.
   Dim topic As ConversationTopic = Conversation.StartTopic()
   Conversation.Device.Send(Command.MoveAbsolute, targetPosition, topic.MessageId)

   Do While Not topic.IsComplete
       Sleep(250) ' pause between poll requests (milliseconds)

       'Request current position
       Dim position As Integer = Conversation.Request(Command.ReturnSetting, Command.SetCurrentPosition).Data
       Output.WriteLine(position)
   Loop

   'Check for any errors during the move request.
   topic.Validate()
End Sub

Simultaneous moves

Here are some example scripts that show how to move two devices at the same time. There are versions for C#, JavaScript, or Visual Basic, as well as for binary or ASCII mode. There is also one example for multi-axis devices.

// C# / JavaScript sample that demonstrates moving two devices simultaneously
// in binary mode.
#template(Simple)

// Get conversations for the two devices you want to move.
var c1 = PortFacade.GetConversation(1);
var c2 = PortFacade.GetConversation(2);

// Start a topic to wait for the response
var topic = c1.StartTopic();
// Send the command using Device.Send() instead of Request()
// Note the topic.MessageId parameter to coordinate request and response
c1.Device.Send(Command.Home, 0, topic.MessageId);

// While c1 is homing, also request c2 to home. This one just uses
// Request() because we want to wait until it finishes.
c2.Request(Command.Home);

// We know c2 has finished homing, so now wait until c1 finishes.
topic.Wait();

// We'll ask c1 to execute a long move, while c2 wiggles back and forth
// with several small moves.
topic = c1.StartTopic();
c1.Device.Send(Command.MoveAbsolute, 100000, topic.MessageId);
while ( ! topic.IsComplete)
{
	c2.Request(Command.MoveAbsolute, 10000);
	c2.Request(Command.MoveAbsolute, 0);
}

// We know it's already complete. This just checks for error responses.
topic.Validate();

c1.Request(Command.MoveAbsolute, 0);


' Visual Basic sample that demonstrates moving two devices simultaneously
' in binary mode.
#template(Simple)

' Get conversations for the two devices you want to move.
Dim c1 As Conversation = PortFacade.GetConversation(1)
Dim c2 As Conversation = PortFacade.GetConversation(2)

' Start a topic to wait for the response
Dim topic As ConversationTopic = c1.StartTopic()
' Send the command using Device.Send() instead of Request()
' Note the topic.MessageId parameter to coordinate request and response
c1.Device.Send(Command.Home, 0, topic.MessageId)

' While c1 is homing, also request c2 to home. This one just uses
' Request() because we want to wait until it finishes.
c2.Request(Command.Home)

' We know c2 has finished homing, so now wait until c1 finishes.
topic.Wait()

' We'll ask c1 to execute a long move, while c2 wiggles back and forth
' with several small moves.
topic = c1.StartTopic()
c1.Device.Send(Command.MoveAbsolute, 100000, topic.MessageId)
While (Not topic.IsComplete)
	c2.Request(Command.MoveAbsolute, 10000)
	c2.Request(Command.MoveAbsolute, 0)
End While

' We know it's already complete. This just checks for error responses.
topic.Validate()

c1.Request(Command.MoveAbsolute, 0)


// C# / JavaScript sample that demonstrates moving two devices simultaneously
// in ASCII mode.
#template(Simple)

// Get conversations for the two devices you want to move.
var c1 = PortFacade.GetConversation(1);
var c2 = PortFacade.GetConversation(2);

c1.Request("home");
// While c1 is homing, also request c2 to home.
c2.Request("home");
// Wait for both to finish moving. It doesn't matter which
// finishes first, this will check both.
c1.PollUntilIdle(); 
c2.PollUntilIdle();

// We'll ask c1 to execute a long move, while c2 wiggles back and forth
// with several small moves.
c1.Request("move abs 10000");

// The response to any command includes the IsIdle flag.
while ( ! c1.Request("get pos").IsIdle)
{
       c2.Request("move abs 10000");
       c2.PollUntilIdle();
       c2.Request("move abs 0");
       c2.PollUntilIdle();
}

c1.Request("move abs 0");
c1.PollUntilIdle();


' Visual Basic sample that demonstrates moving two devices simultaneously
' in ASCII mode.
#template(Simple)

' Get conversations for the two devices you want to move.
Dim c1 As Conversation = PortFacade.GetConversation(1)
Dim c2 As Conversation = PortFacade.GetConversation(2)

c1.Request("home")
' While c1 is homing, also request c2 to home.
c2.Request("home")
' Wait for both to finish moving. It doesn't matter which finishes first,
' this will check both.
c1.PollUntilIdle()
c2.PollUntilIdle()

' We'll ask c1 to execute a long move, while c2 wiggles back and forth
' with several small moves.
c1.Request("move abs 100000")

' The response to any command includes the IsIdle flag.
While (Not c1.Request("get pos").IsIdle)
       c2.Request("move abs 10000")
       c2.PollUntilIdle()
       c2.Request("move abs 0")
       c2.PollUntilIdle()
End While

c1.Request("move abs 0")


// C# / JavaScript sample that demonstrates moving two axes simultaneously
// on a multi-axis device.
#template(Simple)

// Get conversations for the device you want to use, as well as
// the two axes you want to move.
var both = PortFacade.GetConversation(1);
var axis1 = PortFacade.GetConversation(1, 1);
var axis2 = PortFacade.GetConversation(1, 2);

// When you want both axes to do the same thing, you can send it to the
// conversation for the whole device.
both.Request("home");
// Wait for both to finish moving.
both.PollUntilIdle();

// We'll ask axis 1 to execute a long move, while axis 2 wiggles back and forth
// with several small moves.
axis1.Request("move abs 100000");

// The response to any command includes the IsIdle flag.
while ( ! axis1.Request("get pos").IsIdle)
{
       axis2.Request("move abs 10000");
       axis2.PollUntilIdle();
       axis2.Request("move abs 0");
       axis2.PollUntilIdle();
}

axis1.Request("move abs 0");
axis1.PollUntilIdle();


Using the Conversation.Request method with an arbitrary command number

Sometimes you want to use an undocumented command or treat the commands as integers for some reason. This example shows you how to use the undocumented command 60 (Return Current Position). It's undocumented because it was replaced by command 45: Set Current Position. The equivalent to command 60 is Return Setting with data value 45. This functionality is not currently available for Visual Basic.

// Arbitrary command number - C#
// the command number must be cast to the Command type
#template(Simple)

var position = Conversation.Request((Command)60).Data;
Output.WriteLine("Current position is {0}", position);
// Arbitrary command number - JavaScript
// No casting required
#template(Simple)

var position = Conversation.Request(60).Data;
Output.WriteLine("Current position is {0}", position);

Reading and writing user memory

Zaber motion devices let you store any data you like in 128 bytes of user memory. This script shows how to write a text string into that memory, but the same writeByte() method could be used to store binary data, too. For a complete description, see the Read Or Write Memory Command.

// JavaScript sample script that demonstrates writing to user memory
#template(simple)

// Ask the user for some text
var maxLength = 128;
var message = inputMessage(maxLength);
if (message == null)
{
    return;
}

// Write each byte of the text into user memory
for (var i = 0; i < message.Length; i++)
{
    writeByte(i, message[i]);
}

// Write a null byte to mark the end of the text
if (message.Length < maxLength)
{
    writeByte(message.Length, 0);
}


function writeByte(index, value)
{
    // The 128 indicates this is a write command. See the user manual for details.
    var byte3 = 128 | index;
    var byte4 = Convert.ToByte(value);

    // Combine the two bytes into the data value that we send
    Conversation.Request(Command.ReadOrWriteMemory, byte4 << 8 | byte3);
}

function inputMessage(maxLength)
{
    while (true)
    {
        Output.WriteLine("Please type the message you want to write in the device's user memory.");
        var message = Input.ReadLine();
        if (message == null)
        {
            return;
        }

        var bytes = Encoding.UTF8.GetBytes(message);
        if (bytes.Length <= maxLength)
        {
            return bytes;
        }
        Output.WriteLine(
            "Message is {0} bytes longer than the maximum.",
            bytes.Length - maxLength);
    }
}

Once you've written to the memory, this script demonstrates how to read from it.

// JavaScript sample script that demonstrates reading from user memory
// Run the writing sample first, or you'll get random garbage.
#template(simple)

var maxLength = 128;
var message = new Byte[maxLength];
var byteValue = 0;
var byteCount = 0;
do
{
    byteValue = readByte(byteCount);
    if (byteValue != 0)
    {
        message[byteCount++] = byteValue;
    }
}while (byteValue != 0 && byteCount < maxLength);

Output.WriteLine(Encoding.UTF8.GetString(message, 0, byteCount));

function readByte(index)
{
    // If you just send an index, it's a read command.
    var result = Conversation.Request(Command.ReadOrWriteMemory, index).Data;

    // The value from user memory is in the second byte of the response data
    // (byte 3 of the packet). See the user manual for details.
    return (result >> 8) & 0xFF;
}

Simple Move Tracking

// JavaScript sample of tracking position updates during a move
#template(simple)

// Turn on the move tracking device mode.
var oldMode = Conversation.Request(Command.ReturnSetting, Command.SetDeviceMode).Data;
var newMode = oldMode | DeviceModes.EnableMoveTracking;
Conversation.Request(Command.SetDeviceMode, newMode);

// Turn on message ids so the conversation can distinguish
// the updates from the final response at the end of the move.
PortFacade.AreMessageIdsEnabled = true;

var maxRange = 
	Conversation.Request(Command.ReturnSetting, Command.SetMaximumRange).Data;

var listener = new DeviceListener(Conversation.Device);

var topic = Conversation.StartTopic();
Conversation.Device.Send(Command.MoveAbsolute, 0, topic.MessageId);
TrackResponses(listener);
topic.Wait();
topic.Validate();

topic = Conversation.StartTopic();
Conversation.Device.Send(Command.MoveAbsolute, maxRange, topic.MessageId);
TrackResponses(listener);
topic.Wait();
topic.Validate();

function TrackResponses(listener)
{
	var isDone = false;
	while ( ! isDone)
	{
		var isBlocking = true;
		var response = listener.NextResponse(isBlocking);
		Output.WriteLine(
			"{0:HH:mm:ss.fff} {1}",
			DateTime.Now,
			response.FormatResponse());
		isDone = response.Command != Command.MoveTracking;
	}
}


General Move Tracking

// JavaScript sample to track the position of the selected device with time stamps.
// Run this script and then move the device with the control knob or by sending it commands.
// Click the Cancel button to stop tracking.
// If you leave fileName null, this will just write the position data to the
// output window. To write it to a file, assign a value to fileName.
// The default folder for the file is My Documents.
#template(simple)

var fileName = null;
// Uncomment the next line to write the output to a file
// fileName = "TrackPosition.csv";
var defaultFolder = 
    Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

var writer = new Writer(fileName, defaultFolder);
try
{
   var listener = new DeviceListener(Conversation.Device);
   writer.WriteDestinationMessage();
   writer.WriteHeader();

   while (!IsCanceled)
   {
       var response = listener.NextResponse();
       if (IsPositionUpdate(response))
       {
           writer.WriteResponse(response);
       }
   }
   writer.WriteDestinationMessage();
}finally
{
   writer.Close();
}

function IsPositionUpdate(response)
{
   return response.CommandInfo != null &&
       response.CommandInfo.IsCurrentPositionReturned;
}

function Writer(name, defaultFolder)
{
   this.name = name;
   if (name != null)
   {
       if (!Path.IsPathRooted(name))
       {
           this.name = Path.Combine(
               defaultFolder,
               name);
       }
       this.writer = File.CreateText(this.name);
   }

   this.WriteHeader = function()
   {
       var header = "Device number,Date/Time,Position";
       Output.WriteLine(header);
       if (this.writer != null)
           this.writer.WriteLine(header);
   }

   this.WriteResponse = function(response)
   {
       var line = String.Format(
           "{0},{1:yyyy-MM-dd HH:mm:ss.fff},{2}", 
           response.DeviceNumber,
           DateTime.Now,
           response.Data);
       Output.WriteLine(line);
       if (this.writer != null)
           this.writer.WriteLine(line);
   }

   this.WriteDestinationMessage = function()
   {
       Output.WriteLine(
           this.name == null
           ? "Output destination: Display only (edit the script if you wish to specify an output file)"
           : "Output destination: {0}",
           this.name);
   }

   this.Close = function()
   {
       if (this.writer != null)
           this.writer.Close();
   }
}

Read settings and wiggle

This is a helpful diagnostic script to make sure everything is working on a device.

// C# script to dump settings and try movement on a device.
#template(simple)

const int MAX_TIME = 60; // maximum running time in seconds

if ( ! Conversation.Device.IsSingleDevice)
{
   Output.WriteLine("ERROR: Please select a different device number.");
   return;
}

var targetSpeed = -1;
var maxRange = -1;
var commands = Conversation.Device.DeviceType.Commands;
foreach (var command in commands)
{
   if (command.IsReadOnlySetting)
   {
       var value = Conversation.Request(command.Command).Data;
       Output.WriteLine("{0} = {1}", command.Name, value);
   }
   else if (command.IsSetting)
   {
       var value = Conversation.Request(
           Command.ReturnSetting,
           (int)command.Command).Data;
       Output.WriteLine("{0} = {1}", command.Name, value);
       switch (command.Command)
       {
           case Command.SetTargetSpeed:
               targetSpeed = value;
               break;
           case Command.SetMaximumRange:
               maxRange = value;
               break;
       }
   }
}

Output.WriteLine();

if (targetSpeed < 0)
{
   Output.WriteLine("Not a motion device.");
}
else if (targetSpeed == 0)
{
   Output.WriteLine("Target speed is zero, can't move.");
}
else
{
   // Calculate how many wiggles we can do in MAX_TIME seconds
   var cycleCount = Convert.ToInt32(Math.Floor(
       MAX_TIME * 9.375 * targetSpeed / maxRange / 2));
   if (cycleCount <= 0)
   {
       Output.WriteLine("Target speed is too slow for cycling.");
   }
   else
   {
       Output.WriteLine("{0} cycles starting at {1}", cycleCount, DateTime.Now);
   }
   for (int i = 0; i < cycleCount; i++)
   {
       Conversation.Request(Command.MoveAbsolute, 0);
       Output.WriteLine("Position 0 at {0}", DateTime.Now);
       Conversation.Request(Command.MoveAbsolute, maxRange);
       Output.WriteLine("Position {0} at {1}", maxRange, DateTime.Now);
   }
}


Suppress the Stop command when canceled

Sometimes, you don't want to send a Stop command when you cancel a running script. To do that, you can use the Methods template and override the Cancel() method.

/* C# example to show how to suppress the Stop command when the
 * script is canceled. Also, how to interrupt a Sleep command
 * without throwing an exception.
 */
#template(Methods)

public override void Run()
{
    while ( ! IsCanceled)
    {
        Output.WriteLine("Hello, World!");
        Sleep(2000, SleepCancellationResponse.Wake);
    }
}

public override void Cancel()
{
    // Setting IsCanceled will interrupt the Sleep() call.
    IsCanceled = true;
}

Sinusoidal Motion

There is a C# script available for approximating sinusoidal motion. The script sends constant speed commands at 25 ms intervals in a sinusoidal profile. The last 100 ms of the profile is used to move to the endpoints so that the start and end spots do not drift over time. The delay times have been adjusted to get the timing right, but the exact frequency is only accurate to about 2% since the Sleep() command can only be adjusted by integer values. The script was originally written for a T-NA08A25, with a stroke of 2 mm and a maximum frequency of 0.5 Hz in mind, but it is likely usable for a wide range of stages, strokes, and frequencies. Obviously the limits of operation of the exact stage should be taken into account when considering the kinds of oscillations that are possible.

This script relies on precise timing of the speed changes and that timing may differ from one machine to the next. You may need to adjust the parameter DELAY_TIME to get the code to work properly on your computer. In addition, USB to serial port converters introduce variable and unpredictable delays into the system and likely cannot be made to work with this code.

This script uses the speed conversion calculation for T-Series devices, but it includes commented out lines for use with A-Series or X-Series devices. If you'd like to use this script with A-Series or X-Series devices, comment out the lines that mention T-Series by adding // at the beginning of the line, and uncomment lines that mention A-Series or X-Series by deleting the // at the start of their lines.

#template(methods);
private const decimal MSTEP_SIZE = 0.047625M;                                       /* T-NA default microstep size */
private const decimal FREQUENCY = 0.5M;                                             /* Frequency in Hz */
private const int CYCLE_DISTANCE = 2000;                                            /* distance in um */
private const int MAXCYCLES = 10;                                                   /* Number of cycles for test */
private const int DELAY_TIME = 6;                                                   /* Variable delay time depending on computer */
public override void Run()
{
   double W = (double) (FREQUENCY * 6.283185307M);                                 /* Frequency in radians */
   int Amplitude = (int) Decimal.Round(CYCLE_DISTANCE / (2 * MSTEP_SIZE));         /* Amplitude in microsteps */
   int SampleNumber = (int) Decimal.Round( 20 / FREQUENCY - 4 );                   /* Samples required at 25 ms rate*/
   
   Int32 OriginalSpeed = Conversation.Request(Command.ReturnSetting, 42).Data;     /* Read target speed before cycling starts */ 
   Int32 StartPosition = Conversation.Request(Command.ReturnSetting, 45).Data;     /* Make current spot start location */
   Int32 StopPosition = StartPosition + 2 * Amplitude;                             /* Compute end location */
   Int32 DefaultSpeed = (Int32) (10 * Amplitude * (1 - Math.Cos(W * 0.1))/9.375);  /* Default speed for cycle end speed for T-Series */
   //Int32 DefaultSpeed = (Int32) (10 * Amplitude * (1 - Math.Cos(W * 0.1))*1.6384);  /* Default speed for cycle end speed for A-Series or X-Series */
   
   Conversation.Request(Command.SetTargetSpeed, DefaultSpeed);                     /* Send default speed to actuator */
   
   for (int j = 0; j < MAXCYCLES; j++)                                             /* Outer loop for number of cycles */
   {
       for (int i = 1; i < SampleNumber; i++)                                      /* Inner loop for outward motion */
       {
           int Speed = (int) (Amplitude * W * Math.Sin(W * 0.025 * (i+2))/9.375);  /* Calculate sinusoidal speed for T-Series Devices */
           //int Speed = (int) (Amplitude * W * Math.Sin(W * 0.025 * (i+2))*1.6384); /* Calculate sinusoidal speed for A-Series or X-Series Devices */
           Conversation.Request(Command.MoveAtConstantSpeed, Speed);               /* Send speed to actuator */
           Sleep (DELAY_TIME);                                                     /* Delay for required time to total 25 ms */
       }
       
       Conversation.Request(Command.MoveAbsolute, StopPosition);  /* Complete outward move at constant speed to hit end location */
       for (int i = 1; i < SampleNumber; i++)                                      /* Inner loop for return motion */
       {
           int Speed = (int) (-Amplitude * W * Math.Sin(W * 0.025 * (i+2))/9.375); /* Calculate sinusoidal speed for T-Series Devices */
           //int Speed = (int) (-Amplitude * W * Math.Sin(W * 0.025 * (i+2))*1.6384); /* Calculate sinusoidal speed for A-Series or X-Series Devices */
           Conversation.Request(Command.MoveAtConstantSpeed, Speed);               /* Send speed to actuator */
           Sleep (DELAY_TIME);                                                     /* Delay for required time to total 25 ms */
       }
       Conversation.Request(Command.MoveAbsolute, StartPosition); /* Complete inward move at constant speed to hit start location */
       Output.Write("Number of Cycles Completed:  ");                              /* Update cycle counter to screen */
       Output.Write((j+1));
       Output.WriteLine();
   }
   Conversation.Request(Command.SetTargetSpeed, OriginalSpeed);                    /* Return target speed to original speed */
}

Save position when powering down

When you turn a device off, it forgets its current position. This script uses the Store Current Position command to record the position before you turn off the power. It also turns off hold current so that the motor doesn't jump to a different phase when you turn the power on. When you turn the device on, just run this script again. It will set the position back to what it was, and then turn the hold current back on.

Before you use this script, check what the hold current is, and edit the script to match. See the Set Current Position command for more details. In more recent firmware versions, there is a park command that does these steps automatically.


// C# script to store the current position before powering down
// Run it again to restore the position after power up
#template(Simple)

const int NEW_HOLD_CURRENT = 20;
const int STORED_POSITION_INDEX = 0;

// First check what the hold current is.
int oldHoldCurrent = Conversation.Request(
   Command.ReturnSetting,
   (int)Command.SetHoldCurrent).Data;
// Next, determine if we just powered on (haven't homed yet).
DeviceModes mode = (DeviceModes)Conversation.Request(
   Command.ReturnSetting, 
   (int)Command.SetDeviceMode).Data;
bool isHomed = (mode & DeviceModes.HomeStatus) == DeviceModes.HomeStatus;

if (isHomed)
{
   // Record position and power down.
   if (oldHoldCurrent != NEW_HOLD_CURRENT)
   {
       throw new InvalidOperationException(String.Format(
           "Hold current is now {0}, but the script will reset to {1}. " +
           "Edit the script or set the hold current.",
           oldHoldCurrent,
           NEW_HOLD_CURRENT));
   }
   Conversation.Request(Command.StoreCurrentPosition, STORED_POSITION_INDEX);
   Conversation.Request(Command.SetHoldCurrent, 0);
   Output.WriteLine("Position saved.");
}
else
{
   // Restore position and power up.
   if (oldHoldCurrent != 0)
   {
       throw new InvalidOperationException(
           "Hold current is on, the device was not shut down properly.");
   }
   int storedPosition = Conversation.Request(
       Command.ReturnStoredPosition, 
       STORED_POSITION_INDEX).Data;
   Conversation.Request(Command.SetCurrentPosition, storedPosition);
   Conversation.Request(Command.SetHoldCurrent, NEW_HOLD_CURRENT);
   Output.WriteLine("Position restored.");
}


' VB script to store the current position before powering down
' Run it again to restore the position after power up
#template(Simple) 

Dim NEW_HOLD_CURRENT = 20
Dim STORED_POSITION_INDEX = 0 

' First check what the hold current is.
Dim oldHoldCurrent = Conversation.Request(Command.ReturnSetting, Command.SetHoldCurrent).Data
Dim HomeStatus = Conversation.Request(Command.ReturnSetting,103).Data
Dim isHomed As Boolean

If HomeStatus = 1 then isHomed = True Else isHomed = False

If (isHomed)
	'Record position and power down.
   If (oldHoldCurrent <> NEW_HOLD_CURRENT)
       throw new InvalidOperationException(String.Format(
           "Hold current is now {0}, but the script will reset to {1}. " +
           "Edit the script or set the hold current.",
           oldHoldCurrent,
           NEW_HOLD_CURRENT))
   End If
   Conversation.Request(Command.StoreCurrentPosition, STORED_POSITION_INDEX)
   Conversation.Request(Command.SetHoldCurrent, 0)
   Output.WriteLine("Position saved.") 

Else
   ' Restore position and power up.
   If (oldHoldCurrent <> "0")
       throw new InvalidOperationException(
           "Hold current is on, the device was not shut down properly.")
   End if
   Dim storedPosition = Conversation.Request(
       Command.ReturnStoredPosition,STORED_POSITION_INDEX).Data
   Conversation.Request(Command.SetCurrentPosition, storedPosition)
   Conversation.Request(Command.SetHoldCurrent, NEW_HOLD_CURRENT)
   Output.WriteLine("Position restored.")

End If

Units of Measure

The device and conversation classes will handle unit of measure conversion for you. The simplest technique is to declare a measurement variable that holds a single unit, and then multiply that by the actual value you want, so 50mm becomes 50*mm. Units of measure are supported in both ASCII mode and binary mode. See the ZaberDevice class in the help file for more support of units of measure.

// Unit of measure sample in Binary mode - C# or JavaScript
#template(simple)
var mm = new Measurement(1, UnitOfMeasure.Millimeter);
var position = Conversation.Request(Command.ReturnCurrentPosition).Measurement;
Output.WriteLine("Starting position is {0}.", position);

for (var i = 0; i < 5; i++)
{
    Conversation.RequestInUnits(Command.MoveRelative, 10*mm);
}

position = Conversation.Request(Command.ReturnCurrentPosition).Measurement;
Output.WriteLine("Retracted position is {0}.", position);

Conversation.RequestInUnits(Command.MoveRelative, -50*mm);

Note that in ASCII mode, unit of measure is only defined for each axis and not the device itself. Using units of measure on a device conversation will result in an exception.

// Unit of measure sample in ASCII mode - C# or JavaScript
#template(simple)
var mm = new Measurement(1, UnitOfMeasure.Millimeter);
var position = Conversation.Axes[0].Request("get pos").Measurement;
Output.WriteLine("Starting position is {0}.", position);

for (var i = 0; i < 5; i++)
{
    Conversation.Axes[0].RequestInUnits("move rel", 10*mm);
    Conversation.Axes[0].PollUntilIdle();
}

position = Conversation.Axes[0].Request("get pos").Measurement;
Output.WriteLine("Retracted position is {0}.", position);

Conversation.Axes[0].RequestInUnits("move rel", -50*mm);
Conversation.Axes[0].PollUntilIdle();