Subsections

8 Writing a TANGO device server


1 The device server framework


This chapter will present the TANGO device server framework. It will introduce what is the device server pattern and then it will describe a complete device server framework. A definition of classes used by the device server framework is given in this chapter. This manual is not intended to give the complete and detailed description of classes data member or methods, refer to [8] to get this full description. But first, the naming convention used in this project is detailed.
The aim of the class definition given in this chapter is only to help the reader to understand how a TANGO device server works. For a detailed description of these classes (and their methods), refer to chapter [*] or to [8].

1 Naming convention and programming language


TANGO fully supports two different programming languages which are C++ and Java. When the Java code differs from the C++ code, examples in both languages will be given. For C++, its standard library has been used. Details about this library can be found in [9].
Every software project needs a naming convention. The naming convention adopted for the TDSOM is very simple and only defines two guidelines which are: These conventions will be use hereafter for both C++ and Java.

2 The device pattern


Device server are written using the Device pattern. The aim of this pattern is to provide the control programmer with a framework in which s/he can develop new control objects. The device pattern uses other design patterns like the Singleton and Command patterns. These patterns are fully described in [10]. The device pattern class diagram for stepper motor device is drawn in figure [*]
Figure: Device pattern class diagram
[Device pattern class diagram]Image device_et


. In this figure, only classes surrounded with a dash line square are device specific. All the other classes are part of the TDSOM core and are developed by the Tango system team. Different kind of classes are used by the device pattern.


1 The DeviceImpl class


1 Description


This class is the device root class and is the link between the Device pattern and CORBA. It inherits from CORBA classes and implements all the methods needed to execute CORBA operations and attributes. For instance, its method command_inout is executed when a client requests a command_inout operation. The method name of the DeviceImpl class is executed when a client requests the name CORBA attribute. This class also encapsulates some key device data like its name, its state, its status, its black box.... This class is an abstract class and cannot be instantiated as is.

2 Contents


The contents of this class can be summarize as :

2 The DbDevice class


Each DeviceImpl instance is an aggregate with one instance of the DbDevice class. This DbDevice class can be used to query or modify device properties. It provides an easy to use interface for device objects in the database. The description of this class can be found in the Tango java or C++ API documentation.

3 The Command class



1 Description of the inheritance model


Within the TDSOM, each command supported by a device and implemented using the inheritance model is implemented by a separate class. The Command class is the root class for each of these classes. It is an abstract class. It stores the command name, the command argument types and description and mainly defines two methods which are the execute and is_allowed methods. The execute method should be implemented in each sub-class. A default is_allowed method exists for command always allowed. A command also stores a parameter which is the command display type. It is also used to select if the command must be displayed according to the application mode (every day operation or expert mode).

2 Description of the template model


Using this method, it is not necessary to create a separate class for each device command. In this method, each command is represented by an instance of one of the template command classes. They are four template command classes. All these classes inherits from the Command class. These four classes are :
  1. The TemplCommand class. One object of this class must be created for each command without input nor output parameters
  2. The TemplCommandIn class. One object of this class must be created for each command without output parameter but with input parameter
  3. The TemplCommandOut class. One object of this class must be created for each command without input parameter but with output parameter
  4. The TemplCommandInOut class. One object of this class must be created for each command with input and output parameters
These four classes redefine the execute and is_allowed method of the Command class. These classes provides constructors which allow the user to : The method specification is done via pointer to method with C++ and simply with method name for java.
Remember that it is possible to mix command implementation method within the same device pattern.

3 Contents


The content of this class can be summarizes as :


4 The DeviceClass class


1 Description


This class implements all what is specific for a controlled object class. For instance, every device of the same class supports the same list of commands and therefore, this list of available commands is stored in this DeviceClass. The structure returned by the info operation contains a documentation URL[*]. This documentation URL is the same for every device of the same class. Therefore, the documentation URL is a data member of this class. There should have only one instance of this class per device pattern implementation. The device list is also stored in this class. It is an abstract class because the two methods device_factory() and command_factory() are declared as pure virtual. The rule of the device_factory() method is to create all the devices belonging to the device class. The rule of the command_factory() method is to create one instance of all the classes needed to support device commands. This class also stored the attribute_factory method. The rule of this method is to store in a vector of strings, the name of all the device attributes. This method has a default implementation which is an empty body for device without attribute.

2 Contents


The contents of this class can be summarize as :


5 The DbClass class


Each DeviceClass instance is an aggregate with one instance of the DbClass class. This DbClass class can be used to query or modify class properties. It provides an easy to use interface for device objects in the database. The description of this class can be found in the Tango java or C++ API documentation.


6 The MultiAttribute class


1 Description


This class is a container for all the TANGO attributes defined for the device. There is one instance of this class for each device. This class is mainly an aggregate of Attribute object(s). It has been developed to ease TANGO attribute management.

2 Contents


The class contents could be summarizes as :


7 The Attribute class


1 Description


There is one object of this class for each device attribute. This class is used to store all the attribute properties, the attribute value and all the alarm related data. Like commands, this class also stores th attribute display type. It is foreseen to be used by future Tango graphical application toolkit to select if the attribute must be displayed according to the application mode (every day operation or expert mode).

2 Contents



8 The WAttribute class


1 Description


This class inherits from the Attribute class. There is one instance of this class for each writable device attribute. On top of all the data already managed by the Attribute class, this class stores the attribute set value.

2 Contents


Within this class, you will mainly find methods related to attribute set value storage and some data members.

9 The Attr class


Within the TDSOM, each attribute supported by a device is implemented by a separate class. The Attr class is the root class for each of these classes. It is used in conjonction with the Attribute and Wattribute classes to implement Tango attribute behaviour. It defines three methods which are the is_allowed, read and write methods. A default is_allowed method exists for attribute always allowed. Default read and write empty methods are defined. For readable attribute, it is necessary to overwrite the read method. For writable attribute, it is necessary to overwrite the write method and for read and write attribute, both methods must be overwritten.


10 The SpectrumAttr class


This class inherits from the Attr class. It is the base class for user spectrum attribute. It is used in conjonction with the Attribute and WAttribute class to implement Tango spectrum attribute behaviour. From the Attr class, it inherits the Attr is_allowed, read and write methods.


11 The ImageAttr class


This class inherits from the SpectrumAttr class. It is the base class for user image attribute. It is used in conjonction with the Attribute and WAttribute class to implement Tango image attribute behaviour. From the Attr class, it inherits the Attr is_allowed, read and write methods.

12 The StepperMotor class


1 Description


This class inherits from the DeviceImpl class and is the class implementing the controlled object behavior. Each command will trigger a method in this class written by the device server programmer and specific to the object to be controlled. This class also stores all the device specific data.

2 Definition



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 class StepperMotor: public Tango::DeviceImpl
2 {
3 public :
4    StepperMotor(Tango::DeviceClass *,string &);
5    StepperMotor(Tango::DeviceClass *,const char *);
6    StepperMotor(Tango::DeviceClass *,const char *,const char *);
7    ~StepperMotor() {};

9    DevLong dev_read_position(DevLong);
10   DevLong dev_read_direction(DevLong);
11   bool direct_cmd_allowed(const CORBA::Any &);
12 
13   virtual Tango::DevState dev_state();
14   virtual Tango::ConstDevString dev_status();
15 
16   virtual void always_executed_hook();
17 
18   virtual void read_attr_hardware(vector<long> &attr_list);
19 
20   void read_position(Tango::Attribute &);
21   bool is_Position_allowed(Tango::AttReqType req);
22   void write_SetPosition(Tango::WAttribute &);
23   void read_Direction(Tango::Attribute &);
24 
25   virtual void init_device();
26   virtual void delete_device();
27 
28   void get_device_properties();
29 
30 protected : 
31   long axis[AGSM_MAX_MOTORS];
32   DevLong position[AGSM_MAX_MOTORS];
33   DevLong direction[AGSM_MAX_MOTORS];
34   long state[AGSM_MAX_MOTORS];
35 
36   Tango::DevLong *attr_Position_read;
37   Tango::DevLong *attr_Direction_read;
38   Tango::DevLong attr_SetPosition_write;
39 
40   Tango::DevLong min;
41   Tango::DevLong max;
42 
43   Tango::DevLong *ptr;
44 };
45 
46 } /* End of StepperMotor namespace */

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : The StepperMotor class inherits from the DeviceImpl class
Line 4-7 : Class constructors and destructor
Line 9 : Method triggered by the DevReadPosition command
Line 10-11 : Methods triggered by the DevReadDirection command
Line 13 : Redefinition of the dev_state method of the DeviceImpl class. This method will be triggered by the State command
Line 14 : Redefinition of the dev_status method of the DeviceImpl class. This method will be triggered by the Status command
Line 16 : Redefinition of the always_executed_hook method.
Line 25 : Definition of the init_device method (declared as pure virtual by the DeviceImpl class)
Line 26 : Definition of the delete_device method
Line 30-44 : Device data

13 The StepperMotorClass class


1 Description


This class inherits from the DeviceClass class. Like the DeviceClass class, there should be only one instance of the StepperMotorClass. This is ensured because this class is written following the Singleton pattern as defined in [10]. All controlled object class data which should be defined only once per class must be stored in this object.

2 Definition



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 class StepperMotorClass : public DeviceClass
2 {
3 public:
4 static StepperMotorClass *init(const char *);
5 static StepperMotorClass *instance();
6 ~StepperMotorClass() {_instance = NULL;}
7
8 protected:
9 StepperMotorClass(string &);
10 static StepperMotorClass *_instance;
11 void command_factory();
12
13 private:
14 void device_factory(Tango_DevVarStringArray *);
15 };

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : This class is a sub-class of the DeviceClass class
Line 4-5 and 9-10: Methods and data member necessary for the Singleton pattern
Line 6 : Class destructor
Line 11 : Definition of the command_factory method declared as pure virtual in the DeviceClass call
Line 13-14 : Definition of the device_factory method declared as pure virtual in the DeviceClass class

14 The DevReadPosition class


1 Description


This is the class for the DevReadPosition command. This class implements the execute and is_allowed methods defined by the Command class. This class is necessary because this command is implemented using the inheritance model.

2 Definition



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1  class DevReadPositionCmd : public Command
2  {
3  public:
4      DevReadPositionCmd(const char *,Tango_CmdArgType, Tango_CmdArgType, const char *, const char*);
5      ~DevReadPositionCmd() {};
6          
7      virtual bool is_allowed (DeviceImpl *, const CORBA::Any &);
8      virtual CORBA::Any *execute (DeviceImpl *, const CORBA::Any &);
9  };

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : The class is a sub class of the Command class
Line 4-5 : Class constructor and destructor
Line 7-8 : Definition of the is_allowed and execute method declared as pure virtual in the Command class.

15 The PositionAttr class


1 Description


This is the class for the Position attribute. This attribute is a scalar attribute and therefore inherits from the Attr base class. This class implements the read and is_allowed methods defined by the Attr class.

2 Definition



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 class PositionAttr: public Tango::Attr
2 {
3 public:
4 PositionAttr():Attr(Position,Tango::DEV_LONG,Tango::READ);
5 ~PositionAttr() {};
6
7 virtual void read(Tango::DeviceImpl *dev,Tango::Attribute &att)
8 {(static_cast<StepperMotor *>(dev))->read_Position(att);}
9 virtual bool is_allowed(Tango::DeviceImpl *dev,Tango::AttReqType ty)
10 {return (static_cast<StepperMotor *>(dev))->is_Position_allowed(ty);}
11 };

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : The class is a sub class of the Attr class
Line 4-5 : Class constructor and destructor
Line 7 : Re-definition of the read method defined in the Attr class. This is simply a forward to the read_Position method of the StepperMotor class
Line 9 : Re-definition of the is_allowed method defined in the Attr class. This is also a forward to the is_Position_allowed method of the StepperMotor class


3 Startup of a device pattern


To start the device pattern implementation for stepper motor device, four methods of the StepperMotorClass class must be executed. These methods are :
  1. The creation of the StepperMethodClass singleton via its init() method
  2. The command_factory() method of the StepperMotorClass class
  3. The attribute_factory() method of the StepperMotorClass class. This method has a default empty body for device class without attributes.
  4. The device_factory() method of the StepperMotorClass class
This startup procedure is described in figure [*]
Figure: Device pattern startup sequence
Image startup


. The creation of the StepperMotorClass will automatically create an instance of the DeviceClass class. The constructor of the DeviceClass class will create the Status, State and Init command objects and store them in its command list.
The command_factory() method will simply create all the user defined commands and add them in the command list.
The attribute_factory() method will simply build a list of device attribute names.
The device_factory() method will create each StepperMotor object and store them in the StepperMotorClass instance device list. The list of devices to be created and their names is passed to the device_factory method in its input argument. StepperMotor is a sub-class of DeviceImpl class. Therefore, when a StepperMotor object is created, a DeviceImpl object is also created. The DeviceImpl constructor builds all the device attribute object(s) from the attribute list built by the attribute_factory() method.

4 Command execution sequence


The figure [*]
Figure: Command execution timing
Image command


described how the method implementing a command is executed when a command_inout CORBA operation is requested by a client. The command_inout method of the StepperMotor object (inherited from the DeviceImpl class) is triggered by an instance of a class generated by the CORBA IDL compiler. This method calls the command_handler() method of the StepperMotorClass object (inherited from the DeviceClass class). The command_handler method searches in its command list for the wanted command (using its name). If the command is found, the always_executed_hook method of the StepperMotor object is called. Then, the is_allowed method of the wanted command is executed. If the is_allowed method returns correctly, the execute method is executed. The execute method extracts the incoming data from the CORBA object use to transmit data over the network and calls the user written method which implements the command.


5 The automatically added commands


In order to increase the common behavior of every kind of devices in a TANGO control system, three commands are automatically added to each class of devices. These commands are : The default behavior of the method called by the State command depends on the device state. If the device state is ON or ALARM, the method will : For all the other device state, the method simply returns the device state stored in the DeviceImpl class. Nevertheless, the method used to return this state (called dev_state) is defined as virtual and can be redefined in DeviceImpl sub-class. The difference between the default State command and the state CORBA attribute is the ability of the State command to signal an error to the caller by throwing an exception.
The default behavior of the method called by the Status command depends on the device state. If the device state is ON or ALARM, the method returns the device status stored in the DeviceImpl class plus additional message(s) for all the attributes which are in alarm condition. For all the other device state, the method simply returns the device status as it is stored in the DeviceImpl class. Nevertheless, the method used to return this status (called dev_status) is defined as virtual and can be redefined in DeviceImpl sub-class. The difference between the default Status command and the status CORBA attribute is the ability of the Status command to signal an error to the caller by throwing an exception.
The Init command is used to re-initialize a device without changing its network connection. This command calls the device delete_device method and the device init_device method. The rule of the delete_device method is to free memory allocated in the init_device method in order to avoid memory leak.

6 Reading/Writing attributes


1 Reading attributes


A Tango client is able to read Tango attribute(s) with the CORBA read_attributes call. Inside the device server, this call will trigger several methods of the device class (StepperMotor in our example) :
  1. The always_executed_hook() method.
  2. A method call read_attr_hardware(). This method is called one time per read_attributes CORBA call. The aim of this method is to read the device hardware and to store the result in a device class data member.
  3. For each attribute to be read
    1. A method called is_<att name>_allowed(). The rule of this method is to allow (or disallow) the next method to be executed. It is usefull for device with some attributes which can be read only in some precise conditions. It has one parameter which is the request type (read or write)
    2. A method called read_<att name>(). The aim of this method is to extract the real attribute value from the hardware read-out and to store the attribute value into the attribute object. It has one parameter which is a reference to the Attribute object to be read.
The figure [*] is a drawing of these method calls sequencing. For attribute always readable, a default is_allowed method is provided. This method always returns true.
Figure: Read attribute sequencing
\includegraphics[scale=0.7]{ds_writing/r_attribute}



2 Writing attributes


A Tango client is able to write Tango attribute(s) with the CORBA write_attributes call. Inside a device server, this call will trigger several methods of the device class (StepperMotor in our example)
  1. The always_executed_hook() method.
  2. For each attribute to be written
    1. A method called is_<att name>_allowed(). The rule of this method is to allow (or disallow) the next method to be executed. It is usefull for device with some attributes which can be written only in some precise conditions. It has one parameter which is the request type (read or write)
    2. A method called write_<att name>(). It has one parameter which is a reference to the WAttribute object to be written. The aim of this method is to get the data to be written from the WAttribute object and to write this value into the corresponding hardware.
The figure [*] is a drawing of these method calls sequencing. For attribute always writeable, a default is_allowed method is provided. This method always allways returns true.
Figure: Write attribute sequencing
\includegraphics[scale=0.7]{ds_writing/w_attribute}



7 The device server framework



1 Vocabulary


A device server pattern implementation is embedded in a process called a device server. Several instances of the same device server process can be used in a TANGO control system. To identify instances, a device server process is started with an instance name which is different for each instance. The device server name is the couple device server executable name/device server instance name. For instance, a device server started with the following command
Perkin id11
starts a device server process with an instance name id11, an executable name Perkin and a device server name Perkin/id11.


2 The DServer class


In order to simplify device server process administration, a device of the DServer class is automatically added to each device server process. Thus, every device server process supports the same set of administration commands. The implementation of this DServer class follows the device pattern and therefore, its device behaves like any other devices. The device name is
dserver/device server executable name/device server instance name
For instance, for the device server process described in chapter [*], the dserver device name is dserver/perkin/id11. This name is returned by the adm_name CORBA attribute available for every device. On top of the three automatically added commands, this device supports the following commands : These commands will be fully described later in this document.
Several controlled object classes can be embedded within the same device server process and it is the rule of this device to create all these device server patterns and to call their command and device factories as described in [*]. The name and number of all the classes to be created is known to this device after the execution of a method called class_factory. With C++, it is the user responsibility to write this method. Using Java, this method is already written and automatically retrieves which classes must be created and creates them.


3 The Tango::Util class


1 Description


This class merges a complete set of utilities in the same class. It is implemented as a singleton and there is only one instance of this class per device server process. It is mandatory to create this instance in order to run a device server. The description of all the methods implemented in this class can be found in [8].

2 Contents


Within this class, you can find :

4 A complete device server


Within a complete device server, at least two implementations of the device server pattern are created (one for the dserver object and the other for the class of devices to control). On top of that, one instance of the Tango::Util class must also be created.
Figure: A complete device server
Image complete_server


A drawing of a complete device server is in figure [*]


5 Device server startup sequence


The device server startup sequence is the following :
  1. Create an instance of the Tango::Util class. This will initialize the CORBA Object Request Broker
  2. Called the server_init method of the Tango::Util instance The call to this method will :
    1. Create the DServerClass object of the device pattern implementing the DServer class. This will create the dserver object which during its construction will :
      1. Called the class_factory method of the DServer object. This method must create all the xxxClass instance for all the device pattern implementation embedded in the device server process.
      2. Call the command_factory and device_factory of all the classes previously created. The list of devices passed to each call to the device_factory method is retrieved from the TANGO database.
  3. Wait for incoming request with the server_run() method of the Tango::Util class.


