APIs, concepts, guides, and more
Recorder

Utilize the Recorder to capture and store controller data like positions, velocities, digital/analog inputs and outputs, etc, supporting condition-based or continuous recording for in-depth analysis.

๐Ÿ”น What is a Recorder?

A Recorder object provides a mechanism to collect and buffer any data (positions, velocities, etc) in the controllerโ€™s memory. After a recorder is configured and started, the controller copies the data (positions, velocities, etc) from the specified addresses to a local buffer for every โ€œNโ€ sample. Later, the host can collect the data by polling or via interrupt-based events.

๐Ÿ”น How Many Data Objects Can a Recorder Hold?

The controller supports up to 64 data recorders, which can each record data from up to a total of 32 addresses in each sample. The buffers can be dynamically allocated. A larger data recorder buffer may be required for higher sample rates, slow host computers, when running via client/server, or when a large number of data fields are being recorded.

In case the data recording length is greater than the memory available to the recorder, the recorder can be configured to use a circular buffer and the host PC (RapidCode) is only required to copy the recorded data to RAM faster than the recorder circular buffer rolls over. On most systems , this is not an issue.

๐Ÿ”น Can I Trigger a Recorder When an Axis or MultiAxis starts moving?

A recorder can be configured to trigger its starting and stopping when an Axis or MultiAxis starts moving. When the motions starts, the controller will automatically start the data recorder. This is very useful for logging all relevant variables during the motion. When the motion is done
(see Settling), the recorder will automatically stop. See RecorderConfigureToTriggerOnMotion to configure.

๐Ÿ“œ Sample Code

