This is a "how to" document for making data accessible to ACNET from a VxWorks front end. This document will attempt to explain:
This document will not deal with alarms, continuous plots, or snap shot plots. These items can be successfully used in a VxWorks front end but are topics which are best left to a more advanced level discussion.
DataBase Name.All access to ACNET data is performed using a database name. All database names are in the form of X:YYYYYY. A single character followed by a colon, followed by up to six more characters. A database name is created using a utility called Dabbel.
DataBase Device. When a database name is created, several pieces of information are associated with the name for the purpose of defining what front end the data resides on, what format the data is in, what device properties the data has etc. One of the most important pieces of information is called the SSDN. The database name along with the associated information is called the database device.
Device Properties. The typical properties that a device may have are reading, setting, read the setting, basic status, and basic control. A device may have one or more of these properties. The properties that a device uses can be set up using Dabbel.
SSDN. The SSDN (Subsystem Device Number) is an important part of the database device entry. It contains network information about which front end the data resides on so that the access will be directed to the correct computer. It also contains information about the device such as its channel number, and its Object ID. The SSDN is entered along with the other device information using Dabbel.
Dabbel. A program which resides on the VAX Cluster which is used to create or alter a database device.
VxWorks. VxWorks is a commercial multitasking operating system that the AD/Controls Group has adopted for use with microcomputers connected to ACNET.
Front End. A Front End (FE) is any computer that is capable of providing data to ACNET directly. Historically, all front end computers contain the tasks RETDAT (return data) and SETDAT (set data.)
Front End Keeper. This is the person in charge of the front end software.
MOOC. Minimal Object Oriented Communication. MOOC is a library of C functions written by Boris Lublinsky which follows the object oriented paradigm. The MOOC library is used extensively when making front end data accessible to ACNET.
Method. This is an object oriented name for a function which manipulates data associated with an object.
Console Parameter Page. A console parameter page provides minimal access to the data associated with any database name. Using a parameter page, the user can read the device, set the device, read the setting, read the basic status, and set the basic control.
Custom Console Application Page. If basic parameter page access is not enough then a custom console application page can be written. Using a custom application page, the data can be read, set, and displayed in any way that is required. The user interface can be customized so that the data is accessed in any convenient fashion that suits the needs of the user. Another advantage that a custom application has over a parameter page is that the length and offset parameters can be used to access the data in different ways on the fly without changing the database entry.
Length And Offset. Length and offset are two extra parameters that get sent to the front end along with the SSDN when accessing data. Length is used to tell the front end exactly how many bytes of data are being requested. Offset is used if the device is capable of returning an array of data. Offset tells the front end which element in the array to start returning data from. These two parameters are powerful because they are the only parameters that can be changed on the fly without changing the database entry.
Lets use this SSDN for an example.09CC 0034 0001 2101
The first word in the SSDN is the trunk number and node number assigned to the front end. In this case its trunk 9, node CC. Every new front end must get a trunk and node assigned by an ACNET expert. At the present time the person to contact for getting a new trunk and node assigned is Glenn Johnson.
The second word in the SSDN is the OOC ID (OID), in this case it is
0034. The OID is a code which tells the front end which object the
data is to come from. The OID is usually defined in the front end
code in a file called dev_guts.h
.
The third word in the SSDN is a 16 bit User Defined Field also known as the Misc field. In this case the field has been set to 0001. The user defined field can be set to any value for any purpose. It can be used as a code to tell the front end program what format the data should be returned in or which piece of data should be returned. The SSDN is defined when the database device is created using Dabbel. As long as the front end keeper and the database device creator agree on the user defined field, all is well. Often the front end keeper is also the database device creator. That makes it easy.
The last word in the SSDN contains a Device Type byte and a Device
Channel byte. The Device Type is also known as the Class Number which
is defined in the front end program in the file
dev_guts.h
. The front end may have several devices under
its control. The Device Type number in the SSDN tells the front end
which device type this data access pertains to.
Often a device or a device type may have several channels associated with it. The channel number in the SSDN is used to distinguish between device channels.
There are 2 more pieces of information that get sent along with the SSDN when ACNET is accessing data. These are know as length and offset. Length is used to tell the front end exactly how many bytes of data are being requested. Offset is used if the device is capable of returning an array of data. Offset tells the front end which element in the array to start returning data from.
Length and offset are unique because they are the only 2 things that can be specified from within a console application page. The SSDN is locked in when the device is created in the database so none of the data in the SSDN can be changed on the fly. On the other hand, length and offset can be set from within a custom console application page. This is quite a valuable feature at times when flexibility is needed to get data in different formats from the same database device. For example, offset could be used as a code instead of an array offset. The code might tell the front end to return different pieces of information about the same device. A detailed discussion of the use of length and offset follows later in this document.
dev_guts.h The OIDs and the Class numbers are defined here. ratedev.c The access method functions and the init_class function reside here. ooc_ini.c OOC initialization functions.
/* Device class definitions */ #define TESCLS 1 /* Test class device */ #define TIMCLS 2 /* Time since boot class */ #define SLMCLS 3 /* Slam enable class */ #define VERCLS 4 /* Version class */ #define ALRCLS 5 /* Alarm class */ #define SCPCLS 6 /* Slow continuous plot class */ #define RATECLS Ox21 /* Rate class */ /* classes OIDs */ #define TIMOID 2 /* Time from boot OID */ #define SLMOID 3 /* Slam control OID */ #define VEROID 4 /* Version OID */ #define TESTOID 10 /* Starting OID for test device */ #define RATEOID Ox34 /* rate OID */
For our example we only want ACNET to be able to read the count rates. The count rates are not setable and they do not have any binary status associated with them, so we will only have to write the reading method and the init method. It is customary to put the method functions in a file called xxxdev.c. For our example the following methods would be placed into a file called ratedev.c. Lets look at the details of the reading method.
/*----------------------------------------------------------------------*/ /* Reading method for class RATECLS */ /*----------------------------------------------------------------------*/ extern float CountRate[8]; extern float FilteredCountRate[8]; static int MreadD(short cls, READ_REQ* req, float* rep, void* ivs) { OMSP_DEF* omsp; unsigned int chan; if (!req || !rep) return ERR_INMEM; if (req ->OFFSET != 0) return ERR_BADOFF; /* check offset */ if (req->ILEN != 4) return ERR_BADLEN; /* check length */ omsp = (OMSP_DEF *) &req->OMSP; if (omsp->typ != RATECLS) return ERR_WRONGTP; chan = omsp->chan; if (chan >= 8) return ERR_BADCHN; switch(omsp->misc) { case 0: *rep = CountRate[chan]; break; case 1: *rep = FilteredCountRate[chan]; break; default: return ERR_BADFORM; break; } return 0; }
odasocc.h
header file.
typedef struct { short int mt; /* Message type */ int OMSP; /* OOC-Message-Specific Parameters */ short int ILEN; /* Length of data (in bytes) that ACNET is requesting */ short int OFFSET; /* Offset */ short int filler; /* filler */ } READ_REQ;
typedef struct { unsigned int misc:16; /* 16 bit user defined field from SSDN */ unsigned int typ :8; /* 8 bit Device type from SSDN */ unsigned int chan:8; /* 8 bit Channel number from SSDN */ } OMSP_DEF;
In the example reading method the first step performed is checking
the passed in parameters. If the parameters are unacceptable, the
function returns an error code. This error code ultimately gets sent
back to ACNET and will show up on what ever console page was
requesting the data. The error codes shown in the example are standard
errors defined in Boris's ooc_user.h
header file.
First req and rep are checked for NULL. If one of them is NULL, there is an obvious error so the function returns an error code right away. Next, the passed in offset is checked. Since this method only returns one data item per channel and does not return an array of data, offset is always expected to be zero. A full discussion of the use of offset is saved for later in this document. The next thing checked is the length parameter. Length is always specified in terms of bytes. Since in this example the returned data is a float, length should be 4 bytes. If it isn't, a length error code is returned.
The next step performed in the example read method is to obtain the
SSDN information from the OMSP (OOC Method Specific Parameters???).
This gives the programmer access to the misc field (user defined
field), the type field, and channel field from the SSDN. The type is
checked against the class number which is defined in
dev_guts.h
, and the channel number is checked for range.
Now we are finally ready to return the requested data. In this example the misc field is used as a code to decide if the CountRate is to be returned or if the FilteredCountRate is to be returned. The channel field is used as an array index to return data from the specified channel. Notice also that if the misc field is unrecognized, an error code is returned via the default: at the end of the switch statement.
The only other method we need to write for our example is the init method. The init method is used to provide information about alarms, continuous plots, and snap shot plots. For this example we will not be using alarms or plots, so the init method simply returns.
/*---------------------------------------------------------------*/ /* Init method for class RATECLS */ /*---------------------------------------------------------------*/ static int Minit(short cls, short* oid, void* rep, void* ivs) { return 0; }
xxxdev.c
file.
int init_class_rated(void) { int stat; /* short scl[] = {ALRCLS}; super class arrary */ if ((stat = create_class(RATECLS, /* class name */ 0, /* how many super classes */ NULL, /* pointer to super class array */ 2, /* how many methods in the class */ 0)) != NOERR) return stat; if ((stat = name_class(RATECLS, "rate dev")) != NOERR) return stat; if ((stat = add_class_msg(RATECLS, Init, (PMETHOD)Minit)) != NOERR) return stat; if ((stat = add_class_msg(RATECLS, rPRREAD, (PMETHOD) MreadD)) != NOERR) return stat; /* if ((stat = add_class_msg(RATECLS, sPRSET, (PMETHOD) MSetD)) != NOERR) return stat; if ((stat = add_class_msg(RATECLS, rPRSET, (PMETHOD) MReSetD)) != NOERR) return stat; if ((stat = add_class_msg(RATECLS, rPRBSTS, (PMETHOD) MBasStD)) != NOERR) return stat; */ return NOERR; }
The rest of the init_class function just calls the name_class function and the add_class_msg functions. There is one add_class_msg call for every method. The names of the method functions are passed to add_class_msg along with the associated ACNET property. The method names could be anything but it is customary to name them MreadD, MsetD, etc.
ooc_ini.c
file. The ooc_ini function is used to initialize all of the classes
in the system. First the init_class_xxx function is called for each
class in the system. Recall that we created the init_class_rated
function in the file ratedev.c in step 3. Then the create_instance
function is called. There could be more than one instance of a class
but usually there is only one.
#include "vxWorks.h" #include "stdio.h" #include "ctype.h" #include "/users/boris/mooc/ooc_user.h" /* User's definitions for OOC+- */ #include "/users/boris/mooc/mtdefs.h" /* Message type definitions */ #include "/users/boris/mooc/odasooc.h" #include "dev_guts.h" int ERROR_FACILITY = 57; /* Error facility code 57 is standard for MOOC*/ static struct { short ver; short rev; } cur_ver = {1, 1}; /* Current version */ void ooc_ini(void) { short stat, i, startn; /* Initiate Dictionary */ init_clasobj(); /* Initiate classes */ if ((stat = init_class_alarms()) != NOERR) { /* Alarm class */ pr_ooc_error(stat); exit(NULL); } if ((stat = init_class_slcnpl()) != NOERR) { /* Slow continious plot class */ pr_ooc_error(stat); exit(NULL); } if ((stat = init_class_slsnpl()) != NOERR) { /* Slow snapshot plot class */ pr_ooc_error(stat); exit(NULL); } if ((stat = init_class_versd()) != NOERR) { /* Version class */ pr_ooc_error(stat); exit(NULL); } if ((stat = init_class_timd()) != NOERR) { /* Age class */ pr_ooc_error(stat); exit(NULL); } if ((stat = init_class_slamd()) != NOERR) { /* Slam control class */ pr_ooc_error(stat); exit(NULL); } if ((stat = init_class_testd()) != NOERR) { /* Test class */ pr_ooc_error(stat); exit(NULL); } if ((stat = init_class_rated()) != NOERR) { /* rate class */ pr_ooc_error(stat); exit(NULL); } /* Create version devices */ for (i = 0, startn = VEROID; i < 1; i++, startn++) if ((stat = create_instance(startn, VERCLS, &cur_ver, "Version_dev")) != NOERR) { pr_ooc_error(stat); exit(NULL); } /* Create age devices */ for (i = 0, startn = TIMOID; i < 1; i++, startn++) if ((stat = create_instance(startn, TIMCLS, NULL, "Time_dev")) != NOERR) { pr_ooc_error(stat); exit(NULL); } /* Create alarm control devices */ for (i = 0, startn = SLMOID; i < 1; i++, startn++) if ((stat = create_instance(startn, SLMCLS, NULL, "Slam_dev")) != NOERR) { pr_ooc_error(stat); exit(NULL); } /* Create test devices */ for (i = 0, startn = TESTOID; i < 1; i++, startn++) if ((stat = create_instance(startn, TESCLS, &startn, "Test_dev")) != NOERR) { pr_ooc_error(stat); exit(NULL); } /* Create rate devices */ for (i = 0, startn = RATEOID; i < 1; i++, startn++) if ((stat = create_instance(startn, RATECLS, &startn, "rate_dev")) != NOERR) { pr_ooc_error(stat); exit(NULL); } return; }
Makefile
.
# Makefile # in order to do a make type -> make CPU = MC68020 HOST = hpux #these are short hand names which will be used later in the Makefile DESTDIR = ./ LIBDIR = /users/briegel/vw/ACNET/ LIBSSM = /users/briegel/vw/acnet/ssm/ LIBUTILS = /users/briegel/vw/acnet/ LIBMOOC = /users/boris/mooc/ TRIGPATH = /users/boris/triggers/ FTPPATH = /users/boris/ftpman/ UCDPATH = /users/briegel/vw/acnet/trig/trig/ VW = /usr/vw_51/ VWH = $(VW)h/ H1 = $(VWH) H2 = $(VWH)/net H3 = $(VWH)/rpc H4 = $(VWH)68k/ # this tells Make where to get the include files INCLUDES = -I$(H1) -I$(H2) -I$(H3) -I$(H4) -I/usr/include -I$(LIBACNET) \ -I$(TRIGPATH) -I$(LIBMOOC) CC = cc68k CFLAGS = -ansi -g -O2 -pipe -fvolatile -traditional -m68020 -m68881 -DCPU=$(CPU) CPP = cpp68k CPPFLAGS = -P ASM = as68k AFLAGS = -a -mc68020 LINK = ld68k LFLAGS = -r -M AR = ar68k ARFLAGS = crsv # list system software modules here. Make will compile the # corresponding .c files to get .o files if changes have been made OBJS = ooc_ini.o ratedev.o example.o #list system libraries here LIBRARIES = $(LIBDIR)libacnet.a $(LIBUTILS)libutils.a $(LIBSSM)libssm.a \ $(TRIGPATH)libtrig.a $(FTPPATH)libftpm.a $(LIBMOOC)libmooc.a .SUFFIXES: .o .c .asm .s .c.o: $*.c $(CC) -D CPU=$(CPU) $(CFLAGS) $(INCLUDES) -c $*.c .asm.o: $*.asm $(CPP) $(CPPFLAGS) $*.asm $*.s $(ASM) $(AFLAGS) -o $*.o $*.s > $*.lis mv $*.s $*.s.asm # this creates the example.out file # the first line tells make to relink if any of the OBJs have changed # or if the Makefile has changed the rest of it tells make what # modules to link into the example.out file notice that the libraries # are last so that only unresolved functions are taken from the # libraries example.out : $(OBJS) Makefile $(LINK) $(LFLAGS) -o example.out \ $(LIBACNET)echo.o \ $(LIBMOOC)inient.o \ $(OBJS) $(LIBRARIES) > example.map # this is where dependencies go. if these .h files change, Make will #recompile these must come after the big link command(s) above ratedev.o : dev_guts.h ooc_ini.o : dev_guts.h example.o : example.h
Raw count rates:
S:RATE0 SSDNHX READING(09CC/0034/0000/2100) S:RATE1 SSDNHX READING(09CC/0034/0000/2101) " " " " " " " S:RATE7 SSDNHX READING(09CC/0034/0000/2107)
Filtered count rates:
S:FRATE0 SSDNHX READING(09CC/0034/0001/2100) S:FRATE1 SSDNHX READING(09CC/0034/0001/2101) " " " " " " " S:FRATE7 SSDNHX READING(09CC/0034/0001/2107)
Notice that the channel field in the SSDN changes for each of the 8 devices. Also notice that for the filtered count rates the misc field (user defined field) in the SSDN holds 0001. This corresponds with the way that the read access method (MreadD) function was set up in the front end software. The SSDN gets passed into the reading method in the OMSP.
omsp->misc contains the 16 bit user defined field from the SSDN omsp->type contains the class number from the SSDN omsp->chan contains the channel number from the SSDN
At present there are 25 Primary Transforms and 15 Common Transforms available to be used with ACNET devices. A list of the current transforms can be found on page D80 under the Pgm_Tools menu. The primary transforms are generally used to convert the data into a format that can be handled by DEC computers. The common transforms are generally used to convert the data into Engineering Units if needed.
For our example, the data is returned from the front end in a 4 byte float with units of counts per second. The units are already in an acceptable form for the end user, but the 4 byte float is a problem. The front end in our example is a 68000 based computer. The byte ordering of a 68000 is incompatible with a DEC computer. The data needs to be byte swapped and there is a Primary Transform that can do that for us. It is Primary Transform number 24.
We do not need a Common Transform for this example so we will use Common Transform number 0 which is .
The Dabbel entry for our example for transforms would be:
PDB READING('Ct/s','Ct/s',24,0,4,0,0,0,0,0)
For example, if the front end data is a 2 byte integer (know as type "short" in c) declare *rep to be a short in the MreadD function such as;
static int MreadD(short cls, READ_REQ* req, short* rep, void* ivs)
When creating the database device use a data size of 2 bytes and use primary conversion type 10 which is Float (input).
The following primary transforms should be used for 68000 front ends:
If the data is... | Use... |
---|---|
1 Byte Int (char) | Primary Transform 10 |
2 Byte Int (short) | Primary Transform 10 |
4 Byte Int (int) | Primary Transform 28 |
4 Byte Float (float) | Primary Transform 24 |
It is beyond the scope of this document to fully explain parameter pages, but they are very powerful for accessing basic device functionality. If the reader is just learning about ACNET, it would be wise to experiment with a parameter page. (Of course, you should not make any settings to devices that don't belong to you!!) All of the basic functionality built into ACNET can be utilized by a parameter page and it is a good place to learn about ACNET devices.
For some projects, a parameter page is all that is required for the user interface. If a suitable existing parameter page can not be found to house the device entries, a new blank parameter page can be set up by speaking to Jim Smedinghoff.
When retrieving array data from a custom application page you can use the dio_get_ functions with the length and offset parameters. For example if you have an array of 4 byte floats and the array has 100 elements (float data[100]) you can get the data in the following ways.
To get the whole array: length = 4 * 100, offset = 0
To get the 5th element: length = 4, offset = 5
To get the last 50 elements: length = 4 * 50, offset = 50
You will have to write code in the xxxdev.c
file in
order to handle these lengths and offsets. You could set up the
MreadD method like this:
offset = req->OFFSET; numdat = req->ILEN / sizeof(float); for (i = 0; i < numdat; i++) *rep++ = data[offset++];
Length and Offset are the only two parameters that get sent to the front end on the fly. All other parameter such as misc, type, and channel come from the SSDN which can only be changed using Dabbel. That makes Length and Offset very powerful for flexible data access.
Important Note: Dabbel allows you to enter a maximum array size of 16384 but that number is bogus! The maximum array size that can be sent over ACNET is 3982 bytes. If you need to get an array which is larger that 3982 bytes you can do multiple dio_gets in the custom application such as the following pseudo code.
dio_get(length = 3982, offset = 0 * 3982); dio_get(length = 3982, offset = 1 * 3982); dio_get(length = 3982, offset = 2 * 3982);
The length parameter must always be used to state the true amount of data to be transferred, but its OK to lie with the offset parameter. What I mean is that offset does not have to be used for an array offset. Offset could be used for anything as long as the author of the custom application page and the author of the front end code (usually the same person) agree. Offset could be used as a code to tell the front end anything such as which piece of data is required, or which mathematical formula to use upon the data, etc. Perhaps there are several arrays associated with one device, in that case offset could be used to determine which array is required by the application page.