Main Page | Features | Central Services | csv-Files | Types | Transfer | Access | API-C | API-VB/ActiveX | API-Java | Examples | Downloads
The buffered Server for C programs

C Programmers:

There is .. uh .. still a platform dependency. You're probably trying this out on a Windows machine or a Linux machine. Any other UNIX machine should follow the Linux instructions below. The coding you will do is of course independent of platform, but setting up your project or make files will not be independent.

A "do-nothing" server

If you're using Windows, Start Visual C++ and create a new Console application Project (named "myserver" or whatever). Then add a new C file to the project called myserver.c

If you're using Linux, just make a working directory and create a new file with your favorite editor with the name of myserver.c (or your favorite name).

Now, to run your server you'll need to 'attach' it to your database files, so the minimum lines of code needed to make a running (buffered) tine server are the following:

#include <stdio.h>
#include "tbufsrv.h"

int main(int argc,char *argv[])
{
  AttachServer("MYSINE",NULL,0);
  runServer();
  return 0;
}

In Windows you'll need to go to the project settings and change a few things before this will compile.

In the C/C++ tab, and the "Preprocessor" category. In the "Additional include directory" box add Z:\kernel\windows\lib32\include

In the Link tab, under "Object/Library modules" add the following at the end of the list:

wsock32.lib Z:\kernel\windows\lib32\tine32.lib Z:\kernel\windows\lib32\tbufsrv.lib

The above assumes that Z: is mapped to \\mkinthera1\hera ! If not, you should have a copy of everything in your c:\tine.install directory, so instead of 'Z:\kernel' above, use 'C:\tine.install'

We're going to use the dynamic link library tine32.dll instead of the static library tine.lib, which means you should link with tine32.lib as noted above. Tine32.lib is merely a list of entry points to the exported routines from tine32.dll.

You need to do one more thing (owing to the windows way of exporting and importing external routines). You'll need to #define the compile switch TINE32_DLL. This is typically found in the Preprocessor settings of the C/C++ compiler tab in the Project menu. Make sure you remember to do this or else none of the referenced routines will be found.

Note:
The above applies equally well to Visual Studio 2005 as well as previous versions. However, you will have to do the following to build a Visual Studio 2005 solution: In the Property menu, Linker -> Input section, you should click on the empty field next to "Ignore Specific Library" and then input "LIBC.LIB". If you do not do this, Visual Studio 2005 will refuse to build the project.

In Linux you can either type:

cc -c -I/usr/include/tine myserver.c

cc -o myserver myserver.o -lm -ltine -ltbufsrv

chmod a+x myserver

at the command line or make a make file to do it for you:

myserver: myserver.o
  cc -o myserver myserver.c -lm -ltine -ltbufsrv
  chmod a+x myserver

myserver.o: myserver.c
  cc -c -I/usr/include/tine myserver.c

You are now ready to run your server. In Windows, you can run it from inside Visual Studio or make an executable and run it. In Linux, you can run your server by typing

./myserver

You might have noticed something important right away. The device server "Export Name" appears again in this code snippet. This must be the name (case sensitive !) as appears inside of the exports.csv file (and if you're following this tutorial by the letter, also in fecid.csv). If this isn't the case, the server will not find itself in the local database and it will not initialize correctly. As noted earlier, the fecid.csv does not have to even have a column called "Export_name", but if it's there it has to match. This call to 'Attach' inside the above code snippet is a more serious violation of the "A name belongs in one place only" ansatz of a configuration database. In the case of the standard tine server, this violation does not appear, since the standard server does not 'attach' to a database. However this is a small price to pay for an enormous simplification in the server API. And starting with the buffered server will help us understand more of the tine features early on, without having to plough through arcane configuration API calls.

Check the Log file (please !)

Before rushing to the Instant Client, have a look at the server's log file. This will be called 'fec.log' and should be located in c:\tine\log (if that's where FECLOG is pointing to!), or directly in the working area, if there's no FECLOG environment.

Near the end of the log file you should see a message saying that your equipment module was "registered with equipment name server". If you see this then everything went reasonably well so far.

