Schunk Motion Protocol for Python

Documentation:
http://schunk.rtfd.org/
Code:
http://github.com/spatialaudio/schunk/
Python Package Index:
http://pypi.python.org/pypi/SchunkMotionProtocol/
Schunk Motion Protocol manual:

http://www.schunk.com/schunk_files/attachments/MotionControl_en_2010-03.pdf

A newer version of the manual is available in a huge zip file from the Schunk website (have a look in the directory Manuals/Motion Control/)

Disclaimer

This is not a commercial product and the author has no relation whatsoever to SCHUNK GmbH & Co. KG.

Use at your own risk!

Devices

Only 1 device was tested: Schunk PR-70 Servo Electric Swivel Unit.

Defaults for this device: serial (RS232), baudrate=9600, module ID 11 (0x0B).

Other devices may or may not work.

Only firmware version 1.56 was tested, other versions may or may not work.

Requirements

Obviously, Python is required. Any version >= 2.7 should do.

Typically, PySerial handles the serial connection, but any library with a similar API can be used.

py.test is used for the tests.

Limitations

Only a subset of the Schunk Motion Protocol is supported.

Only the direct response to a command can be obtained, impulse messages are not supported. One exception is the “CMD POS REACHED” impulse message which is used to realize movement commands which are waiting until the movement is finished, e.g. move_pos_blocking().

Only floating point unit systems are supported.

The connection is opened and closed for each message. Keeping the connection open is not supported.

Only serial communication is implemented. No CAN, no Profibus.

Installation

Using pip, you can download and install the latest release with a single command:

pip install --user SchunkMotionProtocol

If you want to install it system-wide for all users (assuming you have the necessary rights), you can just drop the --user option.

To un-install, use:

pip uninstall SchunkMotionProtocol

If you prefer, you can also download the package from PyPI, extract it, change to the main directory and install it using:

python setup.py install --user

If you want to get the newest development version from Github:

git clone https://github.com/spatialaudio/schunk.git
cd schunk
python setup.py install --user

Alternatively, you can just copy schunk.py to your working directory.

If you want to make changes to the code, you should type:

python setup.py develop --user

or, alternatively:

pip install --user -e .

... where -e stands for --editable.

Tests

Tests are implemented using py.test, run this in the main directory:

python setup.py test

Examples

This should get you started:

import schunk
import serial

mod = schunk.Module(schunk.SerialConnection(
    0x0B, serial.Serial, port=0, baudrate=9600, timeout=1))

mod.move_pos(42)

Use the ID of your Schunk module instead of 0x0B.

See the documentation of PySerial for all possible serial port options. You probably only have to change port, e.g. port='/dev/ttyS1' or port='COM3'.

It is useful to specify a timeout, otherwise you may have to wait forever for the functions to return if there is an error. On the other hand, if you want to use the blocking commands (*_blocking()), you should disable the timeout (or make it longer than the expected movement times).

If the parameters for your setup don’t change, you can write them into a separate file, e.g. with the name myschunk.py:

import schunk
import serial

module1 = schunk.Module(schunk.SerialConnection(
    0x0B, serial.Serial, port=0, baudrate=9600, timeout=1))

and then use it like this in all our scripts:

from myschunk import module1
module1.move_pos(42)

The file myschunk.py must be in the current directory for this to work.

If you are an object-oriented kind of person, you can of course also write your own class:

import schunk
import serial

class MySchunkModule(schunk.Module):
    def __init__(self):
        schunk.Module.__init__(self, schunk.SerialConnection(
            0x0B, serial.Serial, port=0, baudrate=9600, timeout=1))

module1 = MySchunkModule()
module1.move_pos(42)

API Documentation

Schunk Motion Protocol for Python.

See http://schunk.rtfd.org/

Example

import schunk
import serial

mod = schunk.Module(schunk.SerialConnection(
    0x0B, serial.Serial, port=0, baudrate=9600, timeout=1))

mod.move_pos(42)
class schunk.Module(connection)[source]

Create an object for controlling a Schunk module.

Parameters:

connection – Something that has an open() method which returns a coroutine. This coroutine must accept a bytes object and send it to a Schunk module, read the response (taking D-Len into account) and yield the response (and further messages) as a bytearray.

SerialConnection happens to do exactly that.

reference()[source]

2.1.1 CMD REFERENCE (0x92).

A reference movement is completed.

move_pos(position, velocity=None, acceleration=None, current=None, jerk=None)[source]

2.1.3 MOVE POS (0xB0).

