PHP Classes

File: doc/04-reading-writing.md

Recommend this page to a friend!
  Packages of Gianfrancesco Aurecchia   OPC UA Client   doc/04-reading-writing.md   Download  
File: doc/04-reading-writing.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: OPC UA Client
Control devices that support the OPC UA protocol
Author: By
Last change:
Date: 16 days ago
Size: 12,164 bytes
 

Contents

Class file image Download

Reading & Writing Values

Server BuildInfo

Every OPC UA server exposes build metadata under standard well-known nodes. The client provides convenience methods to read them without memorizing NodeIds:

// Individual fields
$client->getServerProductName();        // ?string  (ns=0;i=2262)
$client->getServerManufacturerName();   // ?string  (ns=0;i=2263)
$client->getServerSoftwareVersion();    // ?string  (ns=0;i=2264)
$client->getServerBuildNumber();        // ?string  (ns=0;i=2265)
$client->getServerBuildDate();          // ?DateTimeImmutable (ns=0;i=2266)

// All at once ? single readMulti() call
$info = $client->getServerBuildInfo();

echo $info->productName;        // e.g. "UA-.NETStandard"
echo $info->manufacturerName;   // e.g. "OPC Foundation"
echo $info->softwareVersion;    // e.g. "1.5.374.126"
echo $info->buildNumber;        // e.g. "1.5.374.126"
echo $info->buildDate;          // DateTimeImmutable

getServerBuildInfo() returns a BuildInfo readonly DTO. Individual methods return null when the server provides no value for that field.

Reading a Value

use PhpOpcua\Client\Types\NodeId;
use PhpOpcua\Client\Types\StatusCode;

// Using string format
$dataValue = $client->read('i=2259'); // ServerStatus_State

// Or with a NodeId object
$dataValue = $client->read(NodeId::numeric(0, 2259));

if (StatusCode::isGood($dataValue->statusCode)) {
    echo "Value: " . $dataValue->getValue() . "\n";
}

> Events: Every read() dispatches a NodeValueRead event. See Events.

Metadata Cache

Attributes like DisplayName, BrowseName, DataType, and NodeClass are static ? they don't change at runtime. Enable metadata caching to avoid redundant server reads:

// Enable on the builder before connecting
$client = ClientBuilder::create()
    ->setReadMetadataCache(true)
    ->connect('opc.tcp://localhost:4840');

// First call: reads from server, caches the result
$name = $client->read('ns=2;i=1001', AttributeId::DisplayName);

// Second call: served from cache (no server round-trip)
$name = $client->read('ns=2;i=1001', AttributeId::DisplayName);

// Force a refresh from the server
$name = $client->read('ns=2;i=1001', AttributeId::DisplayName, refresh: true);

Rules:

  • Disabled by default ? opt-in via `setReadMetadataCache(true)`.
  • Value (attribute 13) is never cached ? always reads from the server, regardless of the setting.
  • refresh: true bypasses the cache and re-reads from the server, then updates the cache.
  • Uses the same PSR-16 cache backend as browse and write type detection.
  • `invalidateCache($nodeId)` clears all cached metadata for that node.

Reading a Specific Attribute

By default, read() targets the Value attribute (id 13). You can read any attribute:

use PhpOpcua\Client\Types\AttributeId;

$displayName = $client->read(NodeId::numeric(0, 2259), AttributeId::DisplayName);
$dataType = $client->read(NodeId::numeric(0, 2259), AttributeId::DataType);

Common attributes:

| Constant | Value | Description | |----------|-------|-------------| | AttributeId::NodeId | 1 | The node's NodeId | | AttributeId::NodeClass | 2 | Node class | | AttributeId::BrowseName | 3 | Browse name | | AttributeId::DisplayName | 4 | Display name | | AttributeId::Description | 5 | Description | | AttributeId::Value | 13 | The value (default) | | AttributeId::DataType | 14 | Data type NodeId | | AttributeId::AccessLevel | 17 | Access level bitmask |

Reading Multiple Values

// Fluent builder
$results = $client->readMulti()
    ->node('i=2259')->value()
    ->node('i=2267')->value()
    ->node('ns=2;s=Temperature')->value()
    ->execute();

foreach ($results as $dataValue) {
    if (StatusCode::isGood($dataValue->statusCode)) {
        echo $dataValue->getValue() . "\n";
    }
}

