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
Working with a Standard Server in C

Standard Server

Call up the Wizard for your project and generate either C code and compile and run the generated projects.

If you want to continue using the generated .csv database files, you can. You should perhaps comment out any lines of code that explicitly call RegisterProperty() or RegisterDeviceName().

You now have full control over all information passed to the server by the caller. It is also much easier to support more complex format types as well as user defined structures.

You should repeat some of the tests made with the buffered server, by editing the configuration files by hand.

With the standard server, you will now have an event handler (usually refered to as the equipment module service routine) which will be called when ever a property is accessed. In particular you can examine all aspects of the request, such as the requested data format, the incoming data format, the data access flags, etc. and make response decisions accordingly.

In the first go-around, if you just take the generated code and compile, link and run it, your server should behave just like the buffered server. You can start refining things by perhaps checking who the caller is (getCaller()) or maybe checking the access flags (check whether the CA_HIST flag is on or not -> indicates whether the call is coming from the local history mechansim) etc. and change your response accordingly (e.g. maybe you want to filter the data a bit more for the local history system than for a network caller).

For our example in the last section, with three properties "Sine", "Amplitude", and "Gaussian", you might generate code something like the following :

/*
* staeqm.c REVISION HISTORY:
* Generated by SRVWIZARD on Sun Dec 15 17:16:05 2013
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "tine.h"
#include "staeqm.h"
UINT32 g_statusBuffer[PRP_STATUS_SIZE];
float g_gaussianBuffer[NUM_STAEQM_DEVICES][PRP_GAUSSIAN_SIZE];
float g_amplitudeBuffer[PRP_AMPLITUDE_SIZE];
float g_amplitudeIn;
float g_sineBuffer[NUM_STAEQM_DEVICES][PRP_SINE_SIZE];
void staeqm_bkg(void)
{
int i, n;
/* TODO: put your IO or other background activity here */
/* Clear alarms at start of IO task (see if they come back) */
ClearAlarm(STAEQM_TAG,-1);
/* TODO: replace SimulateData() with your own data acquisition */
/* and manipulation routines */
for (i=0; i<PRP_STATUS_SIZE; i++)
{
g_statusBuffer[i] = (UINT32)SimulateData(0,100);
}
/* TODO: replace SimulateData() with your own data acquisition */
/* and manipulation routines */
for (n=0; n<NUM_STAEQM_DEVICES; n++)
for (i=0; i<PRP_GAUSSIAN_SIZE; i++)
{
g_gaussianBuffer[n][i] = (float)SimulateData(0,100);
}
/* TODO: replace SimulateData() with your own data acquisition */
/* and manipulation routines */
for (n=0; n<NUM_STAEQM_DEVICES; n++)
for (i=0; i<PRP_SINE_SIZE; i++)
{
g_sineBuffer[n][i] = (float)SimulateData(-100,200);
}
/* make use of SetAlarm() as needed :
SetAlarm(STAEQM_TAG,devNr,almCode,almFlag);
*/
}
/* Alternative to 'exports.csv': You can call 'RegisterStaeqmProperties()' */
/* -> This might be preferable in cases where the server does not have a */
/* -> file system. You should also call RegisterServerName() in lieu of */
/* -> the 'fecid.csv' file. */
int RegisterStaeqmProperties(void)
{
int cc = 0;
DTYPE dout,din;
dout.dArrayLength = PRP_STATUS_SIZE;
dout.dFormat = CF_LONG;
din.dArrayLength = 0;
din.dFormat = CF_NULL;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Status",&dout,&din,CA_READ,AT_CHANNEL,0,"[0:100 ]Device Status",PRP_STATUS,"")) < 0) goto err;
dout.dArrayLength = PRP_GAUSSIAN_SIZE;
dout.dFormat = CF_FLOAT;
din.dArrayLength = 0;
din.dFormat = CF_NULL;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Gaussian",&dout,&din,CA_READ,AT_SPECTRUM,0,"[0:100 V]A Gaussian Curve",PRP_GAUSSIAN,"")) < 0) goto err;
dout.dArrayLength = PRP_AMPLITUDE_SIZE;
dout.dFormat = CF_FLOAT;
din.dArrayLength = PRP_AMPLITUDE_INSIZE;
din.dFormat = CF_FLOAT;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Amplitude",&dout,&din,CA_READ|CA_WRITE,AT_CHANNEL,0,"[0:100 V]A Sine Curve Amplitude",PRP_AMPLITUDE,"")) < 0) goto err;
dout.dArrayLength = PRP_SINE_SIZE;
dout.dFormat = CF_FLOAT;
din.dArrayLength = 0;
din.dFormat = CF_NULL;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Sine",&dout,&din,CA_READ,AT_SPECTRUM,0,"[-100:100 V]A Sine Curve",PRP_SINE,"")) < 0) goto err;
err:
if (cc < 0)
{
printf("Can't register property : %s\n>",erlst[-cc]);
}
return cc;
}
void staeqm_ini(void)
{
char devnam[DEVICE_NAME_SIZE];
int i;
/* TODO: put your initialization here */
/* If you plan to use Property registration via API call rather than */
/* the exports.csv startup file, uncomment the following line: */
/* RegisterStaeqmProperties(); */
/* TODO: register any device names here */
/* below is only an example ... */
for (i=0; i<NUM_STAEQM_DEVICES; i++)
{
sprintf(devnam,"device_%d",i);
RegisterDeviceName(STAEQM_TAG,devnam,i);
}
/* TODO: register any global receive routines as in this example */
/*
glbID = recvNetGlobal("HPMAGEN",&dout,rcvGlobal);
*/
/* TODO: start any links you might need as in this example */
/*
lnkID = AttachLinkEx("/HERA/BPM/WL197X","POSITIONS.X",&dout,&din,CA_READ,1000,showlink,CM_POLL,myid);
*/
}
void staeqm_exi(void)
{
/* TODO: put your shutdown routines here */
}
int staeqm(char *devName,char *devProperty,DTYPE *dout, DTYPE *din,short access)
{
int devnr,prpid,i,cc;
float l_amplitude;
/* get device number from device name */
devnr = GetDeviceNumberEx(STAEQM_TAG,devName,devProperty);
/* you may want to make the following check: */
if (devnr < 0) return illegal_equipment_number;
/* TODO: If READ properties take input data, include code to examine the contents of din. */
/* If different actions need to be taken at the start or end of a link, examine the */
/* 'access' parameter against CA_FIRST or CA_LAST. */
/* If format overloading allowed (you return different data according to the request */
/* format), then replace calls to PutDataFromShort() etc with the desired code. */
prpid = GetPropertyId(STAEQM_TAG,devProperty);
switch (prpid)
{
case PRP_STATUS:
if (access&CA_WRITE) return illegal_read_write;
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_STATUS_SIZE) return dimension_error;
if ((cc=PutValuesFromLongEx(dout,g_statusBuffer,PRP_STATUS_SIZE,devnr)) != 0) return cc;
}
return 0;
case PRP_GAUSSIAN:
if (access&CA_WRITE) return illegal_read_write;
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_GAUSSIAN_SIZE) return dimension_error;
if ((cc=PutValuesFromFloat(dout,g_gaussianBuffer[devnr],PRP_GAUSSIAN_SIZE)) != 0) return cc;
}
return 0;
case PRP_AMPLITUDE:
if (access&CA_WRITE)
{
if (din->dArrayLength > 0)
{
if (din->dArrayLength > PRP_AMPLITUDE_INSIZE) return dimension_error;
if ((cc=GetValuesAsFloat(din,&l_amplitude,PRP_AMPLITUDE_INSIZE)) != 0) return cc;
if (l_amplitude > PRP_AMPLITUDE_UPR_LIMIT) return out_of_range;
if (l_amplitude < PRP_AMPLITUDE_LWR_LIMIT) return out_of_range;
g_amplitudeIn = l_amplitude;
}
}
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_AMPLITUDE_SIZE) return dimension_error;
if ((cc=PutValuesFromFloatEx(dout,g_amplitudeBuffer,PRP_AMPLITUDE_SIZE,devnr)) != 0) return cc;
}
return 0;
case PRP_SINE:
if (access&CA_WRITE) return illegal_read_write;
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_SINE_SIZE) return dimension_error;
if ((cc=PutValuesFromFloat(dout,g_sineBuffer[devnr],PRP_SINE_SIZE)) != 0) return cc;
}
return 0;
default:
}
}

