OPC Server Manual
OPC Server Manual
Computing
OPC Automation Interface 2
Manual
C79000–G7076–C225–01
Edition: 3
Safety Guidelines
This manual contains notices which you should observe to ensure your own personal safety, as well as to
protect the product and connected equipment. These notices are highlighted in the manual by a warning
triangle and are marked as follows according to the level of danger:
Danger
! indicates that death, severe personal injury or substantial property damage will result if proper precau-
tions are not taken.
Warning
! indicates that death, severe personal injury or substantial property damage can result if proper precau-
tions are not taken.
Caution
! indicates that minor personal injury or property damage can result if proper precautions are not taken.
Note
draws your attention to particularly important information on the product, handling the product, or to a
particular part of the documentation.
Qualified Personnel
Only qualified personnel should be allowed to install and work on this equipment. Qualified persons are
defined as persons who are authorized to commission, to ground, and to tag circuits, equipment, and sys-
tems in accordance with established safety practices and standards.
Correct Usage
Note the following:
Warning
! This device and its components may only be used for the applications described in the catalog or the
technical descriptions, and only in connection with devices or components from other manufacturers
which have been approved or recommended by Siemens.
This product can only function correctly and safely if it is transported, stored, set up, and installed cor-
rectly, and operated and maintained as recommended.
Trademarks
SIMATIC, SIMATIC HMI and SIMATIC NET are registered trademarks of SIEMENS AG.
Some of other designations used in these documents are also registered trademarks; the owner’s rights
may be violated if they are used by third parties for their own purposes.
Siemens AG
Automation and Drives (A&D)
Industrial Automation Systems (AS) Siemens AG 1999
Postfach 4848, D- 90327 Nürnberg Technical data subject to change.
Siemens Aktiengesellschaft
Contents
1 OPC Custom Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-1
1.1 Creating and Using an OLE Object in C/C++ . . . . . . . . . . . . . . . . . . . . . . . . 1-2
1.2 Additional Information about the Interface Description for the OPC Custom
Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-5
1.3 The “OPC Server” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-6
1.4 Objects of the “OPC Group” Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-11
1.5 IDataObject Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-16
2 OPC Automation Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-1
2.1 Creating and Using an OLE Object in Visual Basic . . . . . . . . . . . . . . . . . . . 2-2
2.2 Object Model for the Automation Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 2-5
2.3 The “OPCServer” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-6
2.4 The “OPCBrowser” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-8
2.5 The “OPCGroups” Collection Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-10
2.6 The “OPCGroup” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-12
2.7 The “OPCItems” Collection Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-15
2.8 The “OPCItem” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-17
Figures
1-1 OPC Server Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-6
1-2 OPC Group Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-11
1-3 IAdviseSink (Client) and IDataObject (Server) Interfaces . . . . . . . . . . . . . . 1-16
2-1 Activating the Reference for the Automation Interface . . . . . . . . . . . . . . . . 2-2
2-2 Object Model for the Automation Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 2-5
Tables
1-1 Objects and Interfaces of the OPC Custom Interface . . . . . . . . . . . . . . . . 1-5
2-1 Properties of the “OPCServer” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-6
2-2 Properties of the “OPCBrowser” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-8
2-3 Properties of the “OPCGroups” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-10
2-4 Properties of the “OPCGroup” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-12
2-5 Properties of the “OPCItems” Collection Object . . . . . . . . . . . . . . . . . . . . . 2-15
2-6 Properties of the “OPCItem” Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-17
The following sections illustrate step-by-step how you can call the methods of an
instance of an OLE class in C++. Note the difference between the term “Class” in
OLE and in C++:
OLE Classes: A Windows object is an instance of an OLE class. The term OLE
class differs from the class in C++.
C++ Classes: A class in C++ is a type definition. An OLE class is, however, an
object description and does not contain types.
ProgID
To simplify the identification of OPC servers, there is normally a readable name,
the ProgID assigned in the CLSIDs. While a CLSID is always unique due to the
algorithm for compilation, it is possible that a ProgID exists more than once. Just
like the CLSID, the ProgID is specified by the vendor of an OPC server.
The ProgID for the OPC server of Computing is: OPCServer.WinAC
Return Values
All the listed methods return a result of the type HRESULT.
Object Interface
IOPCServer
IOPCServerPublicGroups (optional)
IOPCBrowseServerAddressSpace (optional)
OPCServer IOPCItemProperties (new with V 2.0)
IConnectionPointContainer (new with V 2.0)
IOPCCommon (new with V 2.0)
IPersistFile (optional)
IOPCGroupStateMgt
IOPCPublicGroupStateMgt (optional)
IOPCASyncIO2 (new with V 2.0)
IOPCAsyncIO (no longer necessary with V2.0)
OPCGroup
IOPCItemMgt
IConnectionPointContainer (new with V 2.0)
IOPCSyncIO
IDataObject (no longer necessary with V2.0)
EnumOPCItemAttributes IEnumOPCItemAttributes
The OPC server class has various attributes that contain information about the
status, the version etc. of an OPC server object. The OPC server class also has
methods with which a client can manage the objects of the OPC group class. A
client application addresses only an object of this class directly using COM
mechanisms. The other objects are created by corresponding OPC methods.
The methods of the IOPCServer interface are used to manage the objects in the
OPC group class. Using the methods of the IOPCBrowseServerAddressSpace
interface, it is possible to investigate the address area of the server.
Figure 1-1 illustrates the “OPC server” object with its interfaces.
IUnknown
IOPCServer
IOPCBrowseServerAddressSpace
OPC
IOPCCommon (V2.0) Server Object
IConnectionPointContainer (V2.0)
IOPCItemsProperties (V2.0)
IOPCServer Interface
This interface contains methods to manage groups within a server object. It is also
possible to obtain information about the current status of the server.
Supplies an additional interface pointer for the name of a private group, in other
words the reference counter is incremented.
GetStatus ( ppServerStatus )
IOPCBrowseServerAddressSpace Interface
This interface contains methods with which the address area of the server can be
queried. The address area contains all the OPC items known to the server.
Supplies a string of the type ”IEnumString” whose content is specified by the call
parameters. The position from which the list is created can be set using the
”ChangeBrowsePosition” method.
Notes:
“BRANCH” excludes the filters for Type and AccessRights.
The rules for creating a filter are as follows:
– Asterisk (*) Any character string, including empty strings
– Plus (+) Any character string, however at least one character
– Question marks (?) Any single character
– Square brackets ( [ ] ) One single character from the specified set
To use one of the filter characters, this must be preceded by a back slash (\).
Allows you to browse through the address area. You can change to the higher level
or to a branch.
QueryOrganization ( pNameSpaceType )
Supplies the structure of the address area. The address area can be organized
with a flat or hierarchical structure.
Note: The structure of the address area of the OPC server for Computing is
structured hierarchically.
SetLocaleID (dwLcid)
Sets the language code of the server. The language code specifies the language in
which the server outputs text.
Note: The OPC server for Computing supports English and German.
GetLocaleID (pdwLcid)
QueryAvailableLocaleIDs (pdwLcid)
Provides the error text for a specific error code in the set language.
SetClientName (szName)
Transfers a descriptive text for the client to the server. The descriptive text can be
used for any purpose by the server, for example for logging in trace files.
IConnectionPointContainer Interface
This interface is a standard COM interface for reporting asynchronous events via
connection points. For more detailed information about using connection points,
refer to the documentation of OLE/COM.
Provides (whenever possible for the propertyID) a list of ItemIDs for a list of
PropertyIDs. These ItemIDs can be included in a group simplifying and speeding
up access to the data.
Note: The OPC server for Computing does not support this function. The call is
rejected with error message 0x8004001 (not implemented).
The “OPC Group” class manages the individual process variables, the OPC items.
Using these group objects, a client can form semantically meaningful units of OPC
items and execute operations with them.
Figure 1-2 illustrates an object of the “OPC Group” class and its interfaces.
IUnknown
IOPCItemMgt
IOPCGroupStateMgt
IOPCSyncIO
IOPCAsyncIO
IDataObject
OPC
IOPCItemsAttributes Group Object
IOPCAsyncIO2 (V 2.0)
IConnectionPointContainer (V 2.0)
IOPCItemMgt Interface
This interface provides methods to manage more than one item in a group.
Time Stamp
With each value read, OPC supplies a time stamp. This indicates when this value
was received or when it was changed. Since the SIMATIC systems do not use a
time stamp, the time at which the value is received on the server is used as the
time stamp.
This name can be specified completely in the ItemID. The AccessPath must
then be empty.
As an alternative, the part of the name in square brackets can be included in
the AccessPath.
Example: AccessPath: “”
MD0:Real
It is possible to add the same OPC item to the same group more than once. In
this case each of these items nevertheless has its own server handle.
The server handles of the items are only unique within a group and not for all
items of all groups.
Valid data types are as follows: VT_UI1, VT_UI2, VT_UI4, VT_I1, VT_I2,
VT_I4, VT_R4, VT_BOOL, VT_BSTR
Checks the validity of an OPC item, for example whether it was added to a group
without any error occurring, and supplies information such as the canonical data
type.
Note: See AddItem
IOPCGroupStateMgt Interface
The IOPCGroupStateMgt interface provides methods with which groups can be
managed. It is possible to edit group-specific parameters and to copy groups.
Creates a copy of a group. All group attributes are copied except for the following:
The active state is set to FALSE
A new server handle is assigned
Note: The “szName” parameter can be empty. In this case a unique name is
generated (see AddGroup).
Fetches the status of the group. The client application must inform the OPC server
where the results are to be stored using a pointer.
Notes:
The “pTimeBias” parameter has no significance for the OPC server for
Computing.
The “pPercentDeadband” parameter has no significance for the OPC server for
Computing.
The “LCID” parameter, in other words language-specific textual values in
read/write, has no significance for SIMATIC variables.
SetName ( szName )
Allows the name of a group to be changed. The name must always be unique.
IOPCSyncIO Interface
This interface provides methods for synchronous reading and writing. Synchronous
means that the client waits until the read or write operation is completed and only
then continues execution.
The use of synchronous calls is recommended when the client requires the result
for further processing. Other clients are not blocked since the OPC server for
Computing starts a separate thread for each client.
In general, it is advisable to use the IData interface for processing variable
changes (or IAdviseSink on the client side). This interface guarantees the highest
possible data throughput and also reduces the actual number of calls to the
absolute minimum (only when changes occur).
Reads the values, status information or time stamp of one or more items in a
group. The values can be read from the cache of the server or directly from the
hardware. Reading from the cache is, however, only possible when the group is
activated.
Note: The call is monitored by the timeout monitoring on the server. The
corresponding configuration parameter is “Read/Write Timeout”.
IOPCAsyncIO Interface
This interface of the Group class provides methods for asynchronous reading and
writing of items. Asynchronous means that the client triggers a read or write
operation and then continues operation. Asynchronous operations provide a
transaction ID. When the server has completed the read or write operation, the
client receives a message sent to its IAdviseSink interface.
Cancel ( dwTransactionID )
The IDataObject interface is the standard interface of OLE for data transmission. It
contains methods for establishing a message connection between the client and a
server group.
Client Server
IDataObject::DAdvise
IAdviseSink IDataObject
IAdviseSink::OnDataChange
Figure 1-3 IAdviseSink (Client) and IDataObject (Server) Interfaces
DUnadvise (Connection)
IEnumOPCItemAttributes Interface
This interface based on the IEnum standard interface returns the items of a group.
The interface is supplied only by “IOPCItemMgr:CreateEnumerator”. It is not
obtainable with QueryInterface.
Clone (ppEnumItemAttributes);
Reset (void);
Skip (celt);
Sends an asynchronous read command. The result is sent to the client via a
connection point.
Note: The call is monitored by the timeout monitoring on the server. If the set time
is exceeded, this is indicated by the status E_ABORT.
Cancel2 (dwCancelID )
SetEnable (bEnable)
GetEnable (pbEnable)
Returns the current value of the flag for messages via connection points.
IConnectionPointContainer Interface
This interface is a standard COM interface for reporting asynchronous events via
connection points. For more detailed information about using connection points,
refer to the documentation of OLE or COM.
Components
Available References: OK
Visual Basic For Applications
Cancel
Visual Basic runtime objects and procedures
Visual Basic objects and procedures
Browse
OLE Automation
OPC Automation 2.0
Active Setup Control Library Select (click on) OPC Automation 2.0
ActiveMovie control type library and click on the “OK” button
API Declaration Loader
Automation 1.0 Type Library
Example: The following example creates two items in the previously created OPC
group “GrpObj”. The first item represents MD0, and the second item represents
MD4.
’Declaration
Dim ItemCollection As OPCItems
Dim ItemServerHandle() As Long
Const MAX_INDEX = 2
Dim lNumItems As Long
Dim lClientHandles(MAX_INDEX) As Long
Dim perror() As Long
Dim szItemIDs(MAX_INDEX) As String
Dim AccPath(MAX_INDEX) As String
Dim ReqDataTypes(MAX_INDEX) As Integer
’Definition of ItemIDs
szItemIDs(1) = ”MD0:Real”
szItemIDs(2) = ”MD4:Real”
AccPath(1) = ””
AccPath(2) = ””
ReqDataTypes(1) = vbVLong
ReqDataTypes(2) = vbVString
lClientHandles(1) = 1
lClientHandles(2) = 2
’Add Items to Group
Set ItemCollection = GroupObj.OPCItems
ItemCollection.Add MAX_INDEX, szItemIDs, lClientHandles, _
ItemServerHandle, perror, ReqDataTypes, AccPath
The object model for the OPC automation interface according specification 2.0
differs from the model described in Section 5.3: Separate collection objects
manage the objects OPC-Group and OPC-Item. The collection objects provide
functions for counting the objects assigned to them. The browsing functions are
also brought together in a separate object.
OPCServer
1:1 1:n
OPCGroups
OPC–Browser
(Collection)
1:n
OPCGroup
1:1
OPCItems
(Collection)
1:n
OPCItem
Objects of the OPC server class are created by the client. The properties of an
OPC server contain general information about the server. When an OPC server
object is created, an OPCGroup collection is also created as a property of the OPC
server object.
Properties of “OPCServer”
Notes:
The OPC server for Computing provides the following as vendor information:
“Computing OPC–Server”
Public groups are not supported by the OPC server for Computing.
The Bandwith property is not supported by the OPC server for Computing.
Disconnect ()
ReleaseAll ()
CreateBrowser () As OPCBrowser
Creates an object of the OPCBrowser class to investigate the address area of the
server.
Note: Refer to the description of the object in Section 2.4.
The OPCBrowser object is a collection object with which the address area of the
OPC server can be investigated. An object of the OPCBrowser class must be
created by the CreateBrowser method of the OPCServer object. It is possible to
create several OPCBrowser objects for one server.
Properties of “OPCBrowser”
Notes:
The structure of the address area of the OPC server for Computing is
hierarchical.
The rules for creating a filter are as follows:
– Asterisk (*) Any character string, including empty strings
– Plus (+) Any string of characters, however at least one character
– Question marks (?) Any single character
– Open/close bracket ( [ ] ) Exactly one character from the specified set
To use one of the filter characters, this must be preceded by a back slash (\).
Methods of “OPCBrowser”
ShowBranches ()
Enters the names of the branches of the current browse position into the collection.
Enters the names of the leaves of the current browse position into the collection. If
the parameter ”Flat” is true, the collection with all leaves of the current and deeper
branches are filled starting from the current browse position. The default for ”Flat”
is false.
MoveUp ()
Moves the current position in the address area one level up.
Moves the current position in the address area into the current branch (one level
deeper).
MoveToRoot ()
The OPCGroups object is a collection object for creating and managing OPC
groups. The default properties of OPC groups specify default values for creating all
OPC groups.
Public groups are not supported by the OPC server for Computing.
Properties of “OPCGroups”
Notes:
The DefaultTimeBias property is not evaluated by the OPC server for
Computing.
DefaultLocale is irrelevant for the OPC server for Computing.
The DefaultGroupUpdate is specified by configuration parameter “Minimum
Update Rate” as a multiple of the configuration value.
The DefaultDeadband property has no significance for the OPC server for
Computing.
Methods of “OPCGroups”
Provides the reference to an OPC group indicated by the name or the server
handle.
Events of “OPCGroups”
This event simplifies the processing of events throughout all groups of the
collection by reporting changes in the value and state of all items in all groups.
The “OPC Group” class manages the individual process variables, the OPC items.
Using these group objects, a client can form semantically meaningful units of OPC
items and execute operations with them.
Properties of “OPCGroup”
Notes:
The TimeBias property is not evaluated by the OPC server for Computing.
LocaleID is irrelevant for the OPC server for Computing.
The UpdateRate is specified by the configuration parameter “Minimum Update
Rate” as a multiple of the configuration value.
The PercentDeadBand property has no significance for the OPC server for
Computing.
Methods of “OPCGroup”
Synchronous writing of values for one or more items of a group to the hardware.
Note: The call is monitored by the timeout monitoring on the server. The
corresponding configuration parameter is “Read/Write Timeout”.
Requests a current value for every active OPC item. The results are returned by
the “DataChange” event.
Events of “OPCGroup”
The OPC automation interface supplies the changes to the values of active terms
and the results of asynchronous operations with events.
The DataChange event occurs when it is detected that an active item has a
change value or a change quality. Checking value changes is triggered by the
UpdateRate timer. Only active items are created within a group of events.
The OPCItems object is a collection object for creating and managing OPC items.
The default properties of OPCItems specify default values for all OPC items to be
created.
Properties of “OPCItems”
Methods of “OPCItems”
Checks the validity of an OPC item, for example whether it was added to a group
without any error occurring, and supplies information such as the canonical data
type.
Note: See Add.
An object of the class OPC item represents a link to a process variable, for
example to the input module of a programmable controller. A process variable is
data of the process I/Os that can be written and/or read, for example the
temperature of a tank. Each process variable is associated with a value (variant
data type), a quality, and a time stamp.
Properties of “OPCItem”
Note: The OPC server for Computing does not support units (engineering units)
Methods of “OPCItem”
Reads the value, the quality, and/or the time stamp of this variable.
From
Name: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Job Title: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Company Name: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Street: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
City and State: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Country: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Telephone: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Automotive Pharmaceutical
Chemical Plastic
Electrical Machinery Pulp and Paper
Food Textiles
Instrument and Control Transportation
Non-electrical Machinery Other ___________________________
Petrochemical
Please give each of the following questions your own personal mark within a range from 1 (very
good) to 5 (very poor).
Additional comments:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _