Skip to content

Data Recorder GCS 3.0

Info

These instructions are valid for controllers using GCS 3.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_gcs30.py.

Preliminary

Call qREC_STATE to get the current state of a data recorder. Possible states:

  • Configuration (CFG): Configuration of the data recorder is only possible in this state. Call REC_STOP to set the data recorder to the configuration state.
  • Waiting (WAIT): In this state the data recorder waits for the occurrence of a trigger event which starts the recording. Call REC_START to set the data recorder from the configuration state to the waiting state.
  • Running (RUN): In this state the data recorder is recording signals according to the configuration settings.

Preparing the data recorder

The configuration of data recording consists of the following steps:

Setting the record rate

The Datarecorder property record_rate sets the record rate in number of servo cycles, which is a multiple of the controller-specific servo loop time. Hence the higher the record rate, the slower the data is recorded.

from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
drec.record_rate = 1   # servo cycles

Setting the record source and options

The Datarecorder property traces specifies which signal is to be recorded in which trace. The property gets a list of traces to be recorded, each trace with its own specification:

Syntax: { <trace id>: [<container unit>, <function_unit>, <parameter>], ... }

  • trace id: Identifier of the trace
  • container unit: Container unit (= element) whose signal is to be recorded
  • function unit: Function unit whose signal is to be recorded
  • parameter: Signal to be recorded in this trace

In the example the recording of signals 0x102 (target position) and 0x103 (current position) of AXIS_1 is configured for traces 1 and 2.

from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
...
drec.traces = { 1: [ 'AXIS_1', '-', '0x102' ], 2: [ 'AXIS_1', '-', '0x103' ]}

Setting the trigger event

The Datarecorder property trigger specifies when the recording is to be started, e.g. immediately or with the next command that changes a position.

Syntax: [ <trigger name>, <option 1>, <option 2> ]

To get a list of trigger names and the corresponding options use the command USG? PROP_ REC_# below the block #Recorder Trigger or #Trigger Option Types (# = identifier of the data recorder, eg. 1)

In the example the recording is triggered by the next MOV command on AXIS_1 and is not recurring (0), i.e. done only once.

from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
...
drec.trigger = [ 'MOV', 'AXIS_1', '0' ]

Recording data

Starting the recording

The method Datarecorder.arm() arms the data recorder, i.e., enables the actual recording. For this the method performs the following steps:

  • Checks if the data recorder is in configuration state CFG (by querying qREC_STATE)
  • If necessary, sends REC_STOP to set the data recorder into the configuration state
  • Sends the record rate configuration to the connected device using record_rate
  • Sends the trace configuration to the connected device using traces
  • Sends the trigger configuration to the connected device using trigger
  • Calls REC_START to set the data recorder to the waiting state WAIT
    In this state it waits until the specified trigger event occurs. After receiving the trigger, the data recorder changes to the running state RUN and starts recording.
from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
...
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.MOV(['AXIS_1', 'AXIS_2'], [1.0, 2.0])
pitools.waitontarget(pidevice, ['AXIS_1', 'AXIS_2'])
...

Reading out the recorded data

Retrieving the data via Datarecorder

Recorded data can be read with the Datarecorder properties header and data. The first time you read one of the properties, qREC_DAT is sent to the device, which starts the reading of the data. By default this reads the maximum possible number of data values starting with the first value. To limit the number of data values to be read, you can set the number_of_values property. You can also set the offset property to start the reading not from the first value but with an offset in the trace.

In the following example 1,000 data values are read, starting with the 100th value in the trace. This will read the values 100 to 1100 from the trace.

from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
...
drec.number_of_values = 1000   # read 1,000 values
drec.offset = 100   # start reading with offset 100 
data = drec.data   # start reading the values and wait until 1000 values have been read

Retrieving the data via qREC_DAT

Read out the recorded data from the device with qREC_DAT. This function 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.qREC_DAT('REC_1', 'ASCII', [1, 2], 100, 1000)
while pidevice.bufstate is not True:
    print('read 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 qREC_DAT function, any attempt on communication with the device will result in a deadlock. To prevent this always check the GCSDevice.locked property.

Processing the recorded data

Displaying the data with matplotlib

The following example shows how to create a plot using the header and data from a recording that comprises two traces. This requires matplotlib.

    pyplot.plot(drec.timescale, drec.data[0], color='red')
    pyplot.plot(drec.timescale, drec.data[1], color='blue')
    pyplot.xlabel('time (s)')
    pyplot.ylabel(', '.join((drec.header['NAME0'], drec.header['NAME1'])))
    pyplot.title('Datarecorder data over time')
    pyplot.grid(True)
    pyplot.show()
    print('save GCSArray to file "gcsarray.dat"')
    pitools.savegcsarray('gcsarray.dat', drec.header, drec.data)

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)

Examples

Configure two traces for the same axis and set the MOV trigger

The example configures two traces for the same axis, sets the MOV command trigger, and arms the data recorder.

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

# Configure the data recorder
drec.record_rate = 1
drec.traces = { 1: [ 'AXIS_1', '-', '0x102' ], 2: [ 'AXIS_1', '-', '0x103' ]}
drec.trigger = [ 'MOV', 'AXIS_1', '0' ] # Trigger on next MOV command on AXIS_1
drec.arm()

# Trigger the recording and wait until the motion has finished
pidevice.MOV('AXIS_1', 1.0)
pitools.waitontarget(pidevice, 'AXIS_1')

# Read the recorded values and the header
data = drec.data
header = drec.header

Configure two traces for different axes and set the MOV trigger for one axis

The example configures two traces for different axes, sets the MOV command trigger for AXIS_1, and arms the data recorder.

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

# Configure the data recorder
drec.record_rate = 1
drec.traces = { 1: [ 'AXIS_1', '-', '0x102' ], 2: [ 'AXIS_2', '-', '0x103' ]}
drec.trigger = [ 'MOV', 'AXIS_1', '0' ]
drec.arm()

# Trigger the recording and wait until the motion has finished
pidevice.MOV('AXIS_1', 1.0)
pitools.waitontarget(pidevice, 'AXIS_1')

# Read the recorded values and the header
data = drec.data
header = drec.header

Configure two traces for analog inputs, and two for axes and set the NOW trigger

The example configures four traces for two analog inputs and two axes, sets the 'NOW' trigger which triggers immediately after starting the data recorder, and arms the data recorder.

from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
drec.record_rate = 1
drec.traces = { 1: [ 'CON_1', 'SENS_1', '0x1021 ], 2: [ 'CON_2', 'SENS_1', '0x101' ]
                3: [ 'AXIS_1', '-', '0x102' ], 4: [ 'AXIS_2', '-', '0x103' ]}
drec.trigger = [ 'NOW', '0', '0' ]
drec.arm()

# No specific trigger is required, because the data recorder triggers itself
# automatically after `REC_START`, which ist called within the `arm()` method.

# Read the recorded values and the header
data = drec.data
header = drec.header