Likewise, there is a generated header file staeqm.h and a 'main' file, devsrv.c. You will notice that there are several 'TODO' statements, which indicate where you should concentrate your coding efforts. Most noteably, the 'io' routine does nothing but simulate actual data io, and you should of course change this routine to correspond to your actual data io. The io routine is refered to as a 'background' routine and in this case the wizard gave it the name staeqm_bkg(). In the standard server this is registered in the call to RegisterEquipmentModule() (there is no call to AttachServer() in the standard server).

For our example, you should change this background io routine to generate a sine curve for the property 'Sine' and a temperature array for the property 'Gaussian'.

So do the following: Add a macro define

#define BASE_AMPLITUDE 5.0

and use it in the initialization routine staeqm_ini.c to set the intial values of the amplitude for each of the curves.

for (i=0; i<NUM_STAEQM_DEVICES; i++)
{
sprintf(devnam,"device_%d",i);
RegisterDeviceName(STAEQM_TAG,devnam,i);
g_amplitudeBuffer[i] = BASE_AMPLITUDE;
}

And in the background routine staeqm_bkg(), make use of the amplitude array to fill in a global sine array:

for (n=0; n<NUM_STAEQM_DEVICES; n++)
for (i=0; i<PRP_SINE_SIZE; i++)
{
g_sineBuffer[n][i] = (float)(rand()%25) + g_amplitudeBuffer[n] * (float)sin(i*6.2832/PRP_SINE_SIZE);
}

