Record user variables

KINGSTAR has the Log function that is able to record the change of many variables, including user variables. To facilitate the recording, KINGSTAR Scope is a good choice because it offers graphical user interface that is much easier to operate than coding. The guide of KINGSTAR Scope can be found in KINGSTAR online or offline help. In this section, you'll learn how to record user variables by coding. The way to record regular or user variables are the same.

Before you start to record your variables, you should read Work with user variables, which contains the necessary information for you to learn how to handle user variables.

Code

We break the steps down into a few parts for you to learn it easily.

Before we start, in Log.cpp, add the following preprocessor directives and function. We include the header ctime because the time function will be used and the header wchar.h is for wide characters. We are writing code in the RecordData function. The first line of code, RtPrintf("Use user variables to record data.\n\n");, displays the message that describes what this function is going to do.

NOTE:  Some header files overlaps with others in different sections.

Copy
#include "RT_Project_01.h"
#include <ctime>
#include <wchar.h>

int RecordData()
{
    RtPrintf("Use user variables to record data.\n\n");
    return 0;
}

 

  1. In RecordData, add three user variables and set their default values. You can review the section Add variables to know how to add them.
  2. Copy
    //Add user variables.
    UserVariable uTemperature = { 0, L"Temperature", NULL, logDouble };
    UserVariable uVoltage = { 0, L"Voltage", NULL, logDouble };
    UserVariable uWarning = { 0, L"Warning", NULL, logBool };

    KsError nRet = AddVariable(&uTemperature);
    if (nRet != errNoError)
        RtPrintf("AddVariable failed: %x\n", nRet);

    nRet = AddVariable(&uVoltage);
    if (nRet != errNoError)
        RtPrintf("AddVariable failed: %x\n", nRet);

    nRet = AddVariable(&uWarning);
    if (nRet != errNoError)
        RtPrintf("AddVariable failed: %x\n\n", nRet);

    //Set a default value to each variable.
    *((double*)uTemperature.Value) = 1;
    *((double*)uVoltage.Value) = 1;
    *((BOOL*)uWarning.Value) = FALSE;
  3. Next, we are setting channels to record by creating instances of the KsLogChannel structure. The number of channels is based on how many user variables you are going to record. In this guide we have three variables, so we set three channels. Since we are recording user variables, we select logUser as our KsLogSource.
  4. When you use logUser in a log channel, keep the following in mind:

    1. The channel index is determined by the order you add variables. First add, first assigned. The index is zero-based. We add three variables in this order: uTemperature, uVoltage, uWarning. Their indexes are zero, one, two, respectively.
    2. The value you select from KsLogVariable is not used when the source is logUser. We use logNone to clearly demonstrate the channel settings, but no matter what KsLogVariable value you use, the result is the same.
    Copy
    //Set three channels.
    //KsLogVariable is inactive when you use logUser.
    /*The indexes are determined by the order you add variables.
      Temperature(0), Voltage(1), Warning(2).*/
    KsLogChannel Channel[3] =
    {
        { logUser, 0, logNone, 0, logDouble },
        { logUser, 1, logNone, 0, logDouble },
        { logUser, 2, logNone, 0, logBool }
    };
  5. After the channels are set, we call Log to start recording. We didn't use WaitForCommand because we are recording dummy data generated by our code. If you are recording real data, you can use WaitForCommand to set a length of time for Log to record.
  6. Copy
    /*Start the log. We record 5 seconds. We don't use WaitForCommand
      because we are recording dummy data generated by the program.*/
    KsCommandStatus logState = Log(3, Channel, 0, 0, logImmediately, 5);
  7. To create dummy data, we use srand as the pseudo-random generator and the return value of time to randomize the seed value because time is always changing. After the seed is set, we use a for loop to set the random numbers to our three variables. We set a large number for the loop to run many times because it takes very short time for a loop to run once. The loop needs to run many times to generate enough values for Log to record 5 seconds.
  8. Copy
    //Set a seed to create random numbers for user variables.
    srand(static_cast<double>(time(0)));
    double Num1 = 0;
    double Num2 = 0;
    BOOL Switch = 0;

    for (int i = 0; i < 30000000; i++)
    {
        //Set a seed to create random numbers for user variables.
        Num1 = 0;
        Num2 = 0;
        Num1 = (rand() % 1000 + 100) / 1.05237;
        *((double*)uTemperature.Value) = Num1;
        Num2 = (rand() % 100 + 20) / 1.02496;
        *((double*)uVoltage.Value) = Num2;
        Switch = rand() & 1;
        *((BOOL*)uWarning.Value) = Switch;
    }
  9. After the values are recorded, we use GetCommandStatus to get the state of Log. Then we display ValueLength and Done, from which we can know how many values are recorded for one variable and whether the recording is done, respectively.
  10. Copy
    //Get the Log state to make sure it is done.
    logState = GetCommandStatus(logState);
    RtPrintf("logState.ValueLength: %d\n\n", logState.ValueLength);
    RtPrintf("logState.Done: %d\n\n", logState.Done);
  11. After the recording is completed, we want to pull the data out of the shared memory and save it to a .txt file. To do this, we declare the pointer logData to use it in RtOpenSharedMemory so that the address of logData receives the log data.
  12. Copy
    //If Log is done, start to copy the recorded data to the specified location.
    if (logState.Done && logState.ValueLength > 0)
    {
        /*Declare a pointer, and use the address of this pointer to receive the log data
          in the shared memory, which must be opened by RtOpenSharedMemory.*/
        double* logData = 0;
        HANDLE hlogSpace = RtOpenSharedMemory(SHM_MAP_READ, FALSE, L"KSLogSpace", (void**)&logData);
        if (hlogSpace == NULL || logData == NULL)
        {
            RtPrintf("Shared memory is not opened.\n\n");
            return -1;
        }
  13. To extract the data from logData, we declare the pointer pLog and allocate memory to it. The size of the memory is determined by your ValueLength. Typically, the entire length of the log data is n * ValueLength. n is the number of user variables.
  14. Copy
        /*The value length is determined by how long the recording lasts
          and how fast the cycle time. For example, a 10-second recording
          with 1-millisecond cycle time produces 10000 values for one variable.

          We use 500 microseconds for a cycle and record 5 seconds. Each variable
          in each cycle is recorded once, so each variable's ValueLength is 10000.
          The total ValueLength is 30000.

          1 second = 1000 milliseconds
          1 millisecond = 1000 microseconds*/

          //Declare a pointer.
          double* pLog = 0;

          //Allocate memory and assign the address to the pointer.
          pLog = (double*)(malloc(sizeof(double) * 3 * logState.ValueLength));
  15. Next, use memcpy to copy the content from the address of logData to pLog.
  16. Copy
          //Copy the content from the address of logData to pLog.
          memcpy(pLog, logData, sizeof(double) * 3 * logState.ValueLength);
  17. We declare the FILE pointer logFile to a .txt file opened (created if it doesn't exist) by fopen.
  18. Copy
          //Create a file stream and save it to a .txt file.
          FILE* logFile = fopen("C:\\LogData.txt", "w");
          if (logFile == NULL)
              RtPrintf("Cannot open the file.");
  19. To put the data to the .txt file, we declare the variable value to save each value in the log data, and then use a nested loop to write the data into the .txt file. Since we have three variables, we want to give each one a title and separate their values. The outer loop writes titles for each variable. The inner loop identifies which value belongs to which variable, and puts the values under their corresponding titles.
  20. Copy
          double Value = 0;
          
          //Save the log data to the .txt file.
          for (int i = 0; i < 3; i++)
          {
              //Print titles.
              switch (i)
              {
              case 0:
                  fprintf(logFile, "Temperature:\n\n");
                  break;
              case 1:
                  fprintf(logFile, "Voltage:\n\n");
                  break;
              case 2:
                  fprintf(logFile, "Warning:\n\n");
                  break;
              default:
                  RtPrintf("Unable to write data into the log file.\n\n");
              }
          
              //Print data.
              /*Because the data stream is ABCABCABC...not AAABBBCCC,
                we need to extract it by position.*/
              for (int j = 0; j < 3 * logState.ValueLength; j++)
              {
                  Value = pLog[j];
          
                  //Extract Temperature.
                  if (i == 0 && j % 3 == 0)
                      fprintf(logFile, "%lf\t", Value);
          
                  //Extract Voltage.
                  else if (i == 1 && j % 3 == 1)
                      fprintf(logFile, "%lf\t", Value);
          
                  //Extract Warning.
                  else if (i == 2 && j % 3 == 2)
                      fprintf(logFile, "%d\t", Value);
              }
          
              if (i != 2)
                  fprintf(logFile, "\n\n");
          }
  21. After all the data is written, we close the shared memory, free the allocated memory, assign zero to pLog and logData to avoid dangling pointers, and close the .txt file.
  22. Copy
        RtCloseHandle(hlogSpace);   //Close the open RTSS object handle.
        free(pLog);   //Free the allocated memory.
        pLog = 0;     //Assign zero to avoid a dangling pointer.
        logData = 0;  //Assign zero to avoid a dangling pointer.
        fclose(logFile);   //Close the file stream.
    }

Complete code

The complete code should be as follows:

Copy
int RecordData()
{
    RtPrintf("Use user variables to record data.\n\n");
    
    //Add user variables.
    UserVariable uTemperature = { 0, L"Temperature", NULL, logDouble };
    UserVariable uVoltage = { 0, L"Voltage", NULL, logDouble };
    UserVariable uWarning = { 0, L"Warning", NULL, logBool };

    KsError nRet = AddVariable(&uTemperature);
    if (nRet != errNoError)
        RtPrintf("AddVariable failed: %x\n", nRet);

    nRet = AddVariable(&uVoltage);
    if (nRet != errNoError)
        RtPrintf("AddVariable failed: %x\n", nRet);

    nRet = AddVariable(&uWarning);
    if (nRet != errNoError)
        RtPrintf("AddVariable failed: %x\n\n", nRet);

    //Set a default value to each variable.
    *((double*)uTemperature.Value) = 1;
    *((double*)uVoltage.Value) = 1;
    *((BOOL*)uWarning.Value) = FALSE;

    //Set three channels.
    //KsLogVariable is inactive when you use logUser.
    /*The indexes are determined by the order you add variables.
      Temperature(0), Voltage(1), Warning(2).*/
    KsLogChannel Channel[3] =
    {
        { logUser, 0, logNone, 0, logDouble },
        { logUser, 1, logNone, 0, logDouble },
        { logUser, 2, logNone, 0, logBool }
    };

    /*Start the log. We record 5 seconds. We don't use WaitForCommand
      because we are recording dummy data generated by the program.*/
    KsCommandStatus logState = Log(3, Channel, 0, 0, logImmediately, 5);

    //Set a seed to create random numbers for user variables.
    srand(static_cast<double>(time(0)));
    double Num1 = 0;
    double Num2 = 0;
    BOOL Switch = 0;

    for (int i = 0; i < 30000000; i++)
    {
        //Set a seed to create random numbers for user variables.
        Num1 = 0;
        Num2 = 0;
        Num1 = (rand() % 1000 + 100) / 1.05237;
        *((double*)uTemperature.Value) = Num1;
        Num2 = (rand() % 100 + 20) / 1.02496;
        *((double*)uVoltage.Value) = Num2;
        Switch = rand() & 1;
        *((BOOL*)uWarning.Value) = Switch;
    }

    //Get the Log state to make sure it is done.
    logState = GetCommandStatus(logState);
    RtPrintf("logState.ValueLength: %d\n\n", logState.ValueLength);
    RtPrintf("logState.Done: %d\n\n", logState.Done);

    //If Log is done, start to copy the recorded data to the specified location.
    if (logState.Done && logState.ValueLength > 0)
    {
        /*Declare a pointer, and use the address of this pointer to receive the log data
          in the shared memory, which must be opened by RtOpenSharedMemory.*/
        double* logData = 0;
        HANDLE hlogSpace = RtOpenSharedMemory(SHM_MAP_READ, FALSE, L"KSLogSpace", (void**)&logData);
        if (hlogSpace == NULL || logData == NULL)
        {
            RtPrintf("Shared memory is not opened.\n\n");
            return -1;
        }

        /*The value length is determined by how long the recording lasts
          and how fast the cycle time. For example, a 10-second recording
          with 1-millisecond cycle time produces 10000 values for one variable.

          We use 500 microseconds for a cycle and record 5 seconds. Each variable
          in each cycle is recorded once, so each variable's ValueLength is 10000.
          The total ValueLength is 30000.

          1 second = 1000 milliseconds
          1 millisecond = 1000 microseconds*/

        //Declare a pointer.
        double* pLog = 0;

        //Allocate memory and assign the address to the pointer.
        pLog = (double*)(malloc(sizeof(double) * 3 * logState.ValueLength));

        //Copy the content from the address of logData to pLog.
        memcpy(pLog, logData, sizeof(double) * 3 * logState.ValueLength);

        //Create a file stream and save it to a .txt file.
        FILE* logFile = fopen("C:\\LogData.txt", "w");
        if (logFile == NULL)
            RtPrintf("Cannot open the file.");

        double Value = 0;

        //Save the log data to the .txt file.
        for (int i = 0; i < 3; i++)
        {
            //Print titles.
            switch (i)
            {
            case 0:
                fprintf(logFile, "Temperature:\n\n");
                break;
            case 1:
                fprintf(logFile, "Voltage:\n\n");
                break;
            case 2:
                fprintf(logFile, "Warning:\n\n");
                break;
            default:
                RtPrintf("Unable to write data into the log file.\n\n");
            }

            //Print data.
            /*Because the data stream is ABCABCABC...not AAABBBCCC,
              we need to extract it by position.*/
            for (int j = 0; j < 3 * logState.ValueLength; j++)
            {
                Value = pLog[j];

                //Extract Temperature.
                if (i == 0 && j % 3 == 0)
                    fprintf(logFile, "%lf\t", Value);

                //Extract Voltage.
                else if (i == 1 && j % 3 == 1)
                    fprintf(logFile, "%lf\t", Value);

                //Extract Warning.
                else if (i == 2 && j % 3 == 2)
                    fprintf(logFile, "%d\t", Value);
            }

            if (i != 2)
                fprintf(logFile, "\n\n");
        }

        RtCloseHandle(hlogSpace);   //Close the open RTSS object handle.
        free(pLog);   //Free the allocated memory.
        pLog = 0;     //Assign zero to avoid a dangling pointer.
        logData = 0;  //Assign zero to avoid a dangling pointer.
        fclose(logFile);   //Close the file stream.
    }
    
    return 0;
}

 

Output:

Text file:

Temperature

Voltage

Warning