#include #include #include #include #include #include #include "Automation1.h" void connectToController(); void disconnectFromController(); void startController(); void stopController(); void enableAxis(int32_t axis); void disableAxis(int32_t axis); void homeAxis(int32_t axis); void abortAxis(int32_t axis); void showAxisStatus(int32_t axis); void moveAxisLinear(int32_t axis, double distance, double speed); void runProgram(const char* aeroscriptProgramPath); void showProgramStatus(); void stopProgram(); void getGlobalInteger(int32_t index); void setGlobalInteger(int32_t index, int64_t newValue); void showAxisParameters(int32_t axis); void printHelp(); void printError(); /// /// The handle to the connected Automation1 controller. All controller interaction will be done using this variable. /// This handle is null if we are not connected. See connectToController() and startController() in this source file for /// more details on how to connect to and start the controller. /// Automation1Controller controller = NULL; /// /// The Automation1 console example for C/C++. /// /// This example has a simple command line interface to demonstrate the basics of interacting /// with an Automation1 controller from a user's perspective. The code for each command line command will /// demonstrate how to perform these actions through the C API. /// /// The IntelliSense information on the types, enums, and functions found in the header files document much of the API and how to use it. /// /// This project links against Automation1C64.dll and Automation1Compiler64.dll. These are /// required to compile and run the application. /// /// You might not be able to open or build this project from the installation directory under "C:\Program Files\Aerotech". /// If that is the case, copy this example and the dlls listed above to your user files directory and build from there. /// /// Be sure to read the Readme.txt file in the APIs/C folder. /// int32_t main(int32_t argc, char** argv) { char inputBuffer[2048]; char command[64]; int32_t arg0; double arg1; double arg2; int numParsed; char programPath[1024]; int64_t newGlobalInteger; printf("Enter a command(or \"quit\" to exit or \"help\" for a list of commands)\n"); printf("> "); // This loop checks for input over and over for simple command line commands and then executes the commands with the user // supplied arguments. See the individual functions in this source file for more details on each command. while (true) { // If input is detected from stdin, it will be read into the buffer and the function will return a non-null pointer. if (fgets(inputBuffer, 2048, stdin)) { // This function will attempt to parse the input into the formats specified by the second argument. // On failure, this function returns 0 or EOF (generally -1). numParsed = sscanf_s(inputBuffer, "%s %d %lf %lf", command, (unsigned)_countof(command), &arg0, &arg1, &arg2); if (numParsed <= 0) { printf("Could not parse input\n"); printHelp(); printf("> "); continue; } for (int character = 0; character < 64; character++) { command[character] = tolower(command[character]); } } else { continue; } if (!strcmp(command, "connect")) { connectToController(); } else if (!strcmp(command, "disconnect")) { disconnectFromController(); } else if (!strcmp(command, "start")) { startController(); } else if (!strcmp(command, "stop")) { stopController(); } else if (!strcmp(command, "enable")) { if (numParsed == 2) { enableAxis(arg0); } else { printf("Invalid command: you must specify an axis to enable\n"); } } else if (!strcmp(command, "disable")) { if (numParsed == 2) { disableAxis(arg0); } else { printf("Invalid command: you must specify an axis to disable\n"); } } else if (!strcmp(command, "home")) { if (numParsed == 2) { homeAxis(arg0); } else { printf("Invalid command: you must specify an axis to home\n"); } } else if (!strcmp(command, "abort")) { if (numParsed == 2) { abortAxis(arg0); } else { printf("Invalid command: you must specify an axis to abort motion on\n"); } } else if (!strcmp(command, "axisstatus")) { if (numParsed == 2) { showAxisStatus(arg0); } else { printf("Invalid command: you must specify an axis to get status from\n"); } } else if (!strcmp(command, "movelinear")) { if (numParsed == 4) { moveAxisLinear(arg0, arg1, arg2); } else { printf("Invalid command: you must specify an axis, distance, and speed for the linear move\n"); } } else if (!strcmp(command, "runprogram")) { // Run program is an edge case in terms of input parsing, so we parse the input again with different formatting. numParsed = sscanf_s(inputBuffer, "%s %s", command, (unsigned)_countof(command), programPath, (unsigned)_countof(programPath)); if (numParsed == 2) { runProgram(programPath); } else { printf("Invalid command: you must specify the path to an AeroScript program to run\n"); } } else if (!strcmp(command, "programstatus")) { showProgramStatus(); } else if (!strcmp(command, "stopprogram")) { stopProgram(); } else if (!strcmp(command, "getglobalinteger")) { if (numParsed == 2) { getGlobalInteger(arg0); } else { printf("Invalid command: you must specify an index to get\n"); } } else if (!strcmp(command, "setglobalinteger")) { // Set global integer is another edge case, so we parse the input again with different formatting. numParsed = sscanf_s(inputBuffer, "%s %d %lld", command, (unsigned)_countof(command), &arg0, &newGlobalInteger); if (numParsed == 3) { setGlobalInteger(arg0, (int64_t)arg1); } else { printf("Invalid command: you must specify an index and a new integer value to set\n"); } } else if (!strcmp(command, "showaxisparameters")) { if (numParsed == 2) { showAxisParameters(arg0); } else { printf("Invalid command: you must specify an axis to show parameters for\n"); } } else if (!strcmp(command, "help")) { printHelp(); } else if (!strcmp(command, "quit")) { // Because connecting to the controller allocates memory, we should always disconnect before // exiting the program to avoid leaking memory. if (controller) { disconnectFromController(); } break; } else { printf("Unknown Command\n"); printHelp(); } printf("> "); } } /// /// Connects to the Automation1 controller. /// void connectToController() { // Make sure we aren't connected before trying to do anything. if (controller) { printf("Already connected\n"); return; } // Calling Automation1_Connect(&controller) will connect to the controller installed on the local machine. If // we wanted to connect to a controller installed on a different machine with the IP address 192.168.1.15, // we could instead call Automation1_ConnectWithHost("192.168.1.15", &controller). // // Connecting to the controller will not change its running state, meaning that we might have to also // call Automation1_Controller_Start(controller) on our controller variable before we can run AeroScript programs // or perform motion. We must start the controller before using most of the functions in the C API. See the // startController(controller) function in this source file for more information on starting the controller. // // To avoid leaking memory, be sure to call Automation1_Disconnect(controller) before quitting. See // disconnectFromController(controller) for more information. if (!Automation1_Connect(&controller)) { printError(); return; } printf("Connected to Automation1 controller\n"); } /// /// Disconnects from the Automation1 controller. /// void disconnectFromController() { // Make sure we are connected before trying to do anything. if (!controller) { printf("Already disconnected\n"); return; } // Disconnecting from a controller will not change its running state, meaning that the controller // might still be running after we disconnect. Call Automation1_Controller_Stop(controller) before disconnecting // to stop the controller from running AeroScript programs or performing motion. See the stopController(controller) // function in this source file for more information. Use this function when your application no longer needs to interact // with the controller but you want the controller to continue running. // // Calling Automation1_Disconnect(controller) frees memory associated with the controller handle. if (!Automation1_Disconnect(controller)) { printError(); } controller = NULL; printf("Disconnected from Automation1 controller\n"); } /// /// Starts the Automation1 controller. /// void startController() { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before you can start it\n"); return; } // The act of connecting to the controller on its own will not change the running state of the controller // so we have to explicitly start it by calling Automation1_Controller_Start(controller). We could check // Automation1_Controller_IsRunning(controller) to see if the controller is already running, but since // Automation1_Controller_Start(controller) will just do nothing if the controller is already running we // can call it regardless. We must start the controller before calling any other C API functionality. if (!Automation1_Controller_Start(controller)) { printError(); return; } printf("Controller started\n"); } /// /// Stops the Automation1 controller. /// void stopController() { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before you can stop it\n"); return; } // Calling Automation1_Controller_Stop(controller) will stop the Automation1 controller but not disconnect us. // We could check Automation1_Controller_IsRunning(controller) to see if the controller is already running, // but since Automation1_Controller_Stop(controller) will just do nothing if the controller is already running // we can call it regardless. if (!Automation1_Controller_Stop(controller)) { printError(); return; } printf("Controller stopped\n"); } /// /// Enables an axis. /// /// The index of the axis to enable. void enableAxis(int32_t axis) { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before enabling an axis\n"); return; } // We can enable an axis using the Automation1_Command_Enable(controller, taskIndex, axes, axesLength) // in the Automation1Command.h header. The Automation1Command.h header provides access to many of the AeroScript // commands that are used to perform actions on the controller. This function accepts an array but we can give it a single // value by passing it by-reference and hardcoding the array length to "1". // You can also enable multiple axes at once. // // If an error occurs while executing a command, like if the controller is not started or an axis fault // has occurred, the error can be accessed via functionality provided in the Automation1Error.h header file. // See the function printError() for more information. if (!Automation1_Command_Enable(controller, 1, &axis, 1)) { printError(); return; } printf("Axis %d enabled\n", axis); } /// /// Disables an axis. /// /// The index of the axis to disable. void disableAxis(int32_t axis) { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before disabling an axis\n"); return; } // We can disable an axis using the Automation1_Command_Disable(controller, axes, axesLength) function // in the Automation1Command.h header. The Automation1Command.h header provides access to many of the AeroScript // commands that are used to perform actions on the controller. This function accepts arrays but we can give it a single // value by passing it by-reference and hardcoding the array length to "1". // You can also disable multiple axes at once. // // If an error occurs while executing a command, like if the controller is not started or an axis fault // has occurred, the error can be accessed via functionality provided in the Automation1Error.h header file. // See the function printError() for more information. if (!Automation1_Command_Disable(controller, &axis, 1)) { printError(); return; } printf("Axis %d disabled\n", axis); } /// /// Homes an axis. /// /// The index of the axis to home. void homeAxis(int32_t axis) { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before homing an axis\n"); return; } // We can home an axis using the Automation1_Command_Home(controller, taskIndex, axes, axesLength) function // in the Automation1Command.h header. The Automation1Command.h header provides access to many of the AeroScript // commands that are used to perform actions on the controller. This function accepts an array but we can give it a single // value by passing it by-reference and hardcoding the array length to "1". // You can also home multiple axes at once. // // If an error occurs while executing a command, like if the controller is not started or an axis fault // has occurred, the error can be accessed via functionality provided in the Automation1Error.h header file. // See the function printError() for more information. if (!Automation1_Command_Home(controller, 1, &axis, 1)) { printError(); return; } printf("Axis %d homed\n", axis); } /// /// Aborts motion on an axis. /// /// The index of the axis to abort motion on. void abortAxis(int32_t axis) { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before aborting\n"); return; } // We can abort motion on an axis using theAutomation1_Command_Abort(controller, axes, axesLength) function // in the Automation1Command.h header. The Automation1Command.h header provides access to many of the AeroScript // commands that are used to perform actions on the controller. This function accepts an array but we can give it a single // value by passing it by-reference and hardcoding the array length to "1". // You can also abort multiple axes at once. // // If an error occurs while executing a command, like if the controller is not started or an axis fault // has occurred, the error can be accessed via functionality provided in the Automation1Error.h header file. // See the function printError() for more information. if (!Automation1_Command_Abort(controller, &axis, 1)) { printError(); return; } printf("Motion aborted on axis %d\n", axis); } /// /// Gets common and important informaiton about an axis. /// /// The index of the axis to get information about. void showAxisStatus(int32_t axis) { Automation1StatusConfig statusConfig; double result[3]; bool isEnabled; bool isHomed; bool calibrationEnabled1D; bool calibrationEnabled2D; // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before getting an axis's status.\n"); return; } // We can get information about the current state of the controller, tasks, and axes via status items. To do so, we must first // specify the items we want to query by creating an Automation1StatusConfig struct. We then add each status item to the axis // category using the Automation1_StatusConfig_AddAxisStatusItem(statusConfig, axis, axisStatusItem, argument) function. To // actually get the values of these items, we call GetStatusItem(statusConfig), which populates an array of doubles with the // requested values. Automation1_StatusConfig_Create(&statusConfig); // Status items are defined in enums in the C API. ProgramPosition is the position specified in program-space, before being // transformed and sent to the drive. See the Controller Motion Signals help file topic for more details. Automation1_StatusConfig_AddAxisStatusItem(statusConfig, axis, Automation1AxisStatusItem_ProgramPositionFeedback, 0); // DriveStatus is a series of bits that can be masked. We will use it to get the axis enabled bit. Automation1_StatusConfig_AddAxisStatusItem(statusConfig, axis, Automation1AxisStatusItem_DriveStatus, 0); // AxisStatus is another series of bits that can be masked. We will use it to get the axis homed bit, calibration enabled 1D bit, // and calibration enabled 2D bit. In this case, homed indicates whether or not the axis in question has been homed since the last // controller reset. The calibration bits indicate if the axis is currently calibrated. Automation1_StatusConfig_AddAxisStatusItem(statusConfig, axis, Automation1AxisStatusItem_AxisStatus, 0); // If we fail to get all status items, we should not use the results. Instead we should free up the memory used by the statusConfig // and return. if (!Automation1_Status_GetResults(controller, statusConfig, result, 3)) { printf("Failed to get axis %d status\n", axis); printError(); Automation1_StatusConfig_Destroy(statusConfig); return; } printf("Axis %d Status\n", axis); printf("--------------\n"); // ProgramPosition is acquired directly as a double, which is what we need. printf("Position: %lf\n", result[0]); // DriveStatus is a series of status bits that can be masked to get various information about the state of the drive. // It is acquired as a double, but we need to interperet it as a series of maskable bits. To do so, we cast it to // a 64-bit integer. We next apply the "Enabled" mask from the enum and check if the result equals the mask to // determine if the drive axis is enabled. isEnabled = (Automation1DriveStatus_Enabled & (int64_t)result[1]) == Automation1DriveStatus_Enabled; printf("Enabled: %s\n", isEnabled ? "true" : "false"); // AxisStatus is similar to DriveStatus in that it can be masked to get information about the state of the axis. // It is also acquired as a double, but we again need to interperet it as a series of maskable bits. To do so, we repeat // the process outlined for DriveStatus with AxisStatus. isHomed = (Automation1AxisStatus_Homed & (int64_t)result[2]) == Automation1AxisStatus_Homed; printf("Homed: %s\n", isHomed ? "true" : "false"); // AxisStatus also contains status bits relating to the calibration state of the axis. To get these, we simply need to // apply different masks and "or" the results. calibrationEnabled1D = (Automation1AxisStatus_CalibrationEnabled1D & (int64_t)result[2]) == Automation1AxisStatus_CalibrationEnabled1D; calibrationEnabled2D = (Automation1AxisStatus_CalibrationEnabled2D & (int64_t)result[2]) == Automation1AxisStatus_CalibrationEnabled2D; printf("Calibration State: %s\n", (calibrationEnabled1D || calibrationEnabled2D) ? "true" : "false"); // Destorying the statusConfig frees up any memory associated with it. Automation1_StatusConfig_Destroy(statusConfig); } /// /// Executes a linear move on an axis. /// /// The index of the axis to move. /// The distance to move the axis. /// The speed at which to move the axis. void moveAxisLinear(int32_t axis, double distance, double speed) { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before moving an axis\n"); return; } // The Automation1_Command_MoveLinear(controller, taskIndex, axes, axesLength, distances, distancesLength, speed) // function will not return until the move is complete. This function accepts arrays but we can give it a single // value by passing it by-reference and hardcoding the array length to "1". // You can also move multiple axes at once. // // We can keep this application responsive during the move if we do the move on a background thread. // You should be familiar with multi-threaded programming before doing this. printf("Moving axis %d\n", axis); if (!Automation1_Command_MoveLinear(controller, 1, &axis, 1, &distance, 1, speed)) { printError(); return; } printf("Move complete\n"); } /// /// Runs an AeroScript program on the Automation1 controller. /// /// The path to the .ascript file to run. void runProgram(const char* aeroscriptProgramPath) { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before running an AeroScript program\n"); return; } // When we call Automation1_Task_ProgramRun(controller, taskIndex, aeroscriptProgramPath), it will // load the program on the controller task and begin execution, but it will not wait for the program // to complete before returning. The AeroScript source file will be compiled before running. If there // is a compile error, this function will return false and the error can be acquired via the functionality // provided via the Automation1Error.h header (see printError() in this file for more details). We can use // the functionality from the Automation1Task.h header to check on the status of our program as it runs, // and to find out when it completes. printf("Starting AeroScript program\n"); if (!Automation1_Task_ProgramRun(controller, 1, aeroscriptProgramPath)) { printError(); return; } } /// /// Shows the status of the currently running AeroScript program. /// void showProgramStatus() { Automation1TaskStatus taskStatus[2]; // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before getting an AeroScript program's status\n"); return; } // We can check the state of the task we are running the AeroScript program on (Task 1 in this example) // to find out the status of our running AeroScript program. We can check this task state regularly // to track our AeroScript program if it runs, make sure no errors occur, etc. // Calling Automation1_Task_GetStatus(controller, taskStatus, taskStatusLength) gets a moment in time for // the first taskStatusLength number of tasks on the controller. To get new status you must access the // Status property again. if (!Automation1_Task_GetStatus(controller, taskStatus, 2)) { printError(); return; } switch (taskStatus[1].TaskState) { case Automation1TaskState_Error: printf("An AeroScript error occurred: %s\n", taskStatus[1].ErrorMessage); break; case Automation1TaskState_Idle: printf("No AeroScript program is loaded or running\n"); break; case Automation1TaskState_ProgramReady: printf("The AeroScript program has not started yet\n"); break; case Automation1TaskState_ProgramRunning: printf("The AeroScript program is running\n"); break; case Automation1TaskState_ProgramPaused: printf("The AeroScript program is paused\n"); break; case Automation1TaskState_ProgramComplete: printf("The AeroScript program has completed\n"); break; // We should not encounter these task states in this example program. case Automation1TaskState_ProgramFeedhold: case Automation1TaskState_Inactive: case Automation1TaskState_Unavailable: case Automation1TaskState_QueueRunning: case Automation1TaskState_QueuePaused: default: break; } } /// /// Stops the currently running AeroScript program. /// void stopProgram() { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before stopping a program\n"); return; } // To stop a running AeroScript program, we stop the controller task it is running on (Task 1 in this example) // The call to Automation1_Task_ProgramStop(controller, taskIndex, millisecondTimeout) will terminate the // program and wait for it to stop (or for the timeout to trigger) before returning. if (!Automation1_Task_ProgramStop(controller, 1, 500)) { printError(); return; } printf("Program stopped\n"); } /// /// Gets a value from the global integer array variable. /// /// The index of the integer to get. void getGlobalInteger(int32_t index) { int64_t integerOut = 0; // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before setting a global integer\n"); return; } // The controller has a set of global variable arrays that are accessible from every task on the controller and from every API. // These variables can be used to communicate data between tasks or between the controller and a custom application. // There are three global arrays for each data type: AeroScript integer values ($iglobal), AeroScript real values ($rglobal), and // and AeroScript string values ($sglobal). // // To get a global integer value, we use the Variables functions to return a single, specific global integer index. // This function accepts an array but we can give it a single value by passing it by-reference and hardcoding the array length to "1". // You can also get multiple global variables at once. if (!Automation1_Variables_GetGlobalIntegers(controller, index, &integerOut, 1)) { printError(); return; } printf("$iglobal[%d] is %lld\n", index, integerOut); } /// /// Sets a value of an index of the global integer array variable. /// /// The index of the integer to set. /// The new value of the integer. void setGlobalInteger(int32_t index, int64_t newValue) { // Make sure we are connected before trying to do anything. if (!controller) { printf("You must connect to the controller before setting a global integer\n"); return; } // The controller has a set of global variable arrays that are accessible from every task on the controller and from every API. // These variables can be used to communicate data between tasks or between the controller and a custom application. // There are three global arrays for each data type: AeroScript integer values ($iglobal), AeroScript real values ($rglobal), and // and AeroScript string values ($sglobal). // // To change a global integer value, we use the Variables functions to set a single, specific global integer index. // This function accepts an array but we can give it a single value by passing it by-reference and hardcoding the array length to "1". // You can also set multiple global variables at once. if (!Automation1_Variables_SetGlobalIntegers(controller, index, &newValue, 1)) { printError(); return; } printf("$iglobal[%d] is now set to: %lld\n", index, newValue); } /// /// Shows common parameter values for an axis. /// /// The axis to show parameter values for. void showAxisParameters(int32_t axis) { // Controller parameters can be accessed through the Automation1_Parameter_Get* functions. They are divided into three categories: // Axis, Task, and System parameters. Parameters are then divided into numeric and string parameters, and each have their own // function for getting and setting. // // The FaultMask, DefaultAxisSpeed, and DefaultAxisRampRate parameters are numeric axis parameters, so we use the // Automation1_Parameter_GetAxisValue function to get their values. // // For a string axis parameter, use the Automation1_Parameter_GetAxisStringValue() function to get its value. double axisFaultMask; double defaultAxisSpeed; double defaultAxisRampRate; if (!Automation1_Parameter_GetAxisValue(controller, axis, Automation1AxisParameterId_FaultMask, &axisFaultMask) || !Automation1_Parameter_GetAxisValue(controller, axis, Automation1AxisParameterId_DefaultAxisSpeed, &defaultAxisSpeed) || !Automation1_Parameter_GetAxisValue(controller, axis, Automation1AxisParameterId_DefaultAxisRampRate, &defaultAxisRampRate)) { printError(); return; } // The FaultMask axis parameter is a series of bits that can be masked to get/set the protection status of // a specific axis fault. After casting the parameter value to an "int64_t", we can and the result with // our desired Automation1AxisFault enum value and compare it to itself to determine if a specified AxisFault // bit is enabled. bool isMotorTemperatureFaultProtectionEnabled = ((int64_t)axisFaultMask & Automation1AxisFault_MotorTemperatureFault) == Automation1AxisFault_MotorTemperatureFault; char* motorTemperatureFaultProtectionStatus = isMotorTemperatureFaultProtectionEnabled ? "Enabled" : "Disabled"; printf("Motor Temperature Fault protection: %s\n", motorTemperatureFaultProtectionStatus); printf("Default Axis Speed: %f\n", defaultAxisSpeed); printf("Default Axis Ramp Rate: %f\n", defaultAxisRampRate); // To set the values of controller parameters, use the Automation1_Parameter_Set* functions to set a numeric // or string value for an axis, task, or system parameter. // Example: // // Automation1_Parameter_SetAxisValue(controller, axis, Automation1AxisParameterId_DefaultAxisSpeed, newDefaultAxisSpeed); // Automation1_Parameter_SetAxisValue(controller, axis, Automation1AxisParameterId_DefaultAxisRampRate, newDefaultAxisRampRate); } /// /// Prints the list of commands available to the user. /// void printHelp() { printf("Available commands:\n"); printf(" Connect\n"); printf(" Disconnect\n"); printf(" Start\n"); printf(" Stop\n"); printf(" Enable [Axis Index]\n"); printf(" Disable [Axis Index]\n"); printf(" Home [Axis Index]\n"); printf(" Abort [Axis Index]\n"); printf(" AxisStatus [Axis Index]\n"); printf(" MoveLinear [Axis Index] [Distance] [Speed]\n"); printf(" RunProgram [AeroScript Program Path]\n"); printf(" ProgramStatus\n"); printf(" StopProgram\n"); printf(" GetGlobalInteger [Integer Index]\n"); printf(" SetGlobalInteger [Integer Index] [New Value]\n"); printf(" ShowAxisParameters [Axis Index]\n"); printf(" Quit\n"); } /// /// Gets the most recent error from the controller and prints it for the user. /// void printError() { // When a function from the C API fails, an error code and message are stored. We can use // Automation1_GetLastError() to get the most recent error code from the controller. Note // that a subsequent error will overwrite the currently stored code and message. int32_t lastError = Automation1_GetLastError(); // We can then use Automation1_GetLastErrorMessage(buffer, bufferLength) to populate a char array with // the error message. char lastErrorMessage[2048]; Automation1_GetLastErrorMessage(lastErrorMessage, 2048); printf(" error (%d): %s\n", lastError, lastErrorMessage); }