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.
#include "RT_Project_01.h"
#include <ctime>
#include <wchar.h>
int RecordData()
{
RtPrintf("Use user variables to record data.\n\n");
return 0;
}
- In RecordData, add three user variables and set their default values. You can review the section Add variables to know how to add them.
- 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. - 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. - The value you select from KsLogVariable is not used when the source is
logUser
. We uselogNone
to clearly demonstrate the channel settings, but no matter what KsLogVariable value you use, the result is the same. - 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.
- 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. - 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.
- 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 pointerlogData
to use it in RtOpenSharedMemory so that the address oflogData
receives the log data. - To extract the data from
logData
, we declare the pointerpLog
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. - Next, use memcpy to copy the content from the address of
logData
topLog
. - We declare the
FILE
pointerlogFile
to a.txt
file opened (created if it doesn't exist) by fopen. - To put the data to the
.txt
file, we declare the variablevalue
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. - After all the data is written, we close the shared memory, free the allocated memory, assign zero to
pLog
andlogData
to avoid dangling pointers, and close the.txt
file.
//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;
When you use logUser
in a log channel, keep the following in mind:
//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.
}
Complete code
The complete code should be as follows:
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