03.03.06 12:56:24.078 CET[*unknown*] VERSION : 3.31.0018
03.03.06 12:56:24.094 CET[*unknown*] OS      : WIN32C
03.03.06 12:56:24.094 CET[*unknown*] Work Area: 65536
03.03.06 12:56:24.094 CET[*unknown*] Temp Size: 65536
03.03.06 12:56:24.094 CET[*unknown*] Max Transport Size: 65535
03.03.06 12:56:24.094 CET[*unknown*] FECDB : [c:\tine\database\server\]
03.03.06 12:56:24.125 CET[*unknown*] alias file : found
03.03.06 12:56:24.141 CET[*unknown*] TEST_CONSOLE has WRITE access
03.03.06 12:56:24.141 CET[*unknown*] duval has WRITE access
03.03.06 12:56:24.141 CET[*unknown*] KAROL has WRITE access
03.03.06 12:56:24.141 CET[*unknown*] NONE has DENIED access
03.03.06 12:56:24.141 CET[*unknown*] property list file Device3-properties.csv : found
03.03.06 12:56:24.219 CET[*unknown*] device file for WKSEQM : found
03.03.06 12:56:24.219 CET[*unknown*] exports file c:\tine\database\server\exports.csv : found
03.03.06 12:56:24.219 CET[MSTXPDUVAL03.11] Control Structures Swap information registered
03.03.06 12:56:24.250 CET[MSTXPDUVAL03.11] Using Port Offset 11
03.03.06 12:56:24.328 CET[MSTXPDUVAL03.11] TCP PORT 8514:  initialized
03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] UDP PORT 9014:  bound to 9014 (socket 1956  blocking)
03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] UDP PORT 0:  bound to 8052 (socket 1952  blocking)
03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] IPC server listening on \\.\pipe\MSTXPDUVAL03.11.ipc
03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] ALM: recognizing HECASSRV as Central Alarm Server
03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] ALM: watch file : found
03.03.06 12:56:24.391 CET[MSTXPDUVAL03.11] HIST: WKSEQM Sine #0 (600 s 1 mon)
03.03.06 12:56:24.391 CET[MSTXPDUVAL03.11] HIST: WKSEQM Temperature #0 (600 s 1 mon)
03.03.06 12:56:24.391 CET[MSTXPDUVAL03.11]       WKSEQM task registration: ---- BCKG ----
03.03.06 12:56:24.485 CET[MSTXPDUVAL03.11] TINE HOME : [C:\tine\database\]
03.03.06 12:56:24.610 CET[MSTXPDUVAL03.11] FEC MSTXPDUVAL03.11, Server WKSineGen (WKSEQM) registered with equipment name server
03.03.06 12:56:24.610 CET[MSTXPDUVAL03.11] Context is WORKSHOP
03.03.06 14:28:26.646 CET[MSTXPDUVAL03.11] ALM: Central Alarm Server attached

Browsing your server

Now try to find your server in the Instant Client under the context name you assigned. You will more than likely need to click on the "Reload Names" menu item under options (if the context itself is new) or at a minimum open up and close again the context combo box (this will force a reload of the names under the given context).

You should see your device server and be able to browse the properties and devices you entered in the setup wizard.

You should be able to read a lot of '0' values (not very interesting).

A "do-something" server

So let's give the properties something to read back. Add the following global data declarations:

float sinevals[1024];
float tempvals[100];
float ampl = 256.0;

and inside your main routine, add:

  pushBufferedData("Amplitude","#0",(BYTE *)&ampl,1,FALSE);
  pushBufferedData("Sine","#0",(BYTE *)sinevals,1024,FALSE);
  pushBufferedData("Temperature","#0",(BYTE *)tempvals,100,FALSE);

prior to your call to runServer();

You will still be reading '0's for properties 'Sine' and 'Temperature', but now 'Amplitude' should return 256. To get 'Sine' to return a sine curve, add

#include <math.h>

with the other includes and add

  int i;

  for (i=0; i<1024; i++) sinevals[i] = (float)(rand()%25) + ampl * (float)sin(i*6.2832/1024);
  for (i=0; i<100; i++) tempvals[i] = (float)(rand()%5) + 20;

