Software/Zaber Console/Scripting
Warning: This page describes features that have not been officially released yet.
Scripts 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.
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 mode or ASCII mode. For example, move absolute can either be Conversation.Request(Command.MoveAbsolute, 10000)
(binary mode) or Conversation.Request("move abs 10000")
(ASCII mode). By default, the communication mode depends on the device's connector type. D-sub 9 connectors default to ASCII mode, and Minidin 6 connectors default to binary mode.
Connector | D-sub 9 | Minidin 6 |
---|---|---|
Diagram | ![]() |
![]() |
Default | ASCII | Binary |
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:
- Read the beginning of the script to see what language it's written in.
- Use the mouse to select the script. Make sure you don't select anything else before or after the script.
- Type Ctrl-C to copy the script to the clipboard.
- Run the Zaber Console or switch back to it if it's already running.
- Most scripts assume the Zaber devices are turned on and the port is already open, so click the Open button if you need to.
- Click on the scripts tab.
- 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.
- In the Script Editor window, click on the Language menu and click the language that the script is written in.
- From the File menu, choose New.
- Click in the main field and type Ctrl-V to paste the script.
- Some scripts need you to select a device number, click on the drop down to see a list of device numbers.
- 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()
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 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("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:
- Click the "Run" button next to it in the Zaber Console scripts tab
- Open the script in the Script Editor and select "Run Script" from the Run menu.
- 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.
- Open System Properties (Win+Pause)
- Switch to the Advanced tab
- Click Environment Variables
- Select PATH in the System variables section
- Click Edit
- 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
- 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) 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# or 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# or 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# or 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 1000000"); // 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. #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 1000000"); // 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.
// 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 - Visual Basic ' No casting required #template(Simple) Dim position as Integer = Conversation.Request(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); } }
Rotation Shortcut on T-RS60A
#template(Methods) /// <summary> /// C# sample script for positioning a rotation stage with the shortest move. /// </summary> /// <remarks> /// This is the main method. It reads requests from the user and displays the /// current position. /// </remarks> public override void Run() { if (Conversation.Device.DeviceType.DeviceId != 560) { Output.WriteLine("Please select a T-RS60A device before running the script."); return; } string request; do { Output.WriteLine("Enter an angle in degrees or 'Q' to quit."); request = Input.ReadLine(); double angle; bool isValid = Double.TryParse(request, out angle); if (isValid) { try { double displayAngle = MoveToAngle(angle); Output.WriteLine("Moving to {0}°.", displayAngle); }catch (InvalidOperationException e) { Output.WriteLine(e.Message); } } else if (request == null || request.ToUpper() == "Q") { request = null; Output.WriteLine("Quit."); } else { Output.WriteLine("Please enter a valid number or the letter 'Q'."); } }while (request != null); } /// <summary> /// Calculate the shortest move to a requested angle and move there. /// </summary> /// <param name="requestedAngle">The angle in degrees to move to, may be less /// than 0 or more than 360.</param> /// <returns>The display angle that the device will move to in the /// range [0, 360).</returns> double MoveToAngle(double requestedAngle) { if ( ! PortFacade.IsOpen) { throw new InvalidOperationException("Please open the port before requesting a move."); } var modes = (DeviceModes)Conversation.Request( Command.ReturnSetting, (int)Command.SetDeviceMode).Data; if ((modes & DeviceModes.HomeStatus) == DeviceModes.None) { throw new InvalidOperationException("Please home the device before requesting a move."); } // Disable auto-home mode so that we can rotate past the 0 position without // triggering the home sensor. if ((modes & DeviceModes.DisableAutoHome) == DeviceModes.None) { Conversation.Request( Command.SetDeviceMode, (int)(modes | DeviceModes.DisableAutoHome)); } int microstepResolution = Conversation.Request( Command.ReturnSetting, (int)Command.SetMicrostepResolution).Data; // Calculated this number from the specs page double degreesPerMicrostep = 0.015 / microstepResolution; int currentMicrostep = Conversation.Request( Command.ReturnSetting, (int)Command.SetCurrentPosition).Data; double currentAngle = currentMicrostep * degreesPerMicrostep; double destinationAngle = requestedAngle; // Now adjust the destination angle is at most 180 degrees away while (destinationAngle <= currentAngle - 180) { destinationAngle += 360; } while (destinationAngle > currentAngle + 180) { destinationAngle -= 360; } int destinationMicrostep = (int)Math.Round(destinationAngle / degreesPerMicrostep); // The display on the device has range [0, 360), so calculate where we // will land on the display. double displayAngle = requestedAngle; while (displayAngle >= 360) { displayAngle -= 360; } while (displayAngle < 0) { displayAngle += 360; } CheckRangeAndMoveTo( destinationMicrostep, (int)Math.Round(360 / degreesPerMicrostep)); return displayAngle; } /// <summary> /// Perform a move after checking for collisions with range boundaries. /// </summary> /// <param name="destinationMicrostep">The position we want to move to</param> /// <param name="microstepsPerRotation">The number of microsteps in a full /// rotation at the current microstep resolution</param> void CheckRangeAndMoveTo(int destinationMicrostep, int microstepsPerRotation) { int maxRange = Conversation.Request( Command.ReturnSetting, (int)Command.SetMaximumRange).Data; int adjustedDestination = destinationMicrostep; bool isChangeNeeded = destinationMicrostep < 0 || maxRange < destinationMicrostep; if (isChangeNeeded) { // Send a stop command in case the device is currently moving. // We can't adjust the current position while moving. int adjustedCurrent = Conversation.Request(Command.Stop).Data; while (adjustedDestination < 0) { adjustedCurrent += microstepsPerRotation; adjustedDestination += microstepsPerRotation; } if (adjustedDestination > maxRange || adjustedCurrent > maxRange) { adjustedCurrent -= microstepsPerRotation; adjustedDestination -= microstepsPerRotation; } if (adjustedDestination < 0 || adjustedCurrent < 0) { throw new InvalidOperationException("The maximum range is set too low to allow that angle."); } Conversation.Request( Command.SetCurrentPosition, adjustedCurrent); } // At long last, the actual move command. Notice that this is a send, not // a request, so we don't wait for the move to complete. // Replace Conversation.Device.Send with Conversation.Request if you want // to wait for the move to complete. Conversation.Device.Send(Command.MoveAbsolute, adjustedDestination); }
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.
#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 */ 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 */ 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 */ 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."); }
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 ASCII mode - C# or JavaScript #template(simple) var mm = new Measurement(1, UnitOfMeasure.Millimeter); var position = Conversation.Request("get pos").Measurement; Output.WriteLine("Starting position is {0}.", position); for (var i = 0; i < 5; i++) { Conversation.RequestInUnits("move rel", -10*mm); Conversation.PollUntilIdle(); } position = Conversation.Request("get pos").Measurement; Output.WriteLine("Retracted position is {0}.", position); Conversation.RequestInUnits("move rel", 50*mm); Conversation.PollUntilIdle();