Data recorder GCS 2.0

A PI device has one or more record tables that can be filled with float values (i.e. numbers). The typical workflow is as following.

  • Set the record rate.
  • Configure the data to be recorded.
  • Configure the trigger event that starts the recorder.
  • Perform the action that should be recorded.
  • Wait until the action is finished.
  • Wait until the data has been recorded.
  • Start reading out the data from the controller.
  • Wait until all data has been read out from the device.
  • Process the data.

Please find an according sample in samples/datarecorder.py.

Prepare the data recorder

Set the record rate

With the GCS command RTR you can set the record rate in multiples of the device specific servo loop time. Hence the higher the RTR rate is the slower the data is recorded. For your convenience the Datarecorder() class takes a record rate in Hertz and seconds, too.

from pipython import GCSDevice
from pipython import datarectools, pitools
pidevice = GCSDevice()
pidevice.InterfaceSetupDlg()
...
drec = datarectools.Datarecorder(pidevice)
# drec.samplerate = 1  # servo cycles
# drec.sampletime = 1E-5  # seconds
drec.samplefrequ = 1000  # Hertz
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))

Set the record time

By default the entire data recorder memory is used to record the data. You can reduce the number of points with the numvalues property. Or you set the time to record in seconds with rectime which adjusts numvalues accordingly. The rectimemax property will use the entire data recorder memory and will adjust the samplerate accordingly.

Further on pidevice is referred as instance of pipython.GCSDevice and drec is referred as instance of pipython.datarectools.Datarecorder.

Configure data recorder

With the GCS command DRC you can configure which measurement (the record option) of which record source (e.g. an axis or channel) is recorded in a specified record table.

There is an enumeration pipython.datarectools.RecordOptions available.

The function Datarecorder.record() takes one or more axes, one or more record options and one or more trigger options. If you call it with a single axis and several record options it will take the given axis for all recordings. And vice versa it will take a single record option for several given axes. When you omit the axes argument all connected axes are taken. If you omit the record option then RecordOptions.ACTUAL_POSITION_2 is taken. It will return a list of record table IDs where the desired data will be stored. See the example code below.

Configure the trigger event

With the GCS command DRT you can configure when the recording will start, e.g. immediately or with the next command that changes a position, i.e. that makes a motion.

There is an enumeration pipython.datarectools.TriggerSources available.

If you call the function Datarecorder.record() with a single value as trigger option it will use the DRT command with the record table "0". If it is called with a list as argument it will use DRT with the according record table IDs. See the controller user manual which is the appropriate way for your device. If you omit the trigger options argument then TriggerSources.NEXT_COMMAND_WITH_RESET_2 is taken. If TriggerSources.NEXT_COMMAND_WITH_RESET_2 is used then the error check will be disabled automatically.

The following sample will configure two recordings of one axis triggered by a position changing command and arm the data recorder.

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
drec.arm()

The following sample will configure two recordings of two axes.

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

The following sample will configure four recordings of two analog inputs and two measurements regarding axis X.

drec.sources = [1, 2, 'X', 'X']
drec.options = [datarectools.RecordOptions.ANALOG_INPUT_81,
                datarectools.RecordOptions.ANALOG_INPUT_81,
                datarectools.RecordOptions.MOTOR_OUTPUT_73,
                datarectools.RecordOptions.COMMANDED_POSITION_1]
drec.trigsources = datarectools.TriggerSources.POSITION_CHANGING_COMMAND_1
drec.arm()

Get options from string

If you read the desired options for example from an INI file there are helper functions getrecopt() and gettrigsources() to translate a descriptive string into the according option value. The following example will set trigsources to POSITION_CHANGING_COMMAND_1.

readout = 'pos_chg_cmd'  # e.g. from an INI file
drec.trigsources = datarectools.gettrigsources(readout)

The first exact or abbreviated match of all parts of an option (i.e. in the example "POSITION", "CHANGING" and "COMMAND") is returned. The descriptive string is case insensitive. Use "_" as separator. The trailing number in the option name is not required for a match. Abbreviations must start with the first letter of the according part of an option.

Get the data

Wait for the motion to finish

After you started the triggering event (for example a motion) you can wait until the motion has finished with the "wait" helper functions in pipython.pitools.

pidevice.MVR(axis, 1.0)
pitools.waitontarget(pidevice, axis)

Get the data recorder data

Finally you will read out the recorded data from the device with the GCS command qDRR. This command returns immediately with the GCS header containing information about the data recorder data. Then it starts a background task that keeps on storing the data still coming from the controller in an internal buffer. Check the current state of this buffer with the bufstate property. It will turn True when the task has finshed. Prior to that it is a float value in the range 0..1 indicating the progress of the data transfer. 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('read data {:.1f}%...'.format(pidevice.bufstate * 100))
    sleep(0.1)

Remember that the task running in the background will lock the communication to the device. Hence your application indeed is able to continue after the qDRR command but when you try to communicate to the device during data readout this will result in a deadlock! To prevent this always check the GCSDevice.locked property.

For your convenience you can use Datarecorder.getdata() instead. It will wait until the desired data has been recorded and will then return the header and the data as two-dimensional list where the first index indicates the record table and the second index indicates the value in this record table.

header, data = drec.getdata()

Process data

The sample below shows how to use the header and the data from a recording of two 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('Datarecorder data over time')
    pyplot.grid(True)
    pyplot.show()

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

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

Appendix

Wait for recording

To wait for the data recording to finish you can use wait() and read() instead of getdata()

drec.arm()
drec.wait()
# recording is now finished
header, data = drec.read()