Introduction
TINE is object-based to the extent that device servers offer front-end information in the form of properties and devices. TINE properties can be read-only, write-only, or read-write and should be thought of as corresponding to methods (perhaps get/set methods) as in some cases (e.g. property “initialize”), a property could be simply a trigger. All properties are available via a variety of data access methods.
The current TINE server wizard addresses only the basic server functionality and not hardware IO. The goal is to present the server developer with a setup tool where he can input the functionality the server is supposed to have. The generated project will not have information as to the hardware IO and therefore contains numerous “TODO” statements at strategic locations in the code. Until the developer modifies the code to interface to the real hardware, the data generated for the properties will be simulated.
Note that this frequently follows what actually happens in real situations. Namely, an engineer thumbs through a catalog, chooses a hardware interface card which does what he wants and has the characteristics he needs, and implements it. As it comes with an ActiveX control, he easily builds a stand-alone data acquisition station, and then offers it to the beam diagnostics group, which wants it integrated into the control system as soon as possible. By making use of the TINE server wizard, this turns out to be an easy task .
A Practical Example
As an example consider the input parameters shown in the figure below:
The wizard selections above will generate either a C project and/or a Visual Basic project. Note: One could imagine generating, for instance, a LabView project as another alternative. Although the TINE ActiveX server control works fine with LabView, LabView uses a proprietary binary storage format which makes the rendering task difficult if not impossible.
Generated C Code
Suppose we select a "C" project from input selections. The TINE server wizard will generate the following equipment module:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "tine.h"
#include "bpmeqm.h"
short g_online;
float g_orbit_yBuffer[PRP_ORBIT_Y_SIZE];
float g_orbit_xBuffer[PRP_ORBIT_X_SIZE];
void bpmeqm_bkg(void)
{
int i;
for (i=0; i<PRP_ORBIT_Y_SIZE; i++)
{
g_orbit_yBuffer[i] = (float)SimulateData(-50,100);
}
for (i=0; i<PRP_ORBIT_X_SIZE; i++)
{
g_orbit_xBuffer[i] = (float)SimulateData(-50,100);
}
}
int RegisterBpmeqmProperties(void)
{
int cc = 0;
if ((cc=
RegisterPropertyEx(BPMEQM_TAG,
"ONLINE",PRP_ONLINE_SIZE,CF_SHORT,CA_READ|CA_WRITE,
"[0:1 ]Read/Set on-line status",PRP_ONLINE)) < 0)
goto err;
if ((cc=
RegisterPropertyEx(BPMEQM_TAG,
"ORBIT.Y",PRP_ORBIT_Y_SIZE,CF_FLOAT,CA_READ,
"[-50:50 mm]Vertical Orbit",PRP_ORBIT_Y)) < 0)
goto err;
if ((cc=
RegisterPropertyEx(BPMEQM_TAG,
"ORBIT.X",PRP_ORBIT_X_SIZE,CF_FLOAT,CA_READ,
"[-50:50 mm]Horizontal Orbit",PRP_ORBIT_X)) < 0)
goto err;
err:
if (cc < 0)
{
printf("Can't register property : %s\n>",erlst[-cc]);
}
return cc;
}
void bpmeqm_ini(void)
{
char devnam[16];
int i;
for (i=0; i<100; i++)
{
sprintf(devnam,"device_%d",i);
}
}
void bpmeqm_exi(void)
{
}
int bpmeqm(
char *devName,
char *devProperty,
DTYPE *dout,
DTYPE *din,
short access)
{
int devnr,prpid,i,cc;
short l_online;
switch (prpid)
{
case PRP_ONLINE:
if (access&CA_WRITE)
{
{
if ((cc=getValuesAsShort(din,&l_online,PRP_ONLINE_SIZE)) != 0) return cc;
g_online = l_online;
}
}
{
if ((cc=putValuesFromShort(dout,&g_online,PRP_ONLINE_SIZE)) != 0) return cc;
}
return 0;
case PRP_ORBIT_Y:
{
if ((cc=putValuesFromFloatEx(dout,g_orbit_yBuffer,PRP_ORBIT_Y_SIZE,devnr)) != 0) return cc;
}
return 0;
case PRP_ORBIT_X:
{
if ((cc=putValuesFromFloatEx(dout,g_orbit_xBuffer,PRP_ORBIT_X_SIZE,devnr)) != 0) return cc;
}
return 0;
default:
}
}
This module contains the "meat" of the server's behavior and provides an equipment function handler for processes requests for Property information, a background task which will simulate data but can otherwise be used to supply the data IO, initialization and exit routines, all with numerous 'TODO' comments and 'tips' and 'suggestions'.
In addition, the server's 'main' routine is generated, where various server settings can be tweaked:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "tine.h"
#include "bpmeqm.h"
int SimulateData(int start,int range)
{
return start + rand()%(range);
}
char *SimulateStringData(int length)
{
static char lclStringBuffer[512];
time_t t = time(NULL);
static int counter = 1;
sprintf(lclStringBuffer,"%d : Simulated String generated %s",counter++,ctime(&t));
if (length < 0) length = 0;
if (length < 512) lclStringBuffer[length] = 0;
return lclStringBuffer;
}
void PreSystemInit(void)
{
MinPollingRate = 1000;
MaxPollingRate = 30000;
MaxNumConnections = 100;
StartupDebug = TRUE;
RequireAcknowledgments = TRUE;
MaxRPCTransportSize = 65535;
}
void PostSystemInit(void)
{
}
int main(int argc, char *argv[])
{
int cc;
switch (argc)
{
case 2:
tineDebug = atoi(argv[1]);
break;
default:
tineDebug = 0;
}
PreSystemInit();
if ((cc=SystemInit(TRUE)) != 0)
{
printf(erlst[cc]);
exit(1);
}
PostSystemInit();
for(;;)
{
SystemCycle(TRUE);
}
return 0;
}
Using the generated 'fec.mak' file you can compile and link and build a server executable, which will happily deliver simulated data. Using this code as a starting point, the developer can quickly see what he needs to do to interface his hardware data.
Generated VB Code
Suppose we select a "VB" project from input selections. The TINE server wizard will generate the following equipment module:
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' BpmeqmModule.bas REVISION HISTORY:
' Generated by SRVWIZARD on Fri Feb 27 09:05:51 2004
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Global variables and constants
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Const BpmeqmLocalName$ = "BPMEQM"
Private Const BpmeqmNumDevices% = 100
Global Const PRP_ONLINE_SIZE = 1
Global Const PRP_ONLINE_UPRLIMIT = 1
Global Const PRP_ONLINE_LWRLIMIT = 0
Global Const PRP_ORBIT_Y_SIZE = 100
Global Const PRP_ORBIT_Y_UPRLIMIT = 50
Global Const PRP_ORBIT_Y_LWRLIMIT = -50
Global Const PRP_ORBIT_X_SIZE = 100
Global Const PRP_ORBIT_X_UPRLIMIT = 50
Global Const PRP_ORBIT_X_LWRLIMIT = -50
Global prpOnline As Integer
Global prpOrbit_yBuffer(PRP_ORBIT_Y_SIZE - 1) As Single
Global prpOrbit_xBuffer(PRP_ORBIT_X_SIZE - 1) As Single
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' BpmeqmSimulateData is a useful routine for test servers
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function BpmeqmSimulateData(offset As Double, range As Double) As Double
BpmeqmSimulateData = offset + range * Rnd
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include BpmeqmBackgroundFunction in a Timer to perform background IO, etc.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function BpmeqmBackgroundFunction(srvControl As Srv) As Integer
Dim i As Integer
' TODO: put your IO or other background activity here
' Clear alarms at start of IO task (see if they come back)
srvControl.EqpClearAlarm -1
' TODO: replace BpmeqmSimulateData() with your own data acquisition
' and manipulation routines
For i = 0 To PRP_ORBIT_Y_SIZE - 1
prpOrbit_yBuffer(i) = BpmeqmSimulateData(PRP_ORBIT_Y_LWRLIMIT, PRP_ORBIT_Y_UPRLIMIT - PRP_ORBIT_Y_LWRLIMIT)
Next
For i = 0 To PRP_ORBIT_X_SIZE - 1
prpOrbit_xBuffer(i) = BpmeqmSimulateData(PRP_ORBIT_X_LWRLIMIT, PRP_ORBIT_X_UPRLIMIT - PRP_ORBIT_X_LWRLIMIT)
Next
' make use of SetAlarm() as needed :
' srvControl.EqpSetAlarm devNr, almCode, CADDR(almData(0)), 0
' set error codes as needed:
BpmeqmBackgroundFunction = 0
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include BpmeqmInitFunction in Form_load (for instance) to initialize the server
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function BpmeqmInitFunction(srvControl As Srv, expName As String) As Integer
Dim rc As Integer, id As Integer
' FECNAME: either read from file 'fecid.csv' or user API :
'rc = RegisterFecName(fecname$, desc$, loc$, hdw$, resp$, portoffset)
' DEVICE SERVER: device server parameters
srvControl.EqpNumberModules = BpmeqmNumDevices%
srvControl.EqpName = BpmeqmLocalName$
srvControl.ExportName = expName
'now enable the server
srvControl.Enabled = True
rc = srvControl.EqpStatus
If rc Then GoTo ExitBpmeqmInitFunction
'Property Registration:
id = srvControl.EqpRegisterPropertyEx("ONLINE", PRP_ONLINE_SIZE, CF_SHORT, "", PRP_ONLINE_SIZE, CF_SHORT, "", CA_READ + CA_WRITE, "[0:1 ]Read/Set on-line status")
If id < 0 Then GoTo ExitBpmeqmInitFunction
id = srvControl.EqpRegisterPropertyEx("ORBIT.Y", 0, CF_NULL, "", PRP_ORBIT_Y_SIZE, CF_FLOAT, "", CA_READ, "[-50:50 mm]Vertical Orbit")
If id < 0 Then GoTo ExitBpmeqmInitFunction
id = srvControl.EqpRegisterPropertyEx("ORBIT.X", 0, CF_NULL, "", PRP_ORBIT_X_SIZE, CF_FLOAT, "", CA_READ, "[-50:50 mm]Horizontal Orbit")
If id < 0 Then GoTo ExitBpmeqmInitFunction
'Device Name Registration:
' TODO: fill in the devices name which make sense for your server
For i = 0 To BpmeqmNumDevices% - 1
rc = srvControl.EqpRegisterModule("DEVICE" + Trim$(str$(i)), i)
If rc Then GoTo ExitBpmeqmInitFunction
Next
' Everything should be okay, check for deeper problems:
BpmeqmInitFunction = SRVSTATUS
'
ExitBpmeqmInitFunction:
If rc Then
BpmeqmInitFunction = rc
ElseIf id < 0 Then
BpmeqmInitFunction = -id
End If
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' include BpmeqmEqpFcn in the EqpFcn event of the Srv control
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub BpmeqmEqpFcn(srvControl As Srv, ByVal devName As String, ByVal devProperty As String, ByVal outArrayLen As Long, ByVal inArrayLen As Long, ByVal devAccess As Integer)
Dim i As Integer
Dim devNr As Integer
Dim lclOnline As Integer
' If your properties make use of the device number associated with the device name:
devNr = srvControl.EqpGetModuleNumber(devName)
If devNr < 0 Then
srvControl.EqpSetCompletion illegal_equipment_number, ""
Exit Sub
End If
' check which property was asked for:
Select Case devProperty
Case "ONLINE"
If (devAccess And CA_WRITE) Then
If inArrayLen = 0 Then
srvControl.EqpSetCompletion illegal_read_write, ""
Exit Sub
End If
' Get the incoming data:
srvControl.EqpRecvData lclOnline
' Do range checking:
If lclOnline > PRP_ONLINE_UPRLIMIT Then
srvControl.EqpSetCompletion out_of_range, ""
Exit Sub
End If
If lclOnline < PRP_ONLINE_LWRLIMIT Then
srvControl.EqpSetCompletion out_of_range, ""
Exit Sub
End If
' Copy incoming data into global data buffer
prpOnline = lclOnline
End If
If outArrayLen > 0 Then
srvControl.EqpSendData prpOnline
End If
Case "ORBIT.Y"
If (devAccess And CA_WRITE) Then
srvControl.EqpSetCompletion illegal_read_write, ""
Exit Sub
End If
If (outArrayLen > 0) And (outArrayLen <= (PRP_ORBIT_Y_SIZE - devNr)) Then
srvControl.EqpSendData prpOrbit_yBuffer(devNr)
Else
srvControl.EqpSetCompletion dimension_error, ""
Exit Sub
End If
Case "ORBIT.X"
If (devAccess And CA_WRITE) Then
srvControl.EqpSetCompletion illegal_read_write, ""
Exit Sub
End If
If (outArrayLen > 0) And (outArrayLen <= (PRP_ORBIT_X_SIZE - devNr)) Then
srvControl.EqpSendData prpOrbit_xBuffer(devNr)
Else
srvControl.EqpSetCompletion dimension_error, ""
Exit Sub
End If
Case Else
srvControl.EqpSetCompletion illegal_property, ""
Exit Sub
End Select
' If it's made it this far, it's a success:
srvControl.EqpSetCompletion 0, ""
End Sub
This module contains the "meat" of the server's behavior and provides an equipment function handler for processes requests for Property information, a background task which will simulate data but can otherwise be used to supply the data IO, initialization and exit routines, all with numerous 'TODO' comments and 'tips' and 'suggestions'.
In addition, the server's main form is generated, where various server settings can be tweaked:
Private Sub Form_Load()
Dim cc As Integer
cc = BpmeqmInitFunction(SrvBpmeqm, "TTFBPM")
If cc Then goto SrvInitError
SrvInitError:
If cc Then
StatusLabel.BackColor = &HFF ' RED
StatusLabel.Caption = RPCERROR(cc)
Else
StatusLabel.BackColor = &HFF00& ' GREEN
StatusLabel.Caption = "Server running"
End If
End Sub
Private Sub SrvBpmeqm_EqpFcn(ByVal devName As String, ByVal devProperty As String, ByVal outArrayLen As Long, ByVal inArrayLen As Long, ByVal devAccess As Integer)
BpmeqmEqpFcn SrvBpmeqm, devName, devProperty, outArrayLen, inArrayLen, devAccess
End Sub
Private Sub IOTimer_Timer()
BpmeqmBackgroundFunction SrvBpmeqm
End Sub
Using the generated '.vbp' project file you can compile and link and build a server executable, which will happily deliver simulated data. Using this code as a starting point, the developer can quickly see what he needs to do to interface his hardware data.
Remarks
The TINE server wizard is currently a “one-pass” wizard. This means that there are no “tags” within the generated code which separate the “hands-off” regions from the code sections the developer is allowed to change.
The code generation process consists of supplying the server behavior information through an input panel. This input panel exists as either a VB program or a TCL script. The specifications then generate an intermediate repository. The server wizard uses the TINE “exports.csv” file as a repository, since it is itself useful following the code generation. The repository is then rendered into the desired language.
The generated 'exports.csv' file is shown below:
EXPORT_NAME,LOCAL_NAME,PROPERTY,PROPERTY_SIZE,FORMAT,PROPERTY_INSIZE,INFORMAT,PROPERTY_ID,ACCESS,NUM_MODULES,DESCRIPTION,NUM_STEPS
TTFBPM,BPMEQM,ORBIT.X,100,float,0,NULL, 1,READ,100,[-50:50 mm]Horizontal Orbit,
TTFBPM,BPMEQM,ORBIT.Y,100,float,0,NULL, 2,READ,100,[-50:50 mm]Vertical Orbit,
TTFBPM,BPMEQM,ONLINE,1,short,1,short, 3,READ|WRITE,100,[0:1 ]Read/Set on-line status,