2 Exchanging data between client and server


Exchanging data between clients and server means most of the time passing data between processes running on different computer using the network. Tango limits the type of data exchanged between client and server and defines a way to exchange these data. This chapter details these features. Memory allocation and error reporting are also discussed.
All the rules described in this chapter are valid only for data exchanged between client and server. For device server internal data, classical C++ or Java types can be use.

1 Command / Attribute data types


Commands have a fixed calling syntax - consisting of one input argument and one output argument. Arguments type must be chosen out of a fixed set of 24 data types. Attributes support a sub-set of these data types. These are the data type with the (1) note. The following table details type name, code and the corresponding CORBA IDL types.
The type name used in the type name column of this table is the C++ name. In the IDL file, all the Tango definition are grouped in a IDL module named Tango. The IDL module maps to C++ namespace. Therefore, all the data type are parts of a namespace called Tango. For Java, the IDL module maps to Java package and name are not changed related to the IDL file.



Type name IDL type
Tango::DevBoolean (1) boolean
Tango::DevShort (1) short
Tango::DevLong (1) long
Tango::DevLong64 (1) long long
Tango::DevFloat (1) float
Tango::DevDouble (1) double
Tango::DevUShort (1) unsigned short
Tango::DevULong (1) unsigned long
Tango::DevULong64 (1) unsigned long long
Tango::DevString (1) string
Tango::DevVarCharArray sequence of unsigned char
Tango::DevVarShortArray sequence of short
Tango::DevVarLongArray sequence of long
Tango::DevVarLong64Array sequence of long long
Tango::DevVarFloatArray sequence of float
Tango::DevVarDoubleArray sequence of double
Tango::DevVarUShortArray sequence of unsigned short
Tango::DevVarULongArray sequence of unsigned long
Tango::DevVarULong64Array sequence of unsigned long long
Tango::DevVarStringArray sequence of string
Tango::DevVarLongStringArray structure with a sequence of long and a sequence of string
Tango::DevVarDoubleStringArray structure with a sequence of double and a sequence of string
Tango::DevState (1) enumeration
Tango::DevEncoded (1) structure with a string and a sequence of char





The CORBA Interface Definition Language uses a type called sequence for variable length array. This sequence type is mapped differently according to the language used (C++ or Java). The Tango::DevUxxx types are used for unsigned types. The Tango::DevVarxxxxArray must be used when the data to be transferred are variable length array. The Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray are structures with two fields which are variable length array of Tango long (32 bits) and variable length array of strings for the Tango::DevVarLongStringArray and variable length array of double and variable length array of string for the Tango::DevVarDoubleStringArray. The Tango::State type is used by the State command to return the device state.

1 Using command data types with C++


Unfortunately, the mapping between IDL and C++ was defined before the C++ class library had been standardized. This explains why the standard C++ string class or vector classes are not used in the IDL to C++ mapping.
TANGO commands argument types can be grouped on five groups depending on the IDL data type used. These groups are :
  1. Data type using basic types (Tango::DevBoolean, Tango::DevShort, Tango::DevLong, Tango::DevFloat, Tango::DevDouble, Tango::DevUshort and Tango::DevULong)
  2. Data type using strings (Tango::DevString type)
  3. Data types using sequences (Tango::DevVarxxxArray types except Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray)
  4. Data types using structures (Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray types)
  5. Data type using enumeration (Tango::DevState type)
In the following sub chapters, only summaries of the IDL to C++ mapping are given. For a full description of the C++ mapping, please refer to [2]

1 Basic types


For these types, the mapping between IDL and C++ is obvious and defined in the following table.



Tango type name IDL type C++ typedef
Tango::DevBoolean boolean CORBA::Boolean unsigned char
Tango::DevShort short CORBA::Short short
Tango::DevLong long CORBA::Long int
Tango::DevLong64 long long CORBA::LongLong long long or long (64 bits chip)
Tango::DevFloat float CORBA::Float float
Tango::DevDouble double CORBA::Double double
Tango::DevUShort unsigned short CORBA::UShort unsigned short
Tango::DevULong unsigned long CORBA::ULong unsigned long
Tango::DevULong64 unsigned long long CORBA:ULongLong unsigned long long or unsigned long (64 bits chip)





The types defined in the column named C++ should be used for a better portability. All these types are defined in the CORBA namespace and therefore their qualified names is CORBA::xxx.

2 Strings


Strings are mapped to char *. The use of new and delete for dynamic allocation of strings is not portable. Instead, you must use helper functions defined by CORBA (in the CORBA namespace). These functions are :

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

char *CORBA::string_alloc(unsigned long len);
char *CORBA::string_dup(const char *);
void CORBA::string_free(char *);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

These functions handle dynamic memory for strings. The string_alloc function allocates one more byte than requested by the len parameter (for the trailing 0). The function string_dup combines the allocation and copy. Both string_alloc and string_dup return a null pointer if allocation fails. The string_free function must be used to free memory allocated with string_alloc and string_dup. Calling string_free for a null pointer is safe and does nothing. The following code fragment is an example of the Tango::DevString type usage

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevString str = CORBA::string_alloc(5);
2 strcpy(str,TANGO);
3
4 Tango::DevString str1 = CORBA::string_dup(Do you want to danse TANGO?);
5
6 CORBA::string_free(str);
7 CORBA::string_free(str1);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-2 : TANGO is a five letters string. The CORBA::string_alloc function parameter is 5 but the function allocates 6 bytes
Line 4 : Example of the CORBA::string_dup function
Line 6-7 : Memory deallocation

3 Sequences


IDL sequences are mapped to C++ classes that behave like vectors with a variable number of elements. Each IDL sequence type results in a separate C++ class. Within each class representing a IDL sequence types, you find the following method (only the main methods are related here) :
  1. Four constructors.
    1. A default constructor which creates an empty sequence.
    2. The maximum constructor which creates a sequence with memory allocated for at least the number of elements passed as argument. This does not limit the number of element in the sequence but only the way how memory is allocated to store element
    3. A sophisticated constructor where it is possible to assign the memory used by the sequence with a preallocated buffer.
    4. A copy constructor which does a deep copy
  2. An assignment operator which does a deep copy
  3. A length accessor which simply returns the current number of elements in the sequence
  4. A length modifier which changes the length of the sequence (which is different than the number of elements in the sequence)
  5. Overloading of the [] operator. The subscript operator [] provides access to the sequence element. For a sequence containing elements of type T, the [] operator is overloaded twice to return value of type T & and const T &. Insertion into a sequence using the [] operator for the const T & make a deep copy. Sequence are numbered between 0 and length() -1.
Note that using the maximum constructor will not prevent you from setting the length of the sequence with a call to the length modifier. The following code fragment is an example of how to use a Tango::DevVarLongArray type

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevVarLongArray *mylongseq_ptr;
2 mylongseq_ptr = new Tango::DevVarLongArray();
3 mylongseq_ptr->length(4);
4
5 (*mylongseq_ptr)[0] = 1;
6 (*mylongseq_ptr)[1] = 2;
7 (*mylongseq_ptr)[2] = 3;
8 (*mylongseq_ptr)[3] = 4;
9
10 // (*mylongseq_ptr)[4] = 5;
11
12 CORBA::Long nb_elt = mylongseq_ptr->length();
13
14 mylongseq_ptr->length(5);
15 (*mylongseq_ptr)[4] = 5;
16
17 for (int i = 0;i < mylongseq_ptr->length();i++)
18 cout << Sequence elt << i + 1 << = << (*mylongseq_ptr)[i] << endl;

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Declare a pointer to Tango::DevVarLongArray type which is a sequence of long
Line 2 : Create an empty sequence
Line 3 : Change the length of the sequence to 4
Line 5 - 8 : Initialize sequence elements
Line 10 ; Oups !!! The length of the sequence is 4. The behavior of this line is undefined and may be a core can be dumped at run time
Line 12 : Get the number of element actually stored in the sequence
Line 14-15 : Grow the sequence to five elements and initialize element number 5
Line 17-18 : Print sequence element
Another example for the Tango::DevVarStringArray type is given

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevVarStringArray mystrseq(4);
2 mystrseq.length(4);
3
4 mystrseq[0] = CORBA::string_dup(Rock and Roll);
5 mystrseq[1] = CORBA::string_dup(Bossa Nova);
6 mystrseq[2] = CORBA::string_dup(``Waltz'');
7 mystrseq[3] = CORBA::string_dup(Tango);
8
9 CORBA::Long nb_elt = mystrseq.length();
10
11 for (int i = 0;i < mystrseq.length();i++)
12 cout << Sequence elt << i + 1 << = << mystrseq[i] << endl;

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Create a sequence using the maximum constructor
Line 2 : Set the sequence length to 4. This is mandatory even if you used the maximum constructor.
Line 4-7 : Populate the sequence
Line 9 : Get how many strings are stored into the sequence
Line 11-12 : Print sequence elements.

4 Structures


Only three TANGO types are defined as structures. These types are the Tango::DevVarLongStringArray, the Tango::DevVarDoubleStringArray and the Tango::DevEncoded data type. IDL structures map to C++ structures with corresponding members. For the Tango::DevVarLongStringArray, the two members are named svalue for the sequence of strings and lvalue for the sequence of longs. For the Tango::DevVarDoubleStringArray, the two structure members are called svalue for the sequence of strings and dvalue for the sequence of double. For the Tango::DevEncoded, the two structure members are called encoded_format for a string describing the data coding and encoded_data for the data themselves. The encoded_data field type is a Tango::DevVarCharArray. An example of the usage of the Tango::DevVarLongStringArray type is detailed below.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevVarLongStringArray my_vl;
2
3 myvl.svalue.length(2);
4 myvl.svalue[0] = CORBA_string_dup(Samba);
5 myvl.svalue[1] = CORBA_string_dup(Rumba);
6
7 myvl.lvalue.length(1);
8 myvl.lvalue[0] = 10;

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Declaration of the structure
Line 3-5 : Initialization of two strings in the sequence of string member
Line 7-8 : Initialization of one long in the sequence of long member

5 Enumeration


Only one TANGO type is an enumeration. This is the Tango::DevState type used to transfer device state between client and server. IDL enumerated types map to C++ enumerations (amazing no!) with a trailing dummy enumerator to force enumeration to be a 32 bit type. The first enumerator will have the value 0, the next one will have the value 1 and so on.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevState state;
2
3 state = Tango::ON;
4 state = Tango::FAULT;

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

2 Using command data types with Java


All the rules described in this chapter are valid only for data exchanged between client and server. For device server internal data, classical Java types can be use.
TANGO commands argument types can be grouped on four groups depending on the IDL data type used. These groups are :
  1. Data type using basic types (DevBoolean, DevShort, DevLong, DevFloat, DevDouble, DevUShort, DevULong and DevString)
  2. Data types using sequences (DevVarxxxArray types except DevVarLongStringArray and DevVarDoubleStringArray)
  3. Data types using structures (DevVarLongStringArray and DevVarDoubleStringArray types)
  4. Data type using enumeration (DevState type)
In the following sub chapters, only summaries of the IDL to Java mapping are given. For a full description of the Java mapping, please refer to [12].

1 Basic types


For these types, the mapping between IDL and Java is obvious and defined in the following table.



Tango type name IDL type Java type
DevBoolean boolean boolean
DevShort short short
DevLong long int
DevLong64 long long long
DevFloat float float
DevDouble double double
DevString string String
DevUShort unsigned short short
DevULong unsigned long int
DevULong64 unsigned long long long





The Java int is a 32 bits type[*] and therefore, the DevLong type maps to Java int. Java does not support unsigned types, this is why the DevUShort type maps to short and the DevULong type maps to int. In the contrary of C++, Java does not support a preprocessor and therefore, declaring a data from the DevLong type (or any other type in the previous table) will result in compiler errors. Instead, the Java types must be used.
IDL string maps directly to java.lang.String class.

2 Sequences


IDL sequences map to Java array. The following tables details the mapping used for Tango sequence types.



Tango type name IDL type Java type
DevVarCharArray sequence of byte byte[]
DevVarShortArray sequence of short short[]
DevVarLongArray sequence of long int[]
DevVarLong64Array sequence of long long long[]
DevVarFloatArray sequence of float float[]
DevVarDoubleArray sequence of double double[]
DevVarUShortArray sequence of unsigned short short[]
DevVarULongArray sequence of unsigned long int[]
DevVarULong64Array sequence of unsigned long long long[]
DevVarStringArray sequence of string String[]





3 Structures


IDL structures map to a final Java class with the same name. This class provides instance variables for all IDL structure fields. It also provides a default constructor and a constructor from all structures fields values. The class name, the field name and types are summaries in the following table



Tango type name Java class name field name field Java type
DevVarLongStringArray DevVarLongStringArray lvalue int[]
    svalue String[]
DevVarDoubleStringArray DevVarDoubleStringArray dvalue double[]
    svalue String[]
DevEncoded DevEncoded encoded_format String
    encoded_data char[]





4 Enumeration


Enumeration does not exist in Java. An IDL enumeration is mapped to a final class with the same name as the enum type. This class has the following members :
  1. A value method which returns the value as an integer.
  2. A pair of static data members per label.
    1. The first one is an integer with a name equals to the label name prepended with an underscore (``_'') like _ON for instance.
    2. The second one is a reference to an object of the class representing the enumeration with its value set to the label value.
  3. An integer conversion method called from_int which returns a reference to an object of the class representing the enumeration
  4. A private constructor
