|
1.
|
Developing CDEV Applications
|
|
Overview
|
The purpose of this section is to describe the steps that a CDEV user must perform in
order to effectively develop a CDEV application. This section assumes that the user is
either using one of the CDEV services provided in the CDEV distribution, or has a
local or third party developed CDEV service with which the application will
communicate.
|
|
Steps in
Developing a
CDEV Application
|
|
1.
|
Construct a CDEV device definition file. The CDEV device definition file is the
roadmap that CDEV uses to define services and determine which device and
message combinations are associated with those services. This file should be
constructed and defined in the CDEVDDL environment variable prior to executing
a CDEV application. Check with the CDEV adminstrator to determine if this file
already exists on your system.
|
|
2.
|
Design and write your source code. This step is intentional broad in scope
because the capabilities provided by various CDEV services may be completely
dissimilar in nature. The primary capability that CDEV provides is standardization
of the interface between any application and any underlying CDEV service.
|
|
3.
|
Compile and link application with the CDEV library. This step requires the
source code to include the necessary CDEV C++ header files and linking with the
libcdev.sl or libcdev.a libraries.
|
|
4.
|
Set any required environment variables. The primary environment variables
required by CDEV are CDEVSHOBJ and CDEVDDL. CDEVSHOBJ is used to
located shared objects, and cdevddl is used to specify the complete path to the
CDEV device definition file. Individual services may have additional requirements.
|
|
5.
|
Execute your application.
|
Note: Applications linked with the archive library are not capable of loading
dynamically loadable CDEV services.
|
|
2.
|
Developing Device Definition Language Files
|
|
Device Definition
Language (DDL)
File Overview
|
The Device Definition Language File or DDL File is the roadmap that CDEV uses to
associate certain device and message combinations with a CDEV service. The DDL
file provides the mechanism for defining service definitions, class definitions and
instance definitions.
|
|
Service Definitions
|
The service definition is used to publicly define the existence of a CDEV service. The
service definition declares the name of the service and the names of any special
cdevData tags that the service will use. The following is a simple service definition.
Figure 1:
Service definitions in the CDEV DDL file
|
|
/*
* Definition of service X
*/
service x
{
tags { tag1, tag2 }
}
/*
* Definition of service y
*/
service y
{
tags {}
}
|
|
The service definition above defines the service "x" and service "y". During the
execution of a CDEV application, when a device and message combination is related
to the "x" service, CDEV will attempt to load the dynamic library for the service. By
convention the name of the library will be "xService.so", where "x" is the name of the
service within the DDL file.
The service definition also defines the tags "tag1" and "tag2" in conjunction with
service "x". These tags will be used later to define special service data that is related
to a specific device attribute. Service "y" has no special tags associated with it.
This example also illustrates the formats of comments within the DDL file. The syntax
of the DDL file accommodates the use of C style multi-line comments.
|
|
Class Definitions
|
The class definition is used within the DDL file to declare a collection of verbs,
attributes or messages that may be applied to one or more CDEV device instances.
|
|
Verb Definitions
|
The following examples shows the syntax for declaring a class definition that contains
only a list of verbs.
Figure 2:
CDEV DDL class definition containing a list of verbs
|
|
class stdio
{
verbs { get, set, monitorOn, monitorOff }
}
|
|
In the above example, the "stdio" class is defined and it is declared to contain four
verbs: get, set, monitorOn and monitorOff. Any other class that is derived from this
class, or any device that is instantiated from this class will inherit these four verbs.
|
|
Class Inheritance
|
The CDEV DDL file supports the notion of inheriting one class from another class. The
pre-existing class is called the super-class, while the class that is inheriting from the
pre-existing class is called the sub-class. To specify inheritance within the CDEV DDL
file, the syntax is similar to the syntax used in C++. The name of the new class is
followed by a colon, which is followed by the name of the super-class.
In order to support the possibility of a colon as a character in the class name, a space
must separate the name of the class and the colon.
In the next example, the "magnetIO" class is defined. This class inherits from the
"stdio" class, and receives the verbs get, set, monitorOn, and monitorOff. To this list of
verbs, it adds a new verb: "reset".
Figure 3:
CDEV DDL class definition using inheritance
|
|
class stdio
{
verbs { get, set, monitorOn, monitorOff }
}
class magnetIO : stdio
{
verbs { reset }
}
|
|
|
|
Attribute
Definitions
|
Class definitions are also used to define attributes. An attribute is a component of a
CDEV device that can be combined with a verb to define a message. When an
attribute is declared within a class, CDEV will assume that the attribute supports each
of the verbs that exist within the class.
|
|
Attribute Service
Data
|
When the attribute is defined, the user must define the name of the service that will
receive the messages associated with it, and any service data that is associated with
it. This service data is defined using the tag names that are declared in the tags
section of the service definition.
The following example declares the class "element". This class inherits its verbs from
the "stdio" class. Each attribute is associated with a service name and is followed by
service data.
Figure 4:
CDEV DDL class definition containing a list of attributes
|
|
/*****************************
* Definition of service X
*****************************/
service x
{
tags { tag1, tag2 }
}
/*****************************
* Definition of stdio class
*****************************/
class stdio
{
verbs { get, set, monitorOn, monitorOff }
}
/*****************************
* Definition of element class
*****************************/
class element : stdio
{
attributes
{
value1 x {tag1=1.0, tag2=Value};
}
}
|
|
Service data also supports the notion of expression replacement. If the character
string "<>" appears in the service data, it will be replaced with the instance name of
the device that is instantiated from this class.
|
|
Message
Definitions
|
A message definition can be declared within a CDEV DDL class definition to define
one word atomic commands. Unlike attributes and verbs, a message is a stand-alone
instruction that can be sent to a device.
The structure of a message definition is similar to the attribute definition. The entries
in this section contain the name of the service and special service data that is
associated with the message.
The following example shows a class that contains a single message definition. Note
that the class does not define any verbs or attributes.
Figure 5:
CDEV DDL class definition containing a list of messages
|
|
/*****************************
* Definition of service X
*****************************/
service x
{
tags { tag1, tag2 }
}
/*****************************
* Definition of element class
*****************************/
class element
{
messages
{
on x {tag1=<>.Val, tag2=Text};
}
}
|
|
|
|
Device Instances
|
The device instances section is where devices are created from class definitions.
Each device that is instantiated from a class inherits all of the verbs, attributes and
messages from that class, as well as from each class that it inherits from.
The syntax for instantiating a device is as follows: the name of the class, followed by a
colon, followed by the list of device names that are being defined. The following figure
shows the syntax for defining a list of devices of the "element" class.
Figure 6:
CDEV DDL class definition to instantiate a list of devices
|
|
/*****************************
* Instantiation of devices
*****************************/
element :
device1
device2
device3;
|
|
|
|
Device Name
Substitution
|
As described earlier, angle braces "<>" in the service data definition for an attribute
will be replaced with the name of its associated device. A name substitution
technique is available that allows the DDL writer to specify that a different device
name should be inserted. The substitution device name is specified in conjunction
with the actual device name when the device is instanciated.
The following example illustrates the syntax for instantiating a device and specifing a
substitute name for it. In this example, the string "device1" will be substituted wherever
the string "dev1" would normally be used.
Figure 7:
Implementing device name substitution in a CDEV DDL file
|
|
/*****************************
* Device name substitution
*****************************/
element :
dev1 {device1}
device2
device3;
|
|
|
|
Device Name
Aliasing
|
Device name aliasing is a more powerful alternative to device name substitution. A
device's alias may be used in applications to obtain and communicate with its
corresponding device. Device name aliases are specified in the DDL file on an
individual basis by using the alias keyword.
The syntax for adding an alias to a device is illustrated in the example below. In this
example, the alias "dev1" can be used interchangably with the actual device name
"device1".
Figure 8:
Implementing device name aliasing in a CDEV DDL file
|
|
/*****************************
* Device name aliasing
*****************************/
alias dev1 device1
|
|
|
|
Defining
cdevCollections
|
A cdevCollection device can be implemented in one of two ways; the developer can
create an empty cdevCollection dynamically and add devices to it, or a collection entry
can be placed in the CDEV DDL file that identifies the collection and its constituent
devices. The syntax for defining a cdevCollection device in the CDEV DDL file is as
follows.
Figure 9:
cdevCollection Definition in the CDEV DDL File
|
|
collection cDevice1 :
device0,
device1,
device2
;
collection cDevice2 :
device3
device4
device5
;
|
|
In the example above two cdevCollections are defined. A cdevCollection definition is
started by using the keyword "collection", followed by a space and then the name of
the collection. Note that the name of the collection must be unique and cannot be
shared with any other device in the CDEV system.
The device name is followed by a white space character (which is required), and
then a colon, followed by another white space character and then the list of devices.
The device names that are in the list may be separated by either white space of a
comma delimiter. The list of devices is terminated by a semicolon.
The current implementation of the CDEV DDL is order dependent. Because of this,
the developer should place the collection definitions after the definitions of all devices
and aliases.
|
|
#include Directive
|
The #include directive is supported within the CDEV DDL file syntax. This directive is
used to include the contents of another DDL file within the current DDL file. The
syntax is the same as its C language counterpart.
Figure 10:
Using the #include directive in a CDEV DDL file
|
|
Sample DDL
|
The following sample DDL file demonstrates all current features of the Device
Definition Language.
Figure 11:
Sample complete DDL file
|
|
/*****************************************************************
* Service Definitions:
* This section contains the definitions for individual services.
*****************************************************************/
/*****************************************************************
* ca Service (channel access)
* The library caService.so will be loaded and called whenever a
* call to this service needs to be executed.
*
* This services support the following special tags:
* PV = Process Variable: this tag is used to specify the
* actual name of the attribute that is being
* defined.
* READONLY = This is a logical tag that indicates if the
* attribute is readonly (1) or read/write (0).
*****************************************************************/
service ca {
tags { PV, READONLY }
}
/*****************************************************************
* Class Definitions
* This section contains the definition for individual classes.
*****************************************************************/
/*****************************************************************
* stdio Class:
* This class defines the basic set of verbs that will be
* supported by all other classes in this DDL file. The verbs
* declared in this class are:
* get = Get the value of an attribute of a device
* set = Set the value of an attribute of a device
* monitorOn = Monitor changes of an attribute's value
* monitorOff = End monitoring changes of an attribute's value
*****************************************************************/
class stdio {
verbs { get, set, monitorOn, monitorOff }
}
|
|
Figure 11:
Sample complete DDL file (continued)
|
|
/*****************************************************************
* magnet Class:
* This class inherits its list of verbs from the stdio class. It
* then adds a list of attributes that these verbs may operate
* on. The following attributes are specified.
*
* current : The current attribute will be serviced by ca.
* bdl : The bdl attribute will be serviced by ca.
* length : The length attribute will be serviced by ca.
*
* This class also supports the messages "on" and "off". For
* illustration purposes, these messages will operate on the val
* field, and therefore, have the same service data.
*****************************************************************/
class magnet : stdio
{
attributes {
current ca {PV=<>.VAL, READONLY=0};
bdl ca {PV=BDL_<>.VAL, READONLY=0};
length ca {PV=LEN_<>.VAL, READONLY=1};
}
messages {
on ca {PV=<>.VAL, READONLY=0};
off ca {PV=<>.VAL, READONLY=0};
}
}
/*****************************************************************
* Device Instances:
* This section contains the instantiations of individual devices.
*****************************************************************/
/*****************************************************************
* The following is a list of devices that are memebers of the
* magnet class. That means that each of these devices supports
* all of the attributes, verbs and messages that are defined by
* the magnet class and the stdio class that it inherits from.
*****************************************************************/
magnet :
MQB1S01
MQB1S02
MQB1S03
MQB1S04
/*****************************************************************
* End of Device Definition File
*****************************************************************/
|
|
|
|
3.
|
Using the cdevSystem Object
|
|
Overview of the
cdevSystem Class
|
The cdevSystem C++ class can be described as a communications junction between
a CDEV application and CDEV services. The cdevSystem object is responsible for
interpreting the device definition file, dynamically loading CDEV services upon
request, detecting and responding to I/O events, and providing a mechanism for
polling services for updates.
Currently, only the default cdevSystem object may be used within an application.
Ultimately, multiple cdevSystem objects may be created, and each of them will
maintain individual copies of CDEV services and devices within them.
Most CDEV classes that are accessed directly by applications have static factories
(special constructors). Any CDEV object that is constructed by one of these factories
is automatically a member of the default cdevSystem.
|
|
Public Functions
of the
cdevSystem Class
|
|
attachRef
|
static cdevSystem& attachRef(char *name, char *prefix);
Obtains a reference to a new or existing cdevSystem object
that has the specified name. The prefix parameter is optional,
but, if provided will be prepended to the name of each device
that is requested through the named cdevSystem object before
it is found in the device definition file.
|
|
attachPtr
|
static cdevSystem* attachPtr(char *name, char *prefix);
Obtains a pointer to a new or existing cdevSystem object that
has the specified name. The prefix parameter is optional, but, if
provided will be prepended to the name of each device that is
requested through the named cdevSystem object before it is
found in the device definition file.
|
|
defaultSystem
|
static cdevSystem &defaultSystem (void);
Obtains a reference to the default cdevSystem object. In most
applications this mechanism is used to access the cdevSystem
object.
|
|
getDevice
|
cdevDevice* getDevice (char *device);
Retrieves a pointer to a cdevDevice object by name. The object
that is returned by this function will be owned and controlled by
the cdevSystem object that created it. This means that flush,
poll and pend commands must be routed through the owning
cdevSystem object in order to effect this cdevDevice object.
|
|
getRequestObject
|
int getRequestObject ( char *device, char *msg,
cdevRequestObject &req);
Retrieves a reference to a cdevRequestObject from a specified
device name and message name. The object that is produced
by this function will be owned and controlled by the
cdevSystem object that created it. This means that flush, poll
and pend commands must be routed through the owning
cdevSystem object in order to effect this cdevRequestObject.
Returns CDEV_SUCCESS or an enumerated error code.
|
|
name
|
char *name (void);
Returns the name of this cdevSystem object.
|
|
prefix
|
char *prefix (void);
Retrieves a pointer to the prefix string that is currently being
used by the cdevSystem object. See the attachPtr entry for a
description of the prefix string.
|
|
prefix
|
void prefix (char *pre);
Changes the prefix string that is being used by the cdevSystem
object. See the attachPtr entry for a description of the prefix
string.
|
|
flush
|
int flush (void);
Flushes any pending outbound requests to their respective
services.
|
|
poll
|
int poll (void);
Directly polls each of the cdevSystem's underlying services for
activity, and delivers any asynchronous callbacks that are
ready.
|
|
pend
|
int pend (int fd);
Waits for a default period of time for the specified file descriptor
to have an I/O event. If the fd parameter is not provided, the
cdevSystem object will wait for an I/O event on any of its
contained file descriptors. When an event occurs on one of the
file descriptors, the cdevSystem object will call the respective
cdevService to process the event and dispatch any
asynchronous callbacks that are ready.
|
|
pend
|
int pend (double seconds, int fd);
Processes all I/O events that occur on the file descriptor during
the specified period of time. If the fd parameter is not provided,
the cdevSystem object will wait for I/O events on all of its
contained file descriptors. When an event occurs on one of the
file descriptors, the cdevSystem object will call the respective
cdevService to process the event and dispatch any
asynchronous callbacks that are ready.
|
|
getFd
|
int getFd (int fd[], int &numFD);
Retrieves a list of file descriptors that are contained within the
cdevSystem object. The fd array must be preallocated, and the
maximum number of elements should be specified in the
numFD parameter. Upon completion, the fd array will be
populated with the list of file descriptors, and the numFD
parameter will contain the actual count.
|
|
addFdChanged Callback
|
int addFdChangedCallback (cdevFdChangedCallback cbk,
void*userarg)
Adds a new function to be called each time a service
announces to the system that it has opened or closed a file
descriptor. The function will be called with 3 arguments:
void (*cdevFdChangedCallback) (int fd, int opened,
void *userarg)
The first argument specifies the file descriptor, the second is 1
for opened, 0 for closed, and the last argument is the user
specified argument.
|
|
autoErrorOn
|
int autoErrorOn (void);
Informs the cdevSystem object that it should use its internal
default error handler to process any error messages that are
generated by objects within its control. This is the default
operating condition for the cdevSystem object.
|
|
autoErrorOff
|
int autoErrorOff (void);
Informs the cdevSystem object that it should use the user
supplied error handling function to process any error messages
that are generated by objects within its control.
|
|
reportError
|
int reportError ( int severity, char *name,
cdevRequestObject *obj,
char *formatString,...);
Emits an error message. The severity field indicates the
severity of the error, the name string identifies the object that
generated the error, the obj parameter is the
cdevRequestObject that was in use when the error occurred,
and the formatString and additional parameters (...) should be
formatted in the same manner as the parameters used by
printf.
The integer used by severity should have one of the following
values indicating the severity of the error that has occurred.
|
CDEV_SEVERITY_INFO:
|
No error.
|
|
CDEV_SEVERITY_WARN:
|
An error occurred that should
not impact continued
processing.
|
|
CDEV_SEVERITY_ERROR:
|
An error occurred and should be
corrected before continuing.
|
|
CDEV_SEVERITY_SEVERE:
|
A severe or fatal error has
occurred and CDEV cannot
continue normal execution.
|
|
|
setErrorHandler
|
void setErrorHandler (cdevErrorHandler handler);
Used to to install a user specified error handler. This error
handler will be called when an error occurs if the autoErrorOff
method has been used to disable the default error handler. The
user provided error handler should have the following
prototype:
void handler (int severity, char *text, cdevRequestObject *obj);
The severity parameter will contain one of the integers
specified in the reportError documentation, the text parameter
will contain the text of the error, and the obj parameter will
contain the cdevRequestObject that was in use when the error
occurred.
|
|
setThreshold
|
void setThreshold (int errorThreshold);
Used to specify the level of severity at which errors should be
submitted to the error handler. The value of errorThreshold
should be one of the severity levels specified in the reportError
method.
|
|
|
Sample Code
|
The following sample application shows the use of the three methods for obtain a
cdevSystem object. For examples of how to use the flush, poll and pend methods, see
the documentation for the cdevDevice object.
Figure 12:
Methods for obtaining a cdevSystem object
|
|
#include <cdevSystem.h>
void main()
{
// ************************************************************
// * The first and most common approach is to directly request
// * a reference to the default cdevSystem.
// ************************************************************
cdevSystem & default = cdevSystem::defaultSystem();
// ************************************************************
// * Get a pointer to the name of the default cdevSystem.
// ************************************************************
char * sysName = default.name();
// ************************************************************
// * Use the attachRef method to obtain a reference to the
// * default cdevSystem by name.
// ************************************************************
cdevSystem & default2 = cdevSystem::attachRef(sysName);
// ************************************************************
// * Finally, use the attachPtr method to obtain a pointer to
// * the default cdevSystem by name. Additionally, set the
// * prefix for the default system to "TEST".
// ************************************************************
cdevSystem * default3 = cdevSystem::attachPtr(sysName, "TEST");
// ************************************************************
// * Output the name and prefix for all three systems.
// ************************************************************
printf("%s:%s\\n", default.name(), default.prefix());
printf("%s:%s\\n", default1.name(), default1.prefix());
printf("%s:%s\\n", default2->name(), default2->prefix());
}
|
|
The following sample application illustrates how to install a user specified error
handler into the default cdevSystem object.
Figure 13:
Installing a user specified error handler
|
|
#include <cdevSystem.h>
// ****************************************************************
// * This the default error handler. It simply outputs the error.
// ****************************************************************
void myHandler(int severity, char *text, cdevRequestObject &obj)
{
if(severity>0)
{
fprintf(stderr, "ERROR (%i): %s\\n", severity, text);
}
else fprintf(INFORMATION: %s\\n", text);
}
void main()
{
// ************************************************************
// * Obtain a reference to the default cdevSystem.
// ************************************************************
cdevSystem & default = cdevSystem::defaultSystem();
// ************************************************************
// * Call the setErrorHandler method to install the new
// * errorHandler.
// ************************************************************
default.setErrorHandler(myHandler);
// ************************************************************
// * Turn off auto error handling to tell the system to use
// * the new user specified error handing function.
// ************************************************************
default.autoErrorOff();
}
|
|
|
|
4.
|
Using the cdevDevice Object
|
|
Overview of the
cdevDevice Class
|
The cdevDevice C++ class is the user's primary interface to CDEV. In fact, it is
possible to develop complete CDEV applications using only cdevDevice and cdevData
objects. The cdevDevice class defines the basic interface that all applications must
use to transmit messages to a CDEV service.
A cdevDevice may be created using the static member functions attachPtr or
attachRef. If one of these methods is used, the device will be created within the
context of the default cdevSystem. If the application wishes to create a device within a
cdevSystem other than the default, it must use the getDevice method of the desired
cdevSystem object.
A cdevDevice object is bound by name to a specific device when it is instantiated.
However, the underlying service is not selected until a message is specified with a
send command. Therefore, simply creating a device does not ensure that the device
name is valid, nor that it supports a given message.
Messages may be sent to a device using one of the three send member functions,
these are: send, sendNoBlock, and sendCallback. The syntax and functionality of
these methods is described in the section below.
Input and output to the specified send methods is managed through the use of
cdevData objects. Unlike the cdevDevice object which is bound to a specific system,
the cdevData object is completely independent. These objects store the data values
(called properties) that are sent to and received from devices. The application may
indicate the properties that it wishes to receive as output from the cdevDevice object
by specifying them in a special cdevData object called the context.
Examples at the end of this section will illustrate the correct ways to implement these
operations.
|
|
Public Functions
of the cdevDevice
Class
|
|
attachRef
|
static cdevDevice& attachRef (char *name);
Obtains a reference to a cdevDevice object by name. By
default, the new object will be managed by the default
cdevSystem.
|
|
attachPtr
|
static cdevDevice* attachPtr (char *name);
Obtains a pointer to a cdevDevice object specified by name. By
default, the new object will be managed by the default
cdevSystem.
|
|
detach
|
static void detach (cdevDevice& dev);
Removes a referenced cdevDevice object from its associated
cdevSystem object. Ordinary applications should never use this
command.
|
|
detach
|
static void detach (cdevDevice* dev);
Detaches the cdevDevice object specified by dev from its
associated cdevSystem object. Ordinary applications should
never use this command.
|
|
getRequestObject
|
cdevRequestObject* getRequestObject (char *msg);
Obtains a service specific request object. This function uses
the name of the device specified within the cdevDevice object,
and the message provided by the caller to determine which
CDEV service will be used to service this request. The service
is then loaded (if necessary), and is contacted to provided a
request object for the device/message combination. This
cdevRequestObject will then be used to communicate with the
service directly.
|
|
name
|
const char *name (void) const;
Returns the name of the device.
|
|
| |