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 "Temperature", you might generate code something like the following (where the export name "WKSineGen" was used instead of "MYSINE":
/* * wkseqm.c REVISION HISTORY: * Generated by SRVWIZARD on Fri Mar 03 08:48:54 2006 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <time.h> #include "tine.h" #include "wkseqm.h" float g_temperatureBuffer[PRP_TEMPERATURE_SIZE]; float g_temperatureIn; float g_amplitudeBuffer[PRP_AMPLITUDE_SIZE]; float g_amplitudeIn; float g_sineBuffer[PRP_SINE_SIZE]; void wkseqm_bkg(void) { int i; /* TODO: put your IO or other background activity here */ /* Clear alarms at start of IO task (see if they come back) */ ClearAlarm(WKSEQM_TAG,-1); /* TODO: replace SimulateData() with your own data acquisition */ /* and manipulation routines */ for (i=0; i<PRP_TEMPERATURE_SIZE; i++) { g_temperatureBuffer[i] = (float)SimulateData(0,100); } /* TODO: replace SimulateData() with your own data acquisition */ /* and manipulation routines */ for (i=0; i<PRP_SINE_SIZE; i++) { g_sineBuffer[i] = (float)SimulateData(-500,1000); } /* make use of SetAlarm() as needed : SetAlarm(WKSEQM_TAG,devNr,almCode,almFlag); */ } /* Alternative to 'exports.csv': You can call 'RegisterWkseqmProperties()' */ /* -> 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 RegisterWkseqmProperties(void) { int cc = 0; if ((cc=RegisterPropertyEx(WKSEQM_TAG,"Temperature",PRP_TEMPERATURE_SIZE,CF_FLOAT,CA_READ,"[0:100 'C]Sine Generator Temperature",PRP_TEMPERATURE)) < 0) goto err; if ((cc=RegisterPropertyEx(WKSEQM_TAG,"Amplitude",PRP_AMPLITUDE_SIZE,CF_FLOAT,CA_READ|CA_WRITE,"[0:500 V]Sine Curve Amplitude",PRP_AMPLITUDE)) < 0) goto err; if ((cc=RegisterPropertyEx(WKSEQM_TAG,"Sine",PRP_SINE_SIZE,CF_FLOAT,CA_READ,"[-500:500 V]Sine Curve",PRP_SINE)) < 0) goto err; err: if (cc < 0) { printf("Can't register property : %s\n>",erlst[-cc]); } return cc; } void wkseqm_ini(void) { char devnam[16]; 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: */ /* RegisterWkseqmProperties(); */ /* TODO: register any device names here */ /* below is only an example ... */ for (i=0; i<100; i++) { sprintf(devnam,"device_%d",i); RegisterDeviceName(WKSEQM_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 wkseqm_exi(void) { /* TODO: put your shutdown routines here */ } int wkseqm(char *devName,char *devProperty,DTYPE *dout, DTYPE *din,short access) { int devnr,prpid,i,cc; float l_temperature; float l_amplitude; /* get device number from device name */ devnr = GetDeviceNumber(WKSEQM_TAG,devName); /* 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 allow format overloading (you return different data according to the request */ /* format), then replace calls to putDataFromShort() etc with the desired code. */ prpid = GetPropertyId(WKSEQM_TAG,devProperty); switch (prpid) { case PRP_TEMPERATURE: if (access&CA_WRITE) return illegal_read_write; if (dout->dArrayLength > 0) { if (dout->dArrayLength > PRP_TEMPERATURE_SIZE) return dimension_error; if ((cc=putValuesFromFloatEx(dout,g_temperatureBuffer,PRP_TEMPERATURE_SIZE,devnr)) != 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,PRP_SINE_SIZE)) != 0) return cc; } return 0; default: return illegal_property; } }
Likewise, there is a generated header file wkseqm.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 wkseqm_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 'Temperature'.
So do the following: Add a macro define
#define BASE_AMPLITUDE 100.0
and use it in the background routine wkseqm_bkg() to fill in a global sine array:
for (i=0; i<PRP_SINE_SIZE; i++) { g_sineBuffer[i] = (float)(rand()%25) + BASE_AMPLITUDE * (float)sin(i*6.2832/PRP_SINE_SIZE); }
Likewise fill in the temperature array in the background routine:
for (i=0; i<PRP_TEMPERATURE_SIZE; i++) { g_temperatureBuffer[i] = (float)20.0 + (float)(rand()%5); }
We're going to save some memory space concerning all of our sine curves (one for each of the 100 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];
Let's fillin the values for the Amplitude array in the initialization routine wkseqm_ini:
for (i=0; i<PRP_AMPLITUDE_SIZE; i++) g_amplitudeBuffer[i] = 5.0;
Now create a routine which scales an array by a factor (we'll use this to multiply the sine buffer by the corresponding amplitude).
void scaleArray(float scaleFactor, float *array, int arraySize) { int i; for (i=0; i<arraySize; i++) array[i] *= scaleFactor; }
And now it's on the equipment module. What should happen when a property is accessed? For property "Temperature", the generated code will server our purposes just fine. For property "Amplitude", the input amplitude value during a write operation should actually go directly into the Amplitude channel array buffer, so assign it to the appropriate element as follows:
g_amplitudeBuffer[devnr] = l_amplitude;
Finally, when "Sine" gets called, copy the current sine buffer into the read-back buffer and scale it with the amplitude of the device called.
if (dout->dArrayLength > 0) { if (dout->dArrayLength > PRP_SINE_SIZE) return dimension_error; memcpy(rb_sineBuffer,g_sineBUffer,PRP_SINE_SIZE); scaleArray(g_amplitudeBUffer[devnr],rb_sineBUffer,PRP_SINE_SIZE); if ((cc=putValuesFromFloat(dout,rb_sineBuffer,PRP_SINE_SIZE)) != 0) return cc; }
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, except that here we return a sine curve for any device called and not just those which have been 'pushed'. We've also cheated a bit on the amount of reserved space, as we're using the same sine buffer scaled to a differed amplitude for each of the 100 devices. This is opposed to the buffered server case, where 100 different buffers were reserved up front.
In this case we have only reserved two buffers for the sine curve, an 'io' buffer and a read-back buffer. You might think that we could have done without the read-back buffer, and well we could have, except that we have the generated code's use of the 'putValuesFromFloat()' routine, which does format coversion, among other useful things. To take advantage of this routine, it is best to use a separate read-back buffer.
Your equipment module code should now look something like the following:
/* * wkseqm.c REVISION HISTORY: * Generated by SRVWIZARD on Fri Mar 03 08:48:54 2006 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <time.h> #include "tine.h" #include "wkseqm.h" float g_temperatureBuffer[PRP_TEMPERATURE_SIZE]; float g_temperatureIn; float g_amplitudeBuffer[PRP_AMPLITUDE_SIZE]; float g_amplitudeIn; float g_sineBuffer[PRP_SINE_SIZE]; float rb_sineBuffer[PRP_SINE_SIZE]; #define BASE_AMPLITUDE 100.0 void wkseqm_bkg(void) { int i; /* Clear alarms at start of IO task (see if they come back) */ ClearAlarm(WKSEQM_TAG,-1); for (i=0; i<PRP_TEMPERATURE_SIZE; i++) { g_temperatureBuffer[i] = (float)20.0 + (float)(rand()%5); } for (i=0; i<PRP_SINE_SIZE; i++) { g_sineBuffer[i] = (float)(rand()%25) + BASE_AMPLITUDE * (float)sin(i*6.2832/PRP_SINE_SIZE); } /* make use of SetAlarm() as needed : SetAlarm(WKSEQM_TAG,devNr,almCode,almFlag); */ } /* Alternative to 'exports.csv': You can call 'RegisterWkseqmProperties()' */ /* -> 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 RegisterWkseqmProperties(void) { int cc = 0; if ((cc=RegisterPropertyEx(WKSEQM_TAG,"Temperature",PRP_TEMPERATURE_SIZE,CF_FLOAT,CA_READ,"[0:100 'C]Sine Generator Temperature",PRP_TEMPERATURE)) < 0) goto err; if ((cc=RegisterPropertyEx(WKSEQM_TAG,"Amplitude",PRP_AMPLITUDE_SIZE,CF_FLOAT,CA_READ|CA_WRITE,"[0:500 V]Sine Curve Amplitude",PRP_AMPLITUDE)) < 0) goto err; if ((cc=RegisterPropertyEx(WKSEQM_TAG,"Sine",PRP_SINE_SIZE,CF_FLOAT,CA_READ,"[-500:500 V]Sine Curve",PRP_SINE)) < 0) goto err; err: if (cc < 0) { printf("Can't register property : %s\n>",erlst[-cc]); } return cc; } void wkseqm_ini(void) { char devnam[16]; int i; for (i=0; i<PRP_AMPLITUDE_SIZE; i++) g_amplitudeBuffer[i] = 5.0; /* If you plan to use Property registration via API call rather than */ /* the exports.csv startup file, uncomment the following line: */ /* RegisterWkseqmProperties(); */ /* TODO: register any device names here */ /* below is only an example ... */ for (i=0; i<100; i++) { sprintf(devnam,"device_%d",i); RegisterDeviceName(WKSEQM_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 wkseqm_exi(void) { /* TODO: put your shutdown routines here */ } void scaleArray(float scaleFactor, float *array, int arraySize) { int i; for (i=0; i<arraySize; i++) array[i] *= scaleFactor; } int wkseqm(char *devName,char *devProperty,DTYPE *dout, DTYPE *din,short access) { int devnr,prpid,i,cc; float l_temperature; float l_amplitude; /* get device number from device name */ devnr = GetDeviceNumber(WKSEQM_TAG,devName); /* 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 allow format overloading (you return different data according to the request */ /* format), then replace calls to putDataFromShort() etc with the desired code. */ prpid = GetPropertyId(WKSEQM_TAG,devProperty); switch (prpid) { case PRP_TEMPERATURE: if (access&CA_WRITE) return illegal_read_write; if (dout->dArrayLength > 0) { if (dout->dArrayLength > PRP_TEMPERATURE_SIZE) return dimension_error; if ((cc=putValuesFromFloatEx(dout,g_temperatureBuffer,PRP_TEMPERATURE_SIZE,devnr)) != 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_amplitudeBuffer[devnr] = 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; memcpy(rb_sineBuffer,g_sineBUffer,PRP_SINE_SIZE); scaleArray(g_amplitudeBUffer[devnr],rb_sineBUffer,PRP_SINE_SIZE); if ((cc=putValuesFromFloat(dout,rb_sineBuffer,PRP_SINE_SIZE)) != 0) return cc; } return 0; default: return illegal_property; } }
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: wkseqm_ini()) to redirect calls to specific devices or properties. The one feature you will have to program in yourself is '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(WKSEQM_TAG,"Amplitude");
Now any clients monitoring the "Amplitude" property will be updated immediately with the new value.
1.5.8