Parameters:
  • position (float) – Absolute position.
  • velocity, acceleration, current, jerk (float, optional) – If one of them is not specified, all following arguments must not be specified either.
Returns:

float – Estimated time to reach position. If the time cannot be estimated, 0.0 is returned.

move_pos_blocking(position, velocity=None, acceleration=None, current=None, jerk=None)[source]

Move to position and wait until position is reached.

Note

Impulse messages must be activated for this to work, see toggle_impulse_message() and communication_mode.

This applies to all *_blocking() methods.

Returns:float – The final position.

See also

move_pos()

move_pos_rel(position, velocity=None, acceleration=None, current=None, jerk=None)[source]

2.1.4 MOVE POS REL (0xB8).

Parameters:
  • position (float) – Relative position.
  • velocity, acceleration, current, jerk (float, optional) – If one of them is not specified, the following must not be specified either.
Returns:

float – Estimated time to reach position. If the time cannot be estimated, 0.0 is returned.

move_pos_rel_blocking(position, velocity=None, acceleration=None, current=None, jerk=None)[source]

Move to relative position and wait until position is reached.

Returns:float – The actual relative motion.

See also

move_pos_rel()

move_pos_time(position, velocity=None, acceleration=None, current=None, time=None)[source]

2.1.5 MOVE POS TIME (0xB1).

move_pos_time_blocking(position, velocity=None, acceleration=None, current=None, time=None)[source]

Move to position and wait until position is reached.

Returns:float – The final position.

See also

move_pos_time()

move_pos_time_rel(position, velocity=None, acceleration=None, current=None, time=None)[source]

2.1.6 MOVE POS TIME REL (0xB9).

move_pos_time_rel_blocking(position, velocity=None, acceleration=None, current=None, time=None)[source]

Move to position and wait until position is reached.

Returns:float – The actual relative motion.
set_target_vel(velocity)[source]

2.1.14 SET TARGET VEL (0xA0).

Initially, the target velocity is set to 10% of the maximum.

set_target_acc(acceleration)[source]

2.1.15 SET TARGET ACC (0xA1).

Initially, the target acceleration is set to 10% of the maximum.

set_target_jerk(jerk)[source]

2.1.16 SET TARGET JERK (0xA2).

Initially, the target jerk is set to 50% of the maximum.

set_target_cur(current)[source]

2.1.17 SET TARGET CUR (0xA3).

Initially, the target current is set to the nominal current.

set_target_time(time)[source]

2.1.18 SET TARGET TIME (0xA4).

stop()[source]

2.1.19 CMD STOP (0x91).

toggle_impulse_message()[source]

2.2.6 CMD TOGGLE IMPULSE MESSAGE (0xE7).

Note

Impulse messages must be switched on for *_blocking(), e.g. move_pos_blocking().

Returns:boolTrue if impulse messages were switched on, False if they were switched off.
config[source]

2.3.1 SET CONFIG (0x81) / 2.3.2 GET CONFIG (0x80).

The config object has several attributes which can be queried and changed. Except where otherwise noted, the new settings are immediately stored in the EEPROM but are only applied after the module has been restarted.

Some options are read-only, some can only be set as “Profi” user. See change_user().

module_type

bytes

firmware_version

int

protocol_version

int

hardware_version

int

firmware_date

bytes

eeprom

bytes

All configuration data is read/written in one process. Depending on the type of user certain data might not be written. After successful writing of the data, the module is rebooted.

Note

This command should not be used with one’s own applications, as the structure of the data to be received/sent is not known.

module_id

int (1..255)

group_id

int (1..255)

serial_baudrate

int (1200, 2400, 4800, 9600, 19200, 38400)

can_baudrate

int (50, 100, 125, 250, 500, 800, 1000)

communication_mode

int

See communication_modes.

unit_system

int

See unit_systems.

soft_high

float

The transferred value is not written to the EEPROM. The settings are applied immediately.

soft_low

float

The transferred value is not written to the EEPROM. The settings are applied immediately.

gear_ratio

float

The Gear Ratio 1 is changed (the command has no use with an integer unit system). The transferred value is written to the EEPROM and applied immediately.

max_velocity

float

max_acceleration

float

max_current

float

nom_current

float

max_jerk

float

offset_phase_a

int

offset_phase_b

int

data_crc

int

A CRC16 over all variable and not module specified paramenters (like serial number, current offset).

reference_offset

float

serial_number

int

order_number

int

get_state()[source]