Basic Recorder Usage

  • C#

    /* This sample demonstrates how to use the Recorder to track multiple controller parameters.
    Shows how to configure the recorder, record axis position and digital input values,
    and retrieve recorded data to find when specific events occurred.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("๐Ÿ“œ Recorder");
    // set sample config params
    const int VALUES_PER_RECORD = 2; // how many values to store in each record
    const int RECORD_PERIOD_SAMPLES = 1; // how often to record data (samples between consecutive records)
    const int RECORD_TIME = 250; // how long to record (milliseconds)
    const int INPUT_INDEX = 0;
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // set recorder count before any RapidCodeObject get/create other than the controller
    controller.RecorderCountSet(1);
    // get axis
    Axis axis = controller.AxisGet(Constants.AXIS_0_INDEX);
    // configure phantom axis
    // create simulated digital input using user buffer memory
    ulong userBufferAddress = controller.AddressGet(RSIControllerAddressType.RSIControllerAddressTypeUSER_BUFFER, 0);
    IOPoint digitalInput = IOPoint.CreateDigitalInput(controller, userBufferAddress, INPUT_INDEX);
    // stop recorder if already running
    if (controller.RecorderEnabledGet() == true)
    {
    controller.RecorderStop(); // stop recording
    controller.RecorderReset(); // reset controller
    }
    // configure recorder
    controller.RecorderPeriodSet(RECORD_PERIOD_SAMPLES); // record every n samples
    controller.RecorderCircularBufferSet(false); // do not use circular buffer
    controller.RecorderDataCountSet(VALUES_PER_RECORD); // number of values per record
    controller.RecorderDataAddressSet(0, axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeACTUAL_POSITION)); // record axis position
    controller.RecorderDataAddressSet(1, digitalInput.AddressGet()); // record digital input state
    // start recording
    controller.RecorderStart();
    controller.OS.Sleep(RECORD_TIME); // record for specified time
    // retrieve recorded data
    int recordsAvailable = controller.RecorderRecordCountGet();
    Console.WriteLine($"There are {recordsAvailable} records available");
    // process records to find when input triggered
    for (int i = 0; i < recordsAvailable; i++)
    {
    controller.RecorderRecordDataRetrieve(); // retrieve one record
    double positionRecord = controller.RecorderRecordDataDoubleGet(0); // get axis position value
    int digitalInputValue = controller.RecorderRecordDataValueGet(1); // get digital input value
    // check if digital input bit is high
    if ((digitalInputValue & digitalInput.MaskGet()) == digitalInput.MaskGet())
    {
    Console.WriteLine($"Encoder position was: {positionRecord} when input triggered");
    break;
    }
    }
    // stop and reset recorder
    controller.RecorderStop();
    controller.RecorderReset();
    }
    // handle errors as needed
    finally
    {
    controller.Delete(); // dispose
    }
    Constants used in the C# sample apps.
    Definition _constants.cs:3
    const int AXIS_0_INDEX
    Default: 0.
    Definition _constants.cs:11
    static void CheckErrors(RapidCodeObject rsiObject)
    Checks for errors in the given RapidCodeObject and throws an exception if any non-warning errors are ...
    Definition _helpers.cs:15
    static void PhantomAxisReset(Axis phantomAxis)
    Configures a phantom axis on the controller.
    Definition _helpers.cs:144
    Helpers class provides static methods for common tasks in RMP applications.
    Definition _helpers.cs:5
    uint64_t AddressGet(RSIAxisAddressType addressType)
    Get the an address for some location on the Axis.
    Represents a single axis of motion control. This class provides an interface for commanding motion,...
    Definition rsi.h:5863
    uint64_t AddressGet()
    Get the Host Address for the I/O point.
    static IOPoint * CreateDigitalInput(Axis *axis, RSIMotorDedicatedIn motorDedicatedInNumber)
    Create a Digital Input from an Axis' Dedicated Input bits.
    int32_t MaskGet()
    Get the bit mask for the I/O point.
    Represents one specific point: Digital Output, Digital Input, Analog Output, or Analog Input....
    Definition rsi.h:11550
    static MotionController * Get()
    Get an already running RMP EtherCAT controller.
    Represents the RMP soft motion controller. This class provides an interface to general controller con...
    Definition rsi.h:800
    RSIControllerAddressType
    Used to get firmware address used in User Limits, Recorders, etc.
    Definition rsienums.h:405
    RSIAxisAddressType
    Used to get firmware address used in User Limits, Recorders, etc.
    Definition rsienums.h:434

Recording Performance Metrics

  • C++

    #include <iomanip>
    #include "SampleAppsHelper.h" // Import our helper functions.
    #include "rsi.h" // Import our RapidCode Library.
    using namespace RSI::RapidCode; // Import the RapidCode namespace
    // Helper function to calculate the minimum, maximum, and average of a vector of integers
    static std::vector<int> MinMaxAvg(std::vector<int> data)
    {
    int min = data[0];
    int max = data[0];
    int sum = 0;
    for (int i = 0; i < data.size(); i++)
    {
    if (data[i] < min)
    {
    min = data[i];
    }
    if (data[i] > max)
    {
    max = data[i];
    }
    sum += data[i];
    }
    int avg = sum / data.size();
    return {min, max, avg};
    }
    int main()
    {
    const std::string SAMPLE_APP_NAME = "Utilities: Record Performance";
    // Print a start message to indicate that the sample app has started
    /* CONSTANTS */
    const int RECORD_PERIOD_SAMPLES = 1; // Number of samples between each record.
    const int RECORD_TIME = 1000; // Time in milliseconds to record for.
    /* RAPIDCODE INITIALIZATION */
    // Create the controller
    int exitCode = -1; // Set the exit code to an error value.
    try // Ensure that the controller is deleted if an error occurs.
    {
    /* SAMPLE APP BODY */
    // Check if the network is started
    int valuesPerRecord;
    bool networkTimingEnabled = false;
    if (controller->NetworkStateGet() != RSINetworkState::RSINetworkStateOPERATIONAL)
    {
    std::cout << "Network is not operational. Only Firmware Timing Deltas will be recorded." << std::endl;
    valuesPerRecord = 1;
    }
    else
    {
    // Enable network timing
    controller->NetworkTimingEnableSet(true);
    networkTimingEnabled = true;
    valuesPerRecord = 3;
    }
    // Add a recorder
    int recorderIndex = controller->RecorderCountGet();
    controller->RecorderCountSet(recorderIndex + 1);
    // Check if the recorder is running already, if it is then stop it
    if (controller->RecorderEnabledGet(recorderIndex))
    {
    controller->RecorderStop(recorderIndex);
    controller->RecorderReset(recorderIndex);
    }
    // Configure the recorder
    controller->RecorderPeriodSet(RECORD_PERIOD_SAMPLES);
    controller->RecorderCircularBufferSet(false);
    controller->RecorderDataCountSet(valuesPerRecord);
    controller->RecorderDataAddressSet(0, controller->AddressGet(RSIControllerAddressType::RSIControllerAddressTypeFIRMWARE_TIMING_DELTA));
    if (networkTimingEnabled)
    {
    controller->RecorderDataAddressSet(1, controller->AddressGet(RSIControllerAddressType::RSIControllerAddressTypeNETWORK_TIMING_DELTA));
    controller->RecorderDataAddressSet(2, controller->AddressGet(RSIControllerAddressType::RSIControllerAddressTypeNETWORK_TIMING_RECEIVE_DELTA));
    }
    // Start the recorder
    controller->RecorderStart(recorderIndex);
    controller->OS->Sleep(RECORD_TIME);
    controller->RecorderStop(recorderIndex);
    int recordCount = controller->RecorderRecordCountGet(recorderIndex);
    std::cout << "There are " << recordCount << " records available." << std::endl;
    // Read the records
    std::vector<int> firmawareTimingDeltas(recordCount);
    std::vector<int> networkTimingDeltas(recordCount);
    std::vector<int> networkTimingReceiveDeltas(recordCount);
    for (int i = 0; i < recordCount; i++)
    {
    controller->RecorderRecordDataRetrieve(recorderIndex);
    firmawareTimingDeltas[i] = controller->RecorderRecordDataValueGet(recorderIndex, 0);
    if (networkTimingEnabled)
    {
    networkTimingDeltas[i] = controller->RecorderRecordDataValueGet(recorderIndex, 1);
    networkTimingReceiveDeltas[i] = controller->RecorderRecordDataValueGet(recorderIndex, 2);
    }
    }
    // Calculate the statistics
    std::vector<int> firmwareTimingStats = MinMaxAvg(firmawareTimingDeltas);
    std::cout << "Firmware Timing Deltas (us): ";
    std::cout << " Min = " << std::setw(4) << firmwareTimingStats[0];
    std::cout << " Max = " << std::setw(4) << firmwareTimingStats[1];
    std::cout << " Avg = " << std::setw(4) << firmwareTimingStats[2] << std::endl;
    if (networkTimingEnabled)
    {
    std::vector<int> networkTimingStats = MinMaxAvg(networkTimingDeltas);
    std::cout << "Network Timing Deltas (us): ";
    std::cout << " Min = " << std::setw(4) << networkTimingStats[0];
    std::cout << " Max = " << std::setw(4) << networkTimingStats[1];
    std::cout << " Avg = " << std::setw(4) << networkTimingStats[2] << std::endl;
    std::vector<int> networkTimingReceiveStats = MinMaxAvg(networkTimingReceiveDeltas);
    std::cout << "Network Timing Receive Deltas (us):";
    std::cout << " Min = " << std::setw(4) << networkTimingReceiveStats[0];
    std::cout << " Max = " << std::setw(4) << networkTimingReceiveStats[1];
    std::cout << " Avg = " << std::setw(4) << networkTimingReceiveStats[2] << std::endl;
    }
    // Delete the recorder
    controller->RecorderCountSet(recorderIndex);
    exitCode = 0; // Set the exit code to success.
    }
    catch (const std::exception &ex)
    {
    std::cerr << ex.what() << std::endl;
    exitCode = -1;
    }
    // Delete the controller as the program exits to ensure memory is deallocated in the correct order
    controller->Delete();
    // Print a message to indicate the sample app has finished and if it was successful or not
    SampleAppsHelper::PrintFooter(SAMPLE_APP_NAME, exitCode);
    return exitCode;
    }
    static MotionController * Create(CreationParameters *creationParameters)
    Initialize and start the RMP EtherCAT controller.
    @ RSINetworkStateOPERATIONAL
    EtherCAT operational, good state.
    Definition rsienums.h:573
    @ RSIControllerAddressTypeNETWORK_TIMING_DELTA
    the latest time delta between the current network packet send time and the previous (microseconds)....
    Definition rsienums.h:416
    @ RSIControllerAddressTypeNETWORK_TIMING_RECEIVE_DELTA
    the latest time delta between the current network packet receive time and the previous (microseconds)...
    Definition rsienums.h:417
    @ RSIControllerAddressTypeFIRMWARE_TIMING_DELTA
    the latest time delta between the current RMP sample and the previous (microseconds)
    Definition rsienums.h:415
    static void PrintFooter(std::string sampleAppName, int exitCode)
    Print a message to indicate the sample app has finished and if it was successful or not.
    static void CheckErrors(RapidCodeObject *rsiObject)
    Checks for errors in the given RapidCodeObject and throws an exception if any non-warning errors are ...
    static void PrintHeader(std::string sampleAppName)
    Print a start message to indicate that the sample app has started.
    static MotionController::CreationParameters GetCreationParameters()
    Returns a MotionController::CreationParameters object with user-defined parameters.
    CreationParameters for MotionController::Create.
    Definition rsi.h:866