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 :
#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;
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_gaussianBuffer[n][i] = (float)SimulateData(0,100);
}
for (n=0; n<NUM_STAEQM_DEVICES; n++)
for (i=0; i<PRP_SINE_SIZE; i++)
{
g_sineBuffer[n][i] = (float)SimulateData(-100,200);
}
}
int RegisterStaeqmProperties(void)
{
int cc = 0;
if ((cc=
RegisterPropertyInformation(STAEQM_TAG,
"Status",&dout,&din,CA_READ,AT_CHANNEL,0,
"[0:100 ]Device Status",PRP_STATUS,
"")) < 0)
goto err;
if ((cc=
RegisterPropertyInformation(STAEQM_TAG,
"Gaussian",&dout,&din,CA_READ,AT_SPECTRUM,0,
"[0:100 V]A Gaussian Curve",PRP_GAUSSIAN,
"")) < 0)
goto err;
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;
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;
for (i=0; i<NUM_STAEQM_DEVICES; i++)
{
sprintf(devnam,"device_%d",i);
}
}
void staeqm_exi(void)
{
}
int staeqm(
char *devName,
char *devProperty,
DTYPE *dout,
DTYPE *din,
short access)
{
int devnr,prpid,i,cc;
float l_amplitude;
switch (prpid)
{
case PRP_STATUS:
{
}
return 0;
case PRP_GAUSSIAN:
{
if ((cc=PutValuesFromFloat(dout,g_gaussianBuffer[devnr],PRP_GAUSSIAN_SIZE)) != 0) return cc;
}
return 0;
case PRP_AMPLITUDE:
if (access&CA_WRITE)
{
{
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;
}
}
{
}
return 0;
case PRP_SINE:
{
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);
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:
#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;
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];
}
}
int RegisterStaeqmProperties(void)
{
int cc = 0;
if ((cc=
RegisterPropertyInformation(STAEQM_TAG,
"Status",&dout,&din,CA_READ,AT_CHANNEL,0,
"[0:100 ]Device Status",PRP_STATUS,
"")) < 0)
goto err;
if ((cc=
RegisterPropertyInformation(STAEQM_TAG,
"Gaussian",&dout,&din,CA_READ,AT_SPECTRUM,0,
"[0:100 V]A Gaussian Curve",PRP_GAUSSIAN,
"")) < 0)
goto err;
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;
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;
for (i=0; i<NUM_STAEQM_DEVICES; i++)
{
sprintf(devnam,"device_%d",i);
g_amplitudeBuffer[i] = BASE_AMPLITUDE;
}
}
void staeqm_exi(void)
{
}
int staeqm(
char *devName,
char *devProperty,
DTYPE *dout,
DTYPE *din,
short access)
{
int devnr,prpid,i,cc;
float l_amplitude;
switch (prpid)
{
case PRP_STATUS:
{
}
return 0;
case PRP_GAUSSIAN:
{
if ((cc=PutValuesFromFloat(dout,g_gaussianBuffer[devnr],PRP_GAUSSIAN_SIZE)) != 0) return cc;
}
return 0;
case PRP_AMPLITUDE:
if (access&CA_WRITE)
{
{
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;
}
}
{
}
return 0;
case PRP_SINE:
{
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.