2.5.1 GET STATE (0x95).

Return the module status and other information.

The time parameter (to get state repeatedly) is disabled (because impulse messages are not supported). The mode parameter is always set to request everything (position, velocity and current).

Returns:
  • position, velocity, current (float) – Dito.
  • status (dict) – See decode_status().
  • error_code (int) – See error_codes for a mapping to strings.
reboot()[source]

2.5.2 CMD REBOOT (0xE0).

change_user(password=None)[source]

2.5.6 CHANGE USER (0xE3).

If no password is specified - or if the password is wrong - the user is changed to “User”. The default password for “Profi” is “Schunk”, but don’t tell anyone!

After a reboot, the default user is “User”.

check_mc_pc_communication()[source]

2.5.7 CHECK MC PC COMMUNICATION (0xE4).

Returns:boolTrue on success.
check_pc_mc_communication()[source]

2.5.8 CHECK PC MC COMMUNICATION (0xE5).

Returns:boolTrue on success.
ack()[source]

2.8.1.4 CMD ACK (0x8B).

Acknowledgement of a pending error message.

get_detailed_error_info()[source]

2.8.1.5 GET DETAILED ERROR INFO (0x96).

Returns:
  • command ({“ERROR”, “WARNING”, “INFO”})
  • error_code (int) – See error_codes for a mapping to strings.
  • data (float) – The value can be interpreted by the Schunk Service.
Raises:SchunkError – If no error is active, or no detailed information is available, the command is raising an exception saying: INFO FAILED (0x05).
wait_until_position_reached()[source]

Repeatedly check the state until the position is reached.

This should only be used if impulse messages are disabled (see toggle_impulse_message()).

exception schunk.SchunkError[source]

This exception is raised on all kinds of errors.

schunk.coroutine(func)[source]

Decorator for generator functions that calls next() initially.

class schunk.SerialConnection(id, serialmanager, *args, **kwargs)[source]

Prepare a serial connection.

This can be used to initialize a Module.

The connection is opened with open().

Parameters:
  • id (int) – Module ID of the Schunk device.

  • serialmanager – A callable (to be called with *args and **kwargs) that must return a context manager which in turn must have read() and write() methods (and it should close the connection automatically in the end).

    This is typically serial.Serial from PySerial, but anything with a similar API can be used.

    Note

    there should be a timeout, otherwise you may have to wait forever for the functions to return if there is an error. On the other hand, receiving multiple responses only works if there is no timeout in between. Multiple responses are needed for the blocking movement commands, e.g. Module.move_pos_blocking().

  • *args, **kwargs – All further arguments are forwarded to serialmanager.

See also

Module

Examples

Using PySerial:

>>> import serial
>>> conn = SerialConnection(0x0B, serial.Serial, port=0,
...                         baudrate=9600, timeout=1)
open()[source]

Open a serial connection.

A coroutine (a.k.a. generator object) is returned which can be used to send and receive one or more data frames.

Calling .send(data) on this coroutine creates a serial frame around data, sends it to the module and waits for a response.

data must have at least two bytes, D-Len and command code. The (optional) rest are parameters. 2 Group/ID bytes are added in the beginning and 2 CRC bytes in the end. The first byte is always 0x05 (= message from master to module), the second byte holds the module ID.

When receiving a response, the 2 CRC bytes are checked (and removed), as well as the 2 Group/ID bytes.

The connection is kept open and the coroutine can be invoked repeatedly to receive further data frames. Use .send(None) or the built-in next() function to receive a data frame without sending anything.

When the desired number of frames has been received, the connection has to be closed with the generator’s close() method.

Yields:

bytes – Response data received from the module, including D-Len and command code.

If the first byte indicates an error (0x03), the response is returned normally and the error has to be handled in the calling function. Error responses always have a D-Len of 2, i.e. they have 3 bytes: D-Len, command code and error code.

See also

crc16()

exception schunk.SchunkSerialError[source]

Exception class for errors related to serial connections.

It is derived from SchunkError, so it is normally sufficient to check only for this:

try:
    ...
    # Something that may throw SchunkError or SchunkSerialError
    ...
except SchunkError as e:
    # Do something with e
    ...
schunk.decode_status(status)[source]

This is internally used in Module.get_state().

>>> status = decode_status(0x03)
>>> from pprint import pprint
>>> pprint(status)  # to get pretty dict display
{'brake': False,
 'error': False,
 'move_end': False,
 'moving': True,
 'position_reached': False,
 'program_mode': False,
 'referenced': True,
 'warning': False}
