Quickstart
This quickstart is a collection of basic Wandelscript syntax elements with examples.
Indentation
When using Wandelscript, indentation is crucial. Here are the main rules that apply for Wandelscript:
The indentation level is used to define the scope of a block of code.
do with ur10[0]
move frame("Flange") via p2p() to (50, 20, 30, 0, pi, 0)
We recommend an indentation level of 4 spaces. The amount of spaces is up to you, but it has to be at least one.
do with ur10[0]
move frame("Flange") via p2p() to (50, 20, 30, 0, pi, 0)
You have to use the same amount of indentation when nesting blocks of code.
do with ur10[0]:
for i = 0..5:
move frame("Flange") via p2p() to (50, 20, 30, 0, pi, 0)
# This will throw an error
do with ur10[0]:
for i = 0..5:
move frame("Flange") via p2p() to (50, 20, 30, 0, pi, 0)
As Wandelscript uses an indentation style similar to Python, we recommend this article (opens in a new tab) for further details on Python indentation.
Comments
Comments are declared with a #
as a single line until the end of the line.
# This is a comment
Execution order
Wandelscript execution happens step-by-step, consecutively.
home = (-153.3, -538.1, 454.9, 3.068, -0.623, -0.13)
move via p2p() to home
wait(500)
print("Home reached.")
First, home is declared, second the robot moves to home, and after 500 milliseconds "Home reached." is printed.
There are a couple of exceptions and commands that influence the execution order. Read more about them here.
Precedence
Wandelscript precedence is determined from left to right on the same level. As per usual you can use parentheses ()
to enforce a specific order of precedence.
The following table lists the order of precedence from highest to lowest.
Level of precedence | Description |
---|---|
() | Parentheses, overrules precedence from left to right for operators on the same level |
+n , -n , ~n , not | Unary plus, unary minus, inverse, logical not |
* , / , :: | Multiplication, division, pose concatenation |
+ , - | Addition, subtraction |
> , < , == , <= , >= , != | Comparison expressed by booleans |
and , or | Logical and, logical or |
Learn more about which operators exist in Wandelscript further down the page or skip to the table right now.
Entry point
An entry point of a Wandelscript program is declaring the home
pose of the robot.
The home
pose acts as a reference point for a robot to return to before or after executing a movement.
home = (139.9, -37.3, 319.8, -0.9, -2.5, -1.1)
Print to the standard output
print
prints its argument to the standard output:
print("hello world")
Wait
wait()
waits for the indicated amount of time in milliseconds:
wait(1000)
Functions
Wandelscript functions are declared using the def
keyword.:
def <function_name>(<parameters>):
<code_block>
Separate parameters by commas. Types are not supported in Wandelscript.
def function(value1, value2):
More on how functions can be used as extensions can be found here.
get_controller
To easily identify different controllers, you can use the get_controller
function:
<variable> = get_controller("controller_id")
fluffy_cat = get_controller("ur-controller")
do with fluffy_cat:
move frame("Flange") via p2p() to (100, 200, 300, 0, pi, 0)
More information on how to apply get_controller
can be found here.
Variables
In Wandelscript, you declare a variable by assigning a value to it with =
:
pose = (100, 200, 300, 0, pi, 0)
-
and .
are not allowed in variable names.
When assigning variables, you can specify data types supported by Wandelscript. Here's a selection of the most popular data types, go to Data types for all supported data types.
Primitives
str_val = 'Hello, World!'
int_val = 20
float_val = 20.5
bool_val = True
Pose
# x, y, z, rx, ry, rz
pose = (100, 200, 300, 0, pi, 0)
Lists
a = [1, 2, 3, 4, [5, 6]]
b = a[2] # 3
c = a[4] # [5, 6]
d = c[1] # 6
e = c[1] # 6
Read pose and joint values from robot
As often required during programming, you can read the current pose of the robot with:
current_pose = read(robot, 'pose')
and joint values with:
current_joints = read(robot, 'joints')
Operators
Unary
Operator | Name | Description | Example |
---|---|---|---|
-n | Sign | The variable b is assigned with negation of a . In other words stem:b = -1 * a . | b = -a |
~ | Inverse | The inverse operator calculates the inverse of a pose. | pose_b2a = ~pose_a2b |
not | Logical not | Returns boolean values true for falsy values and false for truthy values. | a = True b = ~a c = ~b d = not c e = not d print(a, b, c, d, e) returns a= True , b=False , c=True , d=False , e=True |
Binary
Operator | Name | Description | Example |
---|---|---|---|
+ | Addition | Add two numbers and assign them to the variable a . | a = 4 + 5 |
- | Subtraction | Subtract two numbers and assign them to the variable b . | b = 4 - 1 |
* | Multiplication | Multiply two numbers and assign them to the variable c . | c = 10 * 2 |
/ | Division | Divide two numbers and assign them to the variable d . When divided by 0 the Wandelengine will throw an error. | d = 4 / 2 |
:: | Pose concatenation | Concatenation of two poses and assigning them to a new variable, e.g. tcp2robot . Given three frames, e.g. tcp , object , and robot , and two frame relations as poses, e.g., tcp2object and object2robot , the relation tcp2robot can be obtained by the pose concatenation. If 2 poses are concatenated, the operator returns a pose. If one or both of the operants are positions, it returns a position. More on pose concatenation here. | tcp2robot = object2robot :: tcp2object |
> , < , == , <= , >= , != | Comparison | Wandelscript supports the most used boolean expressions for comparisons. | Lower: a < b Greater: a > b Lower equal: a <= b Greater equal: a >= b Equal: a == b Not equal: a != b |
and | Logical and | Verifies if all values are truthy. If yes, returns the last truthy value. If not, returns the first falsy value. | All are truthy: 2 and 5 returns 5 At least one is falsy: 0 and 2 returns 0 |
or | Logical or | Verifies if one of the values is truthy. If one of them is truthy, it returns the truthy value. If both of them are truthy, it returns the first truthy value. If both of them are falsy, it returns the last falsy value. | One is truthy: 0 or 5 returns 5 Both are truthy: 2 or 5 returns 2 Both are falsy: 0 or False returns False |
Range
Operator | Name | Description | Example |
---|---|---|---|
.. | Range including last element | Create a range from a to b that does include b . | for a = 1..4: # a = will be 1, 2, 3, and 4 |
..< | Range excluding last element | Create a range from a to b that does not include b . | for a = 1..<4: # a = will be 1, 2, and 3 |
Control Flow
Control flow statements are used to control the flow of execution in a program's script. Wandelscript supports the following control flow statements:
sync
sync
synchronizes all previously executed commands regardless of indentation.
move frame("Flange") via p2p() to (150, -355, 389, 0, 0, 0)
move frame("Flange") via line() to (-95, -363, 387)
sync
write(io, "digital_out[7]", True)
With sync
, the I/O digital_out[7]
is updated after all movements are finished thus ensuring that the step-by-step execution is maintained.
Read more on synchronization here.
If Else
if
is used to run a code block only when a certain condition is met.
Add more conditions with elif
.
Use else
to define items to be executed when non of the if
conditions were met.
a = 10
if a == 10:
print("a is 10")
elif a == 20:
print("a is 20")
else:
print("a is not 10 or 20")
Switch
switch
is syntactic sugar for an if...elif...else, e.g. checking a variable for a specific value. default
is required when using switch
.
s = "c"
b = 0
switch s:
case "a": b = 2
case "b": b = 3
case "c": b = 4
case "c": b = 5
default: b = 6
for loops
for
executes specific iterations until a specific condition has been met.
c = 0
arr = [1, 2, 3]
for i = 0 ..< len(arr):
c = c + arr[i]
Range Operator:
for i = 1..<4:
print(i) # 1, 2, 3
for i = 1..4:
print(i) # 1, 2, 3, 4
While
While
executes the defined code block repeatedly as long as the defined condition is met.
i = 0
while i < 10:
i = i + 1
To create an infinite loop, use while True:
. Remember to add an explicit sync
to end the loop.
Movement commands
move
is a first-class citizen command that describes a robot movement by defining the desired <tcp>
, the movement's motion type <connector>
and target pose <pose>
or joint configuration <joint values>
:
move <tcp> via <connector> to <pose>
move <tcp> via <connector> to <joint values>
Set TCP
Set the TCP to use for the movement by using the TCP's coordinate system with frame
, or by using tcp
:
move frame("<tcp_id>") via p2p() to (100, 200, 300, 0, pi, 0)
move tcp("<tcp_id>") via joint_p2p() to [1.890, -1.135, 2.5690, -1.134, 1.8912, 1.28]
More information on how to get available TCPs here.
In this case, Flange
is one of the available TCPs:
move frame("Flange") via p2p() to (100, 200, 300, 0, pi, 0)
move tcp("Flange") via joint_p2p() to [1.890, -1.135, 2.5690, -1.134, 1.8912, 1.28]
Set connector
The following <connectors>
are available to specify the motion type of the movement:
Point to point (P2P)
p2p
: The fastest possible motion from A to B with cartesian movement. Depends on robot and current joint configuration. The robot's end joint configuration can differ from the starting joint configuration.
Indicate the target pose with a pose with cartesian values.
pose = (100, 200, 300, 0, pi, 0)
move frame("Flange") via p2p() to pose
Modify the target pose at runtime with expressions.
Linear
line
: A linear motion from pose A to B. The robot stays exactly on the linear path between A and B.
pose = (100, 200, 300, 0, pi, 0)
move frame("Flange") via line() to pose
Modify the target pose at runtime with expressions.
Circular
arc
: A circular motion from A to B over a point C.
pose = (100, 200, 300, 0, pi, 0)
move frame("Flange") via arc(150, 200, 300, 0, pi, 0) to pose
Modify the target pose at runtime with expressions.
Joint point to point (Joint P2P)
joint_p2p
: The shortest possible motion from A to B optimized for the robot movement. Not necessarily a straight line from A to B as the robot avoids reaching its joint limits and thus blocking its movements. Ensures that the robot ends in the correct joint configuration.
Indicate the target joint configuration with a list of joint values, using .
for decimal values.
joints = [1.890, -1.135, 2.5690, -1.134, 1.8912, 1.28]
move frame("Flange") via joint_p2p() to joints
Relative to a position
Sometimes you want to move the robot relative to a certain position. You can achieve this with the ::
operator to concatenate the current pose with the relative pose. In the following example the robot will move 50mm in z direction in the flange coordinate system.
pose = (100, 200, 300, 0, pi, 0)
# move 50mm in z direction in the flange coordinate system
move frame("Flange") via p2p() to pose :: (0, 0, 50)
Remember, the order of the poses is important. In the following example the TCP will move in the robot coordinate system, 50mm in z direction.
pose = (100, 200, 300, 0, pi, 0)
# move 50mm in z direction in the robot coordinate system
move frame("Flange") via p2p() to (0, 0, 50, 0, pi, 0) :: pose
Modify the target pose at runtime with expressions.
Execute movements
To execute a movement on the robot, place a sync
after the move
command. If you group multiple movements together, they will get bundled.
The sync
ensures that they are recognized as one movement group and Wandelscript written afterwards is executed once the movements have been completed.
move frame("Flange") via p2p() to (150, -355, 389, 0, 0, 0)
move frame("Flange") via line() to (-95, -363, 387)
sync
print("Movement completed")
More on how to use sync
with movements can be found here.
Movement settings
The movement settings supported by Wandelscript are velocity
, acceleration
, indicated in milliseconds, and blending
, indicated in millimeters.
velocity
Set a target pose and the velocity of the movement to 100 ms.
pose = (100, 200, 300, 0, pi, 0)
move frame("Flange") via p2p() to pose with velocity(100)
acceleration
Set a target pose and the acceleration of the movement to 100 ms.
pose = (100, 200, 300, 0, pi, 0)
move frame("Flange") via p2p() to pose with acceleration(100)
blending
blending
sets the blending radius in millimeters.
Let's say you want to reach home from the current robot position via pose
, blending of the movement set to 20 mm.
pose = (100, 200, 300, 0, pi, 0)
move frame("Flange") via p2p() to pose with blending(20)
move frame("Flange") to home
Imagine two lines between A = the current position of the robot, B = pose
and C = home
.
The robot moves from A over B = pose
to C = home
.
The indicated blending towards B means that the robot will not reach pose
but blend within the indicated blending zone r = 20 millimeters.
Movement scope
To apply movement commands or settings to a motion, set them globally or using with
for inline and block scopes.
During execution, introduced motion settings and movement commands are respected in the following order:
- Inline
- Block scope
- Global
The more precise the settings are, the higher the priority. More on that later here.
Inline
Use with
to set the desired TCP and movement settings inline:
move via p2p() to (100, 200, 300, 0, pi, 0) with tcp("Flange"), blending(20), velocity(120)
or as displayed above, you can set the desired TCP by referencing the coordinate system with frame
:
move frame("Flange") via p2p() to (100, 200, 300, 0, pi, 0) with blending(20), velocity(120)
Block
To apply movement settings and commands for multiple movements within a block of code, apply the settings in a block scope using with
.
pose1 = (150, -355, 389, 0, 0, 0)
pose2 = (223.0, 16.6, 779.7, 1.0758, 2.9742, -0.0871)
with velocity(250), blending(5), tcp("Flange"):
move via p2p() to pose1
move via line() to pose2
Global
To apply movement settings and commands for an entire file, apply the settings in a global scope at the beginning of the file. All following moves wil be executed with the flange TCP with a velocity of 250 ms and an acceleration of 30 until the settings are changed:
tcp("Flange") #Set the TCP to use for the whole Wandelscript file
velocity(250)
acceleration(30)
home= (100, 200, 300, 0, pi, 0)
move via p2p() to (223.0, 16.6, 779.7, 1.0758, 2.9742, -0.0871)
move via p2p() to home
If you prefer to use the frame
function to set the TCP, you can do so globally as well.
flange = frame("Flange")
tcp(flange) #Set the TCP to use for the whole Wandelscript file
velocity(250)
acceleration(30)
move via p2p() to (100, 200, 300, 0, pi, 0)
Changing TCPs
When combining inline, block, and global settings, the inline settings have the highest priority, followed by block settings, and finally global settings. Use this to your advantage and figure out how to scarcely set the settings to achieve lean code. Play around with the following examples to understand how precedence, scope and TCP settings work and lead to different movement executions.
We'll start by setting a global TCP and further down, an inline TCP. The first movement via joint p2p will be executed with Flange.
The second movement via line will be executed with Gripper. Notice how after each movement there's an explicit sync
to mark the end of the motion chain.
Also, the first movement command is executed slower than the second one; inline velocity overruling the global velocity.
flange = frame("Flange")
tcp(flange)
velocity(250)
home = (100, 200, 300, 0, pi, 0)
move via p2p() to (223.0, 16.6, 779.7, 1.0758, 2.9742, -0.0871) with velocity(50)
sync
print("Reached home.")
move frame("Gripper") via p2p() to home
sync
print("Left home.")
Try leaving the sync
out.
flange = frame("Flange")
tcp(flange)
velocity(250)
home = (100, 200, 300, 0, pi, 0)
move via p2p() to (223.0, 16.6, 779.7, 1.0758, 2.9742, -0.0871) with velocity(50)
move frame("Gripper") via p2p() to home
As TCPs can't be changed within one motion chain, the movement fails. Without a sync, both move
commands are considered part of a block.
More on explicit sync
here.
Now what about with
? Same behavior here, changing the TCP within a block is only possible if an explicit sync
is used to mark the end of the first motion chain.
Notice how the block blending overrules the global blending.
flange = frame("Flange")
tcp(flange)
velocity(250)
blending(10)
home = (100, 200, 300, 0, pi, 0)
with blending(0):
move via p2p() to (223.0, 16.6, 779.7, 1.0758, 2.9742, -0.0871)
move via p2p() to home
sync
move via p2p() to (223.0, 16.6, 779.7, 1.0758, 2.9742, -0.0871) with tcp("Gripper")
One robot vs. multiple robots
default_robot
is automatically set when programming with one robot in a cell. This robot is used to execute all commands.
When programming with multiple robots in a cell, you can set one robot as the default robot in a cell by using the POST: /programs/runners
or POST: /programs/execute
endpoints.
{
"code": "...",
"default_robot": "robot_name"
}
Robot context
do with <robot>:
is used to specify which robot should execute the following commands and acts as a implicit sync.
Programs without the use of robot context do with <robot>:
are always executed with the default robot:
# without robot context
home = (...)
move frame("Flange") via p2p() to home
move frame("Flange") via line() to home :: (...)
If you're executing a programm without robot context and without a default robot, you'll get prompted to use robot context do with <robot>:
.
A program with only a robot context looks like this:
# using robot context
do with ur10e[0]:
move frame("Flange") via p2p() to home
and do with ur5e[0]:
move frame("Flange") via p2p() to home
As Wandelscript desires you to be as precise as possible when programming multiple robots for safety reasons,
mixing default_robot
and robot context do with <robot>:
is not allowed.
# executed by the default robot
flange = frame("Flange")
move via p2p() to home
# with robot context
do with ur10e[0]:
move via p2p() to home
and do with ur5e[0]:
move via p2p() to home2
More information on robot context with multiple robots is available here.
Interact with other cell objects
There are three ways to interact with other, non-robot cell objects in Wandelscript:
- Reading from the device with
val = read(<identifer>, <key>)
- Writing to the device with
write(<identifier>, <key>, <value>)
- Calling a method on the device with
return_val = call(<identifer>, <key>, <args>)
The following example shows how Wandelscript can be used to interact with the I/O of the robot.
write(controller, "tool0", 1)
a = read(controller, "tool0")
In this example, the robot's I/O is used to write a value to the tool0 output and read it back.
Read
read(<device>, <key>)
reads an I/O on the indicated device.
value = read(controller, "digital_out[7]")
Add a sync
before read is executed to ensure all desired values are available. Read more on that here.
When you try to read a non-existing I/O a error will be thrown.
Write
write(<device>, <key>, <value>)
updates a value to an I/O.
The <value>
will be written to the I/O <key>
on the indicated <device>
.
write(controller, "digital_out[7]", False)
In this case the boolean value False
will be written to the I/O digital_out[7]
on the device controller
.
When you try to write a incompatible data type to an I/O a error will be thrown.
Call
call
calls an external function or service which takes 0 or more arguments and can return anything available from the external service.
call(liftkit, "2:EwellixLiftkit0S/Move(newPosition)", 42.0)
- Call a function that returns nothing with
call(service, <method_name>, <params...>)
- Call a function that returns parameters with
return_value = call(service, "MethodName", params...)
In the following example an OPC UA (opens in a new tab) device is used to manipulate and access values of an external axis.
# Used to let the OPC UA connect to the liftkit axis
call(liftkit, "2:String:EwellixLiftkit0S/2:String:Connect(host)", "host")
# Read the value pos from the Position node
pos = read(liftkit, "2:String:EwellixLiftkit0S.Position")
# Call the axis to move to a new position of 42mm
call(liftkit, "2:String:EwellixLiftkit0S/2:String:Move(newPosition)", 42.0)
# Read the updated position from the Position node
updated_pos = read(liftkit, "2:String:EwellixLiftkit0S.Position")
Add a sync
before call is executed to ensure all desired values are available.