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
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:
Now you're ready to write code.
Add the following include files to your 'DataGetter.cpp' module.
In the main routine we will want to start a cycler on its own thread.
This is basically a call to 'SystemStartCycleTimer()'.
And while we're at it, block the application from ending immediately by inserting a 'getchar()'. So, something like:
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:
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:
So our main should look something like:
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().
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:
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:
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).
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:
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).