Operations & interoperability¶
Browsing¶
server.browse(target) returns a complete flat list of
ns0.datatypes.ReferenceDescription objects, matching client.browse(target).
Continuation points are followed until exhausted, even when a page is empty.
Synchronous code receives the list directly. Inside an async task, await the call:
Both typed Node handles and NodeIds follow this contract, on the server's event
loop or a separate caller loop. Unknown or deleted targets and failed browse
results raise o6.StatusCodeError. Collection attempts to release outstanding
continuation points on failure. Client collection also attempts release on
cancellation; a lost connection can prevent release.
Typed-node child and attribute access return immediately in synchronous code and on the server's own loop; async tasks on another loop await that access.
Explicit pages and migration¶
Replace server.browse(target).references with server.browse(target) (or the
corresponding awaited call). Move old server.browse(target, maxReferences=...)
callers to server.browsePage(target, maxReferences=...). Only browsePage
returns a BrowseResult with references and continuationPoint fields.
maxReferences=0 requests no caller-imposed limit; backend limits may still
produce a continuation point.
Use browseNext(False, token) to continue and browseNext(True, token) to
release. Both return a list of pages, one per token; a single token still
returns a one-element list. Tokens belong to the server that issued them.
Update the token before processing each page, and release it if processing stops:
page = server.browsePage(target, maxReferences=100)
token = page.continuationPoint
try:
while True:
for reference in page.references:
print(reference.nodeId)
if not token:
break
page = server.browseNext(False, token)[0]
token = page.continuationPoint
finally:
if token:
server.browseNext(True, token)
Async tasks await browsePage and browseNext too. Awaiting an already returned
native BrowseResult itself raises TypeError.
browseNext raises StatusCodeError for failed page statuses. When a batch
contains a failure, it attempts to release continuation points in successful
returned pages before raising. Client applications that need explicit pages use
serviceBrowse and serviceBrowseNext; those raw services retain their response
objects and status fields, which callers must check.
Historical data¶
Configure a history database before starting the server, then register each
Variable with historizing=True or server.historize:
server.config.setHistoryDatabase(maxNodes=10, capacity=1000, maxResponseSize=200)
level = server.addVariable("Level", plant, 0.0, nodeId="ns=1;s=Level", historizing=True)
Variables created before startup register during start(), so the database can
be configured after creating them. Startup raises if the database is missing or
registration fails; pending registrations remain available for retry. Variables
added while running register immediately. Missing database configuration rejects
runtime creation, and failed initialization or registration removes the new node.
Writes are recorded automatically. o6.History configures per-node capacity and
on-write, polling, or manual sampling; server.unhistorize(level) stops recording.
In async code, await server.historize(...) and server.unhistorize(...), including
when the server runs on another loop. Both require a configured history database;
failed configuration restores the node's previous history attributes.
history = client.historyRead("ns=1;s=Level", start, end)
for dataValue in history.dataValues:
print(dataValue.sourceTimestamp, dataValue.value)
historyRead follows server-issued continuation points and preserves target
order for multiple nodes. numValuesPerNode limits the total returned per node;
unused tokens are released when that limit is reached or a read fails or is
cancelled. A broken connection can prevent release, so stores must expire
abandoned tokens.
Custom HistoryBackend stores can override getHistoryPage to return
(dataValues, nextToken). It receives the incoming token, a release flag, and
session identity in addition to the ordinary history-read parameters. Bind tokens
to the session and node, reject invalid tokens, and return ([], b"") after
release. An empty output token finishes the read. The existing getHistoryData
hook remains available for stores that return the complete result in one response.
Discovery and reverse connect¶
A server can register itself with a Local Discovery Server so that clients find
it through FindServers:
server.registerDiscovery("opc.tcp://localhost:4840")
# ... serve ...
server.deregisterDiscovery("opc.tcp://localhost:4840")
semaphoreFilePath= names a semaphore file coordinating shutdown across
instances. Deregistration belongs in your shutdown path.
The two LDS-side callbacks — setRegisterServerCallback, invoked when another
server registers with this one, and setServerOnNetworkCallback, invoked when a
server is discovered via mDNS — are part of the API but raise
NotImplementedError in the current build: they depend on open62541 discovery
features that are compile-time gated. Do not build a design on them without
checking first.
In a reverse-connect deployment the server initiates the TCP connection to a client that is listening, which is how you reach a client behind a firewall:
handle = server.addReverseConnect("opc.tcp://localhost:4843")
# ... the server retries periodically until the client answers ...
server.removeReverseConnect(handle)
callback= receives (handle, state) on every state change. The client side is
client.startReverseConnect(port), which does not return until the server has
connected — see Client.
PubSub¶
PubSub is configured through the standard namespace-zero information model
rather than through a separate Python API: you create PubSubConnection,
WriterGroup, DataSetWriter, and reader components as address-space nodes and
control them with the model's own methods. The o6.pubsub module adds only the
runtime facilities that model cannot express:
import o6.pubsub
o6.pubsub.publish(writerGroup) # publish one message now
o6.pubsub.setStateMachine(component, callback) # custom state transitions
table = o6.pubsub.offsetTable(writerGroup) # fixed-layout byte offsets
offsetTable returns the encoded message together with typed byte offsets
(OffsetType), which is what makes deterministic, pre-encoded publishing
possible. setStateMachine takes a callable
(current, target) -> (StatusCode, newState) and may be called from an
implementation class's __init__; a component must be disabled before its state
machine is replaced. PubSub requires a build and Credential with the PubSub
feature scope enabled; without it these calls raise PermissionError.
Errors and status codes¶
The server reports failures in three ways, and the pattern differs from the client for some operations. Writes share the client status-return contract.
o6.StatusCodeError is raised by read when the value's status is not
Good, by addReference, deleteReference, deleteNode, and node
construction when the operation fails, and by call when a Method returns a bad
status. It carries code and symbol:
try:
server.write("ns=1;s=Missing", 1.0).check()
except o6.StatusCodeError as error:
print(error.symbol) # e.g. BadNodeIdUnknown
o6.StatusCode is an IntFlag, so o6.StatusCode.BAD in status works, and
status.check() raises for anything but Good. write returns one status per
target, attempts later targets after a per-target failure, and raises
StatusCodeError only for service failures. Check returned statuses when a
rejected write must stop your application. Typed-node writes keep their
exception behavior; see write migration.
Local usage errors raise ordinary Python exceptions: ValueError for a
range on a non-Value attribute, a malformed index range, or an out-of-range
event severity; TypeError for a write-only Variable callback, a mismatched
implement combination, an ambiguous server inference, or a @o6.call target
that matches no Method; KeyError from implement when the type node has not
been published yet; RuntimeError when configuration is changed after
start(); and NotImplementedError for the discovery callbacks above.
Callback failures are reported to the client as status codes. Return
(o6.StatusCode.BAD_..., ) from a Method or Variable callback to reject an
operation. An exception escaping a callback is logged rather than propagated to
the caller, so returning an explicit status is always better. Synchronous
callback dispatch also rejects recursion with BadInvalidState: a Variable
cannot re-enter its own read or write callback, and a Method cannot re-enter the
same Method node.
Server log output — access-control decisions, rejected security policies,
session activity — goes to the logger passed to the constructor or to
server.config.logger, defaulting to the o6.server logger. Enabling
logging.DEBUG on it is the fastest way to understand a refused connection.