// Or with array (still works)
$results = $client->readMulti([
    ['nodeId' => 'i=2259'],
    ['nodeId' => 'i=2267'],
    ['nodeId' => 'ns=2;s=Temperature', 'attributeId' => AttributeId::Value],
]);

> Tip: The builder's ->node() adds a node, then you pick the attribute (->value(), ->displayName(), etc.). Call ->execute() to send the request.

DataValue Properties

$dataValue->getValue();           // mixed -- unwrapped value (extracts from Variant)
$dataValue->variant;              // ?Variant -- typed variant
$dataValue->statusCode;           // int -- OPC UA status code
$dataValue->sourceTimestamp;      // ?DateTimeImmutable
$dataValue->serverTimestamp;      // ?DateTimeImmutable

Writing a Value

use PhpOpcua\Client\Types\BuiltinType;

$statusCode = $client->write(
    'ns=2;i=1234',  // or NodeId::numeric(2, 1234)
    42,
    BuiltinType::Int32
);

if (StatusCode::isGood($statusCode)) {
    echo "Write successful\n";
} else {
    echo "Write failed: " . StatusCode::getName($statusCode) . "\n";
}
// Events: dispatches NodeValueWritten on success, NodeValueWriteFailed otherwise

Auto-Detect Write Type

By default, the client automatically detects the node's type before writing. You can omit the BuiltinType parameter:

// Auto-detect type (reads the node first, caches the type)
$client->write('ns=2;i=1234', 42);

// Explicit type (validated against the node when auto-detect is on)
$client->write('ns=2;i=1234', 42, BuiltinType::Int32);

The detected type is cached (PSR-16) so subsequent writes to the same node skip the read.

Behavior:

| Auto-detect | $type passed | What happens | |---|---|---| | ON (default) | No | Reads node, caches type, writes | | ON | Yes | Uses the type directly, no read | | OFF | No | Throws WriteTypeDetectionException | | OFF | Yes | Uses the type directly, no read |

Disable auto-detect:

$client = ClientBuilder::create()
    ->setAutoDetectWriteType(false)
    ->connect('opc.tcp://localhost:4840');

Exceptions:

  • `WriteTypeDetectionException` ? node has no readable value, or auto-detect is off and no type provided

Events:

  • `WriteTypeDetecting` ? dispatched before the type detection starts
  • `WriteTypeDetected` ? dispatched after the type is determined (with `$detectedType` and `$fromCache`)

Cache invalidation:

$client->invalidateCache($nodeId); // clears cached write type (and browse cache)
$client->flushCache();             // clears everything

Writing Multiple Values

// Fluent builder ? auto-detect type
$results = $client->writeMulti()
    ->node('ns=2;i=1001')->value(3.14)
    ->node('ns=2;i=1002')->value('Hello')
    ->node('ns=2;i=1003')->value(true)
    ->execute();

// Fluent builder ? explicit type
$results = $client->writeMulti()
    ->node('ns=2;i=1001')->typed(3.14, BuiltinType::Double)
    ->node('ns=2;i=1002')->typed('Hello', BuiltinType::String)
    ->node('ns=2;i=1003')->typed(true, BuiltinType::Boolean)
    ->execute();

foreach ($results as $i => $statusCode) {
    echo "Item $i: " . StatusCode::getName($statusCode) . "\n";
}

// Or with array (still works)
$results = $client->writeMulti([
    [
        'nodeId' => 'ns=2;i=1001',
        'value' => 3.14,
        'type' => BuiltinType::Double,
    ],
    [
        'nodeId' => 'ns=2;i=1002',
        'value' => 'Hello',
        'type' => BuiltinType::String,
    ],
    [
        'nodeId' => 'ns=2;i=1003',
        'value' => true,
        'type' => BuiltinType::Boolean,
    ],
]);

> Tip: The write builder uses ->node() to pick the target, then ->value($val, $type) to set what to write. Call ->execute() to send.

Writing to a Specific Attribute

By default, write() targets the Value attribute (id 13):

$results = $client->writeMulti([
    [
        'nodeId' => NodeId::numeric(2, 1001),
        'value' => 100,
        'type' => BuiltinType::Int32,
        'attributeId' => 13,
    ],
]);

Writing Arrays

use PhpOpcua\Client\Types\Variant;
use PhpOpcua\Client\Types\DataValue;

