Remote Control |
Remote Interface DLL |
[Related Topics] |
The following remarks address to software developers assigned to integrate remote access on a SymPhoTime 64 server into their (client-) application. We're desperately sorry, but we simply can't anticipate, which programming language developers might choose when it comes to implement remote control of our SymPhoTime 64. So we chose a pseudo-code for our documentation. We assume that most of the experienced developers have at least adequate knowledge of the C/C++ language family. For all these, the supplied demo "Client_w_Stress" is directly understandable. But browsing the supplied material, you'll also find ready to use Object-Pascal import units for Delphi / Lazarus / Free-Pascal projects, too.
All information below is corresponding to PicoQuant's RemoteInterface.DLL V1.0.9
For convenience, we will often use the abbreviation "RI", standing for
Remote Interface.
The DLL was created to encapsulate the implementation of the entire protocol and especially granting for the delicate timing of the handshake. This is realized by introduction of its own threads for the communication via standard Windows™ sockets. From the user's point of view, all functions he directly invokes run in the context of the calling thread. To encouple the DLL from this thread, they only transfer data into the context of the DLL's main thread.
To receive feedback data from the DLL while a measurement is running, the developer has to provide a call-back function, which will be called approximately every 1.2 seconds. When called, this function is running in the context of the DLL. Consider, that the memory allocated for data exchange and the values assigned to this memory are only valid during the runtime of this function. Once terminated, the memory pointers and the contents are void. So, avoid direct access to GUI elements and time consumptive calculations inside this function, but arrange for threadsave copying the feedback data to the "outside". A lack of caution in this point could result in runtime errors hard to debug.
The DLL implements a state machine, which is - from the developer's point of view - much easier
to handle than the low level protocol machine. The following image illustrates this state machine.
Please understand all codelines hereafter as pseudo-code, and never intended to instantely run under whatsoever conditions. Instead, they are to picture out the in-general usage of the RI-DLL as supplied with SymPhoTime 64. We hope this may reduce the time of familiarization with the DLL.
There are many ways to do the import of functions provided by a DLL. In our example client application,
called the "Stress Client", as supplied with SymPhoTime 64, we use an explicite
linking mechanism, enabling the check for complete and successfull linking at initialization time of
the client program. To follow this way, create an DLL import function analogous to
"InitRemoteInterface_DLL
" from the stress client example (as coded in
RemoteInterface_Lib.cpp
).
All DLL functions use the calling convention "stdcall". The functions' names all commence with "RI_" and they all return a long int (i.e. a signed 32-bit integer), coding their result as either finished without error ( == PQ_ERRCODE_NO_ERROR ) or the error number, which might be decoded to a human readable string cErrText, using the function "RI_GetErrorText":
long int lRet;
char cErrText [SPT_RI_ERRORTEXT_MAXLEN + 1];
if (PQ_ERRCODE_NO_ERROR != (lRet = InitRemoteInterface_DLL ()))
{
RI_GetErrorText (lRet, cErrText); // - the meaning of the error code is copied to cErrText
}
Aside from bug fixes, the functionality of the DLL may or may not be subject to changes in future relases
without any further announcements by PicoQuant. But PicoQuant will increase the major high version number on
functional discontinuities and increase the major low version number on major functional enhancements.
The minor high version number will be increased on fundamental changes to the implementation, that don't
break interface continuity, whilst the minor low version number is a build or release counter, where
differences are always tolerable.
Keeping this in mind, it should be common sense to check for version compatibility:
char cLibVersion [SPT_RI_VERSIONSTRING_MAXLEN + 1];
RI_GetLibVersion (cLibVersion);
if (0 != strncmp (cLibVersion, LIB_VERSION_REFERENCE, strlen (LIB_VERSION_REFERENCE)))
{
// handle robustness to version differences...
}
At least if still in debugging process, provide a means to show the debugging window. You may C:\Users\Publicthen compare between the received data and the information sent by SPT64 as shown in the server window. While running with enabled log, all insertations to the log are recorded with timestamp.
if (PQ_ERRCODE_NO_ERROR == (lRet = RI_AssignLogFile ("C:\Users\Public\MyLog.txt")))
{
RI_EnableLog (true); // - using this function, the developer
// // selectively enables/disables logging
RI_AddLineToLog ("Begin of Session"); // - using this function, the developer can
// // write additional information to the log
RI_ShowLogWin (); // - showing the current log in its own window
}
Create a call-back function (or even more, specialized ones) with the respective footprints:
typedef long int (__stdcall *TReceiveNumParamFunc)(char* pcIdent,
float fValue,
long iRecNr);
and
typedef long int (__stdcall *TReceiveStrParamFunc)(char* pcIdent,
char* pcValue,
long iRecNr);
If passed over to the DLL with the start of a new measurement, they will be called on each incoming feedback frame (a.k.a. NACK-frame), once for each named value in each frame. These frames will be sent by SPT64 and SPT32 as well in intervals of approx. 1.2 seconds. For the names of the values transmitted, refer to feedback values. The return value of this function in exchange is evaluated by the DLL and determines whether to continue with the measurement or not. Let this call-back function have side effects, e.g.
The following example will only react on each feedback named "maxcpp" and on the record number. It will terminate the measurement if either the value of any "maxcpp" feedback increases 250 counts or if the record count increases 83 (i.e. more than 100 seconds). Consider, that this call-back function would never stop a time trace measurement, since there will never be a feedback named "maxcpp" sent during time trace measurements! (Refer to feedback values…)
long int __stdcall MyImageNumParamCallBackFunc (char* pcIdent, float fValue, long iRecNr)
{
long int lStopReason;
//
lStopReason = SPT_RI_STOP_REASON_CONTINUE_OK;
//
if (iRecNr > 83)
{
// measurement already running for more than 100 seconds
// without reaching sufficient termination conditions?
// so let's better stop here...
lStopReason = SPT_RI_STOP_REASON_ERROR;
}
else
{
if (0 == strcmp (PQ_OPT_DATANAME_MAX_COUNTS_PER_PIXEL, pcIdent))
{
if (fValue > 250.0f)
{
// measurement reached sufficient counts level
lStopReason = SPT_RI_STOP_REASON_FINISHED_OK;
}
}
}
return lStopReason;
}
char cFileName[256];
char cGroupName[64];
char cLaserName[8][256];
long int __stdcall AStrParamCallBackFunc (char* pcIdent, char* pcValue, long iRecNr)
{
long int lStopReason;
//
lStopReason = SPT_RI_STOP_REASON_CONTINUE_OK;
//
if (0 == strcmp (PQ_OPT_DATANAME_RESULTINGFILENAME, pcIdent))
{
strcpy (cFileName, pcValue);
}
//
if (0 == strcmp (PQ_OPT_DATANAME_RESULTINGGROUPNAME, pcIdent))
{
strcpy (cGroupName, pcValue);
}
//
if (0 == strncmp (PQ_OPT_DATANAME_LASERNAME, pcIdent, strlen(PQ_OPT_DATANAME_LASERNAME)))
{
long int iLaserIdx = (pcIdent [strlen(PQ_OPT_DATANAME_LASERNAME)] - '1');
strcpy (cLaserName[iLaserIdx], pcValue);
}
//
return lStopReason;
}
Then create the infrastructure (e.g. GUI-elements) to hand over individual optional
parameters for the next measurement to be started.
As already known from SPT32, this could be:
"Objective" (or PQ_OPT_INFONAME_OBJECTIVE) // string
"Pinhole" (or PQ_OPT_INFONAME_PINHOLE) // string
"MajorDichroic" (or PQ_OPT_INFONAME_MAJOR_DICHROIC) // string
"TimePerPixel" (or PQ_OPT_INFONAME_TIME_PER_PIXEL) // float, set in seconds
"TimePerImageEstimated" (or PQ_OPT_INFONAME_TIME_PER_IMAGE) // float, set in seconds
Now, for the opening handshake, call the init function "RI_Initialize" where the char* parameter cHost should contain the IP address, like e.g. "127.0.0.1", the long int parameter lPort contains the port number to be used for the comunication, (should equal 6000). If the return code equals PQ_ERRCODE_NO_ERROR, you may rely on the SPT64 version string returned in cSPTVersion.
char cHost [SPT_RI_HOSTNAME_MAXLEN];
long int lPort;
char cSPTVersion [SPT_RI_VERSIONSTRING_MAXLEN];
strcpy (cHost, "127.0.0.1");
lPort = 6000;
lRet = RI_Initialize (cHost, lPort, cSPTVersion);
If terminated without error, continue, else react on the error and maybe retry. An error frequently made is to forget to open a workspace on the server site, resulting in the errocode PQ_ERRCODE_NO_WORKSPACE.
As now the connection is approved to be free of errors (i.e. the DLL signals to be in SPT_RI_STATUS_IDLE state), you may sent the upper mentioned optional parameters to the DLL, e.g.:
RI_SetOptionalString (PQ_OPT_INFONAME_GROUPNAME, "MySubdir");
RI_SetOptionalString (PQ_OPT_INFONAME_FILENAME, "MyRawData");
RI_SetOptionalFloat (PQ_OPT_INFONAME_TIME_PER_IMAGE, 2.56);
Consider, if there is already a file with the given name in the subdirectory,
on start request the measurement will terminate with an error PQ_ERRCODE_FILE_EXISTS.
Notice, that the registration of optional parameters for the next measurement
to start isn't causing any traffic with the server, since other than during
RI_Initialize, for this purpose your application
is solely communicating with the DLL.
Now, that all necessary parameters are registered,
we are ready to register a string handler and then start a measurement,
in this example let's record for an image.
long int lState;
longbool bRecord;
long int lPixX;
long int lPixY;
float fResol;
longbool bBiDirectionalScan;
TReceiveNumParamFunc MyNPCBFunc;
TReceiveStrParamFunc MySPCBFunc;
lState = SPT_RI_STATUS_UNKNOWN;
bRecord = true; // i.e. measurement mode; false would request for test mode
lPixX = 128;
lPixY = 128;
fResol = 1.0e-6f; // given in meter
bBiDirScan = false; // mono-directional scan
MyNPCBFunc = MyImageNumParamCallBackFunc; // see above
MySPCBFunc = AStrParamCallBackFunc; // see above
lRet = RI_RegisterStringHandler (MySPCBFunc);
do
{
if (SPT_RI_STATUS_IDLE == (lState = RI_GetStatus ()))
{
lRet = RI_RequestImage (bRecord, lPixX, lPixY, fResol, bBiDirScan, MyNPCBFunc);
lState = RI_GetStatus ();
}
else
{
lRet = RI_GetStatusText (lState, cStatus);
//
// show cStatus in GUI
//
Sleep (1000);
//
// IMPORTANT: Especially if there was a measurement running before:
// Give SPT64 some time for the après measurement work;
// Don't poll too fast, because this would hinder SPT64
// ever to get ready for the next measurement, again!
}
}
while ( (lRet == PQ_ERRCODE_NO_ERROR) && (lState != SPT_RI_STATUS_MEAS_RUNNING) )
Supposed that
the measurement is accepted and running, else decode the return value and state code and act appropriately. The illustrated state machine shall only show the in principle possibility of reaction on changes of the state. You should properly design your own machine according to the demands of your respective application.
Assuming, the measurement is running, the function MyImageNumParamCallBackFunc will be called for feedback transmission. If a pixel reaches more than 250 counts or the measurement is running for more than 100 seconds (i.e. approx. 83 feedback cycles), it will be terminated, right according to plan. But it might also happen, that a user wants to stop the running measurement, for he or she realizes a mistake in the supplied parameter set or perhaps the preview provided by the SPT64 shows poor imaging conditions. On behalf of this, you should provide e.g. a button, which on click launches a stop measurement request:
if (PQ_ERRCODE_NO_ERROR == (lRet = RI_RequestStopMeas ()))
{
RI_AddLineToLog ("Measurement stopped by user!");
}
As shown in the state machine diagram, you may - in absence of error conditions - cycle these steps ad infinitum. Any error detected by the DLL, however, will change its state to "Unknown", giving you the opportunity for recovering measures. With stable working conditions accomplished again, you may then restart with the initializing sequence.
Before shut-down at last, if running under debugging conditions, don't forget to release the logfile:
RI_AddLineToLog ("End of Session");
RI_EnableLog (false);
RI_HideLogWin ();
We hope, this little excursion made things easier for you. Enjoy remote controlling your SymPhoTime!
Related Topics: |