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:
- Preparing the data recorder
- Recording data
- Reading out the recorded data
- Processing the recorded data
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. CallREC_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. CallREC_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 tracecontainer unit
: Container unit (= element) whose signal is to be recordedfunction unit
: Function unit whose signal is to be recordedparameter
: 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 queryingqREC_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 stateWAIT
In this state it waits until the specified trigger event occurs. After receiving the trigger, the data recorder changes to the running stateRUN
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