Likewise fill in the gaussian array in the background routine:

for (n=0; n<NUM_STAEQM_DEVICES; n++)
for (i=0; i<PRP_SINE_SIZE; i++)
{
g_gaussianBuffer[i] = g_amplitudeBuffer[n] * exp(-0.0001*pow(((float)(i) - 200.0),2)) + noise[i];
}

We're going to save some memory space concerning all of our sine curves and our gaussian curves (one for each of the 10 devices!). So we have made the Amplitude a CHANNEL array as the read-back property. So we'll just multiply the same sine array by the corresponding amplitude when the property gets called. We'll then need a temporary read-back buffer (we'll see why in a minute). So create this as a global buffer of the same dimensions as the g_sineBuffer[] array.

float rb_sineBuffer[PRP_SINE_SIZE];

And now it's on the equipment module. What should happen when a property is accessed? The generated code in the equipment module handler servers our purposes just fine. For properties "Gaussian" and "Sine", the generated code make use of 'PutValuesFromFloat()' which handles any format conversion which might be necessary, for instance if the caller requests an array of doubles rather than floats. If the canonical property format cannot be coerced into the callers requested data type, then 'PutValuesFromFloat' will return illegal_format. You could also require the caller to request a float array by checking the requested data type dout->dFormat against CF_FLOAT. For property "Amplitude", the input amplitude value during a write operation should generally be checked against the allowed amplitude range before being accepted, and this is in fact what the generated code does. One can also make use of the routine AssertRangeValid() to accomplish the same ends.

So we have just tweaked the generated code to serve our purposes for this example. This should behave much the same as in the buffered server example.

Your equipment module code should now look something like the following:

