Skip to content

Container Types

Values that carry metadata, or that act as generic carriers:

o6\Python
o6.DataValue A value with optional StatusCode, sourceTimestamp, serverTimestamp, and sub-millisecond *_picoseconds fields.
o6.ExtensionObject A generic carrier for structured values whose type is identified by a NodeId — used for any value of an NS0 datatype.

Info

o6\Python does not declare an explicit Variant type. We achieve this through Python's dynamic typing — see the next section below for more information.

Variant

Most OPC UA libraries introduce a Variant class that can hold any of the 25 built-in types — you create a Variant and the library figures out the actual type at runtime.

o6\Python does not do this. There is effectively no Variant in the public API.

Python is dynamically typed, so the underlying value's class already carries the type information.

Variant is transparently handled internally by the C implementation without exposing it to the python layer.


DataValue

DataValue is a small struct that travels with every variable read/write response on the wire. It carries:

Field Type Description
value 25 built-in types / ExtensionObject / (NumPy-)array of any of those the actual scalar / array being transferred
status o6.StatusCode optional — what the server thinks of the value (Good, Bad, …)
sourceTimestamp / sourcePicoseconds o6.DateTime / int optional — when the underlying sensor/device produced the value
serverTimestamp / serverPicoseconds o6.DateTime / int optional — when the server received the value from the source

The *_picoseconds fields give you the 10⁻⁷-second sub-tick precision that DateTime cannot represent (it's an integer count of 100-ns ticks).

Clients return DataValues from when read, write, call services, and servers produce them when they hand values to subscriptions.

Constructing a DataValue

dv = o6.DataValue()
dv.value = o6.Int32(42)
dv.status = o6.StatusCode.BadTimeout
dv.sourceTimestamp = o6.DateTime(datetime.datetime.now())

A missing status is implicitly Good.

dv = o6.DataValue()
assert dv.status == o6.StatusCode.Good   # not None

Value member as Variant

The OPC UA specification models DataValue.value as a Variant — a discriminated union over the 25 built-in datatypes, arrays, and matrices. In o6\Python that union is implicit and we can simply assign directly:

dv = o6.DataValue()
dv.value = o6.Int32(42)               # Int32
dv.value = o6.Double(3.14)            # same field, now a Double
dv.value = "hello"                    # a String
dv.value = None                       # empty
dv.value = np.array([1.0, 2.0, 3.0])  # NumPy array of float64
dv.value = [1.0, 2.0, 3.0]            # Python list (internally converted into np.array)

See Variant for more details, how this is achived.


ExtensionObject

An ExtensionObject is the OPC UA spec's open-type carrier: a NodeId identifies the actual datatype bundeled with the encoded payload.

ExtensionObject is mostly handled implicitly in o6\Python: reads return the decoded Python object from o6.ns.ns0 or a custom namespace, and writes accept the Python object directly.

ExtnensionObject appears user-facing at low-level seams where the spec accepts any of N possible structured types — for example, the polymorphic history_update_details field of HistoryUpdateRequest:

import o6
from o6.ns import ns0

details = ns0.datatypes.UpdateDataDetails()
details.nodeId = o6.NodeId("ns=2;s=Temperature")
details.perform_insert_replace = ns0.datatypes.PerformUpdateType.REPLACE
details.update_values = [o6.DataValue(value=o6.Double(23.5))]

request = ns0.datatypes.HistoryUpdateRequest()
request.history_update_details = [o6.ExtensionObject(details)]

Info

You may encounter ExtensionObject also when the Client receives a value of an unknown DataType and therefore doesn't know how to decode it from it on-the-wire representation.


NumPy for arrays and large data

Anything that may carry an array of values internally — bulk numeric arrays, large string / bytestring / XML buffers, DataValue array payloads — is utilizing NumPy, the underlying C implementation stores those values in native contiguous buffers and a NumPy view over that memory keeps the round-trip cheap.

  • Zero-copy reads — when possible, the array you get back is a view over the same memory the C implementation holds, so reading a large array does not allocate.
  • Vectorised math — the standard NumPy operators work on numeric OPC UA values, no conversion step required.
  • Ecosystem fit — the arrays pass straight through to pandas, matplotlib, scikit-learn, etc., with no extra glue.

See also