Skip to content

Data Recorder GCS 2.0

Info

These instructions are valid for controllers using GCS 2.0.

A PI device has one or more record tables that can be filled with data (i.e. values) from a configurable source.

PIPython comes with Datarecorder class for the configuration of data recording: pipython.datarectools.Datarecorder.

from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
...

In the further examples on this page, pidevice refers to an instance of pipython.GCSDevice and drec refers to an instance of pipython.datarectools.Datarecorder.

The typical workflow for data recording is as follows:

For an example see samples/datarecorder.py.

Preparing the data recorder

The configuration of data recording consists of the following steps:

Setting the record rate

With the Datarecorder property samplerate you can configure the record rate in multiples of the controller-specific servo cycle time. The higher the record rate, the slower the data is recorded. With the property sampletime you can configure the record rate in seconds. With the property samplefrequ you can configure the record rate in hertz.

These properties use the GCS command RTR to configure the record table rate on the device.

# drec.samplerate = 1
# drec.sampletime = 1E-5
drec.samplefrequ = 1000
print('data recorder rate: {:d} servo cycles'.format(drec.samplerate))
print('data recorder rate: {:.g} seconds'.format(drec.sampletime))
print('data recorder rate: {:.2f} Hertz'.format(drec.samplefrequ))

Setting the record time

By default the entire data recorder buffer is used to record data. The Datarecorder class offers two properties to limit the data to be recorded:

  • numvalues: number of data points to be recorded
  • rectime: record time in seconds

Setting one of these properties will change the other accordingly.

Use the rectimemax property to use the entire data recorder buffer and adjust samplerate accordingly.

Setting the record source and options

With the Datarecorder property options you can configure which record option (measurement) is to be recorded in a specified record table.

The record options are avaiable via pipython.datarectools.RecordOptions.

With the property sources you can configure which record source (e.g. an axis or channel) is to be recorded in a specified record table.

The properties options and source can each get either a list of values or a single value as argument.

  • If options is a single value and sources is a list, option is used for all sources.
  • If sources is a single value and options is a list, the options are used for the single source.
  • If both are lists, the lists must contain the same number of elements. The fist option is used for the first source and recorded to record table 1, the second option is used for the second source and recorded to record table 2, and so on.

If no record option is specified, RecordOptions.ACTUAL_POSITION_2 is used.

If no source is specified, all connected axes are used.

Examples:

  • Set the recording of two values for one axis

    drec.options = [datarectools.RecordOptions.ACTUAL_POSITION_2,
                    datarectools.RecordOptions.COMMANDED_POSITION_1]
    drec.sources = pidevice.axes[0]
    

  • Set the recording of one value for two axes

    drec.options = datarectools.RecordOptions.ACTUAL_POSITION_2
    drec.sources = ['X', 'Y']
    

  • Configure four recordings: record option 81 for analog input lines 1 and 2, record option 73 for axis X, and record option 1 for axis Y

    drec.sources = [1, 2, 'X', 'Y']
    drec.options = [datarectools.RecordOptions.ANALOG_INPUT_81,
                    datarectools.RecordOptions.ANALOG_INPUT_81,
                    datarectools.RecordOptions.MOTOR_OUTPUT_73,
                    datarectools.RecordOptions.COMMANDED_POSITION_1]
    

Setting the trigger event

With the Datarecorder property trigsources you can specify when the recording is to be started, e.g. immediately or with the next command that changes a position.

The trigger events are avaiable via pipython.datarectools.TriggerSources.

The property trigsources can get either a list of values or a single value as trigger option argument.

  • If you set the property trigsources with a single value, this trigger option is used with all record tables. The shortcut for "to be used for all record tables" is the record table ID "0". (Please note that "real" record table IDs start with "1".)
  • If trigsources is called with a list as argument, the trigger options will be used with the corresponding record table IDs: The fist trigger option is used for record table 1, the second for record table 2, and so on.

If no trigger event is specified, TriggerSources.NEXT_COMMAND_WITH_RESET_2 is used. Using this event deactivates the error check.

Note

Please note that not all controllers support a record table dependent trigger source. Such controllers only support one trigger source for all record tables. See the controller's user manual to find out which is the appropriate way for your device.

