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:
- Preparing the data recorder
- Recording data
- Reading out the recorded data
- Processing the recorded data
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
- Setting the record time
- Setting the record source and options
- Setting the trigger event
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 recordedrectime
: 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 andsources
is a list,option
is used for allsources
. - If
sources
is a single value andoptions
is a list, theoptions
are used for the singlesource
. - If both are lists, the lists must contain the same number of elements. The fist
option
is used for the firstsource
and recorded to record table 1, the secondoption
is used for the secondsource
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, thetrigger options
will be used with the corresponding record table IDs: The fisttrigger 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.
```python
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)