Item Definition
The ItemID is the fully qualified definition of a data item in the server, commonly referred to as the WHAT. No other information is required to identify the data item for the client to be able to read/write values.
The Item definition (ItemID) used in the OPCITEMDEF and elsewhere is a nul-terminated string that uniquely identifies an OPC data item. The syntax of the identifier is server dependent (although it should include only printable UNICODE characters) and it provides a reference or 'key' to an 'item' in the data source. The item is anything that can be represented by a VARIANT although it is typically a single value such as an analog, digital or string value.
For example, an item such as FIC101 might represent an entire record such as a Fieldbus, Hart Foundation or ProfiBus data structure. Such behavior is specifically allowed but not required by OPC - the return of such structures is considered to be vendor specific behavior. Alternately FIC101.PV might represent one attribute of a record such as the process value. This would probably take the form of a double which could be used by any client.
As an extreme example, since the syntax of the item ID is server specific, additional information such as Counts, Engineering Units Scaling and Signal conditioning information could be embedded in the definition string (although this is not recommended).
Examples:
A server which supports access to an existing DCS might support a simple syntax such as
"TIC101.PV"
A server that supports low level access to a PLC might support a syntax such as
"COM1.STATION:42.REG:40001;0,4095,-100.0,+1234.0"
AccessPath
The AccessPath is intended as a way for the client to provide to the server a suggested data path (e.g. a particular modem or network interface). It indicates HOW to get the data.
The ITEM ID provides all of the information needed to locate and process a data item. The Access Path is an optional piece of information that can be provided by the client. Its use is highly server specific but it is intended to allow the client to provide a 'recommendation' to the server regarding how to get to the data. As an analogy, if the ItemID represented a phone number, the access path might represent a request to route the call via satellite (or transatlantic cable or microwave link). The call will go through regardless of whether you specify an access path and also whether or not the server is able to use that suggested path.
For example, suppose you wanted to access a value in an RTU and had a high speed modem on COM1 and a low speed modem on COM2. You might specify COM1 as the preferred access path. Either one will work, but you would prefer to use COM1 if it is available for better performance.
In any case, the use of access path by both the server and the client is optional. Servers need not provide the function and clients need not use it even if it is provided.
Servers which do not support access paths will completely ignore any passed access path (and will not treat this as an error by the client). Also, when queried, such servers will always return a null access path for all items (i.e. a NUL string).
Blob
We will discuss why the Blob exists and how it behaves.
The Blob is basically a scratch area for the server to associate with items in order to speed up access to or processing of those items. The exact way in which it is used is server specific.
The idea is that clients refer to items via ASCII strings while internally, to speed up access, the server will probably need to resolve this string into some internal server specific address; a network address, a pointer into a table, a set of indices or files or register numbers, etc. This address resolution could take considerable time and the resulting internal address could take an arbitrary amount of space. This Blob allows the server to return this internal address and allows the client to save it and to provide the Blob back to the server for future references to this item. The server could use the 'Blob' as a 'hint' to help find the item more quickly the next time; "The Blob says that last time I looked for this tag I found it 'here' - so lets see if its still in that location". However, in all cases, the ITEM ID is still the 'key' to the data. Regardless of the contents of the Blob, the server needs to insure that it is in fact referencing the item referred to by the ITEM ID.
The behavior of the Blob is as follows.
Its use by both client and server is optional. Servers which can perform 'AddItems' quickly based just on the item definition should generally not return a Blob. In cases where servers do return a Blob, clients are free to ignore these Blobs (although this will probably affect the performance of that server).
The Blob is passed to AddItems and ValidateItems and is also returned by the server any time an AddItems or ValidateItems or EnumItemAttributes is done. The returned Blob may differ in size and content from the one passed.
Note that the server can update the Blob for an item at any time entirely at the server's discretion (including, for example, whenever the client changes an attribute of an Item).
Proper behavior of a client that wishes to support the Blob is to Enumerate the item attributes to get a fresh copy of the Blobs for each item prior to deleting an item or group and to save that updated copy along with the other application data related to the items.
Comment:
The difference between the server handle and the Blob is that the server handle is fixed in size (DWORD), should not be stored between sessions by the client and that it's implementation is required since it is the only way to identify items after they have been added. The Blob is variable in length, is optional and may be stored by the client between sessions.
Time Stamps
Time stamps are in the form of a FILETIME as this is more compact than other available standard time structures. There are numerous WIN32 functions for converting between various time formats and time zones. Time stamps are always in UTC, this form is beneficial because it is always increasing and is unambiguous. As discussed earlier in this document, time stamps should reflect the best estimate of the most recent time at which the corresponding value was known to be accurate. If this is not provided by the device itself then it should be provided by the server.
Variant Data Types for OPC Data Items
Under NT 4.0 and Windows 95 with DCOM support, all VARIANT data types can be marshaled through standard marshalling. The OPCDA.NET wrapper converts the Variant data types into object types.
NOTE: Real values in the variant (VT_R4, VT_R8) will contain IEEE floating point numbers. Note that the IEEE standard allows certain non numeric values (called NANs) to be stored in this format. While use of such values is rare, they are specifically allowed. If such a value is returned (in the OPCITEMSTATE or in the DATA STREAM to the IAdviseSink) it is required that the QUALITY flag be set to OPC_QUALITY_BAD.
Group Handles
OPC groups have both a client and a server handle associated with them.
The server group handle is unique across the server and must be returned when the group is created. The handle is then passed by the client to various methods. The server group handle can be assumed to remain valid until the client Removes the group and free's all of the interfaces.
It should not be persistently stored by the client as it may be different the next time the OPC group is created.
The client group handle is provided by the client to the server. It can be any value and does not need to be unique. It is included in the data stream sent to IAdviseSink in order to help the client identify the source of the data.
In practice it is expected that a client will assign a unique value to it's handle if it intends to use any of the asynchronous functions of the OPC interfaces( including IOPCAsyncIO and IDataObject/IAdviseSink interfaces), since this is the only key to the information that the server gives back to the client via the IAdviseSink interface.
OPC items have both a client and a server handle associated with them.
The server item handle is unique within the group and will be returned when the item is created. It is then passed by the client to various methods. The server item handle can be assumed to remain valid until the client Removes the items or Removes the Group containing the items. It should not be persistently stored by the client as it may be different the next time the OPC Item is created.
The client item handle is provided by the client to the server. It can be any value and does not need to be unique. It is included in the asynchronous callback data in order to help the client quickly identify which object in the client application is affected by the changed data.
In practice however it is expected that a client will assign unique values it's handles if it intends to use any of the asynchronous functions of the OPC interfaces (including IOPCAsyncIO and IDataObject interface), since this is the only key to the information that the server gives back to the client via the IDataObject interface.
VARIANT Data Types and Interoperability
In order to promote interoperability, the following rules and recommendations are presented.
General Recommendations:
The VARIANT types VT_I2, I4, R4, R8, CY, DATE, BSTR, BOOL, UI1 as well as single arrays of these types (VT_ARRAY) are expected to be most commonly used (in part because these are the legal types in Visual Basic).
It is recommended that whenever possible, clients request data in one of these formats and that whenever possible, servers be prepared to return data in one of these formats.
It is expected that use of other extended types will most likely occur where the Server and Client were written by the same vendor and the server intends to pass some non-portable vendor specific data back to the client. In the interests of interoperability, such transactions should be minimized.
It has been found in practice that some servers (for example those connecting to remote locations) are unable to determine the Native Datatype at the time an item is added or validated. It has become common practice for such servers to return VT_EMPTY as the native data type. Such servers will retain the requested type (which may also be VT_EMPTY) and will return the data in the requested type (which may be 'Native') when the data becomes available and they are able to determine its actual type. It is recommended (but not required) that clients be prepared to deal with an initial return of VT_EMPTY from AddItems or ValidateItems.
General Rules:
Servers are allowed to maintain and return any legal Canonical Data Type (any legal permutation of VT_ flags) in addition to the recommended types above.
Clients are allowed to request any legal Variant Data Type in addition to the recommended types above.
Servers should be prepared to deal in an elegant way with requested types even when they are unable to convert their data to this type. That is, they should not malfunction, return incorrect results or lose memory. As mentioned elsewhere they may return a variety of errors including any error returned by the Microsoft function: VariantChangeType.
Clients should always be prepared to deal with servers which are unable to handle a requested data type. That is, they should not malfunction or lose memory when an error is returned.
Clients which request VT_EMPTY (which by convention indicates that the server should return it's canonical type) should likewise be prepared to deal with any returned type. That is, even if they find that they are not able to use or display the returned data, they should properly free the data (using VariantClear) and should probably indicate to the user that a data type was returned which is not usable by this client.
Additional Rules regarding Data Conversion
OPC Servers must support at least the following conversions between Canonical and Requested data types. Reading and Writing should be symmetric. Note that the easiest way for most server implementers to provide this functionality is to use the VariantChangeTypeEx() function available in the COM libraries. In the table below, conversions marked OK can be expected to always work. Other conversions may or may not work depending on the specific value of the source.
As noted elsewhere in this specification the Client can specify a localeID to be used and the server should pass this to VariantChangeTypeEx() for all conversions. Note that it is possible for the end user to override some of the default Locale Settings in the Control Panel Regional Settings Dialog. For example in English it is possible to select date formats of either MM/DD/YY or YY/MM/DD as well other formats. Clearly a date of 03/02/01 is ambiguous in this case. It is the End User's responsibility to insure that the Regional Settings for a given localeID are compatible on different machines within his network.
From / To |
I1 |
UI1 |
I2 |
UI2 |
I4 |
UI4 |
R4 |
R8 |
CY |
Date |
BSTR |
Bool |
I1 |
ok (7) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1)(3) |
(4) |
ok | |
UI1 |
ok (7) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1)(3) |
(4) |
ok | |
I2 |
ok |
ok |
(1)(7) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1)(3) |
(4) |
ok | |
UI2 |
ok |
ok |
(1)(7) |
(1) |
(1) |
(1) |
(1) |
(1) |
(1)(3) |
(4) |
ok | |
I4 |
ok |
ok |
ok |
ok |
ok (7) |
(1) |
(1) |
(1) |
ok |
(4) |
ok | |
UI4 |
(1) |
ok |
(1) |
ok |
ok (7) |
(1) |
(1) |
(1) |
ok |
(4) |
ok | |
R4 |
ok |
ok |
ok |
ok |
ok |
ok |
(1) |
ok |
ok |
(4) |
ok | |
R8 |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
(4) |
ok | |
CY |
ok |
ok |
ok |
ok |
ok |
ok |
(1) |
(1) |
ok |
(4) |
ok | |
Date |
ok |
ok |
ok |
ok |
(1) |
(1) |
(1) |
(1) |
(1) |
(4) |
ok | |
BSTR |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok | |
Bool |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
(4) |
Notes:
(1) Conversion on 'downcast' e.g. from I4 to I2 or R8 to R4 is allowed although Overflow is possible. If overflow occurs an error (DISP_E_OVERFLOW) is returned and in the case of Read the quality is set to BAD. In the case of Write the target value is not changed.
(2) Note that the internal storage type of VT_BOOL is 'short' which must have the values VARIANT_TRUE (0xFFFF - i.e. '-1' when stored in a 'short') and VARIANT_FALSE (0). When converting TO bool any non-zero value converts to VARIANT_TRUE. Converting FROM bool to any signed numeric type, VARIANT_TRUE converts to '-1' or '-1.0'. For unsigned types it converts to the maximum value. The recommended OPC standard for conversion of bool to BSTR is "0" or "-1" rather than 'True" or "False". If a server chooses to convert to "True" or "False" is must account for the Locale (e.g. by passing VARIANT_LOCALBOOL to VariantChangeTypeEx). It should also be noted that the C++ keyword 'true' is an abstract type which converts to '1' when assigned to any other value (e.g. to a short). Thus it is an coding error to assign 'true' to 'boolVal' which must always be set to VARIANT_TRUE or VARIANT_FALSE.
(3) Note that DATE is stored as a double where the integer part is the date and the fraction is the time. For the DATE, 0.0 is midnight Dec 30, 1899 (i.e. midnight Jan 1, 1900 is 2.0 and Dec 4, 2001 is 37229.0). For the TIME the fraction represents the time of day moving ahead from midnight (e.g. 0.2500 is 6:00 AM, 0.400 is 9:36:00 AM). This fraction is not affected by the sign of the date and always moves 'ahead' from midnight for both positive and negative values. For example -1.4 is Dec 29, 1899 9:36:00 AM. These conversions are supported by VariantChangeTypeEx(). Generally an OVERFLOW will occur if the TO type is UI1, I1, UI2 or I2. In addition the TIME (being a fraction) will be lost on any conversion of DATE to an Integer.
(4) BSTR conversions will give a DISP_E_TYPE error if the string does not make sense for conversion to the target type. For example "1234" converts to any numeric type (except it generates OVERFLOW for UI1), "12/04/2001" converts to DATE (depending on the Locale) but not to a numeric type and "ABCD" does not convert to any other type.
(5) Conversions from non Integers to Integers must round up according to the sign if the factional part exceeds 0.5. For example 1.6 would round up to 2 and -1.6 would round 'up' to -2.
(6) Keep in mind that Currency (CY) is stored as a scaled (fixed point x 10,000) 8 byte integer (I.e. a '_huge') With 4 digits of precision to the right of the decimal point. For example $12.34 is stored as 123400.
(7) Conversion between signed and unsigned integers should generate an overflow if the requested type cannot hold the value (e.g. I1=-1 should overflow if converted to UI1 and UI1 of 255 should overflow if converted to I1). However some of these conversions behave improperly when performed by ChangeVariantTypeEx. Specifically, for conversions with the same number of bits the value is NOT checked for overflow. So an I1 of -1 turns into a UI1 of 255. Similarly a UI1 value of 254 turns into an I1 value of -2. The same applies for I2 and I4. This is an incorrect behavior by ChangeVariantTypeEx. Client programs and users should be aware that most servers will exhibit this behavior since most servers will use VariantChangeTypeEx. Correcting this is recommended but is NOT required for OPC Compliance. Conversions between types with different numbers of bits (e.g. I1, I2, I4) are properly checked for Overflow by VariantChangeTypeEx.
(8) Loss of Precision may occur when converting between various types (e.g. converting from R8 to R4 or from R4 to I4 or I2). However no error is reported as long as there is no OVERFLOW and Quality is returned as GOOD for Reads.
(9) Note that ChangeVariantTypeEx does not handle arrays. Servers which support arrays must implement conversion logic using additional code. For arrays the required behavior is that if any element of the array suffers a conversion error then the first error detected is returned (DISP_E_OVER or DISP_E_TYPE). For Read the Quality is set to BAD and an empty Variant is returned. For Write, if conversion of any element fails then none of the elements are written and the first error encountered is returned.