/*
* staeqm.c REVISION HISTORY:
* Generated by SRVWIZARD on Sun Dec 15 17:16:05 2013
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "tine.h"
#include "staeqm.h"
UINT32 g_statusBuffer[PRP_STATUS_SIZE];
float g_gaussianBuffer[NUM_STAEQM_DEVICES][PRP_GAUSSIAN_SIZE];
float g_amplitudeBuffer[PRP_AMPLITUDE_SIZE];
float g_amplitudeIn;
float g_sineBuffer[NUM_STAEQM_DEVICES][PRP_SINE_SIZE];
void staeqm_bkg(void)
{
int i, n;
/* TODO: put your IO or other background activity here */
/* Clear alarms at start of IO task (see if they come back) */
ClearAlarm(STAEQM_TAG,-1);
/* TODO: replace SimulateData() with your own data acquisition */
/* and manipulation routines */
for (i=0; i<PRP_STATUS_SIZE; i++)
{
g_statusBuffer[i] = (UINT32)SimulateData(0,100);
}
for (n=0; n<NUM_STAEQM_DEVICES; n++)
for (i=0; i<PRP_GAUSSIAN_SIZE; i++)
{
g_sineBuffer[n][i] = (float)(rand()%25) + g_amplitudeBuffer[n] * (float)sin(i*6.2832/PRP_SINE_SIZE);
}
for (n=0; n<NUM_STAEQM_DEVICES; n++)
for (i=0; i<PRP_SINE_SIZE; i++)
{
g_gaussianBuffer[i] = g_amplitudeBuffer[n] * exp(-0.0001*pow(((float)(i) - 200.0),2)) + noise[i];
}
/* make use of SetAlarm() as needed :
SetAlarm(STAEQM_TAG,devNr,almCode,almFlag);
*/
}
/* Alternative to 'exports.csv': You can call 'RegisterStaeqmProperties()' */
/* -> This might be preferable in cases where the server does not have a */
/* -> file system. You should also call RegisterServerName() in lieu of */
/* -> the 'fecid.csv' file. */
int RegisterStaeqmProperties(void)
{
int cc = 0;
DTYPE dout,din;
dout.dArrayLength = PRP_STATUS_SIZE;
dout.dFormat = CF_LONG;
din.dArrayLength = 0;
din.dFormat = CF_NULL;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Status",&dout,&din,CA_READ,AT_CHANNEL,0,"[0:100 ]Device Status",PRP_STATUS,"")) < 0) goto err;
dout.dArrayLength = PRP_GAUSSIAN_SIZE;
dout.dFormat = CF_FLOAT;
din.dArrayLength = 0;
din.dFormat = CF_NULL;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Gaussian",&dout,&din,CA_READ,AT_SPECTRUM,0,"[0:100 V]A Gaussian Curve",PRP_GAUSSIAN,"")) < 0) goto err;
dout.dArrayLength = PRP_AMPLITUDE_SIZE;
dout.dFormat = CF_FLOAT;
din.dArrayLength = PRP_AMPLITUDE_INSIZE;
din.dFormat = CF_FLOAT;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Amplitude",&dout,&din,CA_READ|CA_WRITE,AT_CHANNEL,0,"[0:100 V]A Sine Curve Amplitude",PRP_AMPLITUDE,"")) < 0) goto err;
dout.dArrayLength = PRP_SINE_SIZE;
dout.dFormat = CF_FLOAT;
din.dArrayLength = 0;
din.dFormat = CF_NULL;
if ((cc=RegisterPropertyInformation(STAEQM_TAG,"Sine",&dout,&din,CA_READ,AT_SPECTRUM,0,"[-100:100 V]A Sine Curve",PRP_SINE,"")) < 0) goto err;
err:
if (cc < 0)
{
printf("Can't register property : %s\n>",erlst[-cc]);
}
return cc;
}
#define BASE_AMPLITUDE 5.0
void staeqm_ini(void)
{
char devnam[DEVICE_NAME_SIZE];
int i;
/* TODO: put your initialization here */
/* If you plan to use Property registration via API call rather than */
/* the exports.csv startup file, uncomment the following line: */
/* RegisterStaeqmProperties(); */
/* TODO: register any device names here */
/* below is only an example ... */
for (i=0; i<NUM_STAEQM_DEVICES; i++)
{
sprintf(devnam,"device_%d",i);
RegisterDeviceName(STAEQM_TAG,devnam,i);
g_amplitudeBuffer[i] = BASE_AMPLITUDE;
}
/* TODO: register any global receive routines as in this example */
/*
glbID = recvNetGlobal("HPMAGEN",&dout,rcvGlobal);
*/
/* TODO: start any links you might need as in this example */
/*
lnkID = AttachLinkEx("/HERA/BPM/WL197X","POSITIONS.X",&dout,&din,CA_READ,1000,showlink,CM_POLL,myid);
*/
}
void staeqm_exi(void)
{
/* TODO: put your shutdown routines here */
}
int staeqm(char *devName,char *devProperty,DTYPE *dout, DTYPE *din,short access)
{
int devnr,prpid,i,cc;
float l_amplitude;
/* get device number from device name */
devnr = GetDeviceNumberEx(STAEQM_TAG,devName,devProperty);
/* you may want to make the following check: */
if (devnr < 0) return illegal_equipment_number;
/* TODO: If READ properties take input data, include code to examine the contents of din. */
/* If different actions need to be taken at the start or end of a link, examine the */
/* 'access' parameter against CA_FIRST or CA_LAST. */
/* If format overloading allowed (you return different data according to the request */
/* format), then replace calls to PutDataFromShort() etc with the desired code. */
prpid = GetPropertyId(STAEQM_TAG,devProperty);
switch (prpid)
{
case PRP_STATUS:
if (access&CA_WRITE) return illegal_read_write;
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_STATUS_SIZE) return dimension_error;
if ((cc=PutValuesFromLongEx(dout,g_statusBuffer,PRP_STATUS_SIZE,devnr)) != 0) return cc;
}
return 0;
case PRP_GAUSSIAN:
if (access&CA_WRITE) return illegal_read_write;
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_GAUSSIAN_SIZE) return dimension_error;
if ((cc=PutValuesFromFloat(dout,g_gaussianBuffer[devnr],PRP_GAUSSIAN_SIZE)) != 0) return cc;
}
return 0;
case PRP_AMPLITUDE:
if (access&CA_WRITE)
{
if (din->dArrayLength > 0)
{
if (din->dArrayLength > PRP_AMPLITUDE_INSIZE) return dimension_error;
if ((cc=GetValuesAsFloat(din,&l_amplitude,PRP_AMPLITUDE_INSIZE)) != 0) return cc;
if (l_amplitude > PRP_AMPLITUDE_UPR_LIMIT) return out_of_range;
if (l_amplitude < PRP_AMPLITUDE_LWR_LIMIT) return out_of_range;
g_amplitudeIn = l_amplitude;
}
}
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_AMPLITUDE_SIZE) return dimension_error;
if ((cc=PutValuesFromFloatEx(dout,g_amplitudeBuffer,PRP_AMPLITUDE_SIZE,devnr)) != 0) return cc;
}
return 0;
case PRP_SINE:
if (access&CA_WRITE) return illegal_read_write;
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_SINE_SIZE) return dimension_error;
if ((cc=PutValuesFromFloat(dout,g_sineBuffer[devnr],PRP_SINE_SIZE)) != 0) return cc;
}
return 0;
default:
}
}