// Using Variant directly
$variant = new Variant(BuiltinType::Int32, [1, 2, 3, 4, 5]);
$dataValue = new DataValue($variant);

// Or through writeMulti
$results = $client->writeMulti([
    [
        'nodeId' => NodeId::numeric(2, 2001),
        'value' => [10, 20, 30],
        'type' => BuiltinType::Int32,
    ],
]);

Automatic Batching

OPC UA servers can limit how many nodes you read or write per request. The client handles this transparently.

How It Works

After connect(), the client reads the server's MaxNodesPerRead and MaxNodesPerWrite limits. When readMulti() or writeMulti() exceeds that limit, the request is split automatically and results are merged in order.

$client = ClientBuilder::create()
    ->connect('opc.tcp://localhost:4840');

// Server says MaxNodesPerRead = 100
// This is split into 10 requests of 100 each
$results = $client->readMulti($items1000);
// $results contains all 1000 DataValues, in order

You can check the discovered limits:

$client->getServerMaxNodesPerRead();  // e.g. 100, or null
$client->getServerMaxNodesPerWrite(); // e.g. 100, or null

Setting a Manual Batch Size

Override the server limit or set one when the server does not report any:

$client = ClientBuilder::create()
    ->setBatchSize(50)
    ->connect('opc.tcp://localhost:4840');

Priority order: your setBatchSize(N) (N > 0) beats the server-reported limit, which beats no batching.

Disabling Batching

Skip both batching and the server limits discovery on connect:

$client = ClientBuilder::create()
    ->setBatchSize(0)
    ->connect('opc.tcp://localhost:4840');

> Tip: Use this if you know the server has no limits and want to save the extra read on connect.

Batching Summary

| getBatchSize() | Server reports | Discovery on connect | Effective batch size | |------------------|----------------|----------------------|----------------------| | null (default) | 100 | Yes | 100 | | null (default) | 0 (no limit) | Yes | No batching | | null (default) | Not supported | Yes | No batching | | 50 | 100 | Yes | 50 | | 50 | 0 | Yes | 50 | | 0 (disabled) | Any | Skipped | No batching |

> Note: Batching only applies to readMulti() and writeMulti(). Single read() and write() calls always go as individual requests.

Supported Data Types

| BuiltinType | PHP Type | Example | |-------------|----------|---------| | Boolean | bool | true | | SByte | int | -128 to 127 | | Byte | int | 0 to 255 | | Int16 | int | -32768 to 32767 | | UInt16 | int | 0 to 65535 | | Int32 | int | -2^31 to 2^31-1 | | UInt32 | int | 0 to 2^32-1 | | Int64 | int | -2^63 to 2^63-1 | | UInt64 | int | 0 to 2^64-1 | | Float | float | 3.14 | | Double | float | 3.141592653589793 | | String | string | 'Hello' | | DateTime | DateTimeImmutable | new DateTimeImmutable() | | Guid | string | '550e8400-e29b-41d4-a716-446655440000' | | ByteString | string | Binary data | | NodeId | NodeId | NodeId::numeric(0, 85) | | QualifiedName | QualifiedName | new QualifiedName(0, 'Name') | | LocalizedText | LocalizedText | new LocalizedText('en', 'Text') |

Status Codes

use PhpOpcua\Client\Types\StatusCode;

$statusCode = $dataValue->statusCode;

StatusCode::isGood($statusCode);      // true if 0x0XXXXXXX
StatusCode::isBad($statusCode);       // true if 0x8XXXXXXX
StatusCode::isUncertain($statusCode); // true if 0x4XXXXXXX
StatusCode::getName($statusCode);     // e.g. "BadNodeIdUnknown"

Common status codes:

| Constant | Value | Meaning | |----------|-------|---------| | StatusCode::Good | 0x00000000 | Success | | StatusCode::BadNodeIdUnknown | 0x80340000 | Node does not exist | | StatusCode::BadTypeMismatch | 0x80740000 | Value type mismatch | | StatusCode::BadNotWritable | 0x803B0000 | Node is read-only | | StatusCode::BadNotReadable | 0x803E0000 | Node is not readable | | StatusCode::BadUserAccessDenied | 0x801F0000 | Access denied | | StatusCode::BadTimeout | 0x800A0000 | Operation timed out |