Introduction
TINE console server applications follow the usual TINE server dichotomy, where the developer's task is to provide an equipment module which handles requests from the client side as well as the internal local archive and local alarm servers.
The developer can also make use of the associated background tasks for dealing with local server i/o.
The example below is a simple java server code module which offers a sine curve to calling applications.
This example makes extensive use of the standard TINE startup configuration .csv files, in particular: fecid.csv, exports.csv, and <local-name>-devices.csv to register all equipment module names, property names, and device names. The history.csv files and almwatch.csv files can also be used to provide local histories and to catch certain types of alarms.
Most the comments are inside the example code, which consists of three modules: 1)a Sine Device Class which inherits from a TDevice and is used to instantiate the devices served by the device server, 2) a Sine Equipment Module Class which inherites from a TEquipmentModule Class and provides the primary server interface, and 3) a Sine Device Server Class which constitutes the device server.
Although it is possible to encode all relevant names (properties, devices, etc.) inside these modules, it is much easier for the purposes of this illustration to make use of the TINE startup configuration files 'fecid.csv' to provide the fec name and other information, the 'exports.csv' file to provide the exported device server name and the exported properties, and a 'devices.csv' file to register the individual device names.
The Sine Device Class
This is an example for a device. The device periodically produces an array of values in its update() method. The device has properties which can be read and set via set/get methods.
package de.desy.tine.example.sine;
import de.desy.tine.server.devices.*;
public class SineDevice extends TDevice
{
public static final int DATASIZE = 1024;
public static final double PHASEDIFF = 2 * Math.PI / 100.0;
private double frequency = 1;
private double amplitude = 1;
private double[] myData;
private double phase = 0.0f;
public SineDevice(int newNumber)
{
super(newNumber);
myData = new double[DATASIZE];
}
public SineDevice(String newName,int newNumber)
{
super(newName,newNumber);
myData = new double[DATASIZE];
}
public double getFrequency()
{
return frequency;
}
public void setFrequency(double frequency)
{
this.frequency = frequency;
}
public double getAmplitude()
{
return amplitude;
}
public void setAmplitude(double amplitude)
{
this.amplitude = amplitude;
}
public double[] getSine()
{
return myData;
}
private void incrementPhase()
{
double phasediff = Math.PI / 100.0;
phase += phasediff;
if (phase + phasediff > 2.0 * Math.PI) phase = 0.0f;
}
public void update()
{
clearAlarm(512);
incrementPhase();
for (int i = 0; i < 1024; i++)
myData[i] = amplitude * Math.sin(phase + frequency * 6.28 * ((double) i / 1024.0));
if (amplitude > 100) setAlarm(512);
}
}
The Sine Equipment Module Class
Example equipment module. The equipment module maps the device properties to TINE properties.
Notes: The routine registerProperties(): The principal property registeration occurs via the 'exports.csv' configuration file. In this case the property 'handlers' need to be attached to the registered properties. An alternative would be the use the registerProperty(TExportProperty, TPropetyHandler) method of TEquipmentModule. In this case you would have to supply all property information inside an instance of TExportProperty prior to making the call. Under some circumstances it might be desireable to keep the property information in an external database (such as 'exports.csv') rather than to hard code it in this manner.
Property Handler examples: Have a look at the examples for handling requests for properties "Amplitude" and "Frequency". In one case ("Frequency") the the entire request is passed to a call routine which handles 'write' and 'read' requests collectively. The other case ("Amplitude") splits the 'write' and 'read' requests into two separate routines. The differences between the two is by and large cosmetic. It is generally useful to allow atomic 'write-read' requests along with individual 'writes' and 'reads'.
Such equipment module routines should be to a large extent generated from a wizard, so that your only task might be to fill in or expand routines such as writeAmplitude to suit your needs.
package de.desy.tine.example.sine;
import de.desy.tine.dataUtils.*;
import de.desy.tine.definitions.*;
import de.desy.tine.server.devices.*;
import de.desy.tine.server.equipment.*;
import de.desy.tine.server.properties.*;
import de.desy.tine.types.*;
public class SineEquipmentModule extends TEquipmentModule
{
boolean wrapMultiChannelArrays = true;
TDeviceList myDeviceSet;
private SineDevice findDevice(String deviceName)
{
if (myDeviceSet == null) return null;
return (SineDevice) myDeviceSet.getDevice(deviceName);
}
public SineEquipmentModule(String localName, SineDevice[] devices)
{
super(localName);
registerDevices(devices);
registerProperties();
myDeviceSet = super.getDeviceList();
}
public SineEquipmentModule(SineDevice[] devices)
{
super();
registerDevices(devices);
registerProperties();
myDeviceSet = super.getDeviceList();
}
private int readString(String devName, TDataType dout)
{
return dout.putData(
new NAME16(devName));
}
private int readSine(String devName, TDataType dout)
{
SineDevice device = findDevice(devName);
if (device ==
null)
return TErrorList.device_not_connected;
return dout.putData(device.getSine());
}
public int writeAmplitude(String devName, TDataType din)
{
int cc = 0;
SineDevice theDevice = null;
if (din.getArrayLength() != 1)
return TErrorList.dimension_error;
double[] input = new double[1];
theDevice = (SineDevice) myDeviceSet.getDevice(devName);
if (theDevice ==
null)
return TErrorList.illegal_equipment_number;
if ((cc=din.getData(input)) != 0) return cc;
theDevice.setAmplitude(input[0]);
return 0;
}
public int readAmplitude(String devName, TDataType dout)
{
int cc = 0;
SineDevice theDevice;
int nret = dout.getArrayLength();
int ndv = myDeviceSet.getNumberOfDevices();
int dv = myDeviceSet.getDeviceNumber(devName);
if (dv < 0 || dv >= ndv)
return TErrorList.illegal_equipment_number;
if (!wrapMultiChannelArrays && dv + nret > ndv) nret = ndv - dv;
double[] output = new double[nret];
for (int i=0; i<nret; i++)
{
theDevice = (SineDevice) myDeviceSet.getDevice(dv++ % ndv);
output[i] = theDevice.getAmplitude();
}
cc = dout.putData(output);
return cc;
}
private int callFrequency(String devName, TDataType dout, TDataType din, TAccess devAccess)
{
int cc = 0;
SineDevice theDevice;
if (devAccess.isWrite())
{
double[] input = new double[1];
if (din.getArrayLength() != 1)
return TErrorList.dimension_error;
theDevice = (SineDevice) myDeviceSet.getDevice(devName);
if (theDevice ==
null)
return TErrorList.illegal_equipment_number;
if ((cc=din.getData(input)) != 0) return cc;
theDevice.setFrequency(input[0]);
}
if (devAccess.isRead())
{
int nret = dout.getArrayLength();
int ndv = myDeviceSet.getNumberOfDevices();
int dv = myDeviceSet.getDeviceNumber(devName);
if (dv < 0 || dv >= ndv)
return TErrorList.illegal_equipment_number;
if (!wrapMultiChannelArrays && dv + nret > ndv) nret = ndv - dv;
double[] output = new double[nret];
for (int i=0; i<nret; i++)
{
theDevice = (SineDevice) myDeviceSet.getDevice(dv++ % ndv);
output[i] = theDevice.getFrequency();
}
cc = dout.putData(output);
}
return cc;
}
private void registerProperties()
{
getExportInformationFromFile();
attachPropertyHandler("SINE",new TPropertyHandler()
{
protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
{
return readSine(devName, dout);
}
});
attachPropertyHandler("Amplitude",new TPropertyHandler()
{
protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
{
if (devAccess.isWrite())
{
rc = writeAmplitude(devName, din);
if (rc != 0) return rc;
}
if (devAccess.isRead())
{
rc = readAmplitude(devName, dout);
}
return rc;
}
});
attachPropertyHandler("Frequency",new TPropertyHandler()
{
protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
{
return callFrequency(devName, dout, din, devAccess);
}
});
attachPropertyHandler("ThisDevice",new TPropertyHandler()
{
protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess)
{
return readString(devName, dout);
}
});
}
}
The Sine Device Server Class
This is an example of a TINE-based server. The device server consists of 10 devices. Each device generates data of a propagating sine wave. For each device, the amplitude and the frequency of the sine wave can be set and read.
package de.desy.tine.example.sine;
import java.util.LinkedList;
import de.desy.tine.server.equipment.*;
public class SineDeviceServer
{
private static SineEquipmentModule sineEqpModule;
private static TEquipmentBackgroundTask sineBkgFcn;
private TEquipmentModuleFactory thisEqmFactory;
private LinkedList sineDeviceSet = new LinkedList();
public SineDeviceServer()
{
initializeDevices();
initializeDeviceServer();
sineEqpModule.dumpProperties();
sineEqpModule.dumpDevices();
}
private void initializeDevices()
{
for (int i = 0; i < 10; i++)
{
sineDeviceSet.add(new SineDevice(i));
}
}
private void initializeDeviceServer()
{
sineEqpModule = new SineEquipmentModule((SineDevice[])sineDeviceSet.toArray(new SineDevice[0]));
sineBkgFcn = new TEquipmentBackgroundTask()
{
public void call()
{
for (int i=0; i<sineDeviceSet.size(); i++)
((SineDevice)sineDeviceSet.get(i)).update();
}
};
sineBkgFcn.setBackgroundTaskInterval(200);
}
public void activate()
{
thisEqmFactory = sineEqpModule.getTEqmFactory();
if (sineBkgFcn != null)
thisEqmFactory.addEquipmentBackgroundTask(sineBkgFcn);
thisEqmFactory.systemInit();
thisEqmFactory.systemWait(-1);
}
public static void main(String[] args)
{
SineDeviceServer theServer = new SineDeviceServer();
theServer.activate();
}
}
Before trying to run this example, make sure the necessary startup files are present in the fec.home location (see the tine.properties Property File).
The fec name is obtained from:
Fec_Name,Context,Export_Name,Port_Offset,SubSystem,Description,Location,Hardware,Responsible
JSINESRV,TEST,JAVASINE, 6,TST,Sine server,Bldg 30 Rm 300,None,"P.Duval,J.Q.Public"
The exported device server name as well as the property names are obtained from:
EXPORT_NAME,LOCAL_NAME,PROPERTY,PROPERTY_ID,ACCESS,FORMAT,PROPERTY_SIZE,INFORMAT,PROPERTY_INSIZE,NUM_MODULES,DESCRIPTION,REDIRECTION
JAVASINE,SineEq,SINE,1,READ,float,1024,,,100,[0:1 bozos !LIN][1000:2024 dinges]Sine Curve,
JAVASINE,SineEq,TEST,2,READ,float.CHANNEL,1024,,,100,[0:1000 bozos]Test Curve,
JAVASINE,SineEq,ECHO,3,READ,long,1024,long,1024,100,[0:1000 bozos]echo input,
JAVASINE,SineEq,MODE,5,READ|WRITE,short,1,short,1,100,[0:1 bozos]noise or travelling,
JAVASINE,SineEq,STRUCTTEST,6,READ|WRITE,struct,1,,,100,struct test,
JAVASINE,SineEq,Amplitude,7,READ|WRITE,double,1,double,1,100,[0:1000 bozos]sine amplitude,
JAVASINE,SineEq,Frequency,8,READ|WRITE,double,1,double,1,100,[0:100 Hz]sine frequency,
JAVASINE,SineEq,HURRICANE,10,READ,float,1024,,,100,[0:1 bozos !LIN][1000:2024 dinges]Sine Curve,SINE[SINE]
JAVASINE,SineEq,NOTREADY,11,READ,float,1,,,100,float value,
JAVASINE,SineEq,ThisDevice,12,READ,NAME16,1,,,100,string value,
The exported device names are obtained from:
DEVICE_NAME,DEVICE_NUMBER,REDIRECTION
SINEDEV_0,0,
SINEDEV_1,1,DOSSINE
SINEDEV_2,2,
SINEDEV_3,3,
SINEDEV_4,4,
SINEDEV_5,5,SINE
SINEDEV_6,6,
SINEDEV_7,7,
SINEDEV_8,8,
SINEDEV_9,9,