Main Page | Features | Central Services | csv-Files | Types | Transfer | Access | API-C | API-.NET | API-Java | Examples | Downloads
page generated on 18.05.2024 - 04:45
Accessing Your Server in C

Basic Synchronous Calls

We'll begin by getting a set of data from our Workshop server and displaying the output.

Linux: Create a C module with your favorite editor called WKClient.c. You can make a Makefile such as

WKClient: WKClient.o
cc -o WKClient WKClient.c -lm -ltine
chmod a+x WKClient
WKClient.o: WKClient.c
cc -c -I/usr/include/tine WKClient.c

to help you out with compiling and linking.

Windows: Start Visual Studio and create a new Project in the workspace. You can call it something like "DataGetter", if you'd like.
We want to make 64-bit code. So do the following:

  • under PROJECT at the bottom of the menu you'll find 'myserver Properties'. Select this.
  • Select Configuration Properties and then the Configuration Manager
  • The Active solution platform is set to Win32 by default. Open up the combo box, select 'New' and then select x64 from among the choices (ARM is also offered).
  • Close the configuration manager and navigate to Configruation Properties -> C/C++ -> General.
  • On the upper right 'Configuration Combo', select 'All Configuration'.
  • Now click into the input area of 'Additional Include Directories'.
  • You should see a 'down arrow'. Click on this. Click on 'Edit'.
  • A new dialog will appear. Click on the input area and a 'browse' arrow will appear.
  • Browse your way to the TINE include directory (should be L: provided you have done a (subst L: C:\Users<username>\AppData\Local\tine).
  • on some versions of Visual Studio you might need to add the C/C++ Preprocessor directive TINE32_DLL.
  • Leave the C/C++ options and go to the Linker options.
  • Go to the 'General' category and click in the input area of 'Additional Library Directories'.
  • As in the case of 'Additional Include Directories', input (via browsing) the directory L:\LIB.
  • Now go to Linker -> Input.
  • In the 'Addtional Dependencies' input the following two libraries: tbufsrv64.lib and tine64.lib.
  • You can now hit the 'OK' button.

Now you're ready to write code.

Add the following include files to your 'DataGetter.cpp' module.

#include "tine.h"
#include "listener.h"

In the main routine we will want to start a cycler on its own thread.
This is basically a call to 'SystemStartCycleTimer()'.

Note
In many GUI frameworks a cycler is automatically started for your. But as this is a bare-bones client, you'll will need to start it explicity (the alternative is to make calls to SystemCycle() within an infinite for-loop yourself - but it's better to let the cycler run on its own thread by launching it in this manner).

And while we're at it, block the application from ending immediately by inserting a 'getchar()'. So, something like:

int _tmain(int argc, _TCHAR* argv[])
{
getchar();
return 0;
}

Now let's make a routine which gets some data for us and displays it on the console.

Put the following routine in your code:

void callAndDumpResults(void)
{
DTYPE dout;
double tnow, datats, dt;
float vals[1024];
DTYPEZERO(dout);
dout.dArrayLength = 100;
dout.dFormat = CF_FLOAT;
dout.data.fptr = vals;
if (ExecLink("/WORKSHOP/Station1/Device 0","Sine",&dout,NULL,CA_READ) == 0)
{
datats = dout.dTimeStamp;
dt = tnow - datats;
printf("data time: %s, ",GetDataTimeString(datats,FALSE));
printf("my time: %s, ",GetDataTimeString(tnow,FALSE));
printf("delta t: %g\n",dt);
}
else
{
char errstr[64];
printf("ERROR! : %s\n",GetLastLinkError(-1,errstr));
}
}

