[Python] OPCUA Copy Server

Roin

Freier Denker
Registriert
22 Juli 2013
Beiträge
581
Hallo zusammen,

ich habe einen OPCUA-Server (OrigServer) zu dem ich subscriben kann. Die Daten gehören rechtlich mir / meiner Firma und wir möchten diese weiteren Menschen zur Verfügung stellen, jedoch möchten wir den Zugriff auf den eigentlichen Server (OrigServer) beschränken.

Unsere Idee: Wir haben einen Server (ServerExtern) für unsere externen Zugreifer, auf dem wir einen OPCUA Server betreiben. Dieser subscribed alle Nodes auf dem OrigServer und published sie NEU.

Da es sich dabei um einige Tausend Nodes handelt, würden wir das gerne mit einem Skript automatisieren.
Ich habe die inzwischen fleißig studiert. Ich habe auf einen Code-Schnippsel gehofft, bei dem ich einmal eine Liste aller Nodes ausgeben lassen kann und diese auf dem neuen Server "einfach" importieren.
Das scheint es nicht zu geben, oder habt ihr da etwa auf die Schnelle gefunden?

Derzeit sehe ich nur die Möglichkeit durch jede Node zu Iterieren und jeden Datentyp, Namen, Identifier usw. zu extrahieren und NEU zu erstellen. Hierfür dann noch die zugehörigen Callbacks zu registieren, damit alle Änderungen übergeben werden scheint relativ umständlich zu sein.
Geht das auch einfacher oder ist mein Ansatz hier generell umständlich und ich habe etwas übersehen?

P.S.: Externe Zugreifer sollen zwar auf dem ExternServer schreiben können, jedoch sollen diese Änderungen NICHT an OrigServer übergeben werden. An dieser Stelle soll nur eine lesende Verbindung bestehen.

Über jegliche Tipps und Hinweise bin ich glücklich!
 
  • Thread Starter Thread Starter
  • #2
Derzeit bin ich soweit einige Daten von einem beliebigen OPCUA-Server auszulesen. Dazu habe ich exemplarisch das folgende Skript gebaut. Ich bin mir aktuell noch nicht sicher, ob ich damit alle notwendigen Daten auslese um die Nodes in gleicher Art und Weise zu replizieren.
Insbesondere die (numerische) NodeId könnte mir Probleme bereiten. Ich überlege aktuell, ob ich diese einfach ignorieren kann und nur die allgemeine Struktur erhalten möchte.

[src=python]import logging
from opcua import Client
from opcua import Server as opcuaServer
from opcua import ua
import socket
import threading
import time

if __name__ == "__main__":
logging.basicConfig(level=logging.WARN)
address = "opc.tcp://127.0.0.1:4840"
client = Client(address)
try:
client.connect()
# Client has a few methods to get proxy to UA nodes that should always be in address space such as Root or Objects
root = client.get_root_node()
print("Root node is: ", root)
objects = client.get_objects_node()
print("Objects node is: ", objects)

# Node objects have methods to read and write node attributes as well as browse or populate address space
children = root.get_children()
print("Children of root are: ", children)

def recursive_print_children_browse_names(c):

def recursive_var_check(variables):
for v in variables:
print("Variables:", v, "\tName:", v.get_browse_name(), "\tPath:", v.get_path(), "\tValue:", v.get_value())
new_vars = v.get_variables()
if len(new_vars) != 0:
recursive_var_check(new_vars)

for child in c:
try:
print("Childname:", child.get_browse_name())
print(child.get_description()) # LocalizedText(Encoding:2, Locale:None, Text:The browse entry point when looking for objects in the server address space.)
print(child.get_description_refs()) # []
print(child.get_node_class()) # NodeClass.Object
print(child.get_parent()) # i=84
print(child.get_path()) # [Node(TwoByteNodeId(i=84)), Node(TwoByteNodeId(i=85))]
print(child.get_properties()) # []
print(child.get_type_definition()) # TwoByteNodeId(i=61)
variables = child.get_variables()

recursive_var_check(variables)
except ua.UaStatusCodeError as e:
print("Error catched:", e)
new_childs = child.get_children()
if len(new_childs) != 0:
recursive_print_children_browse_names(new_childs)
#if len(new_childs) >=6:
# recursive_print_children_browse_names(new_childs[:6])
#else:
# recursive_print_children_browse_names(new_childs)

for child in children:
print("##")
print(dir(child))
# 'get_access_level', 'get_array_dimensions', 'get_attribute', 'get_attributes', 'get_browse_name', 'get_child', 'get_children', 'get_children_descriptions', 'get_data_type', 'get_data_type_as_variant_type', 'get_data_value', 'get_description', 'get_description_refs', 'get_display_name', 'get_encoding_refs', 'get_event_notifier', 'get_methods', 'get_node_class', 'get_parent', 'get_path', 'get_properties', 'get_referenced_nodes', 'get_references', 'get_type_definition', 'get_user_access_level', 'get_value', 'get_value_rank', 'get_variables'
# print(child.get_access_level())
# print(child.get_array_dimensions())
# print(child.get_attributes())
# print(child.get_data_type())
# print(child.get_data_type_as_variant_type())
# print(child.get_data_value())
print(child.get_description()) # LocalizedText(Encoding:2, Locale:None, Text:The browse entry point when looking for objects in the server address space.)
print(child.get_description_refs()) # []
print(child.get_node_class()) # NodeClass.Object
print(child.get_parent()) # i=84
print(child.get_path()) # [Node(TwoByteNodeId(i=84)), Node(TwoByteNodeId(i=85))]
print(child.get_properties()) # []
print(child.get_type_definition()) # TwoByteNodeId(i=61)
# print(child.get_value())

print("### Starting the recursive loop")
recursive_print_children_browse_names(children)
#recursive_print_children_browse_names([objects])

finally:
client.disconnect()
[/src]

Dieses Skript baucht ungefähr ein ganzes Jahrhundert um alle Nodes abzugreifen. Ich bin mir nicht sicher, ob das wirklich der eleganteste Weg ist.
Allerdings ist meine derzeitige Idee während ich durch die Client-Objekte iteriere, parallel neue Objekte für den Server zu erstellen, die den gleichen Namen und die gleichen Kinder haben. Zudem müsste ich auch entsprechende Callbacks definieren, sobald sich einer der Werte bei OrigServer (hier client VerbindunG) ändern, diese auch auf den neu erstellen Server zu übertragen.

Alternativ habe ich auch versucht mit export_xml und import_xml zu experimentieren. Abgesehen von langer Wartezeit und anschließenden Fehlermeldungen habe ich da bisher wenig Erfolg gehabt.
 
  • Thread Starter Thread Starter
  • #3
Ich habe ein paar Fortschritte in der Zwischenzeit gemacht.
Ich habe mir aktuell manuell die fehlenden Datentypen rausgesucht und erstelle diese nachträglich.
Leider scheint dabei irgendwas von mir noch zu fehlen.

Der Baum sieht anders aus, obwohl die eigentlichen Elemente anscheinend gleich sind.
Ich kopiere die Datentypen von der Simatic (Siemens) Steuerung und erstelle damit meinen OwnServer.
OwnServer.jpg Siemens_Server.jpg

Hat da jemand eine Ahnung, was ich anscheinend anders mache? Ich dachte es liegt vielleicht am Display_Name aber der scheint ja ebenfalls gleich zu sein...
 
Zurück
Oben