Examples:

  • Configure two recordings for one axis triggered by a position changing command

    drec.options = (datarectools.RecordOptions.ACTUAL_POSITION_2,
                    datarectools.RecordOptions.COMMANDED_POSITION_1)
    drec.sources = pidevice.axes[0]
    drec.trigsources = datarectools.TriggerSources.POSITION_CHANGING_COMMAND_1
    

  • Set 'trigsources' to a position changing command for record table 1, and "triggerd by the next GCS command" for record table 2

...
drec.trigsources = (datarectools.TriggerSources.POSITION_CHANGING_COMMAND_1,
                    datarectools.TriggerSources.NEXT_COMMAND_WITH_RESET_2)
...

Useful helper functions

The helper functions getrecopt() and gettrigsources() enable converting a descriptive string, e.g., from an INI file, into the corresponding option value.
These functions split the string argument by the underscore delimiter ("_") and return the first option that matches all elements of the string. Matches are not case-sensitive and may be abbreviated. Abbreviations must start with the first letter of the option name element, e.g., "pos_chg_cmd" for "POSITION_CHANGING_COMMAND_1". The following example sets the trigger source to "POSITION_CHANGING_COMMAND_1" using a descriptive string.

desc = 'pos_chg_cmd' # descriptive string, e.g. from INI file
drec.trigsources = datarectools.gettrigsources(readout)

Recording data

Starting the recording

The method Datarecorder.arm() arms the data recorder, i.e., prepares it for the actual recording by

  • writing the record sources and options for the data recorder to the device
  • writing the trigger events for the record tables to the device

When armed, the data recorder starts recording as soon as the specified trigger event occurs.

drec.options = datarectools.RecordOptions.ACTUAL_POSITION_2
drec.sources = ['X', 'Y']
drec.trigsources = datarectools.TriggerSources.POSITION_CHANGING_COMMAND_1
drec.arm()

Waiting for the recording to finish

To wait for the triggered motion to finish, you can use the "wait" helper functions in pipython.pitools, for example waitontarget(). The function halts the application until the triggered motion has finished. It is called with an instance of pidevice and a single or a list of axis identifiers (string or integer).

...
pidevice.MVR(['X', 'Y'], [1.0, 2.0])
pitools.waitontarget(pidevice, ['X', 'Y'])
...

If the application is supposed to continue immediately after the data recorder has stopped recording, regardless of a motion having finished, the Datarecorder.wait() function may be used.

drec.arm()   # data is being recorded if trigger event occurs
drec.wait()  # recording has finished

Reading out the recorded data

Recorded data can be read with the function getdata(). If you used Datarecorder.wait(), use the function Datarecorder.read() instead of getdata().

...
header, data = drec.getdata()
...
drec.arm()
drec.wait()
header, data = drec.read()

Both functions call the GCS command qDDR() and wait for the recording to finish. Then they return the header and the data as a two-dimensional list with the first index indicating the record table and the second index indicating the recorded values in the table.

If you don't want to wait until the recording has finished - as this could take a while, depending on the amount of data and the interface - you can call the GCS command qDRR directly.

qDRR returns immediately with the GCS header containing information on the recorded data. Then it starts a background task which internally buffers the data from the device as it is being transmitted. The status of the internal buffer can be queried with the GCSDevice.bufstate property. It indicates the transmission progress by float values in the range 0...1 and becomes True when the transmission has completed. Hence end a loop with while bufstate is not True and not with while not bufstate.

header = pidevice.qDRR(rectables, offset, numvalues)
while pidevice.bufstate is not True:
    print('Reading data {:.1f}%...'.format(pidevice.bufstate * 100))
    sleep(0.1)

Warning

The background task will lock any communication to the device for the duration of the data transmission. This means that although your application is actually able to continue after having called the qDRR command, any attempt on communication with the device will result in a deadlock. To prevent this always check pidevice.bufstate.

Processing the recorded data

Displaying the data with matplotlib

The following example shows how to use the header and data from a recording stored in two record tables to create a plot. This requires matplotlib.

    timescale = [header['SAMPLE_TIME'] * i for i in range(len(data[0]))]
    pyplot.plot(timescale, data[0], color='red')
    pyplot.plot(timescale, data[1], color='blue')
    pyplot.xlabel('time (s)')
    pyplot.ylabel(', '.join((header['NAME0'], header['NAME1'])))
    pyplot.title('Recorded data over time')
    pyplot.grid(True)
    pyplot.show()

Converting the data into a NumPy array

If you are used to NumPy you can easily convert the recorded data into a NumPy array.

import numpy as np
...
header, data = drec.getdata()
npdata = np.array(data)