schunk.crc16_increment(crc, data)[source]

Incrementally calculate CRC16.

Implementation according to Schunk Motion Protocol documentation.

Parameters:
  • crc (int) – Previous CRC16 (2 bytes)
  • data (int) – Data to append (1 byte)
Returns:

int – New CRC16 (again 2 bytes) after appending data.

See also

crc16()

schunk.crc16(data)[source]

Calculate CRC16 for a sequence of bytes.

Parameters:data (iterable of integers (0..255)) – A sequece of bytes.
Returns:bytes – CRC16 of data (2 bytes, little endian, a.k.a. ‘<H’).
schunk.communication_modes = {0: 'AUTO', 1: 'serial', 2: 'CAN', 3: 'Profibus DPV0', 4: 'serial silent'}

Available communication modes.

See Module.config.

schunk.unit_systems = {0: '[mm]', 1: '[m]', 2: '[inches]', 3: '[rad]', 4: '[degrees]', 5: '[internal]', 6: '[µm] Integer', 7: '[µdegrees] Integer', 8: '[µinches] Integer', 9: '[millidegrees] Integer'}

Available unit systems.

See Module.config.

Note

This Python module doesn’t support integer unit systems.

schunk.error_codes = {0: 'NO ERROR', 1: 'INFO BOOT', 2: 'INFO NO FREE SPACE', 3: 'INFO NO RIGHTS', 4: 'INFO UNKNOWN COMMAND', 5: 'INFO FAILED', 6: 'NOT REFERENCED', 7: 'INFO SEARCH SINE VECTOR', 8: 'INFO NO ERROR', 9: 'INFO COMMUNICATION ERROR', 16: 'INFO TIMEOUT', 22: 'INFO WRONG BAUDRATE', 25: 'INFO CHECKSUM', 29: 'INFO MESSAGE LENGTH', 30: 'INFO WRONG PARAMETER', 31: 'INFO PROGRAM END', 64: 'INFO TRIGGER', 65: 'INFO READY', 66: 'INFO GUI CONNECTED', 67: 'INFO GUI DISCONNECTED', 68: 'INFO PROGRAM CHANGED', 200: 'ERROR WRONG RAMP TYPE', 210: 'ERROR CONFIG MEMORY', 211: 'ERROR PROGRAM MEMORY', 212: 'ERROR INVALID PHRASE', 213: 'ERROR SOFT LOW', 214: 'ERROR SOFT HIGH', 215: 'ERROR PRESSURE', 216: 'ERROR SERVICE', 217: 'ERROR EMERGENCY STOP', 218: 'ERROR TOW', 219: 'ERROR VPC3', 220: 'ERROR FRAGMENTATION', 221: 'ERROR COMMUTATION', 222: 'ERROR CURRENT', 223: 'ERROR I2T', 224: 'ERROR INITIALIZE', 225: 'ERROR INTERNAL', 226: 'ERROR HARD LOW', 227: 'ERROR HARD HIGH', 228: 'ERROR TOO FAST', 236: 'ERROR MATH', 112: 'ERROR TEMP LOW', 113: 'ERROR TEMP HIGH', 114: 'ERROR LOGIC LOW', 115: 'ERROR LOGIC HIGH', 116: 'ERROR MOTOR VOLTAGE LOW', 117: 'ERROR MOTOR VOLTAGE HIGH', 118: 'ERROR CABLE BREAK', 120: 'ERROR MOTOR TEMP'}

Error codes.

See also Module.get_state().

Index

Index

Version History

Version 0.2.2 (2015-03-03):
  • Python 2.x support
  • better handling (= ignoring) of impulse messages
Version 0.2.1 (2015-02-24):
  • Handle KeyboardInterrupt and SystemExit in *_blocking()
  • Add Module.wait_until_position_reached() (this also handles KeyboardInterrupt and SystemExit)
  • A few bugfixes, refactorings and doc updates
Version 0.2.0 (2014-06-17):
  • Some documentation changes based on a newer version of the Schunk manual
  • Rename everything with “RS232” to “serial” (as in the Schunk manual)
Version 0.1.3 (2014-06-16):
  • Fix version info in setup.py (hopefully it works this time!)
Version 0.1.2 (2014-06-16):
  • Better installation instructions, including PyPI
  • Proper __version__ attribute
Version 0.1.1 (2014-06-02):
  • Improvements to setup.py
Version 0.1 (2014-05-28):
  • Initial release