This routine, callAndDumpResults, makes a synchronous call (uses the API call from tine64.lib ExecLink) to get the "Sine" values for Device "Device 0" (assuming you haven't renamed the property or device from our previous example).

If the call is successfull, it dumps the result to the console and prints out the latency by comparing the data timestamp (coming from the server) with the caller's own clock (delta_t). If there's an error, it prints this out.

Now we need to call this routine in our main. So add an infinite 'while' loop which calls our new routine and then sleeps a bit:

while (TRUE)
{
callAndDumpResults();
millisleep(2000);
}

So our main should look something like:

int _tmain(int argc, _TCHAR* argv[])
{
while (TRUE)
{
callAndDumpResults();
millisleep(2000);
}
getchar();
return 0;
}

Notice that we're sleeping a good 2 seconds between calls. This will help us examine the latency between different techniques for obtaining data.

AND to see things CLEARLY, go back to your example server and make sure that it doesn't cycle any faster than every 1000 milliseconds!

Also, please turn the 'scheduling' OFF on the 'PushBufferedData' calls at the server (change the TRUE back to FALSE).

There's something else we should do to our example server if there's a TINE Time server running, and that is to turn OFF time synchronization. To do this add the following line of code at the top of 'main' to your SERVER (PAY Attention, please. It doesn't make sense to add this line of code to your client).

THe point is: we don't want any interfence from a time sycnhronization offset at the server when we're measuring the latency between a client and server running on the same host.

So when you added this to your SERVER and changed the 'millisleep' to 1000 in your SERVER, and changed the scheduling to FALSE on your SERVER then please restart your server.

Now run your client program and have a look at the 'delta_t' values.

So, let's compare this to the results we would obtain if instead of making explicit synchronous calls we use an 'asynchronous listener'. The call looks synchronous to the client application, but it is buffering the remote data locally (i.e. 'reflecting' the remote data locally) and using this buffered data when the client make sychronous calls.

You could replace the call to ExecLink() with a call to alsnExecLink() in our 'callAndDumpResults' routine, which would the easiest thing to do as the prototypes are very similar. Instead we will use the more versatile call to alsnCall().

void callAndDumpResults(void)
{
DTYPE dout;
double tnow, datats, dt;
float vals[1024];
DTYPEZERO(dout);
dout.dArrayLength = 1024;
dout.dFormat = CF_FLOAT;
dout.data.fptr = vals;
if (alsnCall("/WORKSHOP/Station1/Device 0[Sine]",&dout,NULL,CA_READ,NULL) == 0)
{
datats = dout.dTimeStamp;
dt = tnow - datats;
printf("data time: %s, ",GetDataTimeString(datats,FALSE));
printf("my time: %s, ",GetDataTimeString(tnow,FALSE));
printf("delta t: %g\n",dt);
}
else
{
char errstr[64];
printf("ERROR! : %s\n",GetLastLinkError(-1,errstr));
}
}
Note
the target address is contained in the call in single string parameter which denotes the target property by placing it inside of brackets '[]'. This make a clear delineation between 'where' and 'what'.
In case you're wondering, the 'alsn' prefix denotes an 'asynchronous listener', indicating that the data are reflecting asynchronously into a local buffer due to an underlying listening data link to the target.

Now repeat the test and see what differences there are in the latency of the returned data.

Right now we are using the 'typical data update ansatz' of 'polling' and making a synchronous get call. A much better strategy is to 'know' when there are in fact new data to have a look at and then get the data. We can achieve this by attaching a 'notifier' to our data link.

We looked at notifiers in our server example and the idea is the same. We will provide a notifier callback routine that will be called by the underlying system when new data are available.

Write a simple callback notifier (call id 'cb') like this:

void cb(int id)
{
callAndDumpResults();
}

So the notifier just calls our callAndDumpData() routine. If we're using an asynchronous listener in our 'callAndDumpDataRoutine()' this is the best way to code this, as the listener just gets the data from a local buffer and the notifier is called whenever there are new data in the buffer. Perfect!

Now attach the notifier to the data link by added a couple of lines of code to your main routine:

int _tmain(int argc, _TCHAR* argv[])
{
callAndDumpResults();
tAttachNotifier("/WORKSHOP/Station1/Device 0[Sine]",cb,0);
while (TRUE)
{
callAndDumpResults();
millisleep(2000);
}
getchar();
return 0;
}

That is, we make our call to 'callAndDumpData' outside our 'while loop', and then immediately attach a notifier to the same link parameters, passing the address of our notificer routine 'cb' as the 2nd argument.

We don't really need the infinite for loop, as the notifier will handle everything for us. It doesn't hurt anything to leave it in the code, but you can remove (there should be no difference in the behavior one way or the other).

Try this out and see what the latency values are telling you (the 'delta_t' value). Any improvement?

Finally (and this is a very good test!) go back to your SERVER and put the scheduling back in! (change all those 'FALSE's back to 'TRUE's). Now repeat all of the tests! In particular you should see next to no latency in the 'notifier' technique. (Note that a clock tick on a windows machine is still around 15 milliseconds).

Adding Explicit Asynchronous Calls

Let's see if we can add a monitor link for one of the properties explicity (i.e. with the 'normal' asyncrhonous call 'AttachLink' from the TINE library. We'll want to access our Sine array from a callback routine. Let's avoid extra buffering by making global declaration for a sinbuffer to hold the data, and using that in a call to AttachLink.

The simplest way to code this is show below:

float sinbuffer[1024];
void lnkCb(int id, int cc)
{
double tnow = MakeDataTimeStamp();
double datats = GetCurrentDataTimeStamp(id);
double dt = tnow - datats;
if (cc != 0) return;
printf("data time: %s, ",GetDataTimeString(datats,FALSE));
printf("my time: %s, ",GetDataTimeString(tnow,FALSE));
printf("delta t: %g\n",dt);
}
int _tmain(int argc, _TCHAR* argv[])
{
DTYPE dout;
DTYPEZERO(dout);
dout.dArrayLength = 1024;
dout.dFormat = CF_FLOAT;
dout.data.fptr = sinbuffer;
AttachLink("/TEST/SineServer/SineGen0","Sine",&dout,NULL,CA_READ,1000,lnkCb,CM_TIMER);
getchar();
return 0;
}

Notice that we use the access mode of CM_TIMER. This instructs the server to monitor the requested data at the given polling interval and to return the data at that interval. Another common access mode is CM_DATACHANGE. This instructs the server to monitor the data at the given polling interval, but to return the data only if they have changed since the last access (zero tolerance). DATACHANGE mode is by itself very good for 'status' properties, i.e. properties which do not change their values very often. On the other hand, you may want to apply a tolerence regarding your callback notification. You can do this by calling the setNotificationTolerance() routine, passing say 20 as the percent tolerance you will allow. If you do this you should see fewer updates in your temperature readout.

Occasionally it is desirable to start a link, but nonetheless to know the initial results of the link before executing the next few lines of code. YOu can accomplish this by ORing CM_WAIT to the access mode (for instance CM_TIMER|CM_WAIT or CM_DATACHANGE|CM_WAIT). You can try this out by printing something out immediately following the AttachLink(). Either you will see the results of the callback prior to your printout (with CM_WAIT) or you will see your printout (without CM_WAIT).

GetCurrentDataTimeStamp
TINE_EXPORT double GetCurrentDataTimeStamp(int i)
Returns the data timestamp associated with the input link index.
Definition: client.c:9844
DTYPE::data
DUNION data
Definition: tinetype.h:1006
GetLastLinkError
TINE_EXPORT char * GetLastLinkError(short cc, char *errstr)
The error string associated with the input error number.
Definition: client.c:9974
MakeDataTimeStamp
TINE_EXPORT double MakeDataTimeStamp(void)
Returns a data timestamp according to the current system time.
Definition: syslib.c:5939
AttachLink
TINE_EXPORT int AttachLink(const char *devName, const char *devProperty, DTYPE *dout, DTYPE *din, short access, int pollingRate, void(*cbFcn)(int, int), int mode)
Initiates an asynchronous link.
Definition: client.c:9273
GetDataTimeString
TINE_EXPORT char * GetDataTimeString(double ts, int useLongStringFormat)
Returns the TINE data timestamp as a human-readable string.
Definition: syslib.c:5764
DTYPE::dTimeStamp
double dTimeStamp
Definition: tinetype.h:1003
DTYPE::dFormat
short dFormat
Definition: tinetype.h:999
DTYPE
Defines a TINE data object.
Definition: tinetype.h:996
DUNION::fptr
float * fptr
Definition: tinetype.h:987
SystemStartCycleTimer
TINE_EXPORT void SystemStartCycleTimer(void)
Starts a TINE engine 'cycler' running (SystemCycle()) in a separate thread.
Definition: tine32.c:243
ExecLink
TINE_EXPORT int ExecLink(const char *devName, const char *devProperty, DTYPE *dout, DTYPE *din, short access)
Executes a synchronous link.
Definition: client.c:9705
SetUseGlobalSynchronization
TINE_EXPORT void SetUseGlobalSynchronization(int value)
Determines whether data timestamps are to be externally synchronized.
Definition: syslib.c:5525
DTYPE::dArrayLength
UINT32 dArrayLength
Definition: tinetype.h:998

Impressum   |   Imprint   |   Datenschutzerklaerung   |   Data Privacy Policy   |   Declaration of Accessibility   |   Erklaerung zur Barrierefreiheit
Generated for TINE API by  doxygen 1.5.8