One note about envirnoment variables up front: There are no environment variables in Java (since release 1.0). Directives you would normally set with an environment variable in WIndows or Unix (note: MACOS doesn't have environment variables!) are generally set with a 'properties' file. If you look at the external jar files directory or on windows in c:\tine\java or on Linux /usr/local/tine, you should find a tine.properties file containing entires such as
We'll refer to fec.home and log.home in the instructions which follow.
Sorry no buffered API is yet available, you'll have to make a full-blown tine server for now if you want to use java! But we'll start simple.
Start Eclipse and create a new Project
MyEquipmentModule() which should extend TEquipmentModule
and
MyDeviceServer()
in MyEquipmentModule() add a constructor with the following lines of code:
public MyEquipmentModule() { super("MYSEQM"); // MAKE SURE this is the local name found in your exports.csv file registerDevices(10); // MAKE SURE this is the number of device modules in your exports.csv file getExportInformationFromFile(); }
in MyDeviceServer() add the following lines:
public class MyDeviceServer { private static MyEquipmentModule myEqpModule; public MyDeviceServer() { myEqpModule = new MyEquipmentModule(); } public void activate() { myEqpModule.getTEqmFactory().systemInit(); // initialize the factory myEqpModule.getTEqmFactory().systemWait(-1); // wait here forever ... } public static void main(String[] args) { new MyDeviceServer().activate(); } }
You will need to import the following classes so that the java compiler will be able to resolve the references in the above and in what follows. Note: if tine.jar is included in the project, Eclipse will suggest which class packages to import).
import de.desy.tine.dataUtils.*; import de.desy.tine.definitions.*; import de.desy.tine.server.devices.TDevice; import de.desy.tine.server.equipment.TEquipmentModule; import de.desy.tine.server.properties.TPropertyHandler;
Now run your java server.
Before rushing to the Instant Client, have a look at the server's log file. This will be called 'fec.log.0' and should be located in c:\tine\log (if that's where log.home is pointing to!), or directly in the fec.home or working area, if there's no log.home property.
Near the end of the log file you should see a message saying that your equipment module was "registered with equipment name server". If you see this then everything went reasonably well so far.
03.03.06 12:56:24.078 CET[*unknown*] VERSION : 3.31.0018 03.03.06 12:56:24.094 CET[*unknown*] OS : WIN32C 03.03.06 12:56:24.094 CET[*unknown*] Work Area: 65536 03.03.06 12:56:24.094 CET[*unknown*] Temp Size: 65536 03.03.06 12:56:24.094 CET[*unknown*] Max Transport Size: 65535 03.03.06 12:56:24.094 CET[*unknown*] FECDB : [c:\tine\database\server\] 03.03.06 12:56:24.125 CET[*unknown*] alias file : found 03.03.06 12:56:24.141 CET[*unknown*] TEST_CONSOLE has WRITE access 03.03.06 12:56:24.141 CET[*unknown*] duval has WRITE access 03.03.06 12:56:24.141 CET[*unknown*] KAROL has WRITE access 03.03.06 12:56:24.141 CET[*unknown*] NONE has DENIED access 03.03.06 12:56:24.141 CET[*unknown*] property list file Device3-properties.csv : found 03.03.06 12:56:24.219 CET[*unknown*] device file for WKSEQM : found 03.03.06 12:56:24.219 CET[*unknown*] exports file c:\tine\database\server\exports.csv : found 03.03.06 12:56:24.219 CET[MSTXPDUVAL03.11] Control Structures Swap information registered 03.03.06 12:56:24.250 CET[MSTXPDUVAL03.11] Using Port Offset 11 03.03.06 12:56:24.328 CET[MSTXPDUVAL03.11] TCP PORT 8514: initialized 03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] UDP PORT 9014: bound to 9014 (socket 1956 blocking) 03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] UDP PORT 0: bound to 8052 (socket 1952 blocking) 03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] IPC server listening on \\.\pipe\MSTXPDUVAL03.11.ipc 03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] ALM: recognizing HECASSRV as Central Alarm Server 03.03.06 12:56:24.360 CET[MSTXPDUVAL03.11] ALM: watch file : found 03.03.06 12:56:24.391 CET[MSTXPDUVAL03.11] HIST: WKSEQM Sine #0 (600 s 1 mon) 03.03.06 12:56:24.391 CET[MSTXPDUVAL03.11] HIST: WKSEQM Temperature #0 (600 s 1 mon) 03.03.06 12:56:24.391 CET[MSTXPDUVAL03.11] WKSEQM task registration: ---- BCKG ---- 03.03.06 12:56:24.485 CET[MSTXPDUVAL03.11] TINE HOME : [C:\tine\database\] 03.03.06 12:56:24.610 CET[MSTXPDUVAL03.11] FEC MSTXPDUVAL03.11, Server WKSineGen (WKSEQM) registered with equipment name server 03.03.06 12:56:24.610 CET[MSTXPDUVAL03.11] Context is WORKSHOP 03.03.06 14:28:26.646 CET[MSTXPDUVAL03.11] ALM: Central Alarm Server attached
Now try to find your server in the Instant Client under the context name you assigned. You will more than likely need to click on the "Reload Names" menu item under options (if the context itself is new) or at a minimum open up and close again the context combo box (this will force a reload of the names under the given context).
You should see your device server and be able to browse the properties and devices you entered in the setup wizard.
You should receive an error message 'not implemented' (not surprisingly).
You will have to add some form of property handler in order to get around the "not implemented" situation (until there is a buffered api for Java as well). In the mean time do the following.
Add three propery handlers inside your MyEquipmentModule class such as :
attachPropertyHandler("Sine",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { return 0; } }); attachPropertyHandler("Temperature",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { return 0; } }); attachPropertyHandler("Amplitude",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { return 0; } });
Now your equipment module class should look something like:
public class MyEquipmentModule extends TEquipmentModule { TDevice[] deviceList = new TDevice[10]; public MyEquipmentModule() { super("MYSEQM"); registerDevices(10); getExportInformationFromFile(); attachPropertyHandler("Sine",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { return 0; } }); attachPropertyHandler("Temperature",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { return 0; } }); attachPropertyHandler("Amplitude",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { return 0; } }); } }
If you access your java server now, you shoudl see the nice 0's that everyone else sees with their buffered server at this stage.
So let's give the properties something to read back.
You'll have to make those property handlers do something. In your equipment module class add fields for the property data such as:
float amplitude = 300; float[] mySineData = new float[1024]; float[] myTemperatureData = new float[100];
Then add code that will return data inside the handler. When you're finished, your equipment module class might look like:
public class MyEquipmentModule extends TEquipmentModule { TDevice[] deviceList = new TDevice[10]; float amplitude = 300; float[] mySineData = new float[1024]; float[] myTemperatureData = new float[100]; public MyEquipmentModule() { super("MYSEQM"); registerDevices(10); getExportInformationFromFile(); attachPropertyHandler("Sine",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { for (int i = 0; i < 1024; i++) mySineData[i] = (float)(25.0 * Math.random()) + amplitude * (float)Math.sin(6.2832 * ((double) i / 1024.0)); return dout.putData(mySineData); } }); attachPropertyHandler("Temperature",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { for (int i = 0; i < 100; i++) myTemperatureData[i] = 20 + (float)(5.0 * Math.random()); return dout.putData(myTemperatureData); } }); attachPropertyHandler("Amplitude",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { return dout.putData(amplitude); } }); } }
Now you can read something for all of your properties. To be able to set values for the ampltude for instance, you would have to add a bit of logic in the Amplitude property handler. Also, you will have to do range checking, and any other data filtering here. So in the Amplitude property handler check to see if the devAccess object contains the CA_WRITE bit and if so examine the contents of the din object to see what setting was sent. An example snippet for this handle code is shown below:
attachPropertyHandler("Amplitude",new TPropertyHandler() { protected int call(String devName, TDataType dout, TDataType din, TAccess devAccess) { int compCode = 0; if (devAccess.isWrite()) { float[] setting = new float[1]; if ((compCode=din.getData(setting)) != 0) return compCode; if (setting[0] < 1 || setting[0] > 500) return TErrorList.out_of_range; amplitude = setting[0]; } return dout.putData(amplitude); } });
Notice one (rather annoying) API detail when examining the incoming data in Java. The 'din' object will contain the data object that was sent, but you want to have a look at it. A caller's request at this level will not be blocked unless it makes no sense whatsoever. But the caller may (for reasons of his own) have sent you a double value or integer value instead of the float value you would like to work with. The 'din' object will happily reformat what was sent (if it can) to what you would like, so the easiest way of getting this information is to call the getData() method, passing a reference to the value object which should contain the setting. A simple 'float' cannot be passed as a reference! So even though the Amplitude is only one single float value, you have to make an array object of dimension 1 and use this. Hence the variable 'setting' is an array object, which IS passed as a reference. So the getData() method can fill it in with what was sent (reformatting if necessary). Now you can check this value against allowable values and accept it and assign it to amplitude if everything is okay.
Why is there a putData() method for the 'dout' object that takes a single float values as an argument? The reason here, is that we don't need the reference, so passing it by value is okay.
An alternative to making an array of 1 float value for the case of examining what was sent, would be to use the the getFormat() method followed by the getDataObject() method, which returns on object containing the data. If you choose this path, you might have to cast the incoming data into the float value you want. Also note that the incoming data will always be an array object.
Unless you thought in advance while using the server wizard, there are probably no device names registered. The instant client will just show a "#0" as a device name. You can re-visit the wizard and enter "100" for the "Number of Devices" (in the device server information frame).
After entering this number, click on the Property Name field, that will signal the wizard to fill in the Device Panel with some device names ("Device 0", "Device 1", etc.) which you can either just leave as it is, or provide some of your favorites.
Also let's specify what kind of array property "Temperature" and "Sine" are. Click on Property "Sine" in the display panel on the right hand side, so that all of the text and combo boxes are filled with it's parameters. Now for "Output Array Type" choose "SPECTRUM". And click on the "Edit" button on the right (If you try to "Add" this property, you'll get a message telling you it is already there, but you can edit it!). Click on property "Temperature" on the property display panel and choose array type "CHANNEL". And then click on "Edit" again.
When you're finished, click on "Done" once more.
If you have a look at the new generated "exports.csv" file, you should see that the number of modules is now 100, and the format type for property "Sine" is now float.SPECTRUM and for property "Temperature" float.CHANNEL.
Copy this new exports.csv into your fec.home database area and restart your server. Note: you can also copy the exports.csv into a subdirectory with the same name as your 'local' equipment module name (e.g. MYSEQM) located under the fec.home database area. In fact the server wizard will offer to copy the exports.csv there for you! This is indeed a more logical location for the exports file, but keep in mind that when the server initializes and it needs to attach to a database it will first try to find an 'exports.csv' file in the local name's subdirectory. If that fails, it will then try to find an 'export.csv' file at the top level of the fec.home repository.
Now when you browse for it with the instant client, you should notice that when you click on property "Sine" the display type automatically changes to "PolyLine", i.e. a SPECTRUM data type automatically gets displayed as a trace.
Click on the "Temperature" property. The display type automatically changes to "Histogram", i.e. CHANNEL array data generally refer to an array where each element refers to value of the property for a particular channel. And this is best displayed as a histogram.
If you make the instant client read the Temperature, you will also see that the x-axis label also includes the device names and the element positions.
Before leaving the server wizard, let's have a look at local history files (followd by alarm files). Use the wizard to call up your database again and select the Temperature property. Now check the "Keep History" check box, followed by clicking the "Edit" button and then the "Done" Button. You can also edit the history panel if you wish. The default settings should be good enough for our demonstation purposes.
You should have generated a "history.csv" file, which you can now copy to your fec.home area as well. Now restart your server and have a look at the log file. It should now tell you that it is keeping a history of "Temperature". If you re-browse your server in the Instant Client, you should see a "history" check box appear, which will let you trivially check the history of the selected device. A better way to see what is going on, is to add by hand ".HIST" to the property name in the combo box (make sure the history check box is NOT checked), and then hit the read button. Without any further input the local history server will ask for history data starting from "now" minus the depth in seconds given by the data size in the request. You can also have some fun by repeating the call with a format type of "FLTINT" which will return an array of data-timestamp pairs.
If you want to hava sneek preview of the local history viewer, you can call it up and under "Options" choose "Data Sources" and then choose the "LOcal History Chooser". You can then navigate via the Context combo box to the "WORKSHOP" context and find your server. It should display everything you are keeping a local history of. Choose one of your properties and devices and the click on "Add Selection".
Now lets have a look at the local alarm system. Start the wizard again, select "Temperature" and check the "Alarm Watch" check box. You should change some of the settings in the alarm watch panel, namely the "Value too high" Threshold, you can set to 24, with a warning at 22 and the "Value too low" threshold to 16, with a warning at 18. Click the "Edit" button and then the "Done" button.
You should now have generated an "almwatch.csv" file. Have a look at this file (if you like) and copy it into your fec.home area and restart your server.
You can check the alarms on your server via the instant client, by including the "Stock Properties" in the property list. The relevant properties are "NALARMS" (number of alarms as a 5-parameter snapshot - when the first number > 0 then you have local alarms) and "ALARMS", which will return the current local alarm list (you might want to ask for fewer then the default 512 alarms).
Another simple way to view the alarms is to start the Remote Fec Control panel, find your server and click on the Alarms tab.
Now let's see how we can set the amplitude. So far we can only read it. Use the server wizard to make sure that the "Amplitude" property is a READ/WRITE property. Click on the "Amplitude" property in the right-hand side display, change the access to READ/WRITE and make sure the input parameters allow 1 float (Single) values to be sent. Click on "Edit" and then on "Done". Copy the new exports.csv file into the fec.home area.
We're not through! Now a client will send us data, so we have to examine what was sent, decide whether to use it or not and then return.
You can test property scheduling in your java code by directly calling the scheduler. This has the effect of notifiying all clients listening to the property in question immediately. That is, NO LATENCY.
(Finish this section)
Most classic device servers have many instances (devices) and each device supports the same set of properties. Many servers however are more "property-oriented" than not. Still others are more "device-oriented" than not.
Property-oriented would mean that each property potentially has a completely different set of devices. This is typical of middle-layer gateway servers, where there might be a property "VacPressure" and a property "Orbit-X". The getter pumps in the one case have nothing to do with the bpm monitors in the other.
The wizard is capable of setting up this kind of behavior in the configuration database. That is the purpose behind the Server or Property option button in the device panel. The default is "Server-oriented" device lists, where the devices given are valid for all properties on the server. If you select "Property", then a separate list is made specifically for the property in question. If you try this, you will generate an addition .csv file with the name <Property>-names.csv. In our case, "Temperature" might be such a property, so you would generate a Temperature-names.csv. (Of course if everything is called "Device 0", etc. you won't see the difference.
Device-oriented would mean that the device server supports all devices listed, but some devices might have a different property set than others. The wizard is not capable of setting up the Device-Oriented behavior, but you can add it yourself (fairly) easily.
You need to modify your <eqpmod>-devices.csv file. For instance, if this is called MYSEQM-devices.csv, it might look like:
DEVICE_NUMBER,DEVICE_NAME 0,Device 0 1,Device 1 2,Device 2 3,Device 3 4,Device 4 5,Device 5 ...
You need to add by hand another column called PROPERTY_LIST. If the device in question deviates from the default property list (all registered properties), then you need to supply a file name (your choice) for the entry in this column. For instance, let's say that "Device 3" only supports property "Temperature". Then you would have:
DEVICE_NUMBER,DEVICE_NAME,PROPERTY_LIST 0,Device 0, 1,Device 1, 2,Device 2, 3,Device 3,Device3-properties.csv 4,Device 4, 5,Device 5, ...
Then you need to create the file called Device3-properties.csv. This is a .csv file with one important column, namely "PROPERTY_NAME".
So you would have
PROPERTY_NAME Temperature
And that would be that.
Once again, when you start editing the .csv files yourself, you'll probably want to stop using the wizard (or else get used to copy-and-paste.
It's time to try Redirection. The wizard doesn't offer this, but you can do it again by hand.
Often it is the case that a server will offer a property which lives on another server, or a device which lives on another server.
Let's look at the device case first. Suppose you want to redirect any request involving Device 1 to your neigbor's server. Ask your neighbor what his server is called. Open up the <eqpmod>-devices.csv file and add a column called REDIRECTION and leave all the entries in this column blank except for the row corresponding to Device 1. In this row, put your neighbor's server. For example, if his server is called "MYNEIGHBORSERVER",you might have:
DEVICE_NUMBER,DEVICE_NAME,PROPERTY_LIST,REDIRECTION 0,Device 0,, 1,Device 1,,MYNEIGHBORSERVER 2,Device 2,, 3,Device 3,Device3-properties.csv, 4,Device 4,, 5,Device 5,, ...
Copy this new -devices.csv file to your fec.home area and restart your server. When you (re)browse your server and access Device 2, your call will be redirected to MYNEIGHBORSERVER.
ONE IMPORTANT THING: If the "MYNEIGHBORSERVER" Server isn't running or doesn't have a device namded "Device 2" or a property equal to one you're trying to access then you will get an error code returned.
We're finished with Buffered Servers, which maybe illustrate just how far you can get with databases alone. Sometimes you might already have a different database structure or more complicated needs, So let's move on to the generated servers.
1.5.8