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()