The following code fragment is an example of Tango command data types usage

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 short l = 2;
2
3 String[] str_array = new String[2];
4 str_array[0] = new String(Be Bop);
5 str_array[1] = new String(Break dance);
6
7 System.out.println(Elt nb in DevVarStringArray data + str_array.length);
8 for (int i = 0;i < str_array.length;i++)
9 System.out.println(Element value = + str_array[i]);
10
11 DevVarLongStringArray ls = new DevVarLongStringArray();
12 ls.lvalue = new int[1];
13 ls.lvalue[0] = 1;
14 ls.svalue = new String[2];
15 ls.svalue[0] = new String(Smurf);
16 ls.svalue[1] = new String(Pogo);
17
18 DevState st = DevState.FAULT;
19 switch (st.value())
20 {
21 case DevState._ON :
22 System.out.println(The state is ON);
23 st = DevState.FAULT;
24 break;
25
26 case DevState._FAULT :
27 System.out.println(The state is FAULT);
28 st = DevState.ON;
29 break;
30 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Use of a DevShort type (pretty simple no)
Line 3-5 : Use of a DevVarStringArray data type with 2 elements
Line 7-9 : Print DevVarStringArray data element number and value
Line 11-16 : Use of a DevVarLongStringArray data type
Line 18 : Initialization of a DevState data with the FAULT state
Line 19 : Test on the DevState data value
Line 21 : Use the integer value associated to each enumeration label to test DevState data
Line 23 : Update DevState data value

2 Passing data between client and server


In order to have one definition of the CORBA operation used to send a command to a device whatever the command data type is, TANGO uses CORBA IDL any object. The IDL type any provides a universal type that can hold a value of arbitrary IDL types. Type any therefore allows you to send and receive values whose types are not fixed at compile time.
Type any is often compared to a void * in C. Like a pointer to void, an any value can denote a datum of any type. However, there is an important difference; whereas a void * denotes a completely untyped value that can be interpreted only with advance knowledge of its type, values of type any maintain type safety. For example, if a sender places a string value into an any, the receiver cannot extract the string as a value of the wrong type. Attempt to read the contents of an any as the wrong type cause a run-time error.
Internally, a value of type any consists of a pair of values. One member of the pair is the actual value contained inside the any and the other member of the pair is the type code. The type code is a description of the value's type. The type description is used to enforce type safety when the receiver extracts the value. Extraction of the value succeeds only if the receiver extracts the value as a type that matches the information in the type code.
Within TANGO, the command input and output parameters are objects of the IDL any type. Only insertion/extraction of all types defined as command data types is possible into/from these any objects.

1 C++ mapping for IDL any type


The IDL any maps to the C++ class CORBA::Any. This class contains a large number of methods with mainly methods to insert/extract data into/from the any. It provides a default constructor which builds an any which contains no value and a type code that indicates ``no value''. Such an any must be used for command which does not need input or output parameter. The operator <<= is overloaded many times to insert data into an any object. The operator >>= is overloaded many times to extract data from an any object.

1 Inserting/Extracting TANGO basic types


The insertion or extraction of TANGO basic types is straight forward using the <<= or >>= operators. Nevertheless, the Tango::DevBoolean type is mapped to a unsigned char and other IDL types are also mapped to char C++ type (The unsigned is not taken into account in the C++ overloading algorithm). Therefore, it is not possible to use operator overloading for these IDL types which map to C++ char. For the Tango::DevBoolean type, you must use the CORBA::Any::from_boolean or CORBA::Any::to_boolean intermediate objects defined in the CORBA::Any class.

2 Inserting/Extracting TANGO strings


The <<= operator is overloaded for const char * and always makes a deep copy. This deep copy is done using the CORBA::string_dup function. The extraction of strings uses the >>= overloaded operator. The main point is that the Any object retains ownership of the string, so the returned pointer points at memory inside the Any. This means that you must not deallocate the extracted string and you must treat the extracted string as read-only.

3 Inserting/Extracting TANGO sequences


Insertion and extraction of sequences also uses the overloaded <<= and >>= operators. The insertion operator is overloaded twice: once for insertion by reference and once for insertion by pointer. If you insert a value by reference, the insertion makes a deep copy. If you insert a value by pointer, the Any assumes the ownership of the pointed-to memory.
Extraction is always by pointer. As with strings, you must treat the extracted pointer as read-only and must not deallocate it because the pointer points at memory internal to the Any.

4 Inserting/Extracting TANGO structures


This is identical to inserting/extracting sequences.

5 Inserting/Extracting TANGO enumeration


This is identical to inserting/extracting basic types

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 CORBA::Any a;
2 Tango::DevLong l1,l2;
3 l1 = 2;
4 a <<= l1;
5 a >>= l2;
6
7 CORBA::Any b;
8 Tango::DevBoolean b1,b2;
9 b1 = true;
10 b <<= CORBA::Any::from_boolean(b1);
11 b >>= CORBA::Any::to_boolean(b2);
12
13 CORBA::Any s;
14 Tango::DevString str1,str2;
15 str1 = I like dancing TANGO;
16 s <<= str1;
17 s >>= str2;
18
19 // CORBA::string_free(str2);
20 // a <<= CORBA::string_dup(Oups);
21
22 CORBA::Any seq;
23 Tango::DevVarFloatArray fl_arr1;
24 fl_arr1.length(2);
25 fl_arr1[0] = 1.0;
26 fl_arr1[1] = 2.0;
27 seq <<= fl_arr1;
28 const Tango::DevVarFloatArray *fl_arr_ptr;
29 seq >>= fl_arr_ptr;
30
31 // delete fl_arr_ptr;

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-5 : Insertion and extraction of Tango::DevLong type
Line 7-11 Insertion and extraction of Tango::DevBoolean type using the CORBA::Any::from_boolean and CORBA::Any::to_boolean intermediate structure
Line 13-17 : Insertion and extraction of Tango::DevString type
Line 19 : Wrong ! You should not deallocate a string extracted from an any
Line 20 : Wrong ! Memory leak because the <<= operator will do the copy.
Line 22-29 : Insertion and extraction of Tango::DevVarxxxArray types. This is an insertion by reference and the use of the <<= operator makes a deep copy of the sequence. Therefore, after line 27, it is possible to deallocate the sequence
Line 31: Wrong.! You should not deallocate a sequence extracted from an any

2 The insert and extract methods of the Command class


In order to simplify the insertion/extraction into/from Any objects, small helper methods have been written in the Command class. The signatures of these methods are :

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 void extract(const CORBA::Any &,<Tango type> &);
2 CORBA::Any *insert(<Tango type>);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

An extract method has been written for all Tango types. These method extract the data from the Any object passed as parameter and throw an exception if the Any data type is incompatible with the awaiting type. An insert method have been written for all Tango types. These method create an Any object, insert the data into the Any and return a pointer to the created Any. For Tango types mapped to sequences or structures, two insert methods have been written: one for the insertion from pointer and the other for the insertion from reference. For Tango strings, two insert methods have been written: one for insertion from a classical Tango::DevString type and the other from a const Tango::DevString type. The first one deallocate the memory after the insert into the Any object. The second one only inserts the string into the Any object.
The previous example can be rewritten using the insert/extract helper methods (We suppose that we can use the Command class insert/extract methods)

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevLong l1,l2;
2 l1 = 2;
3 CORBA::Any *a_ptr = insert(l1);
4 extract(*a_ptr,l2);
5
6 Tango::DevBoolean b1,b2;
7 b1 = true;
8 CORBA::Any *b_ptr = insert(b1);
9 extract(*b_ptr,b2);
10
11 Tango::DevString str1,str2;
12 str1 = I like dancing TANGO;
13 CORBA::Any *s_ptr = insert(str1);
14 extract(*s_ptr,str2);
15
16 Tango::DevVarFloatArray fl_arr1;
17 fl_arr1.length(2);
18 fl_arr1[0] = 1.0;
19 fl_arr1[1] = 2.0;
20 insert(fl_arr1);
21 CORBA::Any *seq_ptr = insert(fl_arr1);
22 Tango::DevVarFloatArray *fl_arr_ptr;
23 extract(*seq_ptr,fl_arr_ptr);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-4 : Insertion and extraction of Tango::DevLong type
Line 6-9 : Insertion and extraction of Tango::DevBoolean type
Line 11-14 : Insertion and extraction of Tango::DevString type
Line 16-23 : Insertion and extraction of Tango::DevVarxxxArray types. This is an insertion by reference which makes a deep copy of the sequence. Therefore, after line 20, it is possible to deallocate the sequence

3 Java mapping for IDL any type


The IDL any maps to the Java class org.omg.CORBA.Any . This class has all the necessary methods to insert and extract instances of IDL native types (short, int, float, string..). The method name to insert native IDL types is insert_<type name> (insert_short(), insert_float(), insert_string()). They all take a reference to the element to be inserted as argument. The method name to extract basic types is extract_<type name> (extract_short(), extract_float() or extract_string()). These extract methods do not need argument and return a reference to the extracted data. If the extraction operations have a mismatched type, the CORBA BAD_OPERATION exception is raised. An ``any'' object is constructed with the create_any() method of the CORBA ``orb'' object. This orb object represents the Object Request Broker. Within a Tango device server, you can retrieve it with a method of the TangoUtil class described in [8].

1 Inserting/Extracting TANGO basic types and strings


The insertion or extraction of TANGO basic types and strings is straight forward using the insert or extract methods provided by the org.omg.CORBA.Any class.

2 Inserting/Extracting TANGO sequences, structures or enumeration


The IDL to Java compiler generates Helper classes for all types defined in the IDL file. The generated classes name is the name of the type followed by the suffix Helper (DevVarCharArrayHelper, DevLongHelper). Classes are generated even for types which directly map to native Java types. Several static methods needed to manipulate the type are supplied in these classes. These include ``Any'' insert and extract operations for the type. For a data type <typename>, the insert and extract method are : Such classes exists for all the TANGO data types. The following code fragment is an example of the insertion/extraction in/from Any object with Java

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Any a = TangoUtil.instance().get_orb().create_any();
2 int l1 = 1;
3 a.insert_long(l1);
4 int l2 = a.extract_long();
5
6 DevLongHelper.insert(a,l1);
7 int l3 = DevLongHelper.extract(a);
8
9 Any s = TangoUtil.instance().get_orb().create_any();
10 String str = new String(I like dancing TANGO);
11 s.insert_string(str);
12 String str_ex = s.extract_string();
13
14 DevStringHelper.insert(s,str);
15 String str_help = DevStringHelper.extract(s);
16
17 Any arr = TangoUtil.instance().get_orb().create_any();
18 int[] array = new int[2];
19 array[0] = 1;
20 array[1] = 2;
21 DevVarLongArrayHelper.insert(arr,array);
22 int[] array_ext = DevVarLongArrayhelper.extract(arr);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Create an instance of the Any class.
Line 3 : Insert a DevLong data into the Any object. The method name is insert_long because this is a method to insert an IDL long type into the object even if the IDL long type maps to an int in Java.
Line 4 : Extract a DevLong type from the Any
Line 6-7 : Insert or Extract DevLong data type to/from the Any object using the Helper class.
Line 9-12 : Create an Any object and a DevString data. Insert and Extract this string into/from the Any using the method provided by the any object
Line 14-15 : Insert or Extract string into/from the Any using methods provided by the Helper class
Line 17-22 : The same thing for data of the DevVarLongArray type. Note that DevVarLongArray is not a basic IDL type and the Any class does not provide method to insert/extract data of this type into/from the Any. The use of the methods provided by the Helper class is mandatory in this case.

4 The insert and extract methods of the Command class for Java


In order to simplify the insertion/extraction into/from Any objects, small helper methods have been written in the Command class. The signatures of these methods are :

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 <java type> extract_<Tango type_name>(Any);
2 Any insert(<Tango type>);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

An extract method has been written for all Tango types. These method extract the data from the Any object passed as parameter and throw an exception if the Any data type is incompatible with the awaiting type. All these extract methods take the same input parameter and only differ in their return type which is not taken into account for method overloading. Therefore, the name of the method depends on the type of the data to be extracted. The following is some example of these method names and signatures : An insert method have been written for all Tango types. These method create an Any object, insert the data into the Any and return a pointer to the created Any. The previous example can be rewritten using the insert/extract helper methods (We suppose that we can use the Command class insert/extract methods)

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 int l1 = 1;
2 Any a = insert(l1);
3 int l2 = extract_DevLong(a);
4
5 String str = new String(I like dancing TANGO);
6 Any s = insert(str);
7 String str_ex = extract_DevString(s);
8
9 int[] array = new int[2];
10 array[0] = 1;
11 array[1] = 2;
12 Any arr = insert(array);
13 int[] array_ext = extract_DevVarLongArray(arr);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-3 : Insertion/Extraction of DevLong type
Line 5-7 : Insertion/Extraction of DevString type
Line 9-13 : Insertion/Extraction of DevVarLongArray type

3 C++ memory management


The rule described here are valid for variable length command data types like Tango::DevString or all the Tango:: DevVarxxxxArray types.
The method executing the command must allocate the memory used to pass data back to the client or use static memory (like buffer declares as object data member. If necessary, the ORB will deallocate this memory after the data have been sent to the caller. Fortunately, for incoming data, the method have no memory management responsibilities. The details about memory management given in this chapter assume that the insert/extract methods of the Tango::Command class are used and only the method in the device object is discussed.

1 For string


Example of a method receiving a Tango::DevString and returning a Tango::DevString is detailed just below

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevString MyDev::dev_string(Tango::DevString argin)
2 {
3 Tango::DevString argout;
4
5 cout << the received string is << argin << endl;
6
7 string str(Am I a good Tango dancer ?);
8 argout = new char[str.size() + 1];
9 strcpy(argout,str.c_str());
10
11 return argout;
12 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Note that there is no need to deallocate the memory used by the incoming string. Memory for the outgoing string is allocated at line 8, then it is initialized at the following line. The memory allocated at line 8 will be automatically freed by the usage of the Command::insert() method. Using this schema, memory is allocated/freed each time the command is executed. For constant string length, a statically allocated buffer can be used.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::ConstDevString MyDev::dev_string(Tango::DevString argin)
2 {
3 Tango::ConstDevString argout;
4
5 cout << the received string is << argin << endl;
6
7 argout = Hello world;
8 return argout;
9 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

A Tango::ConstDevString data type is used. It is not a new data Tango data type. It has been introduced only to allows Command::insert() method overloading. The argout pointer is initialized at line 7 with memory statically allocated. In this case, no memory will be freed by the Command::insert() method. There is also no memory copy in the contrary of the previous example. A buffer defined as object data member can also be used to set the argout pointer.

2 For array/sequence


Example of a method returning a Tango::DevVarLongArray is detailed just below

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevVarLongArray *MyDev::dev_array()
2 {
3 Tango::DevVarLongArray *argout = new Tango::DevVarLongArray();
4
5 long output_array_length = ...;
6 argout->length(output_array_length);
7 for (int i = 0;i < output_array_length;i++)
8 (*argout)[i] = i;
9
10 return argout;
11 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

In this case, memory is allocated at line 3 and 6. Then, the sequence is populated. The sequence is created and returned using pointer. The Command::insert() method will insert the sequence into the CORBA::Any object using this pointer. Therefore, the CORBA::Any object will take ownership of the allocated memory. It will free it when it will be destroyed by the CORBA ORB after the data have been sent away. It is also possible to use a statically allocated memory and to avoid copying in the sequence used to returned the data. This is explained in the following example assuming a buffer of long data is declared as device data member and named buffer.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevVarLongArray *MyDev::dev_array()
2 {
3 Tango::DevVarLongArray *argout;
4
5 long output_array_length = ...;
6 argout = create_DevVarLongArray(buffer,output_array_length);
7 return argout;
8 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

At line 3 only a pointer to a DevVarLongArray is defined. This pointer is set at line 6 using the create_DevVarLongArray() method. This method will create a sequence using this buffer without memory allocation and with minimum copying. The Command::insert() method used here is the same than the one used in the previous example. The sequence is created in a way that the destruction of the CORBA::Any object in which the sequence will be inserted will not destroy the buffer. The following create_xxx methods are defined in the DeviceImpl class :



Method name data type
create_DevVarCharArray() unsigned char
create_DevVarShortArray() short
create_DevVarLongArray() DevLong
create_DevVarLong64Array() DevLong64
create_DevVarFloatArray() float
create_DevVarDoubleArray() double
create_DevVarUShortArray() unsigned short
create_DevVarULongArray() DevULong
create_DevVarULong64Array() DevULong64





3 For string array/sequence


Example of a method returning a Tango::DevVarStringArray is detailed just below

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevVarStringArray *MyDev::dev_str_array()
2 {
3 Tango::DevVarStringArray *argout = new Tango::DevVarStringArray();
4
5 argout->length(3);
6 (*argout)[0] = CORBA::string_dup(Rumba);
7 (*argout)[1] = CORBA::string_dup(Waltz);
8 string str(Jerck);
9 (*argout)[2] = CORBA::string_dup(str.c_str());
10 return argout;
11 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Memory is allocated at line 3 and 5. Then, the sequence is populated at lines 6,7 and 9. The usage of the CORBA::string_dup function also allocates memory. The sequence is created and returned using pointer. The Command::insert() method will insert the sequence into the CORBA::Any object using this pointer. Therefore, the CORBA::Any object will take ownership of the allocated memory. It will free it when it will be destroyed by the CORBA ORB after the data have been sent away. For portability reason, the ORB uses the CORBA::string_free function to free the memory allocated for each string. This is why the corresponding CORBA::string_dup or CORBA::string_alloc function must be used to reserve this memory.It is also possible to use a statically allocated memory and to avoid copying in the sequence used to returned the data. This is explained in the following example assuming a buffer of pointer to char is declared as device data member and named int_buffer.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Tango::DevVarStringArray *DocDs::dev_str_array()
2 {
3 int_buffer[0] = first;
4 int_buffer[1] = second;
5
6 Tango::DevVarStringArray *argout;
7 argout = create_DevVarStringArray(int_buffer,2);
8 return argout;
9 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

The intermediate buffer is initialized with statically allocated memory at lines 3 and 4. The returned sequence is created at line 7 with the create_DevVarStringArray() method. Like for classical array, the sequence is created in a way that the destruction of the CORBA::Any object in which the sequence will be inserted will not destroy the buffer.

4 For Tango composed types


Tango supports only two composed types which are Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray. These types are translated to C++ structure with two sequences. It is not possible to use memory statically allocated for these types. Each structure element must be initialized as described in the previous sub-chapters using the dynamically allocated memory case.


4 Reporting errors


Tango uses the C++ and Java try/catch plus exception mechanism to report errors. Two kind of errors can be transmitted between client and server :
  1. CORBA system error. These exceptions are raised by the ORB and indicates major failures (A communication failure, An invalid object reference...)
  2. CORBA user exception. These kind of exceptions are defined in the IDL file. This allows an exception to contain an arbitrary amount of error information of arbitrary type.
TANGO defines one user exception called DevFailed. This exception is a variable length array of DevError type (a sequence of DevError). The DevError type is a four fields structure. These fields are :
  1. A string describing the type of the error. This string replaces an error code and allows a more easy management of include files.
  2. The error severity. It is an enumeration with the three values which are WARN, ERR or PANIC.
  3. A string describing in plain text the reason of the error
  4. A string describing the origin of the error
The Tango::DevFailed type is a sequence of DevError structures in order to transmit to the client what is the primary error reason when several classes are used within a command. The sequence element 0 must be the DevError structure describing the primary error. A method called print_exception() defined in the Tango::Except class prints the content of exception (CORBA system exception or Tango::DevFailed exception). Some static methods of the Tango::Except class called throw_exception() can be used to throw Tango::DevFailed exception. Some other static methods called re_throw_exception() may also be used when the user want to add a new element in the exception sequence and re-throw the exception. With Java, these functions are static methods of the Except class. Details on these methods can be found in [8].

1 Example of throwing exception using C++


This example is a piece of code from the command_handler() method of the DeviceImpl class. An exception is thrown to the client to indicate that the requested command is not defined in the command list.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 TangoSys_OMemStream o;
2
3 o << Command << command << not found << ends;
4 Tango::Except::throw_exception((const char *)API_CommandNotFound,
5 o.str(),
6 (const char *)DeviceClass::command_handler);
7
8
9 try
10 {
11 .....
12 }
13 catch (Tango::DevFailed &e)
14 {
15 TangoSys_OMemStream o;
16
17 o << Command << command << not found << ends;
18 Tango::Except::re_throw_exception(e,
19 (const char *)API_CommandNotFound,
20 o.str(),
21 (const char *)DeviceClass::command_handler);
22 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Build a memory stream. Use the TangoSys_MemStream because memory streams are not managed the same way between Windows and Unix
Line 3 : Build the reason string in the memory stream
Line 4-5 : Throw the exception to client using one of the throw_exception static method of the Except class. This throw_exception method used here allows the definition of the error type string, the reason string and the origin string of the DevError structure. The remaining DevError field (the error severity) will be set to its default value. Note that the first and third parameters are casted to a const char *. Standard C++ defines that such a string is already a const char * but the GNU C++ compiler (release 2.95) does not use this type inside its function overloading but rather uses a char * which leads to calling the wrong function.
Line 13-22 : Re-throw an already catched tango::DevFailed exception with one more element in the exception sequence.

2 Example of throwing exception using Java


This example is a fragment of code from the command_handler() method of the DeviceImpl class. An exception is thrown to the client to indicate that the requested command is not defined in the command list.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 StringBuffer o = new StringBuffer(Command );
2 o.append(command);
3 o.append( not found);
4
5 Except.throw_exception(API_CommandNotFound,
6 o.toString(),
7 DeviceClass.command_handler);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-3 : Build a string with a message describing the error. The StringBuffer class is used instead of the String class because the StringBuffer class allows dynamic resizing of the string.
Line 5-7 : Throw the exception to client using the static throw_exception method of the Except class. The throw_exception method used here allows the definition of the reason string, the description string and the origin string of the DevError structure. The remaining DevError field (the error severity) will be set to its default value. Like C++, some static re_throw_exception() methods also exist to re-throw DevFailed exception with one more sequence element.
Note that the CORBA system exception inherits from the java.lang.RuntimeException. Exception derivate from this class do not need to be catched or re-thrown. This is the case for the BAD_OPERATION exception thrown when a mismatched type is used to extract data from an Any object. CORBA user exception (like the DevFailed exception) inherits from the java.Exception class and needs to be catched or re-thrown.


3 The Tango Logging Service


A first introduction about this logging service has been done in chapter [*]
The TANGO Logging Service (TLS) gives the user the control over how much information is actually generated and to where it goes. In practice, the TLS allows to select both the logging level and targets of any device within the control system.

1 Logging Targets


The TLS implementation allows each device logging requests to print simultaneously to multiple destinations. In the TANGO terminology, an output destination is called a logging target. Currently, targets exist for console, file and log consumer device.
CONSOLE: logs are printed to the console (i.e. the standard output),
FILE: logs are stored in a XML file. A rolling mechanism is used to backup the log file when it reaches a certain size (see below),
DEVICE: logs are sent to a device implementing a well known TANGO interface (see section [*] for a definition of the log consumer interface). One implementation of a log consumer associated to a graphical user interface is available within the Tango package. It is called the LogViewer.
The device's logging behavior can be control by adding and/or removing targets.
Note : When the size of a log file (for file logging target) reaches the so-called rolling-file-threshold (rft), it is backuped as current_log_file_name + _1 and a new current_log_file_name is opened. Obviously, there is only one backup file at a time (i.e. any existing backup is destroyed before the current log file is backuped). The default threshold is 2Mb, the minimum is 500 Kb and the maximum is 20 Mb.

2 Logging Levels


Devices can be assigned a logging level. It acts as a filter to control the kind of information sent to the targets. Since, there are (usually) much more low level log statements than high level statements, the logging level also control the amount of information produced by the device. The TLS provides the following levels (semantic is just given to be indicative of what could be log at each level):
OFF: Nothing is logged
FATAL: A fatal error occurred. The process is about to abort
ERROR: An (unrecoverable) error occurred but the process is still alive
WARN: An error occurred but could be recovered locally
INFO: Provides information on important actions performed
DEBUG: Generates detailed information describing the internal behavior of a device
Levels are ordered the following way:
DEBUG < INFO < WARN < ERROR < FATAL < OFF

For a given device, a level is said to be enabled if it is greater or equal to the logging level assigned to this device. In other words, any logging request which level is lower than the device's logging level is ignored.
Note: The logging level can't be controlled at target level. The device's targets shared the same device logging level.

3 Sending TANGO Logging Messages


1 Logging macros in C++


The TLS provides the user with easy to use C++ macros with printf and stream like syntax. For each logging level, a macro is defined in both styles: These macros are supposed to be used within the device's main implementation class (i.e. the class that inherits (directly or indirectly) from the Tango::DeviceImpl class). In this context, they produce logging messages containing the device name. In other words, they automatically identify the log source. Section [*] gives a trick to log in the name of device outside its main implementation class. Printf like example:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

LOG_DEBUG((Msg#%d - Hello world, i++));

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Stream like example:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

DEBUG_STREAM << Msg# << i++ << - Hello world << endl;

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

These two logging requests are equivalent. Note the double parenthesis in the printf version.


2 C++ logging in the name of a device


A device implementation is sometimes spread over several classes. Since all these classes implement the same device, their logging requests should be associated with this device name. Unfortunately, the C++ logging macros can't be used because they are outside the device's main implementation class. The Tango::LogAdapter class is a workaround for this limitation.
Any method not member of the device's main implementation class, which send log messages associated to a device must be a member of a class inheriting from the Tango::LogAdapter class. Here is an example:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 class MyDeviceActualImpl: public Tango::LogAdapter
2 {
3 public :
4 MyDeviceActualImpl(...,Tango::DeviceImpl *device,...)
5 :Tango::LogAdpater(device)
6 {
7 ....
8 //
9 // The following log is associated to the device passed to the constructor
10 //
11 DEBUG_STREAM << In MyDeviceActualImpl constructor << endl;
12
13 ....
14 }
15 };

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}


3 Logging in Java


In order to send a log from a device implementation method (i.e. a method of a class inheriting from TangoDs.DeviceImpl), the developer makes use of the org.apache.log4j.Logger instance which reference is returned by the DeviceImpl.get_logger method. The org.apache.log4j.Logger.{fatal,error,warn,info and debug} methods provide the actual logging features. See for more information about the Logger class. Here is an example of Logging usage with Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public class myDevice extends DeviceImpl implements TangoConst
2 {
3 ...
4
5 public void init_device()
6 {
7
8 // A Debug log
9
10 get_logger().debug(Initializing device + get_name());
11
12 try
13 {
14 // Initialization code
15 String p = get_property(startup property);
16 if (p == null)
17 {
18 get_logger().warn(No startup property defined for + get_name());
19 ...
20 }
21 }
22 catch (Exception e)
23 {
24 // An error log
25
26 get_logger().error(unknown exception caught);
27 }
28 }
29 ...
30 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

4 Logging in the name of a device with Java


Using Java, you can log in the name of a device from anywhere in your code as far as you get a reference to this device. Use the device get_logger public method to obtain its associated logger then proceed as describe in [*].


4 Writing a device server


Writing a device server can be made easier by adopting the correct approach. This chapter will describe how to write a device server. It is divided into the following parts : understanding the device, defining device commands, choosing device state and writing the necessary classes. All along this chapter, examples will be given using the stepper motor device server. Writing a device server for our stepper motor example device means writing : All these functions and classes will be detailed. The stepper motor device server described in this chapter supports 2 commands and 3 attributes which are : As the reader will understand during the reading of the following sub-chapters, the command and attributes classes (DevReadPositionCmd, DevReadDirectionCmd, PositionAttr, SetPositionAttr and DirectionAttr) are very simple classes. A tool called Pogo has been developped to automatically generate/maintain these classes and to write part of the code needed in the remaining one. See xx to know more on this Pogo tool.
In order to also gives an example of how the database objects part of the Tango device pattern could be used, our device have two properties. These properties are of the Tango long data types and are named ``Max'' and ``Min''.

1 Understanding the device


The first step before writing a device server is to develop an understanding of the hardware to be programmed. The Equipment Responsible should have description of the hardware and its operating modes (manuals, spec sheets etc.). The Equipment Responsible must also provide specifications of what the device server should do. The Device Server Programmer should demand an exact description of the registers, alarms, interlocks and any timing constraints which have to be kept. It is very important to have a good understanding of the device interfacing before starting designing a new class.
Once the Device Server Programmer has understood the hardware the next important step is to define what is a logical device i.e. what part of the hardware will be abstracted out and treated as a logical device. In doing so the following points of the TDSOM should be kept in mind The above points have to be taken into account when designing the level of device abstraction. The definition of what is a device for a certain hardware is primarily the job of the Device Server Programmer and the Applications Programmer but can also involve the Equipment Responsible. The Device Server Programmer should make sure that the Applications Programmer agrees with her definition of what is a device.
Here are some guidelines to follow while defining the level of device abstraction -

2 Defining device commands


Each device has a list of commands which can be executed by the application across the network or locally. These commands are the Application Programmer's network knobs and dials for interacting with the device.
The list of commands to be implemented depends on the capabilities of the hardware, the list of sensible functions which can be executed at a distance and of course the functionality required by the application. This implies a close collaboration between the Equipment Responsible, Device Server Programmer and the Application Programmer.
When drawing up the list of commands particular attention should be paid to the following points

1 Standard commands


A minimum set of three commands exist for all devices. These commands are These commands have already been discussed in [*]

3 Choosing device state


The device state is a number which reflects the availability of the device. To simplify the coding for generic application, a predefined set of states are supported by TANGO. This list has 14 members which are



State name
ON
OFF
CLOSE
OPEN
INSERT
EXTRACT
MOVING
STANDBY
FAULT
INIT
RUNNING
ALARM
DISABLE
UNKNOWN





The names used here have obvious meaning.

4 Device server utilities to ease coding/debugging


The device server framework supports one in C++ and two set of utilities to ease the process of coding and debugging device server code. These utilities are :
  1. The device server verbose option
  2. The device server output redirection system (Java specific)
Using these two facilities avoids the usage of the classical ``#ifdef DEBUG'' style which makes code less readable.

1 The device server verbose option


Each device server supports a verbose option called -v. Four verbose levels are defined from 1 to 4. Level 4 is the most talkative one. If you use the -v option without specifying level, level 4 will be assumed.
Since Tango release 3, a Tango Logging Service has been introduced (detailed in chapter [*]). This -v option set-up the logging service. If it used, it will automatically add a console target to all devices embedded within the device server process. Level 1 and 2 will set the logging level to all devices embedded within the device server to INFO. Level 3 and 4 will set the logging level to all devices embedded within the device server to DEBUG. All messages sent by the API layer are associated to the administration device.
Java specific: A device server started with output level n will print all the messages of level between 1 and n. For instance, if you start a device server using -v3 option, only the output for level 1,2 and 3 will be displayed. Output for level 4 will not be printed. If you don't used the -v option, the output level is set to 0. By convention, level 3 and 4 are reserved for print message embedded into the Tango library. Level 1 and 2 are free for the user.

1 Choosing the output level using C++


In C++ device server, this feature is now implemented using the Tango Logging Service (TLS), see chapter [*] to get all details on this service.

2 Choosing the output level using Java


With Java, four static objects inside the Util class have been defined. These objects are called out1, out2, out3 and out4. These four objects support the println method exactly as the out object inside the System class does. The first object (out1) defines a message which should be printed only when output level 1 or more is requested. The second one (out2) defines a message which should be printed only when output level 2 or more is requested. The same philosophy is used for out3 and out4. The usage of these outx objects is the same than the classical out.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 Util.out3.println(What a nice dance);
2 Util.out3.println(What's its name ?);
3
4 System.out.println(Its name is TANGO);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-2 : The two questions are level 3 messages.
Line 4 : This print will be printed whatever the print level is.
If this piece of code is part of a device server started with a -v2 option, only the message defined line 4 will be displayed. If the device server is started with a -v3, -v4 or -v option, the two messages defined at lines 1 and 2 will also be displayed.

3 Changing the output level at run time (Java specific)


It is possible to change the output level at run time. You do so using commands of the dserver device. These two commands are :

2 Device server output redirection (Java specific)


Two commands of the dserver device allow device server output redirection. Theses two commands are :

3 Java usage example


These two previously described features can ease device server debugging. Suppose a device server process is started with the following command line (UNIX command line)
Java -DTANGO_HOST=xxx Perkin/Perkin id22 >/dev/null
This command line does not define any output level. Therefore the default output level is chosen (0) and no message are printed. Sending a SetTraceLevel command requesting level 4 and a SetTraceOutput command with a file name /tmp/server.out will make the device server sending all the output to the /tmp/server.out file without stopping the process. The inspection of the /tmp/server.out file will hopefully help to find the reason of the device server problem. When the output are not needed anymore, sending a SetTraceOutput command with the input parameter set to ``Initial Output'' followed by a SetTraceLevel command with a requested level of 0 will return the server to its original state.

4 C++ utilities to ease device server coding


Some utilities functions have been added in the C++ release to ease Tango device server development. These utilities allow the user to They mainly used the ``<<'' operator overloading features. The following code lines are an example of usage of these utilities.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 vector<string> v1;
2 v1.push_back(one);
3 v1.push_back(two);
4 v1.push_back(three);
5
6 Tango::DevVarStringArray s;
7 s << v1;
8 cout << s << endl;
9
10 vector<string> v2;
11 v2 << s;
12
13 for (int i = 0;i < v2.size();i++)
14 cout << vector element = << v2[i] << endl;

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-4 : Create and Init a C++ string vector
Line 7 : Init a Tango::DevVarStringArray data from the C++ vector
Line 8 : Print all the Tango::DevVarStringArray element in one line of code.
Line 11 : Init a second empty C++ string vector with the content of the Tango::DevVarStringArray
Line 13-14 : Print vector element

Warning: Note that due to a strange behavior of the Windows VC++ compiler compared to other compilers, to use these utilities with the Windows VC++ compiler, you must add the line ``using namespace tango'' at the beginning of your source file.

5 Avoiding name conflicts


1 Using C++


Namespace are used to avoid name conflicts. Each device pattern implementation is defined within its own namespace. The name of the namespace is the device pattern class name. In our example, the namespace name is StepperMotor.

2 Using Java


Package are used to avoid name conflicts. Each device pattern implementation is defined within its own package. The name of the package is the device pattern class name. In our example, the package name is StepperMotor.

6 The device server main function


A device server main function (or method) always follows the same framework. It exactly implements all the action described in chapter [*]. Even if it could be always the same, it has not been included in the library because some linkers are perturbed by the presence of two main functions.

1 Using C++



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2
3 int main(int argc,char *argv[])
4 {
5
6 Tango::Util *tg;
7
8 try
9 {
10
11 tg = Tango::Util::init(argc,argv);
12
13 tg->server_init();
14
15 cout << Ready to accept request << endl;
16 tg->server_run();
17 }
18 catch (bad_alloc)
19 {
20 cout << Can't allocate memory!!! << endl;
21 cout << Exiting << endl;
22 }
23 catch (CORBA::Exception &e)
24 {
25 Tango::Except::print_exception(e);
26
27 cout << Received a CORBA::Exception << endl;
28 cout << Exiting << endl;
29 }
30
31 tg->server_cleanup();
32
33 return(0);
34 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Include the tango.h file. This file is a master include file. It includes several other files. The list of files included by tango.h can be found in [8]
Line 11 : Create the instance of the Tango::Util class (a singleton). Passing argc,argv to this method is mandatory because the device server command line is checked when the Tango::Util object is constructed.
Line 13 : Start all the device pattern creation and initialization with the server_init() method
Line 16 : Put the server in a endless waiting loop with the server_run() method. In normal case, the process should never returns from this line.
Line 18-22 : Catch all exceptions due to memory allocation error, display a message to the user and exit
Line 23 : Catch all standard TANGO exception which could occur during device pattern creation and initialization
Line 25 : Print exception parameters
Line 27-28 : Print an additional message
Line 31 : Cleanup the server before exiting by calling the server_cleanup() method.

2 Using Java


The main method can be defined in any class. There is no mandatory class where it should be defined. In our StepperMotor example, the main method has been implemented in the StepperMotor class because it is the most logical place.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 package StepperMotor
2
3 import java.util.*;
4 import org.omg.CORBA.*;
5 import fr.esrf.Tango.*;
6 import fr.esrf.TangoDs.*;
7
8 public class StepperMotor extends DeviceImpl implements TangoConst
9 {
10 public static void main(String[] argv)
11 {
12 try
13 {
14
15 Util tg = Util.init(argv,StepperMotor);
16
17 tg.server_init();
18
19 System.out.println(Ready to accept request);
20
21 tg.server_run();
22 }
23 catch (OutOfMemoryError ex)
24 {
25 System.err.println(Can't allocate memory !!!!);
26 System.err.println(Exiting);
27 }
28 catch (UserException ex)
29 {
30 Except.print_exception(ex);
31
32 System.err.println(Received a CORBA user exception);
33 System.err.println(Exiting);
34 }
35 catch (SystemException ex)
36 {
37 Except.print_exception(ex);
38
39 System.err.println(Received a CORBA system exception);
40 System.err.println(Exiting);
41 }
42
43 System.exit(-1);
44
45 }
46 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

line 1 : The StepperMotor class is part of the StepperMotor package
Line 3-6 : Import several packages. The reason of importing these package will be explained when the StepperMotor class will be detailed later in this chapter
Line 8 : Definition of the StepperMotor class (will be explained later)
Line 10 : Definition of the main method
Line 15 : Create the instance of the Util class (a singleton). Passing argv to this method is mandatory because the device server command line is checked when the Util object is constructed. The second argument of this init method is the device server executable name as defined in [*]
Line 17 : Start all the device pattern creation and initialization
Line 21 : Put the server in a endless waiting loop. In normal case, the process should never returns from this line.
Line 23-27 : Catch all exceptions due to memory error and display a message to the user. It seems strange to deal with memory allocation error with Java.The Java garbage collection system reclaims memory only for object which have a reference count equal to zero. If, inside a program, objects are created and stay with an object reference count different than zero, they will never be destructed. If many of these objects are created, memory allocation errors can occurs. You may think that the author of this manual is paranoid but have a look at [13]
Line 28-34 : Catch CORBA user exception included the TANGO DevFailed exception which could occur during device pattern creation and initialization
Line 30 : Use the static print_exception method of the Except class to print all the data members of the exception object.
Line 35-41 : catch CORBA system exception.
Line 37 : Use the static print_exception method of the Except class to print all the data members of the exception object.
Line 43 : Exit the device server

7 The DServer::class_factory method (C++ specific)


As described in chapter [*], C++ device server needs a class_factory() method. This method creates all the device pattern implemented in the device server by calling their init() method. The following is an example of a class_factory method for a device server with one implementation of the device server pattern for stepper motor device.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2 #include <steppermotorclass.h>
3
4 void Tango::DServer::class_factory()
5 {
6
7 add_class(StepperMotor::StepperMotorClass::init(StepperMotor));
8
9 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Include the Tango master include file
Line 2 : Include the steppermotorclass class definition file
Line 7 : Create the StepperMotorClass singleton by calling its init method and stores the returned pointer into the DServer object. Remember that all classes for the device pattern implementation for the stepper motor class is defined within a namespace called StepperMotor.


8 Writing the StepperMotorClass class


1 Using C++


1 The class definition file



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2
3 namespace StepperMotor
4 {
5
6 class StepperMotorClass : public Tango::DeviceClass
7 {
8 public:
9 static StepperMotorClass *init(const char *);
10 static StepperMotorClass *instance();
11 ~StepperMotorClass() {_instance = NULL;}
12
13 protected:
14 StepperMotorClass(string &);
15 static StepperMotorClass *_instance;
16 void command_factory();
17 void attribute_factory(vector<Tango::Attr *> &);
18
19 public:
20 void device_factory(const Tango::DevVarStringArray *);
21 };
22
23 } /* End of StepperMotor namespace */

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Include the Tango master include file
Line 3 : This class is defined within the StepperMotor namespace
Line 6 : Class StepperMotorClass inherits from Tango::DeviceClass
Line 9-10 : Definition of the init and instance methods. These methods are static and can be called even if the object is not already constructed.
Line 11: The destructor
Line 14 : The class constructor. It is protected and can't be called from outside the class. Only the init method allows a user to create an instance of this class. See [10] to get details about the singleton design pattern.
Line 15 : The instance pointer. It is static in order to set it to NULL during process initialization phase
Line 16 : Definition of the command_factory method
Line 17 : Definition of the attribute_factory method
Line 20 : Definition of the device_factory method

2 The singleton related methods



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2
3 #include <steppermotor.h>
4 #include <steppermotorclass.h>
5
6 namespace StepperMotor
7 {
8
9 StepperMotorClass *StepperMotorClass::_instance = NULL;
10
11 StepperMotorClass::StepperMotorClass(string &s):
12 Tango::DeviceClass(s)
13 {
14 INFO_STREAM << Entering StepperMotorClass constructor << endl;
15
16 INFO_STREAM << Leaving StepperMotorClass constructor << endl;
17 }
18
19
20 StepperMotorClass *StepperMotorClass::init(const char *name)
21 {
22 if (_instance == NULL)
23 {
24 try
25 {
26 string s(name);
27 _instance = new StepperMotorClass(s);
28 }
29 catch (bad_alloc)
30 {
31 throw;
32 }
33 }
34 return _instance;
35 }
36
37 StepperMotorClass *StepperMotorClass::instance()
38 {
39 if (_instance == NULL)
40 {
41 cerr << Class is not initialised !! << endl;
42 exit(-1);
43 }
44 return _instance;
45 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-4 : include files: the Tango master include file (tango.h), the StepperMotorClass class definition file (steppermotorclass.h) and the StepperMotor class definition file (steppermotor.h)
Line 6 : Open the StepperMotor namespace.
Line 9 : Initialize the static _instance field of the StepperMotorClass class to NULL
Line 11-18 : The class constructor. It takes an input parameter which is the controlled device class name. This parameter is passed to the constructor of the DeviceClass class. Otherwise, the constructor does nothing except printing a message
Line 20-35 : The init method. This method needs an input parameter which is the controlled device class name (StepperMotor in this case). This method checks is the instance is already constructed by testing the _instance data member. If the instance is not constructed, it creates one. If the instance is already constructed, the method simply returns a pointer to it.
Line 37-45 : The instance method. This method is very similar to the init method except that if the instance is not already constructed. the method print a message and abort the process.
As you can understand, it is not possible to construct more than one instance of the StepperMotorClass (it is a singleton) and the init method must be called prior to any other method.

3 The command_factory method


Within our example, the stepper motor device supports two commands which are called DevReadPosition and DevReadDirection. These two command takes a Tango::DevLong argument as input and output parameter. The first command is created using the inheritance model and the second command is created using the template command model.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1
2 void StepperMotorClass::command_factory()
3 {
4 command_list.push_back(new DevReadPositionCmd(DevReadPosition,
5 Tango::DEV_LONG,
6 Tango::DEV_LONG,
7 Motor number (0-7),
8 Motor position));
9
10 command_list.push_back(
11 new TemplCommandInOut<Tango::DevLong,Tango::DevLong>
12 ((const char *)DevReadDirection,
13 static_cast<Tango::Lg_CmdMethPtr_Lg>
14 (&StepperMotor::dev_read_direction),
15 static_cast<Tango::StateMethPtr>
16 (&StepperMotor::direct_cmd_allowed))
17 );
18 }
19

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 4 : Creation of one instance of the DevReadPositionCmd class. The class is created with five arguments which are the command name, the command type code for its input and output parameters and two strings which are the command input and output parameters description. The pointer returned by the new C++ keyword is added to the vector of available command.
Line 10-14 : Creation of the object used for the DevReadDirection command. This command has one input and output parameter. Therefore the created object is an instance of the TemplCommandInOut class. This class is a C++ template class. The first template parameter is the command input parameter type, the second template parameter is the command output parameter type. The second TemplCommandInOut class constructor parameter (set at line 13) is a pointer to the method to be executed when the command is requested. A casting is necessary to store this pointer as a pointer to a method of the DeviceImpl class[*]. The third TemplCommandInOut class constructor parameter (set at line 15) is a pointer to the method to be executed to check if the command is allowed. This is necessary only if the default behavior (command always allowed) does not fulfill the needs. A casting is necessary to store this pointer as a pointer to a method of the DeviceImpl class. When a command is created using the template command method, the input and output parameters type are determined from the template C++ class parameters.

4 The device_factory method


The device_factory method has one input parameter. It is a pointer to Tango::DevVarStringArray data which is the device name list for this class and the instance of the device server process. This list is fetch from the Tango database.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 void StepperMotorClass::device_factory(const Tango::_DevVarStringArray *devlist_ptr)
2 {
3
4 for (long i = 0;i < devlist_ptr->length();i++)
5 {
6 DEBUG_STREAM << Device name : << (*devlist_ptr)[i] << endl;
7
8 device_list.push_back(new StepperMotor(this,
9 (*devlist_ptr)[i]));
10
11 if (Tango::Util::_UseDb == true)
12 export_device(device_list.back());
13 else
14 export_device(device_list.back(),(*devlist_ptr[i]));
15 }
16 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 4 : A loop for each device
Line 8 : Create the device object using a StepperMotor class constructor which needs two arguments. These two arguments are a pointer to the StepperMotorClass instance and the device name. The pointer to the constructed object is then added to the device list vector
Line 11-14 : Export device to the outside world using the export_device method of the DeviceClass class.


5 The attribute_factory method


The rule of this method is to fulfill a vector of pointer to attributes. A reference to this vector is passed as argument to this method.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 void StepperMotorClass::attribute_factory(vector<Tango::Attr *> &att_list)
2 {
3 att_list.push_back(new PositionAttr());
4
5 Tango::UserDefaultAttrProp def_prop;
6 def_prop.set_label(Set the motor position);
7 def_prop.set_format(scientific;setprecision(4));
8 Tango::Attr *at = new SetPositionAttr();
9 at->set_default_properties(def_prop);
10 att_list.push_back(at);
11
12 att_list.push_back(new DirectcionAttr());
13 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3 : Create the PositionAttr class and store the pointer to this object into the attribute pointer vector.
Line 5-7 : Create a Tango::UserDefaultAttrProp instance and set the label and format properties default values in this object
Line 8 : Create the SetPositionAttr attribute.
Line 9 : Set attribute user default value with the set_default_properties() method of the Tango::Attr class.
Line 10 : Store the pointer to this object into the attribute pointer vector.
Line 12 : Create the DirectionAttr class and store the pointer to this object into the attribute pointer vector.
Please, note that in some rare case, it is necessary to add attribute to this list during the device server life cycle. This attribute_factory() method is called once during device server start-up. A method add_attribute() of the DeviceImpl class allows the user to add a new attribute to the attribute list outside of this attribute_factory() method. See [8] for more information on this method.

2 Using Java


1 The singleton related method



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 package StepperMotor;
2
3 import java.util.*;
4 import fr.esrf.Tango.*;
5 import fr.esrf.TangoDs.*;
6
7 public class StepperMotorClass extends DeviceClass implements TangoConst
8 {
9 private static StepperMotorClass _instance = null;
10
11
12 public static StepperMotorClass instance()
13 {
14 if (_instance == null)
15 {
16 System.err.println(StepperMotorClass is not initialised !!!);
17 System.err.println(Exiting);
18 System.exit(-1);
19 }
20 return _instance;
21 }
22
23
24 public static StepperMotorClass init(String class_name) throws DevFailed
25 {
26 if (_instance == null)
27 {
28 _instance = new StepperMotorClass(class_name);
29 }
30 return _instance;
31 }
32
33 protected StepperMotorClass(String name) throws DevFailed
34 {
35 super(name);
36
37 Util.out2.println(Entering StepperMotorClass constructor);
38
39 Util.out2.println(Leaving StepperMotorClass constructor);
40 }
41 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : This class is part of the StepperMotor package.
Line 3-5 : Import different packages. The first one (java.lang.util) is a classical Java package from the JDK. The second one (fr.esrf.Tango) is the package generated by the IDL compiler from the Tango IDL file. The last one (fr.esrf.TangoDs) is the name of the package with all the root classes of the device server framework.
Line 7 : The StepperMotorClass inherits from the DeviceClass and implements the TangoConst interface. The TangoConst interface does not defines any method but simply defines constant variables. The TangoConst interface is a member of the TangoDs package.
Line 9 : The instance pointer. It is static and private. It is initialized to NULL
Line 12-21 : The instance method. This method is very similar to the init method except that if the instance is not already constructed. the method print a message and abort the process.
Line 24-31: The init method. This method needs an input parameter which is the controlled device class name (StepperMotor in this case). This method checks is the instance is already constructed by testing the _instance data member. If the instance is not constructed, it creates one. If the instance is already constructed, the method simply returns a pointer to it.
Line 33-40 : The class constructor which is protected. It takes an input parameter which is the controlled device class name. This parameter is passed to the constructor of the DeviceClass class (line 35). Otherwise, the constructor does nothing except printing a message
As you can understand, it is not possible to construct more than one instance of the StepperMotorClass (it is a singleton) and the init method must be called prior to any other method.

2 The command_factory method


Within our example, the stepper motor device supports two commands which are called DevReadPosition and DevReadDirection. These two command takes a Tango_DevLong argument as input and output parameter. The first command is created using the inheritance model and the second command is created using the template command model.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public void command_factory()
2 {
3 String str = new String(DevReadPosition);
4 command_list.addElement(new DevReadPositionCmd(str,
5 Tango_DEV_LONG,Tango_DEV_LONG,
6 Motor number (0-7),
7 Motor position));
8
9 str = new String(DevReadDirection);
10 command_list.addElement(new TemplCommandInOut(str,
11 dev_read_direction,
12 direct_cmd_allowed));
13 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 4: Creation of one instance of the DevReadPositionCmd class. The class is created with five arguments which are the command name, the command type code for its input and output parameters and the parameters description (input and output). The Tango_DEV_LONG constant is defined in the TangoConst interface. The reference returned by the new Java keyword is added to the vector of available command via the addElement method of the Java Vector class.
Line 10-12 : Creation of the object used for the DevReadDirection command. This command has one input and output parameter. Therefore the created object is an instance of the TemplCommandInOut class. The second TemplCommandInOut class constructor parameter (set at line 11) is the method name to be executed when the command is requested. The third TemplCommandInOut class constructor parameter (set at line 12) is the method name to be executed to check if the command is allowed. This is necessary only if the default behavior (command always allowed) does not fulfill the needs. When a command is created using the template command method, the input and output parameter types are determined from the given method declaration.

3 The device_factory method


The device_factory method has one input parameter. It is a pointer to a DevVarStringArray[*]data which is the device name list for this class and the instance of the device server process. This list is fetch from the Tango database.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public void device_factory(String[] devlist) throws DevFailed
2 {
3 for (int i = 0;i < devlist.length;i++)
4 {
5 Util.out4.println(Device name : + devlist[i]);
6
7 device_list.addElement(new StepperMotor(this,
8 devlist[i],
9 A Tango motor,
10 DevState.ON,
11 The motor is ON));
12
13 if (Util.instance()._UseDb == true)
14 export_device(((DeviceImpl)(device_list.lastElement())));
15 else
16 export_device(((DeviceImpl)(device_list.lastElement())),
17 devlist[i]);
18 }
19 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3 : A loop for each device
Line 7 : Create the device object using a StepperMotor class constructor which needs five arguments. These five arguments are a reference to the StepperMotorClass instance, the device name, the device description, the device original state and the device original status. The reference to the constructed object is then added to the device list vector with the addElement method of the java.util.Vector class.
Line 13-17 : Export device to the outside world using the export_device method of the DeviceClass class. The lastElement method of the java.util.Vector class returns a reference to an object of the java Object class. It must be casted before being passed to the export_device method


4 The attribute_factory method


The rule of this method is to fulfill a vector of references to attribute. A reference to this vector is passed to this method. The Tango core classes will use this vector to build all the attributes related objects (An instance of the MultiAttribute class and one Attribute or WAttribute object for each attribute defined in this vector).

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public void attribute_factory(Vector att) throws DevFailed
2 {
3 att.addElement(new Attr(Position,
4 Tango_DEV_LONG,
5 AttrWriteType.READ_WITH_WRITE,
6 SetPosition));
7
8 UserDefaultAttrProp def_prop = new UserDefaultAttrProp();
9 def_prop.set_label(set the motor position);
10 def_prop.set_format(scientific;setprecision(4));
11 Attr at = new Attr(SetPosition,
12 Tango_DEV_LONG,
13 AttrWriteType.WRITE));
14 at.set_default_properties(def_prop);
15 att.addElement(at);
16
17 att.addElement(new Attr(Direction,
18 Tango_DEV_LONG));
19 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3-6 : Build a one dimension attribute of TANGO_DEV_LONG type with an associate writable attribute. Store a reference to this attribute in the vector. In this example, the attribute display type is not defined in the Attr class constructor. Therefore, it will be initialized with its default value (OPERATOR). Several Attr class constructors are defined with or without the attribute display type. See [8] for a complete constructor list.
Line 8-10 : Create a UserDefaultAttrProp instance and set the label and format properties default values in this object
Line 11-13 : Build a one dimension writable attribute.
Line 14 : Set attribute user default value with the set_default_properties() method of the Tango::Attr class.
Line 15 : Store the reference to this attribute object into the attribute vector.
Line 17-18 : Build a one dimension attribute. Store the reference to this attribute object into the attribute vector.
Please, note that in some rare case, it is necessary to add attribute to this list during the device server life cycle. This attribute_factory() method is called once during device server start-up. A method add_attribute() of the DeviceImpl class allows the user to add a new attribute to the attribute list outside of this attribute_factory() method. See [8] for more information on this method.

9 The DevReadPositionCmd class


1 Using C++


1 The class definition file



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2
3 namespace StepperMotor
4 {
5
6 class DevReadPositionCmd : public Tango::Command
7 {
8 public:
9 DevReadPositionCmd(const char *,Tango::CmdArgType,
10 Tango::CmdArgType,
11 const char *,const char *);
12 ~DevReadPositionCmd() {};
13
14 virtual bool is_allowed (Tango::DeviceImpl *, const CORBA::Any &);
15 virtual CORBA::Any *execute (Tango::DeviceImpl *, const CORBA::Any &);
16 };
17
18 } /* End of StepperMotor namespace */

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Include the tango master include file
Line 3 : Open the StepperMotor namespace.
Line 6 : The DevReadPositionCmd class inherits from the Tango::Command class
Line 9 : The constructor
Line 12 : The destructor
Line 14 : The definition of the is_allowed method. This method is not necessary if the default behavior implemented by the default is_allowed method fulfill the requirements. The default behavior is to always allows the command execution (always return true).
Line 15: The definition of the execute method

2 The class constructor


The class constructor does nothing. It simply invoke the Command constructor by passing it its five arguments which are:
  1. The command name
  2. The command input type code
  3. The command output type code
  4. The command input parameter description
  5. The command output parameter description
With this 5 parameters command class constructor, the command display level is not specified. Therefore it is set to its default value (OPERATOR). If the command does not have input or output parameter, it is not possible to use the Command class constructor defined with five parameters. In this case, the command constructor execute the Command class constructor with three elements (class name, input type, output type) and set the input or output parameter description fields with the set_in_type_desc or set_out_type_desc Command class methods. To set the command display level, it is possible to use a 6 parameters constructor or it is also possible to set it in the constructor code with the set_disp_level method. Many Command class constructors are defined. See [8]for a complete list.


3 The is_allowed method


In our example, the DevReadPosition command is allowed only if the device is in the ON state. This method receives two argument which are a pointer to the device object on which the command must be executed and a reference to the command input Any object. This method returns a boolean which must be set to true if the command is allowed. If this boolean is set to false, the DeviceClass command_handler method will automatically send an exception to the caller.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 bool DevReadPositionCmd::is_allowed(Tango::DeviceImpl *device,
2 const CORBA::Any &in_any)
3 {
4 if (device->get_state() == Tango::ON)
5 return true;
6 else
7 return false;
8 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 4 : Call the get_state method of the DeviceImpl class which simply returns the device state
Line 5 : Authorize command if the device state is ON
Line 7 : Refuse command execution in all other cases.


4 The execute method


This method receives two arguments which are a pointer to the device object on which the command must be executed and a reference to the command input Any object. This method returns a pointer to an any object which must be initialized with the data to be returned to the caller.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 CORBA::Any *DevReadPositionCmd::execute(
2 Tango::DeviceImpl *device,
3 const CORBA::Any &in_any)
4 {
5 INFO_STREAM << DevReadPositionCmd::execute(): arrived << endl;
6 Tango::DevLong motor;
7
8 extract(in_any,motor);
9 return insert(
10 (static_cast<StepperMotor *>(device))->dev_read_position(motor));
11 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 8 : Extract incoming data from the input any object using a Command class extract helper method. If the type of the data in the Any object is not a Tango::DevLong, the extract method will throw an exception to the client.
Line 9 : Call the stepper motor object method which execute the DevReadPosition command and insert the returned value into an allocated Any object. The Any object allocation is done by the insert method which return a pointer to this Any.

2 Using Java


1 The class constructor


The class constructor does nothing. It simply invoke the Command constructor by passing it its five arguments which are:
  1. The command name
  2. The command input type code
  3. The command output type code
  4. The command input parameter description
  5. The command output parameter description
With this 5 parameters command class constructor, the command display level is not specified. Therefore it is set to its default value (OPERATOR). If the command does not have input or output parameter, it is not possible to use the Command class constructor defined with five parameters. In this case, the command constructor execute the Command class constructor with three elements (class name, input type, output type) and set the input or output parameter description fields with the set_in_type_desc or set_out_type_desc Command class methods. To set the command display level, it is possible to use a 6 parameters constructor or it is also possible to set it in the constructor code with the set_disp_level method. Many Command class constructors are defined. See [8]for a complete list.


2 The is_allowed method


In our example, the DevReadPosition command is allowed only if the device is in the ON state. This method receives two argument which are a reference to the device object on which the command must be executed and a reference to the command input Any object. This method returns a boolean which must be set to true if the command is allowed. If this boolean is set to false, the DeviceClass command_handler method will automatically send an exception to the caller.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 package StepperMotor;
2
3 import org.omg.CORBA.*;
4 import fr.esrf.Tango.*;
5 import fr.ersf.TangoDs.*;
6
7 public class DevReadPositionCmd extends Command implements TangoConst
8 {
9 public boolean is_allowed(DeviceImpl dev, Any data_in)
10 {
11 if (dev.get_state() == DevState.ON)
12 return(true);
13 else
14 return(false);
15 }
16
17 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : This class is part of the StepperMotor package
Line 3-5 : Import different packages. The first one (org.omg.CORBA) is a package which contains all the CORBA related classes. The second one (fr.esrf.Tango) is the package generated by the IDL compiler from the Tango IDL file. The last one (fr.ersf.TangoDs) is the name of the package with all the root classes of the device server pattern.
Line 7 : The DevReadPositionCmd class inherits from the Command class and implements the TangoConst interface. The TangoConst interface does not defines any method but simply defines constant variables. The TangoConst interface is a member of the TangoDs package.
Line 11 : Call the get_state method of the DeviceImpl class which simply returns a reference to the device state
Line 12 : Authorise command if the device state is ON
Line 14 : Refuse command execution in all other cases.


3 The execute method


This method receives two arguments which are a reference to the device object on which the command must be executed and a reference to the command input Any object. This method returns a reference to an any object which must be initialized with the data to be returned to the caller.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public Any execute(DeviceImpl device,Any in_any) throws DevFailed
2 {
3 Util.out2.println(DevReadPositionCmd.execute(): arrived);
4
5 int motor = extract_DevLong(in_any);
6
7 return insert(((StepperMotor)(device)).dev_read_position(motor));
8 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 5 : Extract incoming data from the input any object
Line 7 : Call the stepper motor object method which execute the DevReadPosition command, insert its return value into an any and return.

10 The PositionAttr class


1 Using C++


1 The class definition file



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2 #include <steppermotor.h>
3
4 namespace StepperMotor
5 {
6
7
8 class PositionAttr: public Tango::Attr
9 {
10 public:
11 PositionAttr():Attr(Position,
12 Tango::DEV_LONG,
13 Tango::READ_WITH_WRITE,
14 SetPosition) {};
15 ~PositionAttr() {};
16
17 virtual void read(Tango::DeviceImpl *dev,Tango::Attribute &att)
18 {(static_cast<StepperMotor *>(dev))->read_Position(att);}
19 virtual bool is_allowed(Tango::DeviceImpl *dev,Tango::AttReqType ty)
20 {return (static_cast<StepperMotor *>(dev))->is_Position_allowed(ty);}
21 };
22
23 } /* End of StepperMotor namespace */
24
25 #endif // _STEPPERMOTORCLASS_H

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-2 : Include the tango master include file and the steppermotor class definition include file
Line 4 : Open the StepperMotor namespace.
Line 8 : The PosiitionAttr class inherits from the Tango::Attr class
Line 11-14 : The constructor with 4 arguments
Line 15 : The destructor
Line 17 : The definition of the read method. This method forwards the call to a StepperMotor class method called read_Position()
Line 19 : The definition of the is_allowed method. This method is not necessary if the default behaviour implemented by the default is_allowed method fulfills the requirements. The default behaviour is to always allows the attribute reading (always return true). This method forwards the call to a StepperMotor class method called is_Position_allowed()

2 The class constructor


The class constructor does nothing. It simply invoke the Attr constructor by passing it its four arguments which are:
  1. The attribute name
  2. The attribute data type code
  3. The attribute writable type code
  4. The name of the associated write attribute
With this 4 parameters Attr class constructor, the attribute display level is not specified. Therefore it is set to its default value (OPERATOR). To set the attribute display level, it is possible to use in the constructor code the set_disp_level method. Many Attr class constructors are defined. See [8]for a complete list.
This Position attribute is a scalar attribute. For spectrum attribute, instead of inheriting from the Attr class, the class must inherits from the SpectrumAttr class. Many SpectrumAttr class constructors are defined. See [8]for a complete list.
For Image attribute, instead of inheriting from the Attr class, the class must inherits from the ImageAttr class. Many ImageAttr class constructors are defined. See [8]for a complete list.


3 The is_allowed method


This method receives two argument which are a pointer to the device object to which the attribute belongs to and the type of request (read or write). In the PositionAttr class, this method simply forwards the request to a method of the StepperMotor class called is_Position_allowed() passing the request type to this method. This method returns a boolean which must be set to true if the attribute is allowed. If this boolean is set to false, the DeviceImpl read_attribute method will automatically send an exception to the caller.


4 The read method


This method receives two arguments which are a pointer to the device object to which the attribute belongs to and a reference to the corresponding attribute object. This method forwards the request to a StepperMotor class called read_Position() passing it the reference on the attribute object.

11 The StepperMotor class


1 Using C++


1 The class definition file



\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>

3 #define AGSM_MAX_MOTORS 8 // maximum number of motors per device

5 namespace StepperMotor
6 {

8 class StepperMotor: public Tango::DeviceImpl
9 {
10 public :
11    StepperMotor(Tango::DeviceClass *,string &);
12    StepperMotor(Tango::DeviceClass *,const char *);
13    StepperMotor(Tango::DeviceClass *,const char *,const char *);
14    ~StepperMotor() {};
15 
16    DevLong dev_read_position(DevLong);
17    DevLong dev_read_direction(DevLong);
18    bool direct_cmd_allowed(const CORBA::Any &);
19 
20    virtual Tango::DevState dev_state();
21    virtual Tango::ConstDevString dev_status();
22 
23    virtual void always_executed_hook();
24 
25    virtual void read_attr_hardware(vector<long> &attr_list);
26 
27    void read_position(Tango::Attribute &);
28    bool is_Position_allowed(Tango::AttReqType req);
29    void write_SetPosition(Tango::WAttribute &);
30    void read_Direction(Tango::Attribute &);
31 
32    virtual void init_device();
33    virtual void delete_device();
34 
35    void get_device_properties();
36 
37 protected : 
38    long axis[AGSM_MAX_MOTORS];
39    DevLong position[AGSM_MAX_MOTORS];
40    DevLong direction[AGSM_MAX_MOTORS];
41    long state[AGSM_MAX_MOTORS];
42 
43    Tango::DevLong *attr_Position_read;
44    Tango::DevLong *attr_Direction_read;
45    Tango::DevLong attr_SetPosition_write;
46 
47    Tango::DevLong min;
48    Tango::DevLong max;
49 
50    Tango::DevLong *ptr;
51 };
52 
53 } /* End of StepperMotor namespace */


\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Include the Tango master include file
Line 5 : Open the StepperMotor namespace.
Line 8 : The StepperMotor class inherits from the DeviceImpl class
Line 11-13 : Three different object constructors
Line 14 : The destructor which calls the delete_device() method
Line 16 : The method to be called for the execution of the DevReadPosition command. This method must be declared as virtual if it is needed to redefine it in a class inheriting from StepperMotor. See chapter [*] for more details about inheriting.
Line 17 : The method to be called for the execution of the DevReadDirection command
Line 18 : The method called to check if the execution of the DevReadDirection command is allowed. This method is necessary because the DevReadDirection command is created using the template command method and the default behavior is not acceptable
Line 20 : Redefinition of the dev_state. This method is used by the State command
Line 21 : Redefinition of the dev_status. This method is used by the Status command
Line 23 : Redefinition of the always_executed_hook method. This method is the place to code mandatory action which must be executed prior to any command.
Line 25-30 : Attribute related methods
Line 32 : Definition of the init_device method.
Line 33 : Definition of the delete_device method
Line 35 : Definition of the get_device_properties method
Line 38-50 : Data members.
Line 43-44 : Pointers to data for readable attributes Position and Direction
Line 45 : Data for the SetPosition attribute
Line 47-48 : Data members for the two device properties

2 The constructors


Three constructors are defined here. It is not mandatory to defined three constructors. But at least one is mandatory. The three constructors take a pointer to the StepperMotorClass instance as first parameter[*]. The second parameter is the device name as a C++ string or as a classical pointer to char array. The third parameter necessary only for the third form of constructor is the device description string passed as a classical pointer to a char array.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2 #include <steppermotor.h>
3
4 namespace StepperMotor
5 {
6
7 StepperMotor::StepperMotor(Tango::DeviceClass *cl,string &s)
8 :Tango::DeviceImpl(cl,s.c_str())
9 {
10 init_device();
11 }
12
13 StepperMotor::StepperMotor(Tango::DeviceClass *cl,const char *s)
14 :Tango::DeviceImpl(cl,s)
15 {
16 init_device();
17 }
18
19 StepperMotor::StepperMotor(Tango::DeviceClass *cl,const char *s,const char *d)
20 :Tango::DeviceImpl(cl,s,d)
21 {
22 init_device();
23 }
24
25 void StepperMotor::init_device()
26 {
27 cout << StepperMotor::StepperMotor() create << device_name << endl;
28
29 long i;
30
31 for (i=0; i< AGSM_MAX_MOTORS; i++)
32 {
33 axis[i] = 0;
34 position[i] = 0;
35 direction[i] = 0;
36 }
37
38 ptr = new Tango::DevLong[10];
39
40 get_device_properties();
41 }
42
43 void StepperMotor::delete_device()
44 {
45 delete [] ptr;
46 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-2 : Include the Tango master include file (tango.h) and the StepperMotor class definition file (steppermotor.h)
Line 4 : Open the StepperMotor namespace
Line 7-11 : The first form of the class constructor. It execute the Tango::DeviceImpl class constructor with the two parameters. Note that the device name passed to this constructor as a C++ string is passed to the Tango::DeviceImpl constructor as a classical C string. Then the init_device method is executed.
Line 13-17 : The second form of the class constructor. It execute the Tango::DeviceImpl class constructor with its two parameters. Then the init_device method is executed.
Line 19-23: The third form of constructor. Again, it execute the Tango::DeviceImpl class constructor with its three parameters. Then the init_device method is executed.
Line 25-41 : The init_device method. All the device data initialization is done in this method. The device properties are also retrieved from database with a call to the get_device_properties method at line 40. The device data member called ptr is initialized with allocated memory at line 38. It is not needed to have this pointer, it has been added only for educational purpose.
Line 43-46 : The delete_device method. The rule of this method is to free memory allocated in the init_device method. In our case , only the device data member ptr is allocated in the init_device method. Therefore, its memory is freed at line 45. This method is called by the automatically added Init command before it calls the init_device method. It is also called by the device destructor.

3 The methods used for the DevReadDirection command


The DevReadDirection command is created using the template command method. Therefore, there is no specific class needed for this command but only one object of the TemplCommandInOut class. This command needs two methods which are the dev_read_direction method and the direct_cmd_allowed method. The direct_cmd_allowed method defines here implements exactly the same behavior than the default one. This method has been used only for pedagogic issue. The dev_read_direction method will be executed by the execute method of the TemplCommandInOut class. The direct_cmd_allowed method will be executed by the is_allowed method of the TemplCommandInOut class.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 DevLong StepperMotor::dev_read_direction(DevLong axis)
2 {
3 if (axis < 0 || axis > AGSM_MAX_MOTORS)
4 {
5 WARNING_STREAM << Steppermotor::dev_read_direction(): axis out of range !;
6 WARNING_STREAM << endl;
7 TangoSys_OMemStream o;
8
9 o << Axis number << axis << out of range << ends;
10 throw_exception((const char *)StepperMotor_OutOfRange,
11 o.str(),
12 (const char *)StepperMotor::dev_read_direction);
13 }
14
15 return direction[axis];
16 }
17
18
19 bool StepperMotor::direct_cmd_allowed(const CORBA::Any &in_data)
20 {
21 INFO_STREAM << In direct_cmd_allowed() method << endl;
22
23 return true;
24 }
25

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-16 : The dev_read_direction method
Line 5-12 : Throw exception to client if the received axis number is out of range
Line 7 : A TangoSys_OMemStream is used as stream. The TangoSys_OMemStream has been defined in improve portability across platform. For Unix like operating system, it is a ostrtream type. For operating system with a full implementation of the standard library, it is a ostringstream type.
Line 19-24 : The direct_cmd_allowed method. The command input data is passed to this method in case of it is needed to take the decision. This data is still packed into the CORBA Any object.

4 The methods used for the Position attribute


To enable reading of attributes, the StepperMotor class must re-define two or three methods called read_attr_hardware(), read_<Attribute_name>() and if necessary a method called
is_<Attribute_name>_allowed(). The aim of the first one is to read the hardware. It will be called only once at the beginning of each read_attribute CORBA call. The second method aim is to build the exact data for the wanted attribute and to store this value into the Attribute object. Special care has been taken in order to minimize the number of data copy and allocation. The data passed to the Attribute object as attribute value is passed using pointers. It must be allocated by the method[*] and the Attribute object will not free this memory. Data members called attr_<Attribute_name>_read are foreseen for this usage. The read_attr_hardware() method receives a vector of long which are indexes into the main attributes vector of the attributes to be read. The read_Position() method receives a reference to the Attribute object. The third method (is_Position_allowed()) aim is to allow or dis-allow, the attribute reading. In some cases, some attributes can be read only if some conditions are met. If this method returns true, the read_<Attribute_name>() method will be called. Otherwise, an error will be generated for the attribute. This method receives one argument which is an emumeration describing the attribute request type (read or write). In our example, the reading of the Position attribute is allowed only if the device state is ON.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 void StepperMotor::read_attr_hardware(vector<long> &attr_list)
2 {
3 INFO_STREAM << In read_attr_hardware for << attr_list.size();
4 INFO_STREAM << attribute(s) << endl;
5
6 for (long i = 0;i < attr_list.size();i++)
7 {
8 string attr_name;
9 attr_name = dev_attr->get_attr_by_ind(attr_list[i]).get_name();
10
11 if (attr_name == Position)
12 {
13 attr_Position_read = &(position[0]);
14 }
15 else if (attr_name == Direction)
16 {
17 attr_Direction_read = &(direction[0]);
18 }
19 }
20 }
21
22 void read_Position(Tango::Attribute &att)
23 {
24 att.set_value(attr_Position_read);
25 }
26
27 bool is_Position_allowed(Tango::AttReqType req)
28 {
29 if (req == Tango::WRITE_REQ)
30 return false;
31 else
32 {
33 if (get_state() == Tango::ON)
34 return true;
35 else
36 return false;
37 }
38 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 6 : A loop on each attribute to be read
Line 9 : Get attribute name
Line 11 : Test on attribute name
Line 13 : Read hardware (pretty simple in our case)
Line 24 : Set attribute value in Attribute object using the set_value() method. This method will also initializes the attribute quality factor to Tango::ATTR_VALID if no alarm level are defined and will set the attribute returned date. It is also possible to use a method called set_value_date_quality() which allows the user to set the attribute quality factor as well as the attribute date.
Line 33 : Test on device state

5 The methods used for the SetPosition attribute


To enable writing of attributes, the StepperMotor class must re-define one or two methods called write_<Attribute_name>() and if necessary a method called is_<Attribute_name>_allowed(). The aim of the first one is to write the hardware. The write_Position() method receives a reference to the WAttribute object. The value to write is in this WAttribute object. The third method (is_Position_allowed()) aim is to allow or dis-allow, the attribute writing. In some cases, some attributes can be write only if some conditions are met. If this method returns true, the write_<Attribute_name>() method will be called. Otherwise, an error will be generated for the attribute. This method receives one argument which is an emumeration describing the attribute request type (read or write). For read/write attribute, this method is the same for reading and writing. The input argument value makes the difference.
For our example, it is always possible to write the SetPosition attribute. Therefore, the StepperMotor class only defines a write_SetPosition() method.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 void StepperMotor::write_SetPosition(Tango::WAttribute &att)
2 {
3 att.get_write_value(sttr_SetPosition_write);
4
5 INFO_STREAM << Attribute SetPosition value = ;
6 INFO_STREAM << attr_SetPosition_write << endl;
7
8 position[0] = attr_SetPosition_write;
9 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3 : Retrieve new attribute value
Line 5-6 : Send some messages using Tango Logging system
Line 8 : Set the hardware (pretty simple in our case)

6 Retrieving device properties


Retrieving properties is fairly simple with the use of the database object. Each Tango device is an aggregate with a DbDevice object (see figure [*]). This has been grouped in a method called get_device_properties(). The classes and methods of the Dbxxx objects are described in the Tango API documentation.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 void DocDs::get_device_property()
2 {
3 Tango::DbData data;
4 data.push_back(DbDatum(Max));
5 data.push_back(DbDatum(Min));
6
7 get_db_device()->get_property(data);
8
9 if (data[0].is_empty()==false)
10 data[0] >> max;
11 if (data[1].is_empty()==false)
12 data[1] >> min;
13 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 4-5 : Two DbDatum (one per property) are stored into a DbData object
Line 7 : Call the database to retrieve properties value
Line 9-10 : If the Max property is defined in the database, extract its value from the DbDatum object and store it in a device data member
Line 11-12 : If the Min property is defined in the database, extract its value from the DbDatum object and store it in a device data member

7 The remaining methods


The remaining methods are the dev_state, dev_status, always_executed_hook, dev_read_position and read_Direction() methods. The dev_state method parameters are fixed. It does not receive any input parameter and must return a Tango_DevState data type. The dev_status parameters are also fixed. It does not receive any input parameter and must return a Tango string. The always_executed_hook receives nothing and return nothing. The dev_read_position method input parameter is the motor number as a long and the returned parameter is the motor position also as a long data type. The read_Direction() method is the method for reading the Direction attribute.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 DevLong StepperMotor::dev_read_position(DevLong axis)
2 {
3
4 if (axis < 0 || axis > AGSM_MAX_MOTORS)
5 {
6 WARNING_STREAM << Steppermotor::dev_read_position(): axis out of range !;
7 WARNING_STREAM << endl;
8
9 TangoSys_OMemStream o;
10
11 o << Axis number << axis << out of range << ends;
12 throw_exception((const char *)StepperMotor_OutOfRange,
13 o.str(),
14 (const char *)StepperMotor::dev_read_position);
15 }
16
17 return position[axis];
18 }
19
20 void always_executed_hook()
21 {
22 INFO_STREAM << In the always_executed_hook method << endl;
23 }
24
25 Tango_DevState StepperMotor::dev_state()
26 {
27 INFO_STREAM << In StepperMotor state command << endl;
28 return DeviceImpl::dev_state();
29 }
30
31 Tango_DevString StepperMotor::dev_status()
32 {
33 INFO_STREAM << In StepperMotor status command << endl;
34 return DeviceImpl::dev_status();
35 }
36
37 void read_Direction(Tango::Attribute att)
38 {
39 att.set_value(attr_Direction_read);
40 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-18 : The dev_read_position method
Line 6-14 : Throw exception to client if the received axis number is out of range
Line 9 : A TangoSys_OMemStream is used as stream. The TangoSys_OMemStream has been defined in improve portability across platform. For Unix like operating system, it is a ostrtream type. For operating system with a full implementation of the standard library, it is a ostringstream type.
Line 20-23 : The always_executed_hook method. It does nothing. It has been included here only as pedagogic usage.
Line 25-29 : The dev_state method. It does exactly what the default dev_state does. It has been included here only as pedagogic usage
Line 31-35 : The dev_status method. It does exactly what the default dev_status does. It has been included here only as pedagogic usage
Line 37-40 : The read_Direction method. Simply set the Attribute object internal value

2 Using Java


1 The constructor


The constructor take a reference to the StepperMotorClass instance as first parameter[*]. The second parameter is the device name as a Java string.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 package StepperMotor;
2
3 import java.util.*;
4 import org.omg.CORBA.*;
5 import fr.esrf.Tango.*;
6 import fr.esrf.TangoDs.*;
7
8 public class StepperMotor extends DeviceImpl implements TangoConst
9 {
10 protected final int SM_MAX_MOTORS = 8;
11
12 protected int[] axis = new int[SM_MAX_MOTORS];
13 protected int[] position = new int[SM_MAX_MOTORS];
14 protected int[] direction = new int[SM_MAX_MOTORS];
15 protected int[] state = new int[SM_MAX_MOTORS];
16
17 protected int[] attr_Direction_read = new int[1];
18 protected int[] attr_Position_read = new int[1];
19 protected int attr_SetPosition_write;
20
21
22 StepperMotor(DeviceClass cl,String s,String desc,
23 DevState state,String status) throws DevFailed
24 {
25 super(cl,s,desc,state,status);
26 init_device();
27 }
28
29 public void init_device()
30 {
31 System.out.println(StepperMotor() create motor + dev_name);
32
33 int i;
34
35 for (i=0; i< SM_MAX_MOTORS; i++)
36 {
37 axis[i] = 0;
38 position[i] = 0;
39 direction[i] = 0;
40 state[i] = 0;
41 }
42
43 }
44 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3-6: Import different packages. The first one (java.lang.util) is a classical Java package from the JDK. The second one (org.omg.CORBA) is a package which contains all the CORBA related classes. The third one (fr.esrf.Tango) is the package generated by the IDL compiler from the Tango IDL file. The last one (fr.esrf.TangoDs) is the name of the package with all the root classes of the device server pattern.
Line 8 : The StepperMotor class inherits from the DeviceImpl class and implements the TangoConst interface. The TangoConst interface does not defines any method but simply defines constant variables. The TangoConst interface is a member of the TangoDs package.
Line 10 : Define an internal constant
Line 12-15 : Device internal variable
Line 17-19 : Device internal variable linked to attributes
Line 22-27 : The class constructor. It execute the DeviceImpl class constructor with five parameters. Then the init_device method is executed.
Line 29-43 : The init_device method. All the device data initialization is done in this method.

2 The methods used for the DevReadDirection command


The DevReadDirection command is created using the template command method. Therefore, there is no specific class needed for this command but only one object of the TemplCommandInOut class. This command needs two methods which are the dev_read_direction method and the direct_cmd_allowed method. The direct_cmd_allowed method defines here implements exactly the same behavior than the default one. This method has been used only for pedagogic issue. The dev_read_direction method will be executed by the execute method of the TemplCommandInOut class. The direct_cmd_allowed method will be executed by the is_allowed method of the TemplCommandInOut class.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public int dev_read_direction(int axis) throws DevFailed
2 {
3 if (axis < 0 || axis > SM_MAX_MOTORS)
4 {
5 Util.out1.println(Steppermotor.dev_read_direction(): axis out of range !);
6
7 StringBuffer o = new StringBuffer(Axis number );
8 o.append(axis);
9 o.append( out of range);
10
11 Except.throw_exception(StepperMotor_AxisOutOfRange,
12 o.toString(),
13 StepperMotor.dev_read_direction());
14 }
15
16 return direction[axis];
17 }
18
19 public boolean direct_cmd_allowed(Any data_in)
20 {
21 Util.out2.println(In StepperMotor.direct_cmd_allowed method);
22
23 return true;
24 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-17 : The dev_read_direction method
Line 3-14 : Throw exception to client if the received axis number is out of range
Line 19-24 : The direct_cmd_allowed method. The command input data is passed to this method in case of it is needed to take the decision. This data is still packed into the CORBA Any object.

3 The write attribute related method


To enable writing of writable attributes, the StepperMotor class must re-define a method called write_attr_hardware(). The aim of this method is to write the hardware. This method receives a vector of Integer objects as parameters. These data are the indexes of the attributes to be written into the main attribute vector stored in the MultiAttribute object. Methods of the MultiAttribute class allow the retrieval of the the correct attribute object from these indexes. The value to be written is stored in the WAttribute object and can be retrieved with WAttribute class methods called get_xx_write_value(). A data member called attr_<Attribute_name>_write is foreseen to temporary store this extracted value.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public void write_attr_hardware(Vector attr_list)
2 {
3 Util.out2.println(In write_attr_hardware for +attr_list.size()+ attribute(s));
4
5 for (int i = 0;i < attr_list.size();i++)
6 {
7 int ind = ((Integer)(attr_list.elementAt(i))).intValue();
8 WAttribute att = dev_attr.get_w_attr_by_ind(ind);
9 String att_name = att.get_name();
10
11 if (att_name.equals(SetPosition) == true)
12 {
13 attr_SetPosition_write = att.get_lg_write_value();
14 Util.out2.println(Attribute SetPosition value = +attr_SetPosition_write);
15 position[0] = attr_SetPosition_write;
16 }
17 }
18 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 5 : A loop on each attribute to be written
Line 7-9 : Retrieve attribute name
Line 11 : A test on attribute name
Line 13 : Retrieve new attribute value
Line 15 : Set the hardware (very simple in our example)

4 The read attribute related methods


To enable reading of attributes, the StepperMotor class must re-define two methods called read_attr_hardware() and read_attr(). The aim of the first one is to read the hardware. It will be called only once at the beginning of each read_attributes CORBA call. The second method aim is to build the exact data for the wanted attribute and to store this value into the Attribute object. This method will be called for each attribute to be read. Special care has been taken in order to minimize the number of data copy and allocation. The data passed to the Attribute object as attribute value is passed using pointers. It must be allocated by the method[*] and the Attribute object will not free this memory. Data members called attr_<Attribute_name>_read are foreseen for this usage. As for the write_attr_hardware() method, the read_attr_hardware() method receives a vector of long which are indexes into the main attributes vector of the attributes to be read. The read_attr() method receives a reference to the Attribute object.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public void read_attr_hardware(Vector attr_list)
2 {
3 Util.out2.println(In read_attr_hardware for +attr_list.size()+ attribute(s));
4 for (int i = 0;i< attr_list.size();i++)
5 {
6 int ind = ((Integer)(attr_list.elementAt(i))).intValue();
7 String attr_name = dev_attr.get_attr_by_ind(ind).get_name();
8
9 if (attr_name == Position)
10 {
11 attr_Position_read[0] = position[0];
12 }
13 else if (attr_name == Direction)
14 {
15 attr_Direction_read[0] = direction[0];
16 }
17 }
18 }
19
20
21 public void read_attr(Attribute attr) throws DevFailed
22 {
23 String attr_name = attr.get_name();
24 Util.out2.println(In read_attr for attribute +attr_name);
25 if (attr_name.equals(Position) == true)
26 {
27 attr.set_value(attr_Position_read);
28 }
29 else if (attr_name.equals(Direction) == true)
30 {
31 attr.set_value(attr_Direction_read);
32 }
33 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 4 : A loop on each attribute to be read
Line 6 -7: Get attribute name
Line9 : Test on attribute name
Line 11 : Read hardware (pretty simple in our case)
Line 23 : Get attribute name
Line 25 : Test on attribute name
Line 27 : Set attribute value in Attribute object

5 Retrieving device properties


Retrieving properties is fairly simple with the use of the database object. Each Tango device is an aggregate with a DbDevice object (see figure [*]). This has been grouped in a method called get_device_properties(). The classes and methods of the Dbxxx objects are described in the Tango API documentation.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 void public get_device_property() throws DevFailed
2 {
3 String[] prop_names = {Max,Min};
4
5 DbDatum[] res_value = db_dev.get_property(prop_names);
6
7 if (res_value[0].is_empty() == false)
8 min = res_value[0].extractInt();
9 if (res_value[1].is_empty() == false)
10 max = res_value[1].extractInt();
11 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3 : Define the names of the properties to be retrieved
Line 5 : Call the database to retrieve properties value
Line 7-8 : If the Max property is defined in the database, extract its value from the DbDatum object and store it in a device data member
Line 9-10 : If the Min property is defined in the database, extract its value from the DbDatum object and store it in a device data member

6 The remaining methods


The remaining methods are the dev_state, dev_status, always_executed_hook() and dev_read_position methods. The dev_state method parameters are fixed. It does not receive any input parameter and must return a DevState data type. The dev_status parameters are also fixed. It does not receive any input parameter and must return reference to a Java string. The always_executed_hook receives nothing and return nothing The dev_read_position method input parameter is the motor number as an int and the returned parameter is the motor position also as an int data type.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 int dev_read_position(int axis) throws DevFailed
2 {
3
4 if (axis < 0 || axis > SM_MAX_MOTORS)
5 {
6 Util.out1.println(Steppermotor.dev_read_position(): axis out of range !);
7
8 StringBuffer o = new StringBuffer(Axis number );
9 o.append(axis);
10 o.append( out of range);
11
12 Except.throw_exception(StepperMotor_AxisOutOfRange,
13 o.toString(),
14 StepperMotor.dev_read_position());
15 }
16
17 return position[axis];
18 }
19
20 public void always_executed_hook()
21 {
22 Util.out2.println(In always_executed_hook method);
23 }
24
25 public DevState dev_state() throws DevFailed
26 {
27 Util.out2.println(In StepperMotor state command);
28 return super.dev_state();
29 }
30
31 public String dev_status() throws DevFailed
32 {
33 Util.out2.println(In StepperMotor status command);
34 return super.dev_status();
35 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-18 : The dev_read_position method
Line 4-15 : Throw exception to client if the received axis number is out of range
Line 20-23 : The always_executed_hook method.It does nothing. It has been included here only as pedagogic usage.
Line 25-29 : The dev_state method. It does exactly what the default dev_state does. It has been included here only as pedagogic usage
Line 31-35 : The dev_status method. It does exactly what the default dev_status does. It has been included here only as pedagogic usage.

5 Device server under Windows


Two kind of programs are available under Windows. These kinds of programs are called console application or Windows application. A console application is started from a MS-DOS window and is very similar to classical UNIX program. A Windows application is most of the time not started from a MS-DOS window and is generally a graphical application without standard input/output. Writing a device server in a console application is straight forward following the rules described in the previous sub-chapters. Writing a device server in a Windows application needs some changes detailed in the following sub-chapters.


1 The Tango device server graphical interface


Within the Windows operating system, most of the running application has a window user interface. This is also true for the Windows Tango device server. Using or not this interface is up to the device server programmer. The choice is done with an argument to the server_init() method of the Tango::Util class. This interface is pretty simple and is based on three windows which are :

1 The device server main window


This window looks like :



Figure: Tango device server main window

\includegraphics[width=10cm]{ds_writing/nt_server/main}





Four menus are available in this window. The File menu allows the user to exit the device server. The View menu allows you to display/hide the device server console window. The Debug menu allows the user to change the server output verbose level. All the outputs goes to the console window even if it is hidden. The Help menu displays the help window. The device server name is displayed in the window title. The text displayed at the bottom of the window has a default value (the one displayed in this window dump) but may be changed by the device server programmer using the set_main_window_text() method of the Tango::Util class. If used, this method must be called prior to the call of the server_init() method. Refer to [8] for a complete description of this method.

2 The console window


This window looks like :



\includegraphics[width=14cm]{ds_writing/nt_server/cons}





It simply displays all the logging message when a console target is used in the device server.

3 The help window


This window looks like :



\includegraphics[width=9cm]{ds_writing/nt_server/help}





This window displays

2 MFC device server


There is no main function within a classical MFC program. Most of the time, your application is represented by one instance of a C++ class which inherits from the MFC CWinApp class. This CWinApp class has several methods that you may overload in your application class. For a device server to run correctly, you must overload two methods of the CWinApp class. These methods are the InitInstance() and ExitInstance() methods. The rule of these methods is obvious following their names.
Remember that if the Tango device server graphical user interface is used, you must link your device server with the Tango windows resource file. This is done by adding the Tango resource file to the Project Settings/Link/Input/Object, library modules window in VC++.

1 The InitInstance method


The code to be added here is the equivalent of the code written in a classical main() function. Don't forget to add the tango.h file in the list of included files.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 BOOL FluidsApp::InitInstance()
2 {
3 AfxEnableControlContainer();
4
5 // Standard initialization
6 // If you are not using these features and wish to reduce the size
7 // of your final executable, you should remove from the following
8 // the specific initialization routines you do not need.
9
10 #ifdef _AFXDLL
11 Enable3dControls(); // Call this when using MFC in a shared DLL
12 #else
13 Enable3dControlsStatic(); // Call this when linking to MFC statically
14 #endif
15 Tango::Util *tg;
16 try
17 {
18
19 tg = Tango::Util::init(m_hInstance,m_nCmdShow);
20
21 tg->server_init(true);
22
23 tg->server_run();
24
25 }
26 catch (bad_alloc)
27 {
28 MessageBox((HWND)NULL,Memory error,Command line,MB_ICONSTOP);
29 return(FALSE);
30 }
31 catch (Tango::DevFailed &e)
32 {
33 MessageBox((HWND)NULL,,e.errors[0].desc.in(),Command line,MB_ICONSTOP);
34 return(FALSE);
35 }
36 catch (CORBA::Exception &)
37 {
38 MessageBox((HWND)NULL,Exception CORBA,Command line,MB_ICONSTOP);
39 return(FALSE);
40 }
41
42 m_pMainWnd = new CWnd;
43 m_pMainWnd->Attach(tg->get_ds_main_window());
44
45 return TRUE;
46 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 19 : Initialise Tango system. This method also analises the argument used in command line.
Line 21 : Create Tango classes requesting the Tango Windows graphical interface to be used
Line 23 : Start Network listener. Note that under NT, this call returns in the contrary of UNIX like operating system.
Line 26-30 : Display a message box in case of memory allocation error and leave method with a return value set to false in order to stop the process
Line 31-35 : Display a message box in case of error during server initialization phase.
Line 36-40 : Display a message box in case of error other than memory allocation. Leave method with a return value set to false in order to stop the process.
Line 37-38 : Create a MFC main window and attach the Tango graphical interface main window to this MFC window.


2 The ExitInstance method


This method is called when the application is stopped. For Tango device server, its rule is to destroy the Tango::Util singleton if this one has been correctly constructed.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 int FluidsApp::ExitInstance()
2 {
3 bool del = true;
4
5 try
6 {
7 Tango::Util *tg = Tango::Util::instance();
8 }
9 catch(Tango::DevFailed)
10 {
11 del = false;
12 }
13
14 if (del == true)
15 delete (Tango::Util::instance());
16
17 return CWinApp::ExitInstance();
18 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 7 : Try to retrieve the Tango::Util singleton. If this one has not been constructed correctly, this call will throw an exception.
Line 9-12 : Catch the exception in case of incomplete Tango::Util singleton construction
Line 14-15 : Delete the Tango::Util singleton. This will unregister the Tango device server from the Tango database.
Line 17 : Execute the ExitInstance method of the CWinApp class.
If you don't want to use the Tango device server graphical interface, do not pass any parameter to the server_init() method and instead of the code display in lines 37 and 38 in the previous example of the InitInstance() method, use your own code to initialize your own application.

3 Example of how to build a Windows device server MFC based


This sub-chapter gives an example of what it is needed to do to build a MFC Windows device server. Rather than being a list of actions to strictly follow, this is some general rules of how using VC++ to build a Tango device server using MFC.
  1. Create your device server using Pogo. For a class named MyMotor, the following files will be needed : class_factory.cpp, MyMotorClass.h, MyMotorClass.cpp, MyMotor.h and MyMotor.cpp.
  2. On a Windows computer running VC++, create a new project of type ``MFC app Wizard (exe)'' using static MFC libs. Ask for a dialog based project without ActiveX controls.
  3. Copy the five files generated by Pogo to the Windows computer and add them to your project
  4. Remove the dialog window files (xxxDlg.cpp and xxxDlg.h), the Resource include file and the resource script file from your project
  5. Add #include <stdafx.h> as first line of the include files list in class_factory.cpp, MyMotorClass.cpp and MyMotor.cpp file. Also add your own directory and the Tango include directory to the project pre-compiler include directories list.
  6. Enable RTTI in your project settings (see chapter [*])
  7. Change your application class:
    1. Add the definition of an ExitInstance method in the declaration file. (xxx.h file)
    2. Remove the include of the dialog window file in the xxx.cpp file and add an include of the Tango master include files (tango.h)
    3. Replace the InitInstance() method as described in previous sub-chapter. (xx.cpp file)
    4. Add an ExitInstance() method as described in previous sub-chapter (xxx.cpp file)
  8. Add all the libraries needed to compile a Tango device server (see chapter [*]) and the Tango resource file to the linker Object/Libraries modules.

3 Win32 application


Even if it is more natural to use the C++ structure of the MFC class to write a Tango device server, it is possible to write a device server as a Win32 application. Instead of having a main() function as the application entry point, the operating system, provides a WinMain() function as the application entry point. Some code must be added to this WinMain function in order to support Tango device server. Don't forget to add the tango.h file in the list of included files. If you are using the project files generated by Pogo, don't forget to change the linker SUBSYSTEM option to Windows (Under Linker/System in the project properties window).

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 int APIENTRY WinMain(HINSTANCE hInstance,
2 HINSTANCE hPrevInstance,
3 LPSTR lpCmdLine,
4 int nCmdShow)
5 {
6 MSG msg;
7 Tango::Util *tg;
8
9 try
10 {
11 tg = Tango::Util::init(hInstance,nCmdShow);
12
13 string txt;
14 txt = Blabla first line\n;
15 txt = txt + Blabla second line\n;
16 txt = txt + Blabla third line\n;
17 tg->set_main_window_text(txt);
18 tg->set_server_version(2.2);
19
20 tg->server_init(true);
21
22 tg->server_run();
23
24 }
25 catch (bad_alloc)
26 {
27 MessageBox((HWND)NULL,Memory error,Command line,MB_ICONSTOP);
28 return (FALSE);
29 }
30 catch (Tango::DevFailed &e)
31 {
32 MessageBox((HWND)NULL,e.errors[0].desc.in(),Command line,MB_ICONSTOP);
33 return (FALSE);
34 }
35 catch (CORBA::Exception &)
36 {
37 MessageBox((HWND)NULL,Exception CORBA,Command line,MB_ICONSTOP);
38 return(FALSE);
39 }
40
41 while (GetMessage(&msg, NULL, 0, 0))
42 {
43 TranslateMessage(&msg);
44 DispatchMessage(&msg);
45 }
46
47 delete tg;
48
49 return msg.wParam;
50 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 11 : Create the Tango::Util singleton
Line 13-18 : Set parameters for the graphical interface
Line 20 : Initialize Tango device server requesting the display of the graphical interface
Line 22 : Run the device server
Line 25-39 : Display a message box for all the kinds of error during Tango device server initialization phase and exit WinMain function.
Line 41-45 : The Windows message loop
Line 47 : Delete the Tango::Util singleton. This class destructor unregisters the device server from the Tango database.
Remember that if the Tango device server graphical user interface is used, you must add the Tango windows resource file to your project.
If you don't want to use the tango device server graphical user interface, do not use any parameter in the call of the server_init() method and do not link your device server with the Tango Windows resource file.

4 Device server as NT service


With Windows, if you want to have processes which survive to logoff sequence and/or are automatically started during computer startup sequence, you have to write them as service. It is possible to write Tango device server as service. You need to
  1. Write a class which inherits from a pre-written Tango class called NTService. This class must have a start method.
  2. Write a main function following a predefined skeleton.

1 The service class


It must inherits from the NTService class and defines a start method. The NTService class must be constructed with one argument which is the device server executable name. The start method has three arguments which are the number of arguments passed to the method, the argument list and a reference to an object used to log info in the NT event system. The first two args must be passed to the Tango::Util::init method and the last one is used to log error or info messages. The class definition file looks like

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2 #include <ntservice.h>
3
4 class MYService: public Tango::NTService
5 {
6 public:
7 MYService(char *);
8
9 void start(int,char **,Tango::NTEventLogger *);
10 };

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1-2 : Some include files
Line 4 : The MYService class inherits from Tango::NTService class
Line 7 : Constructor with one parameter
Line 9 : The start() method

The class source code looks like

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <myservice.h>
2 #include <tango.h>
3
4 using namespace std;
5
6 MYService::MYService(char *exec_name):NTService(exec_name)
7 {
8 }
9
10 void MYService::start(int argc,char **argv,Tango::NTEventLogger *logger)
11 {
12 Tango::Util *tg;
13 try
14 {
15 Tango::Util::_service = true;
16
17 tg = Tango::Util::init(argc,argv);
18
19 tg->server_init();
20
21 tg->server_run();
22 }
23 catch (bad_alloc)
24 {
25 logger->error(Can't allocate memory to store device object);
26 }
27 catch (Tango::DevFailed &e)
28 {
29 logger->error(e.errors[0].desc.in());
30 }
31 catch (CORBA::Exception &)
32 {
33 logger->error(CORBA Exception);
34 }
35 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 6-8 : The MYService class constructor code.
Line 15 : Set to true the _service static variable of the Tango::Util class.
Line 17-21 : Classical Tango device server startup code
Line 23-34 : Exception management. Please, note that within a service. it is not possible to print data on a console. This method receives a reference to a logger object. This object sends all its output to the Windows event system. It is used to send messages when an exception has occurred.

2 The main function


The main function is used to create one instance of the class describing the service, to check the service option and to run the service. The code looks like :

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #include <tango.h>
2 #include <MYService.h>
3
4 using namespace std;
5
6
7 int main(int argc,char *argv[])
8 {
9 MYService service(argv[0]);
10
11 int ret;
12 if ((ret = service.options(argc,argv)) <= 0)
13 return ret;
14
15 service.run(argc,argv);
16
17 return 0;
18 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 9 : Create one instance of the MYService class with the executable name as parameter
Line 12 : Check service option with the options() method inherited from the NTService class.
Line 15 : Run the service. The run() method is inherited from the NTService class. This method will after some NT initialization sequence execute the user start() method.

3 Service options and messages


When a Tango device server is written as a Windows service, it supports several new options. These option are linked to Windows service usage.
Before it can be used, a service must be installed. A name and a title is associated to each service. For Tango device server used as service, the service name is build from the executable name followed by the underscore character and the instance name. For example, a device server service executable file named ``opc'' and started with ``fluids'' as instance name, will be named ``opc_fluids''. The title string is built from the service executable name followed by the sentence ``Tango device server'' and the instance name between parenthesis. In the previous example, the service title will be ``opc Tango device server (fluids)''. Once a service is installed, you can configure it with the ``Services'' application of the control panel. Services title are displayed by this application and allow the user to select one specific service. Once a service is selected, it is possible to start/stop it and to configure its startup type as manual (with the Services application) or as automatic. When the automatic mode is chosen, the service starts when the computer is started. In this case, the service executable code must resides on the computer local disk.
Tango device server logs message in the Windows event system when the service is started or stopped. You can see these messages with the ``Event Viewer'' application (Start->Programs->Administrative tools->Event Viewer) and choose the Application events.
The new options are -i, -s, -u, -h and -d. On the command line, all these options must be used after the device server instance name (``opc fluids -i'' to install the service, ``opc fluids -u'' to un-install the service, ``opc fluids -v -d'' to debug the service)

4 Tango device server using MFC as Windows service


If your Tango device server uses MFC and must be written as a Windows NT service, follow these rules :

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 int main(int argc,char *argv[])
2 {
3 if (!AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0))
4 {
5 cerr << Can't initialise MFC ! << endl;
6 return -1;
7 }
8
9 service serv(argv[0]);
10
11 int ret;
12 if ((ret = serv.options(argc,argv)) <= 0)
13 return ret;
14
15 serv.run(argc,argv);
16
17 return 0;
18 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3 : The MFC classes are initialized with the AfxWinInit() function call.


6 Compiling, linking and executing a TANGO device server process


1 Compiling and linking a C++ device server


1 On UNIX like operating system


1 Supported development tools


The supported compiler for Linux is gcc release 3.3 and above. Please, note that to debug a Tango device server running under Linux, gdb release 7 and above is needed in order to correctly handle threads.


2 Compiling


TANGO for C++ uses omniORB (release 4) as underlying CORBA Object Request Broker [11] and starting with Tango 8, the ZMQ library. To compile a TANGO device server, your include search path must be set to :


3 Linking


To build a running device server process, you need to link your code with several libraries. Nine of them are always the same whatever the operating system used is. These nine libraries are: On top of that, you need additional libraries depending on the operating system : The following table summarizes the necessary options to compile a Tango C++ device server. Please, note that starting with Tango 8 and for gcc release 4.3 and later, some C++11 code has been used. This requires the compiler option -std=c++0x. Obviously, the options -I and -L must be updated to reflect your file system organization.



Operating system Compiling option Linking option
Linux gcc -D_REENTRANT -std=c++0x -I.. -L.. -ltango -llog4tango -lomniORB4 -lomniDynamic4 -lCOS4 -lomnithread -lzmq -lpthread





The following is an example of a Makefile for Linux. Obviously, all the paths are set to the ESRF file system structure.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

[language=make]ds_writing/compiling/Makefile_doc.lines

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 5-7 : Define Makefile macros
Line 9-10 : Set the include file search path
Line 12 : Set the linker library search path
Line 15 : The compiler option setting
Line 16-23 : The linker option setting
Line 26-30 : All the object files needed to build the executable
Line 33-35 : Define rules to generate object files
Line 38 : Define a ``all'' dependency
Line 40-41 : How to generate the StepperMotor device server executable
Line 43-44 : Define a ``clean'' dependency


2 On Windows using Visual Studio


Supported Windows compiler for Tango is Visual C++ 2008 (VC 9) and above. Most problems in building a Windows device server revolve around the /M compiler switch family. This switch family controls which run-time library names are embedded in the object files, and consequently which libraries are used during linking. Attempt to mix and match compiler settings and libraries can cause link error and even if successful, may produce undefined run-time behavior.
Selecting the correct /M switch in Visual Studio is done through a dialog box. To open this dialog box, click on the ``Project'' menu (once the correct project is selected in the Solution Explorer window) and select the ``Properties'' option. To change the compiler switch open the ``C/C++'' tree and select ``Code Generation''. The following options are supported. Compiling a file with a value of the /M switch family will impose at link phase the use of libraries also compiled with the same value of the /M switch family. If you compiled your source code with the /MT option (Multithreaded), you must link it with libraries also compiled with the /MT option.
On both 32 or 64 bits computer, omniORB and TANGO relies on the preprocessor identifier WIN32 being defined in order to configure itself. If you build an application using static libraries (option /MT or /MTd), you must add _WINSTATIC to the list of the preprocessor identifiers. If you build an application using DLL (option /MD or /MDd), you must add LOG4TANGO_HAS_DLL and TANGO_HAS_DLL to the list of preprocessor identifiers.
To build a running device server process, you need to link your code with several libraries on top of the Windows libraries. These libraries are:
Compile mode Libraries
Debug Multithreaded omniORB4d.lib, omniDynamic4d.lib, omnithreadd.lib and COS4d.lib
Multithreaded omniORB4.lib, omniDynamic4.lib, omnithread.lib and COS4.lib
Debug Multithreaded DLL omniORB416_rtd.lib, omniDynamic416_rtd.lib, omnithread34_rtd.lib,
  and COS416_rtd.lib
Multithreaded DLL omniORB416_rt.lib, omniDynamic416_rt.lib, omnithread34_rt.lib
  and COS416_rt.lib

To add these libraries in Visual Studio, open the project property pages dialog box and open the ``Link'' tree. Select ``Input'' and add these library names to the list of library in the ``Additional Dependencies'' box.
The ``Win32 Debug'' or ``Win32 Release'' configuration that you change within the Configuration Manager window changes the /M switch compiler. For instance, if you select a ``Win32 Debug'' configuration in a non-DLL project, use the omniORB4d.lib, omniDynamic4d.lib and omnithreadd.lib libraries plus the tangod.lib, log4tangod.lib and zmqd.lib libraries. If you select the ``Win32 Release'' configuration, use the omniORB4.lib, omniDynamic4.lib and omnithread.lib libraries plus the tango.lib, log4tango.lib and zmq.lib libraries.
WARNING: In some cases, the Microsoft Visual Studio wizard used during project creation generates one include file called Stdafx.h. If this file itself includes windows.h file, you have to add the preprocessor macro _WIN32_WINNT and set it to 0x0500.


2 Running a C++ device server


To run a C++ Tango device server, you must set an environment variable. This environment variable is called TANGO_HOST and has a fixed syntax which is
TANGO_HOST=<host>:<port>
The host field is the host name where the TANGO database device server is running. The port field is the port number on which this server is listening. For instance, a valid syntax is TANGO_HOST=dumela:10000. For UNIX like operating system, setting environment variable is possible with the export or setenv command depending on the shell used. For Windows, setting environment variable is possible with the ``Environment'' tab of the ``System'' application in the control panel.
If you need to start a Tango device server on a pre-defined port (For Tango database device server or device server without database usage), you must use one of the underlying ORB option endPoint like
myserver myinstance_name -ORBendPoint giop:tcp::<port number>

3 Compiling a Java device server


1 Supported java release


Tango device server written using Java language needs release 1.5.0 (or above) of the Java environment.

2 Setting the CLASSPATH


To correctly compile a Java Tango device server, the CLASSPATH environment variable must be set to : For UNIX like operating system, setting environment variable is done with the export or setenv command depending on the shell used. For Windows, setting environment variable is possible with the ``Environment'' tab of the ``System'' application in the control panel.

3 Makefile


The following is an example of a Makefile for a Java Tango device server. Obviously, all the paths are set to the ESRF file system structure.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 #
2 # Makefile to generate a TANGO java device server
3 #
4
5 JAVAC = javac -classpath $(CLASSPATH):..
6
7 # ------------------------
8 #
9 # The compiler flags
10 #
11 #-------------------------
12
13 JAVAFLAGS = -g
14
15 #-------------------------
16
17
18 CL_LIST = DevReadPositionCmd.class \
19 StepperMotor.class \
20 StepperMotorClass.class
21
22 PACKAGE = server
23
24 #
25 # Rule for compiling
26 #
27
28 .SUFFIXES: .class .java
29 .java.class:
30 $(JAVAC) $(JAVAFLAGS) $<
31
32 #--------------------------
33
34
35 all: $(PACKAGE)
36
37 $(PACKAGE): $(CL_LIST)
38
39 clean:
40 rm -f *.class

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 5 : Definition of the java compiler
Line 13 : The java compiler flag
Line 18 : List of class to be compiled
Line 28 : Define a dependency name
Line 29-30 : Define how source files must be compiled
Line 35 : The ``all'' dependency
Line 47 : The device server dependency
Line 39-40 : The ``clean'' dependency

4 Tango core software release number


All the Tango core classes are packaged in the Tango.jar file. A little utility tool called TangoVers allows a user to know which release of the Tango core classes he(she) is using. This utility is available only with Java 1.2 virtual machine. To run this utility, simply type
TangoVers <path to Tango.jar file>
if the directory /segfs/tango/bin is in your PATH environment variable.

4 Running a Java device server


A correct setting of the CLASSPATH environment variable is not enough to run a Java Tango device server. You must also set a Java system property. The name of the system property is TANGO_HOST and its syntax is the same than the syntax described in chapter [*]. Setting a Java system property is done by using -D option of the java interpreter command. To run a Java Tango device server, the command line must start with
java -DTANGO_HOST=<host>:<port> xxxx
As all the device server files are part of a package, you have to run this command in the directory above the package directory. For instance, for our StepperMotor device server started with et as instance name, all files must be stored in a directory called StepperMotor and the command line must be
java -DTANGO_HOST=<host>:<port> StepperMotor/StepperMotor et
run from the directory above the StepperMotor one.
If you need to start a Tango device server on a pre-defined port (For Tango database device server or device server without database usage), you must use one of the underlying ORB option OAPort like
java -DOAPort=<port number> myserver myinstance_name

7 Advanced programming techniques


The basic techniques for implementing device server pattern are required by each device server programmer. In certain situations, it is however necessary to do things out of the ordinary. This chapter will look into programming techniques which permit the device server serve more than simply the network.

1 Receiving signal (C++ specific)


It is UNSAFE to use any CORBA call in a signal handler. It is also UNSAFE to use some system calls in a signal handler. Tango device server solved this problem by using threads. A specific thread is started to handle signals. Therefore, every Tango device server is automatically a threaded process. This allows the programmer to write the code which must be executed when a signal is received as ordinary code. All device server threads masks all signals except the specific signal thread which is permanently waiting for signal. If a signal is sent to a device server process, only the signal thread will receive it because it is the single thread which does not mask signals.
Nevertheless, signal management is not trivial and some care have to be taken. The signal management differs from operating system to operating system. It is not recommended that you install your own signal routine using any of the signal routines provided by the operating system calls or library.

1 Using signal


It is possible for C++ device server to receive signals from drivers or other processes. The TDSOM supports receiving signal at two levels: the device level and the class level. Supporting signal at the device level means that it is possible to specify interest into receiving signal on a device basis. This feature is supported via three methods defined in the DeviceImpl class. These methods are called register_signal, unregister_signal and signal_handler.
The register_signal method has one parameter which is the signal number. This method informs the device server signal system that the device want to be informed when the signal passed as parameter is received by the process. There is a special case for Linux as explained in the previous sub-chapter. It is possible to register a signal to be executed in the a signal handler context (with all its restrictions). This is done with a second parameter to this register_signal method. This second parameter is simply a boolean data. If it is true, the signal_handler will be executed in a signal handler context in the device server main thread. A default value (false) has been defined for this parameter.
The unregister_signal method also have an input parameter which is the signal number. This method removes the device from the list of object which should be warned when the signal is received by the process.
The signal_handler method is the method which is triggered when a signal is received if the corresponding register_signal has been executed. This method is defined as virtual and can be redefined by the user. It has one input argument which is the signal number.
The same three methods also exist in the DeviceClass class. Their action and their usage are similar to the DeviceImpl class methods. Installing a signal at the class level does not mean that all the device belonging to this class will receive the signal. This only means that the signal_handler method of the DeviceClass instance will be executed. This is useful if an action has to be executed once for a class of devices when a signal is received.
The following code is an example with our stepper motor device server configured via the database to serve three motors. These motors have the following names : id04/motor/01, id04/motor/02 and id04/motor/03. The signal SIGALRM (alarm signal) must be propagated only to the motor number 2 (id04/motor/02)

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

 1  void StepperMotor::init_device()
 2  {
 3      cout << StepperMotor::StepperMotor() create motor  << dev_name << endl;
 4  
 5      long i;
 6  
 7      for (i=0; i< AGSM_MAX_MOTORS; i++)
 8      {
 9          axis[i] = 0;
10          position[i] = 0;
11          direction[i] = 0;
12      }
13  
14      if (dev_name == id04/motor/02)
15          register_signal(SIGALRM);
16  }
17  
18  StepperMotor::~StepperMotor()
19  {
20      unregister_signal(SIGALRM);
21  }
22  
23  void StepperMotor::signal_handler(long signo)
24  {
25      INFO_STREAM << Inside signal handler for signal  << signo << endl;
26  
27  //  Do what you want here
28  
29  }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

The init_device method is modified.
Line 14-15 : The device name is checked and if it is the correct name, the device is registered in the list of device wanted to receive the SIGALARM signal.
The destructor is also modified
Line 20 : Unregister the device from the list of devices which should receives the SIGALRM signal. Note that unregister a signal for a device which has not previously registered its interest for this signal does nothing.
The signal_handler method is redefined
Line 25 : Print signal number
Line 27 : Do what you have to do when the signal SIGALRM is received.
If all devices must be warned when the device server process receives the signal SIGALRM, removes line 14 in the init_device method.

2 Exiting a device server gracefully


A device server has to exit gracefully by unregistering itself from the database. The necessary action to gracefully exit are automatically executed on reception of the following signal : This does not prevents device server to also register interest at device or class levels for those signals. The user installed signal_handler method will first be called before the graceful exit.


2 Inheriting


This sub-chapter details how it is possible to inherit from an existing device pattern implementation. As the device pattern includes more than a single class, inheriting from an existing device pattern needs some explanations.
Let us suppose that the existing device pattern implementation is for devices of class A. This means that classes A and AClass already exists plus classes for all commands offered by device of class A. One new device pattern implementation for device of class B must be written with all the features offered by class A plus some new one. This is easily done with the inheritance. Writing a device pattern implementation for device of class B which inherits from device of class A means :

1 Using C++


The miscellaneous code fragments given below detail only what has to be updated to support device pattern inheritance

1 Writing the BClass


As you can guess, BClass has to inherit from AClass. The command_factory method must also be adapted.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 namespace B
2 {
3
4 class BClass : public A::AClass
5 {
6 .....
7 }
8
9 BClass::command_factory()
10 {
11 A::AClass::command_factory();
12
13 command_list.push_back(....);
14 }
15
16 } /* End of B namespace */

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Open the B namespace
Line 4 : BClass inherits from AClass which is defined in the A namespace.
Line 11 : Only the command_factory method of the BClass will be called at start-up. To create the AClass commands, the command_factory method of the AClass must also be executed. This is the reason of the line
Line 13 : Create BClass commands

2 Writing the B class


As you can guess, B has to inherits from A.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 namespace B
2 {
3
4 class B : public A:A
5 {
6 .....
7 };
8
9 B::B(Tango::DeviceClass *cl,const char *s):A::A(cl,s)
10 {
11 ....
12 init_device();
13 }
14
15 void B::init_device()
16 {
17 ....
18 }
19
20 } /* End of B namespace */

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : Open the B namespace.
Line 4 : B inherits from A which is defined in the A namespace
Line 9 : The B constructor calls the right A constructor

3 Writing B class specific command


Noting special here. Write these classes as usual


4 Redefining A class command


It is possible to redefine a command which already exist in class A only if the command is created using the inheritance model (but keeping its input and output argument types). The method which really execute the class A command is a method implemented in the A class. This method must be defined as virtual. In class B, you can redefine the method executing the command and implement it following the needs of the B class.

2 Using Java


The miscellaneous code fragments given below detail only what has to be updated to support device pattern inheritance

1 Writing the BClass


As you can guess, BClass has to inherit from AClass. Some change must be done in the definition of the init and instance methods. The command_factory method must also be adapted.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public class BClass extends AClass implements TangoConst
2 {
3 public static AClass init(String name) throws DevFailed
4 {
5
6 }
7
8 public static AClass instance()
9 {
10
11 }
12
13 public void command_factory()
14 {
15 super.command_factory();
16
17 command_list.addElement(....);
18 }
19 };

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : BClass inherits from AClass and implements TangoConst interface
Line 3 : The return data type of the init method must be the same as the type defines in the AClass (therefore a reference to AClass) otherwise, the compiler complains. BClass inherits from AClass and a reference to a BClass is also a reference to the AClass
Line 8 : The return data type of the instance method must also be adapted as explained for the init method
Line 15 : Only the command_factory method of the BClass will be called at start-up. To create the AClass commands, the command_factory method of the AClass must also be executed. This is the reason of the line
Line 17 : Create BClass commands

2 Writing the B class


As you can guess, B has to inherits from A. The init_device method must be adapted, the constructor has to be modified and an instance variable must be added

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public class B extends A implements TangoConst
2 {
3 boolean constructed = false;
4
5 A(DeviceClass cl,String s)
6 {
7 super(cl,s);
8 constructed = true;
9 ...
10 init_device();
11 }
12
13 public void init_device()
14 {
15 if (constructed == false)
16 {
17 return;
18 }
19 super.init_device();
20
21 ...
22 }
23 };

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 1 : B inherits from A and implements TangoConst interface
Line 3 : A boolean initialized to false is added as instance variable
Line 8 : The constructor is modified to set the constructed boolean to true after all the super classes have been created and before the call to the init_device method.
Line 15-18 : The init_device method immediately returns if the constructed boolean is false (if the super classes are not correctly created)
Line 19 : The init_device method of class A is called

3 Writing B class specific command


Noting special here. Write these classes as usual

4 Redefining A class command


It is possible to to redefine a command which already exist in class A only if the command is created using the inheritance model (but keeping its input and output argument types). The method which really execute the class A command is a method implemented in the A class. With Java, it is possible to redefine all methods except those which are declared as ``final''. Therefore, in class B, you can redefine the method executing the command and implement it following the needs of the B class. The following is an example for a command xxx which is programmed to call a my_cmd method[*].

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

1 public class A extends DeviceImpl implements TangoConst
2 {
3 public void my_cmd(long input)
4 {
5 }
6 }
7
8 public class B extends A implements TangoConst
9 {
10 public void my_cmd(long input)
11 {
12 }
13 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Line 3 : The my_cmd method is defined in class A
Line 10 : The my_cmd method is redefined in class B
Inside the device pattern, the device object is created as an instance of class B[*]. Java will call the my_cmd method of the B class when the command is received. It is still possible to call the my_cmd method of the A class with the help of the Java ``super'' keyword inside the code of the my_cmd method of the B class.

3 Using another device pattern implementation within the same server


It is often necessary that inside the same device server, a method executing a command needs a command of another class to be executed. For instance, a device pattern implementation for a device driven by a serial line class can use the command offered by a serial line class embedded within the same device server process. To execute one of the command (or any other CORBA operations/attributes) of the serial line class, just call it as a normal client will do by using one instance of the Deviceproxy class. The ORB will recognize that all the devices are inside the same process and will execute calls as a local calls. To create the DeviceProxy class instance, the only thing you need to know is the name of the device you gave to the serial line device. Retrieving this could be easily done by a Tango device property. The DeviceProxy class is fully described in chapters related to the Java or C++ Tango Application Programming Interface (API)




Image tango-08-39


Emmanuel Taurel 2012-06-06