You should now compile and run this server code and see that it basically behaves like the buffered server at this stage of development.

If you stick to the .csv configuration files, all of the lessons learned in the case of the buffered server apply here as well.
Of course all features can be accessed via API calls instead. For instance, calls to RedirectProperty() or RedirectDeviceName() can be used at intialization time (in the equipment modules 'ini' routine - here: staeqm_ini()) to redirect calls to specific devices or properties. The one feature you will have to program in yourself is 'Property Scheduling.

Property Scheduling

To try this out, add a call to the scheduler when the amplitude changes. This can only change because a caller changed it, so this happens directly inside the equipment module.

SystemScheduleProperty(STAEQM_TAG,"Amplitude");

Now any clients monitoring the "Amplitude" property will be updated immediately with the new value.

PutValuesFromLongEx
TINE_EXPORT int PutValuesFromLongEx(DTYPE *d, SINT32 *lval, int num, int offset)
Submits outgoing data from an array of long integers.
Definition: toolkit.c:3670
illegal_read_write
@ illegal_read_write
Definition: errors.h:161
RegisterPropertyInformation
TINE_EXPORT int RegisterPropertyInformation(char *eqm, char *prp, DTYPE *dout, DTYPE *din, short acc, short atype, UINT16 rowlen, char *dsc, int pid, char *rdr)
Assigns pertinent information for the specified property.
Definition: srvdbase.c:8633
GetDeviceNumberEx
TINE_EXPORT int GetDeviceNumberEx(char *eqm, char *devname, char *prpname)
Gives the registered device number for the specified device name and property name.
Definition: srvdbase.c:7707
illegal_property
@ illegal_property
Definition: errors.h:118
DTYPE::dFormat
short dFormat
Definition: tinetype.h:999
DTYPE
Defines a TINE data object.
Definition: tinetype.h:996
ClearAlarm
TINE_EXPORT void ClearAlarm(char *eqm, int devNr)
Instructs the local alarm server table that the given alarm is to be cleared.
Definition: almlib.c:2314
out_of_range
@ out_of_range
Definition: errors.h:119
illegal_equipment_number
@ illegal_equipment_number
Definition: errors.h:115
PutValuesFromFloatEx
TINE_EXPORT int PutValuesFromFloatEx(DTYPE *d, float *fval, int num, int offset)
Submits outgoing data from an array of floats.
Definition: toolkit.c:3656
GetValuesAsFloat
TINE_EXPORT int GetValuesAsFloat(DTYPE *d, float *fval, int num)
Retrieves incoming data as an array of floats.
Definition: toolkit.c:3536
dimension_error
@ dimension_error
Definition: errors.h:102
GetPropertyId
TINE_EXPORT int GetPropertyId(char *eqm, char *prpName)
Gives the associated property identifier for the given property name.
Definition: srvdbase.c:7785
RegisterDeviceName
TINE_EXPORT int RegisterDeviceName(char *eqm, char *devname, int devnr)
Assigns a device name to the specified device number.
Definition: srvdbase.c:7883
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