The easiest way to get started writing TINE servers is to use the Server Wizard to create the skeleton to be used to provide the desired functionality. Likewise, 'buffered' servers are also very easy to put together and use, as they 'hide' numerous details that the novice server writer either doesn't want to see or is prone to forget. Buffered servers also offer only a reduced set of functionality, however.
It is nonetheless straightforward to write your own TINE server, either from scratch or beginning from a clone of a working project. To this end, we present here a brief tutorial on writing a simple TINE server from scratch.
The example below is a simple server code module which contains one equipment function and supports one property “SINE”. It simulates data IO by running a routine in a background task which does nothing more than update a buffer containing a sine curve (filled in upon initialization).
First some necessary #includes, #defines, and global variables:
#include <stdlib.h> #include <string.h> #include <math.h> #include <time.h> #include "tine.h" #define PRP_SINE 1 #define TRAVELLING 0x01 #define NOISE 0x02 #define NUMVALUES 1024 double dbuf[NUMVALUES], sinbuf[NUMVALUES]; short noise[NUMVALUES]; short dataMode = TRAVELLING;
We've assigned a global buffer sinbuf[] for keeping our sine values, a global buffer noise[] for holding some random values to simulate a noisy signal, and a global buffer dbuf[], which we'll use for preparing our data buffer.
The following routine (fakeSine()) fills in the array buffer passed as an argument with values representing a sine curve ranging from 0.0 to 512.0. The array “noise[]” is also filled in with random numbers between 0 and 20. Note that if your operating system does not offer the random() routine in a standard library, you will have to supply this routine yourself.
void fakeSine(double s[NUMVALUES]) { time_t t = time(NULL); int i; for (i=0; i<NUMVALUES; i++) { s[i] = (double)((1.0 + sin(i*6.28/NUMVALUES))*255); noise[i] = random(20); } return; }
Below, sininit() simply calls fakeSine(sinbuf) from above. Sinbkg() simulates data IO. It updates a data buffer (dbuf[]) by copying the contents of sinbuf[] into it. According to the dataMode setting (set to TRAVELLING above), sinbuf[] is shifted by a small amount before copying into dbuf[]. This will give the appearance of a traveling sine wave, when the server is later accessed.
void sininit(void) /* fill in a buffer with a sine curve */ { fakeSine(sinbuf); } void sinbkg(void) { static int ptr = 0; int i; /* do IO or something useful here */ /* we'll simply update the data buffer dbuf for now */ for (i=0; i<NUMVALUES; i++) { /* update our data buffer according to the data mode */ dbuf[i] = dataMode == TRAVELLING ? sinbuf[(ptr+i)%NUMVALUES] : sinbuf[i] + noise[(ptr+i)%NUMVALUES]; } ptr = (ptr+16)%NUMVALUES; }
Below is the equipment module, with 1 allowable property: SINE. This routine allows a caller to ask for formats other than the native “double” format. A check against the property being called can be made with a “strcmp()”, or as shown below, by using a a check against the hash code assigned during property registration. This is a much more efficient way of determining the requested property where there a many, many registered properties. The trick is to assign a known property ID in a call to RegisterPropertyEx() or in the 'exports.csv' file. Then a simple call to GetPropertyId() will return to ID of the requested property which can then be used in a case switch.
int sineqm(char *devName, char *devProperty, DTYPE *dout, DTYPE *din, short access) { int i; /* devName is a 16 char name which is not tested against in this example. */ prpid = GetPropertyId("SINSIM",devProperty); switch (prpid) { case PRP_SINE: if (dout->dArrayLength == 0 || dout->dArrayLength > NUMVALUES) return out_of_range; if (access&CA_WRITE && din == NULL) return illegal_read_write; if ((cc=putValuesFromFloat(dout,dbuf,NUMVALUES)) != 0) return cc; return 0; } return illegal_property; }
The example code shown here uses putValuesFromFloat() to covert the native format (float) of the data buffer into the format requested by the caller. The 'devName' parameter is not checked against, as is the input data set 'din'. The 'access' flag is used to return an error code if WRITE access is demanded.
Below is a simple initialization routine, postSystemInit(). This registers the one equipment module and sets the minimum polling rate to 200 milliseconds (which is also the default value). This lets a client get updates at 5 Hz. Note the arguments for RegisterEquipmentModule(). The first argument NULL informs the kernel that the exported equipment module name should be obtained from an “exports.csv” startup file. The second argument “SINSIM” says that the local 6-character equipment name is “SINSIM” (see discussion on “What’s in a Name”). The third argument 0 informs the kernel that the number of devices serviced by this equipment module is likewise to be obtained from the “exports.csv”. (Note that 0 is by definition an illegal number of devices). The fourth argument sineqm is the address of the equipment function shown above. Similarly, the fifth and sixth arguments (sininit and sinbkg) are the addresses of the initialization and background routines shown above. The seventh argument 200 says that the background task should be entered every 200 milliseconds. The last argument NULL says that there is no exit routine associated with this equipment module.
void postSystemInit(void) { RegisterEquipmentModule(NULL,"SINSIM",0,sineqm,sininit,sinbkg,200,NULL); MinPollingRate = 200; }
Finally, the main() routine. This simply calls SystemInit(), followed by the postSystemInit () routine from above, followed by an infinite for-loop, which calls SystemCycle(). Note that SystemCycle() will in general block at the system polling rate (guaranteed not be longer than MinPollingRate), so that the CPU is not 100 busy due to the infinite loop. The one exception is MSDOS, which does not block. Any other regular activity can be included inside this for-loop, keeping in mind that for proper performance, no other action should block the CPU for any longer than the MinPollingRate. Also note that in multi-threaded builds (which link against the multi-threaded tine libraries), an independent cycler thread can be started by calling SystemStartCycleTimer() instead of inclosing SystemCycle() within a for-loop. In such cases, main() should of course be prevented from returning until the application is no longer needed.
int main() { int cc; if ((cc=SystemInit(TRUE)) != 0) { printf("init error: %d\n",cc); exit(1); } postSystemInit (); /* call FEC specific initialization: */ for(;;) { SystemCycle(TRUE); } return 0; }
You can try compiling and linking the above code. You will need to have a repository containing the TINE kernel library and the TINE include files. Please see the section on 'Recipes' for your platform as to how to generate the library and suggestions as to where to keep the include files and library.
If the above code is compiled and linked with a TINE kernel library, it needs to be accompanied by the appropriate set of startup files in order to be visible to TINE clients. Keep in mind that the startup files are all CSV files (Comma Separated Value files). Such files are human-readable ascii files, which are understood by every spreadsheet application. They are also editable by your favorite editor, and that could be a problem if you forget that a comma “,” has a special meaning in a CSV file. So when including say a list of persons responsible for a front-end server, avoid writing something like Tom, Joe, and Sally with your favorite editor. Either use a spreadsheet editor, or remember to enclose it in quotes: “Tom, Joe, and Sally”, or avoid commas altogether: Tom + Joe + Sally.
An example 'fecid.csv' file relevant to the above code might be:
FEC_NAME,CONTEXT,PORT_OFFSET,SubSystem,DESCRIPTION,LOCATION,HARDWARE,RESPONSIBLE SINESRV.0,TEST,0,TST,Sine Test Server,Bldg 1 Rm 1,none,John Doe
And an example 'exports.csv' file relevant to the above code might be:
EXPORT_NAME,LOCAL_NAME,PROPERTY,PROPERTY_SIZE,FORMAT,PROPERTY_INSIZE,INFORMAT,PROPERTY_ID,ACCESS,NUM_MODULES,DESCRIPTION,NUM_STEPS SINEWAVE,SINSIM,SINE,1024,float,0,NULL, 1,READ,0,[0:256 A]Sine Curve,0
If the cshosts.csv startup file is also present, then the server will register itself with the name server and be immediately accessible to all TINE clients.
An alternative is to make use of a single fec.xml file containing all of the FEC and exported property information. See the section on Configuration Files for more details.
1.5.8