in main. Now you should be able to see a sine curve with property 'Sine' and a range of temperatures (around 20 'C with some jitter) for the 100 temperature values.

Now restart your server and see that the properties return something.

Making your server dynamic

Next, the data returned from the server are now static, initialized in form_load and that was it. This is probably okay for 'Amplitude' but 'Sine' and 'Temperature' correspond to quantities that we might be reading out of our hardware, so let's simulate hardware readout by adding so dynamic jitter to both 'Sine' and 'Amplitude'.

Add an io routine with prototype void (*)(void), which should simulate hardware readout of some monitor(s).

for example:

void io(void)
{
  int i;
  for (i=0; i<1024; i++) sinevals[i] = (float)(rand()%25) + ampl * (float)sin(i*6.2832/1024);
  pushBufferedData("Sine","#0",(BYTE *)sinevals,1024,FALSE);
  for (i=0; i<100; i++) tempvals[i] = (float)(rand()%5) + 20;
  pushBufferedData("Temperature","#0",(BYTE *)tempvals,100,FALSE);
}

And change the 'Attach' statement in your main routine so that this io routine gets called in the background:

  AttachServer("MYSINE",io,1000);

will make sure that io() gets called every 1000 msecs.

Adding device names

Unless you thought in advance while using the server wizard, there are probably no device names registered. The instant client will just show a "#0" as a device name. You can re-visit the wizard and enter "100" for the "Number of Devices" (in the device server information frame).

After entering this number, click on the Property Name field, that will signal the wizard to fill in the Device Panel with some device names ("Device 0", "Device 1", etc.) which you can either just leave as it is, or provide some of your favorites.

Also let's specify what kind of array property "Temperature" and "Sine" are. Click on Property "Sine" in the display panel on the right hand side, so that all of the text and combo boxes are filled with it's parameters. Now for "Output Array Type" choose "SPECTRUM". And click on the "Edit" button on the right (If you try to "Add" this property, you'll get a message telling you it is already there, but you can edit it!). Click on property "Temperature" on the property display panel and choose array type "CHANNEL". And then click on "Edit" again.

When you're finished, click on "Done" once more.

If you have a look at the new generated "exports.csv" file, you should see that the number of modules is now 100, and the format type for property "Sine" is now float.SPECTRUM and for property "Temperature" float.CHANNEL.

Channel versus Spectrum Arrays

Copy this new exports.csv into your FEC_HOME database area and restart your server.

Now when you browse for it with the instant client, you should notice that when you click on property "Sine" the display type automatically changes to "PolyLine", i.e. a SPECTRUM data type automatically gets displayed as a trace.

Note:
you should open up and close the Device Server Combo Box on the instant client, before you re-browse for your server. The reason is that the instant client will cache the property information for a server and not know about the array type changes you just made unless you force it to re-acquire this information.

Click on the "Temperature" property. The display type automatically changes to "Histogram", i.e. CHANNEL array data generally refer to an array where each element refers to value of the property for a particular channel. And this is best displayed as a histogram.

If you make the instant client read the Temperature, you will also see that the x-axis label also includes the device names and the element positions.

Note:
When a property returns a spectrum array (also known as a waveform or trace), it usually has x-axis units as well as y-axis units. (Voltage versus time for instance). The Wizard currently doesn't have room for x-axis units, but you can add it by hand by editing the export.csv file yourself. You need to find the description of the property in question (for instance "Sine" in our case). And instead of "[-500:500 V]Sine Curve", you would expand it to read for instance "[-500:500 V][-100:900 usec]Sine Curve". A client program reading this information would then know how to position the min and max settings for the horizontal axis as well as what the x-axis units are. HOWEVER: only make these kinds of changes when you're finished using the server wizard, otherwise you'll have to copy-and-paste them back in.

Before leaving the server wizard, let's have a look at local history files (followd by alarm files). Use the wizard to call up your database again and select the Temperature property. Now check the "Keep History" check box, followed by clicking the "Edit" button and then the "Done" Button. You can also edit the history panel if you wish. The default settings should be good enough for our demonstation purposes.

Local histories

You should have generated a "history.csv" file, which you can now copy to your FEC_HOME area as well. Now restart your server and have a look at the log file. It should now tell you that it is keeping a history of "Temperature". If you re-browse your server in the Instant Client, you should see a "history" check box appear, which will let you trivially check the history of the selected device. A better way to see what is going on, is to add by hand ".HIST" to the property name in the combo box (make sure the history check box is NOT checked), and then hit the read button. Without any further input the local history server will ask for history data starting from "now" minus the depth in seconds given by the data size in the request. You can also have some fun by repeating the call with a format type of "FLTINT" which will return an array of data-timestamp pairs.

wkLocalHist1.jpg

If you want to hava sneek preview of the local history viewer, you can call it up and under "Options" choose "Data Sources" and then choose the "LOcal History Chooser". You can then navigate via the Context combo box to the "WORKSHOP" context and find your server. It should display everything you are keeping a local history of. Choose one of your properties and devices and the click on "Add Selection".

Local Alarm Server

Now lets have a look at the local alarm system. Start the wizard again, select "Temperature" and check the "Alarm Watch" check box. You should change some of the settings in the alarm watch panel, namely the "Value too high" Threshold, you can set to 24, with a warning at 22 and the "Value too low" threshold to 16, with a warning at 18. Click the "Edit" button and then the "Done" button.

You should now have generated an "almwatch.csv" file. Have a look at this file (if you like) and copy it into your FEC_HOME area and restart your server.

You can check the alarms on your server via the instant client, by including the "Stock Properties" in the property list. The relevant properties are "NALARMS" (number of alarms as a 5-parameter snapshot - when the first number > 0 then you have local alarms) and "ALARMS", which will return the current local alarm list (you might want to ask for fewer then the default 512 alarms).

wkLocalAlarm1.jpg

Another simple way to view the alarms is to start the Remote Fec Control panel, find your server and click on the Alarms tab.

Commands (changing settings)

Now let's see how we can set the amplitude. So far we can only read it. Use the server wizard to make sure that the "Amplitude" property is a READ/WRITE property. Click on the "Amplitude" property in the right-hand side display, change the access to READ/WRITE and make sure the input parameters allow 1 float (Single) values to be sent. Click on "Edit" and then on "Done". Copy the new exports.csv file into the FEC_HOME area.

We're not through! Now a client will send us data, so we have to examine what was sent, decide whether to use it or not and then return.

In C, you'll need to create a callback handler to intercept the incoming requests. Create a server callback routine with prototype int (*)(void). For example:

int amplCb(void)
{
  return 0;
}

This callback routine doesn't do anything. Let's do something simple. First we'll pull the incoming data into our global variable ampl, then we'll push the global variable back. The sounds silly, but there are two distinct buffers in the buffered server, one for what a client has sent us, and another for what he will read back (so pull and push are working with different buffers).

int amplCb(void)
{
  pullBufferedData("Amplitude","#0",(BYTE *)&ampl,1);
  pushBufferedData("Amplitude","#0",(BYTE *)&ampl,1,FALSE);
  return 0;
}

Now register this callback in the main routine as follows:

  RegisterServerCallback("Amplitude",amplCb);

Your C main routine should now look something like:

int main(int argc,char *argv[])
{
  int i;

  for (i=0; i<1024; i++) sinevals[i] = (float)(rand()%25) + ampl * (float)sin(i*6.2832/1024);
  for (i=0; i<100; i++) tempvals[i] = (float)(rand()%5) + 20;

  AttachServer("MYSINE",NULL,0);
  pushBufferedData("Amplitude","#0",(BYTE *)&ampl,1,FALSE);
  pushBufferedData("Sine","#0",(BYTE *)sinevals,1024,FALSE);
  pushBufferedData("Temperature","#0",(BYTE *)tempvals,100,FALSE);
  RegisterServerCallback("Amplitude",amplCb);
  runServer();
  return 0;
}

If you crave simplicity, you can get rid of the references to "Sine" and "Temperature" inside the main routine, since all of these operations will happen anyway inside your io() routine.

int main(int argc,char *argv[])
{
  AttachServer("MYSINE",NULL,0);
  pushBufferedData("Amplitude","#0",(BYTE *)&ampl,1,FALSE);
  RegisterServerCallback("Amplitude",amplCb);
  runServer();
  return 0;
}

Try compiling and running your server now and setting the amplitude property. Inside the callback, you can also make use of the routines

hasInputChanged()

and

GetInputDeviceNumber()

to determined if and what input values have changed when the callback is called. This is useful if the same callback routine is used for different properties or if WRITE property needs to know which device was input.

Property Scheduling

You may be wondering what the "FALSE" refers to in the calls to pushBufferedData.

This is a scheduling flag. If you pass "TRUE" here, it calls the TINE scheduler, which has the effect of notifiying all clients listening to the property in question immediately. That is, NO LATENCY.

To test this, change one of the pushBufferedData calls to pass TRUE.

Now recompile and restart your server and poll the property in question with the Instant Client, BUT .... pull it with a polling interval of 30000 msec. Ordinarily, you wouldn't expect data updates any faster than once every 30 sec. But you'll be updating whenever the value changes at the server, no matter what!

device-oriented versus property-oriented

Most classic device servers have many instances (devices) and each device supports the same set of properties. Many servers however are more "property-oriented" than not. Still others are more "device-oriented" than not.

Property-oriented would mean that each property potentially has a completely different set of devices. This is typical of middle-layer gateway servers, where there might be a property "VacPressure" and a property "Orbit-X". The getter pumps in the one case have nothing to do with the bpm monitors in the other.

The wizard is capable of setting up this kind of behavior in the configuration database. That is the purpose behind the Server or Property option button in the device panel. The default is "Server-oriented" device lists, where the devices given are valid for all properties on the server. If you select "Property", then a separate list is made specifically for the property in question. If you try this, you will generate an addition .csv file with the name <Property>-names.csv. In our case, "Temperature" might be such a property, so you would generate a Temperature-names.csv. (Of course if everything is called "Device 0", etc. you won't see the difference.

Device-oriented would mean that the device server supports all devices listed, but some devices might have a different property set than others. The wizard is not capable of setting up the Device-Oriented behavior, but you can add it yourself (fairly) easily.

You need to modify your devices.csv file. For instance it might look like:

DEVICE_NUMBER,DEVICE_NAME
 0,Device 0
 1,Device 1
 2,Device 2
 3,Device 3
 4,Device 4
 5,Device 5
 ...


You need to add by hand another column called PROPERTY_LIST. If the device in question deviates from the default property list (all registered properties), then you need to supply a file name (your choice) for the entry in this column. For instance, let's say that "Device 3" only supports property "Temperature". Then you would have:

DEVICE_NUMBER,DEVICE_NAME,PROPERTY_LIST
 0,Device 0,
 1,Device 1,
 2,Device 2,
 3,Device 3,Device3-properties.csv
 4,Device 4,
 5,Device 5,
 ...

Note:
The above editing might be easier in Excel.

Then you need to create the file called Device3-properties.csv. This is a .csv file with one important column, namely "PROPERTY_NAME".

So you would have

PROPERTY_NAME
Temperature

And that would be that.

Note:
You can't do both! Your server is going to be classic (one device list, one property list) or it is either device oriented or property oriented.

Once again, when you start editing the .csv files yourself, you'll probably want to stop using the wizard (or else get used to copy-and-paste).

Redirection

It's time to try Redirection. The wizard doesn't offer this, but you can do it again by hand.

Often it is the case that a server will offer a property which lives on another server, or a device which lives on another server.

Let's look at the device case first. Suppose you want to redirect any request involving Device 1 to your neigbor's server. Ask your neighbor what his server is called. Open up the <eqpmod>-devices.csv file and add a column called REDIRECTION and leave all the entries in this column blank except for the row corresponding to Device 1. In this row, put your neighbor's server. For example, if his server is called "MYNEIGHBORSERVER",you might have:

DEVICE_NUMBER,DEVICE_NAME,PROPERTY_LIST,REDIRECTION
 0,Device 0,,
 1,Device 1,,MYNEIGHBORSERVER
 2,Device 2,,
 3,Device 3,Device3-properties.csv,
 4,Device 4,,
 5,Device 5,,
 ...


Copy this new -devices.csv file to your FEC_HOME area and restart your server. When you (re)browse your server and access Device 2, your call will be redirected to MYNEIGHBORSERVER.

ONE IMPORTANT THING: If the "MYNEIGHBORSERVER" Server isn't running or doesn't have a device namded "Device 2" or a property equal to one you're trying to access then you will get an error code returned.

On to the Standard Server

We're finished with Buffered Servers, which maybe illustrate just how far you can get with databases alone. Sometimes you might already have a different database structure or more complicated needs, So you should now move on to the generated servers.


Generated for TINE API by  doxygen 1.5.8