Node Management¶
Demonstrates how to extend the server's address space at runtime from
the client side, using the high-level o6.Client API. The example
adds an Object, a Variable, a Method, and an ObjectType, links two
Objects with an explicit reference, browses the result, and finally
deletes everything it created.
The example talks to S03_firststeps.py.
This example needs a running server
Start Server First Steps first:
The relevant API surface lives on the o6.Client instance:
addObjectNode/addVariableNode/addMethodNode/addObjectTypeNodeaddReference/deleteReferencedeleteNode(accepts a single NodeId or a list)
All add*Node calls are keyword-only and return the NodeId the
client (or the server, if none was requested) chose for the new node.
1. Connection Setup¶
The endpoint URL is the same one S03_firststeps.py listens on. The
Objects folder (i=85) is the conventional parent for user-defined
nodes, and Organizes (i=35) is the standard "folder contains
folder" reference type used to attach a new node to its parent.
localhost = "localhost"
endpoint_url = EXAMPLE_ENDPOINT
OBJECTS_FOLDER = "i=85"
ORGANIZES = "i=35"
print("--- 1. Connection Setup ---")
print(f"Connecting to {endpoint_url} ...")
2. Add an Object Node¶
Every node creation follows the same shape: build an *Attributes
struct, set the human-readable fields (displayName,
description), and pass the struct to the matching
add*Node method along with parent, browseName, and the
reference type that connects the new node to its parent.
browseName can be either a plain string or an
o6.QualifiedName; passing o6.QualifiedName(1, "MyFolder")
explicitly places the new node in namespace 1, the same namespace
S03_firststeps.py uses for its own objects.
Every add*Node call also accepts a requestedNodeId=
keyword (e.g. requestedNodeId="ns=1;i=5000") to pin a stable
address for the new node; omitting it lets the server pick one. The
server-side equivalent is the nodeId= keyword on addObject /
addVariable / addMethod.
with Client(endpoint_url) as client:
print("Connected.")
print()
print("--- 2. Add an Object Node ---")
obj_attr = ObjectAttributes()
obj_attr.displayName = o6.LocalizedText("MyFolder")
obj_attr.description = o6.LocalizedText("An example folder object")
folder_id = client.addObjectNode(
parent=OBJECTS_FOLDER,
browseName=o6.QualifiedName(1, "MyFolder"),
attributes=obj_attr,
parentReference=ORGANIZES,
)
print(f"Added object: {folder_id}")
3. Add a Variable Node¶
VariableAttributes is the only one of the four attribute types
that carries a value: the initial value of the variable.
valueRank = -1 means scalar; positive values indicate an
array of that rank. accessLevel = 3 means "readable and
writable".
print()
print("--- 3. Add a Variable Node ---")
var_attr = VariableAttributes()
var_attr.displayName = o6.LocalizedText("Temperature")
var_attr.description = o6.LocalizedText("A temperature sensor value")
var_attr.value = o6.Double(21.5)
var_attr.dataType = o6.NodeId(11) # i=11 in ns=0 is the Double data type
var_attr.valueRank = -1 # scalar
var_attr.accessLevel = 3 # readable + writable
var_attr.userAccessLevel = 3
temp_id = client.addVariableNode(
parent=folder_id,
browseName=o6.QualifiedName(1, "Temperature"),
requestedNodeId="ns=1;i=5001", # pin a stable address
attributes=var_attr,
)
print(f"Added variable: {temp_id}")
# Read back the initial value we just stored on the server
initial = client.read(temp_id)
print(f"Initial value = {initial}")
4. Add a Method Node¶
Adding a method node is structurally identical to adding an
object: build a MethodAttributes and call
addMethodNode. The node exists once this call returns, but
it is not invokable until the server registers a callback for
it — S03_firststeps.py does not register one for Reset, so a
later client.call(method_id, ...) would return
BAD_NOT_IMPLEMENTED.
print()
print("--- 4. Add a Method Node ---")
method_attr = MethodAttributes()
method_attr.displayName = o6.LocalizedText("Reset")
method_attr.description = o6.LocalizedText("Reset the sensor")
method_attr.executable = True
method_attr.userExecutable = True
method_id = client.addMethodNode(
parent=folder_id,
browseName=o6.QualifiedName(1, "Reset"),
attributes=method_attr,
)
print(f"Added method: {method_id}")
5. Add an ObjectType Node¶
ObjectTypes are templates, so adding one to the address space
does not affect existing objects. To use it, add an object node
whose typeDefinition references the new ObjectType's NodeId.
print()
print("--- 5. Add an ObjectType Node ---")
objtype_attr = ObjectTypeAttributes()
objtype_attr.displayName = o6.LocalizedText("SensorType")
objtype_attr.description = o6.LocalizedText("A custom sensor type")
objtype_attr.isAbstract = False
objtype_id = client.addObjectTypeNode(
parent="i=58", # BaseObjectType
browseName=o6.QualifiedName(1, "SensorType"),
attributes=objtype_attr,
)
print(f"Added ObjectType: {objtype_id}")
6. Browse the New Object's Children¶
client.browse(nodeid) returns the list of references leading
out from the given node. With result_mask=BrowseResultMask.ALL
each reference carries every field (browse name, display name,
node class, type definition, ...).
print()
print("--- 6. Browse the New Object's Children ---")
print("MyFolder children:")
for ref in client.browse(folder_id, resultMask=ns0dt.BrowseResultMask.ALL):
print(
f" {ref.browseName} class={ref.nodeClass} "
f"forward={ref.isForward} target={ref.nodeId}"
)
7. Add an Explicit Reference Between Two Objects¶
To demonstrate addReference / deleteReference we add a
second folder and create an Organizes reference from
MyFolder to MyFolder2 — a forward reference to a sibling,
the simplest valid case.
print()
print("--- 7. Add an Explicit Reference Between Two Objects ---")
obj_attr2 = ObjectAttributes()
obj_attr2.displayName = o6.LocalizedText("MyFolder2")
obj_attr2.description = o6.LocalizedText("A second folder object")
folder2_id = client.addObjectNode(
parent=OBJECTS_FOLDER,
browseName=o6.QualifiedName(1, "MyFolder2"),
attributes=obj_attr2,
parentReference=ORGANIZES,
)
print(f"Added second object: {folder2_id}")
add_status = client.addReference(
source=folder_id,
reftype=ORGANIZES,
target=folder2_id,
)
print(f"Added reference {folder_id} -Organizes-> {folder2_id}, status = {add_status}")
print("MyFolder children after adding the reference:")
for ref in client.browse(folder_id, resultMask=ns0dt.BrowseResultMask.ALL):
print(
f" {ref.browseName} class={ref.nodeClass} "
f"forward={ref.isForward} target={ref.nodeId}"
)
del_status = client.deleteReference(
source=folder_id,
reftype=ORGANIZES,
target=folder2_id,
)
print(f"Deleted reference {folder_id} -Organizes-> {folder2_id}, status = {del_status}")
print("MyFolder children after deleting the reference:")
for ref in client.browse(folder_id, resultMask=ns0dt.BrowseResultMask.ALL):
print(
f" {ref.browseName} class={ref.nodeClass} "
f"forward={ref.isForward} target={ref.nodeId}"
)
8. Clean Up¶
deleteNode accepts either a single NodeId or a list of
NodeIds. deleteTargetReferences defaults to True, so the
Organizes reference created above is removed as part of the
second folder's deletion.
print()
print("--- 8. Clean Up ---")
targets = [method_id, temp_id, objtype_id, folder2_id, folder_id]
del_statuses = client.deleteNode(targets)
for target, status in zip(targets, del_statuses):
print(f"Delete {target}: {status}")
status.check()
print()
print("=== Example completed ===")
Source Code¶
Download C07_nodemanagement.py to run it locally.