Mit Skript auf CODESYS-Funktionalitäten zugreifen
Alle Objekte und Befehle, die CODESYS für Skripte bietet, werden im Python-Modul „scriptengine“ bereitgestellt. Immer wenn ein Skript gestartet wird, erfolgt ein implizites <code>from scriptengine import *</code>. Dies erlaubt einen einfachen Zugriff auf CODESYS. Wenn Ihr Skript allerdings Module importiert, die Zugriff auf CODESYS-APIs benötigen, müssen diese Module selbst das Modul scriptengine importieren!
Die Hauptobjekte (Kategorien), die in Python-Skripten als Einstiegspunkte verwendet werden können, sehen Sie in der nachfolgenden Tabelle. Die ausführliche Dokumentation der Einstiegspunkte finden Sie in der API-Referenzdokumentation zur CODESYS ScriptEngine : CODESYS Scripting API.
Objekte | Beschreibung |
|---|---|
system | Zugriff auf allgemeine CODESYS-Funktionalitäten Beispiele:
|
projects | Zugriff auf das CODESYS-Projekt als Objektbaum, der die drei Navigatoransichten (Geräte, POUs, Module) in einem einzigen Projektbaum kombiniert Ermöglicht auch das Laden, Erzeugen, Speichern und Schließen von Projekten Für die meisten Objekte in einem Projekt gibt es spezielle Methoden mit detaillierter Funktionalität, beispielsweise Kompilierung, Zugriff auf ST-Bausteine, Export, Import, Gerätekonfiguration etc. |
online | Zugriff auf Online-Funktionalitäten Beispiele:
|
librarymanager | Erlaubt die Verwaltung von Bibliotheksrepositorys und das Ansehen, Installieren und Entfernen von Bibliotheken |
device_repository | Handhabung des Geräte-Repositorys, Import und Export von Gerätebeschreibungen |
modulerepository | Verwaltung von CODESYS Application Composer-Modulen und CODESYS Application Composer-Repositorys |
Sehen Sie im Folgenden konkrete Beispielskripte für Zugriffe auf CODESYS-Funktionalitäten. Für detaillierte Informationen sehen Sie bitte die API-Referenzdokumentation zur CODESYS ScriptEngine : CODESYS Scripting API.
Beispiel: Gerätebaum des aktuellen Projekts ausdrucken
Das Skript PrintDeviceTree.py ist ein Beispiel dafür, wie man in einem Projekt navigieren kann. Es erzeugt die Ausgabe einer hierarchischen Darstellung aller Geräte im gerade geöffneten Projekt.
Laden Sie ein Projekt, das einige Geräteobjekte enthält und führen Sie dann das Skript aus.
PrintDeviceTree.py# encoding:utf-8
# We enable the new python 3 print syntax
from __future__ import print_function
# Prints out all devices in the currently open project.
print("--- Printing the devices of the project: ---")
# Define the printing function. This function starts with the
# so called "docstring" which is the recommended way to document
# functions in python.
def print_tree(treeobj, depth=0):
""" Print a device and all its children
Arguments:
treeobj -- the object to print
depth -- The current depth within the tree (default 0).
The argument 'depth' is used by recursive call and
should not be supplied by the user.
"""
# if the current object is a device, we print the name and device identification.
if treeobj.is_device:
name = treeobj.get_name(False)
deviceid = treeobj.get_device_identification()
print("{0}- {1} {2}".format("--"*depth, name, deviceid))
# we recursively call the print_tree function for the child objects.
for child in treeobj.get_children(False):
print_tree(child, depth+1)
# We iterate over all top level objects and call the print_tree function for them.
for obj in projects.primary.get_children():
print_tree(obj)
print("--- Script finished. ---")Im Meldungsfenster wird der Gerätebaum (aus der Ansicht „Geräte“) wiedergegeben, wobei alle Nicht-Geräteobjekte weggelassen werden:

Beispiel: Variablen lesen
Das Skript ReadVariable.py bewirkt ein Einloggen auf das Gerät und startet die Applikation wenn nötig. Danach wird der Wert der Variablen PLC_PRG.iVar1 gelesen und ausgegeben. Wenn Sie das Skript ausprobieren wollen, müssen Sie Projektpfad und Variablennamen anpassen.
ReadVariable.py# encoding:utf-8
from __future__ import print_function
# close open project if necessary:
if projects.primary:
projects.primary.close()
# opens project
proj = projects.open(r"D:\data\projects\Ampel.project")
# set "Ampel.project" to active application
app = proj.active_application
onlineapp = online.create_online_application(app)
# login to device
onlineapp.login(OnlineChangeOption.Try, True)
# set status of application to "run", if not in "run"
if not onlineapp.application_state == ApplicationState.run:
onlineapp.start()
# wait 1 second
system.delay(1000)
# read value of iVar1
value = onlineapp.read_value("PLC_PRG.iVar1")
# display value in message view or command line
print(value)
# log out from device and close "Ampel.project"
onlineapp.logout()
proj.close()In Erweiterung des Skripts ReadVariable.py lädt das Skript MailVariables.py Variablen und Ausdrücke aus einer Rezepturdatei und liest deren aktuelle Werte von der Steuerung. IM Anschluss werden diese Werte in dieselbe Datei zurückgeschrieben. Außerdem verwendet es
die Python-SMTP-Bibliothek, um eine Email zu versenden, die eine Liste der Variablen als Anhang enthält.
Wenn Sie das Skript verwenden wollen, müssen Sie die Pfade, Email-Adresse und den Namen des SMTP-Servers an Ihre Umgebung anpassen.
MailVariables.py# encoding:utf-8
from __future__ import print_function
# Close current project if necessary and open "ScriptTest.project"
if not projects.primary == None:
projects.primary.close()
project = projects.open("D:\\Data\\projects\\scriptTest.project")
# retrieve active application
application = project.active_application
# create online application
online_application = online.create_online_application(application)
# login to application.
online_application.login(OnlineChangeOption.Try, True)
# start PLC if necessary
if not online_application.application_state == ApplicationState.run:
online_application.start()
# wait 2 seconds
system.delay(2000)
# open recipe file to read values.
recipe_input_file = open("D:\\Data\\projects\\RecipeInput.txt", "r")
watch_expressions = []
for watch_expression in recipe_input_file:
watch_expressions.append(watch_expression.strip())
print watch_expressions
# read values from the controllerd
watch_values = online_application.read_values(watch_expressions)
print watch_values
# open output file to write values
recipe_output_file = open("D:\\Data\\projects\\RecipeOutput.txt", "w")
for i in range(len(watch_expressions)):
recipe_output_file.write(watch_expressions[i])
recipe_output_file.write(" = ")
recipe_output_file.write(watch_values[i])
recipe_output_file.write("\n")
# Close files
recipe_input_file.close()
recipe_output_file.close()
# send Email
# import respective libraries
import smtplib
from email.mime.text import MIMEText
#open output file
recipe_output_file = open("D:\\Data\\projects\\RecipeOutput.txt", "r")
mail = MIMEText(recipe_output_file.read())
recipe_output_file.close()
#email address sender and recipient
fromm = "info@example.com"
to = "info@example.com"
# set sender and recipient
mail["Subject"] = "Attention value has changed"
mail["From"] = fromm
mail["To"] = to
# send email
smtp = smtplib.SMTP("name of smtp server")
smtp.sendmail(fromm, [to], mail.as_string())
smtp.quit()
# logout and close application
online_application.logout()
project.close()Beispiel: Programmierbausteine anlegen und bearbeiten
Das Skript CreateDut.py legt im CODESYS-Projekt die Objekte MyStruct, MyAlias und MyUnion an. Der Ordner DataTypes muss dazu bereits vorhanden sein.
CreateDut.py# encoding:utf-8
from __future__ import print_function
STRUCT_CONTENT = """\
a : BOOL;
b : BIT;
c : BIT;
"""
UNION_WHOLE = """\
TYPE MyUnion :
UNION
Zahl : INT;
Prozent : MyAlias;
Bits : MyStruct;
END_UNION
END_TYPE
"""
proj = projects.primary
folder = proj.find('DataTypes', recursive = True)[0]
# Create a struct DUT and insert the list of variables just into the right
# place in line two, row 0 (line numbering starts with line 0)
struktur = folder.create_dut('MyStruct') # DutType.Structure is the default
struktur.textual_declaration.insert(2, 0, STRUCT_CONTENT)
# Alias types get their "content" via the base type, which will just end up
# as one line in the declaration part:
# TYPE MyAlias : INT (0..100); END_TYPE
bereich = folder.create_dut('MyAlias', DutType.Alias, "INT (0..100)")
# Instead of injecting the variables into the existing declaration,
# one can also just replace the complete declaration part, including the
# boilerplate code.
union = folder.create_dut('MyUnion', DutType.Union)
union.textual_declaration.replace(UNION_WHOLE)Beispiel: Benutzeroberfläche / Interaktion mit dem Benutzer
In manchen Fällen müssen Skripte mit dem Benutzer interagieren. Für die üblichsten Interaktionen stellen wir einige einfache APIs bereit. Das Beispielskript System_UI_Test.py zeigt alle der in dieser Hinsicht möglichen Funktionen.
System_UI_Test.py# encoding:utf-8
from __future__ import print_function
"""Performs some tests on the messagestore and UI."""
print("Some Error, Warning and Information popups:")
system.ui.error("Fatal error: Everything is OK. :-)")
system.ui.warning("Your bank account is surprisingly low")
system.ui.info("Just for your information: 42")
print("Now, we ask the user something.")
res = system.ui.prompt("Do you like this?", PromptChoice.YesNo, PromptResult.Yes);
print("The user selected '%s'" % res)
print("Now, the user can choose between custom options:")
res = system.ui.choose("Please choose:", ("First", 2, 7.5, "Something else"))
print("The user selected option '%s'" % str(res)) # res is a tuple
print("Now, the user can choose several options:")
res = system.ui.select_many("Please select one or more options", PromptChoice.OKCancel, PromptResult.OK, ("La Premiere", "The Second", "Das Dritte"))
print("The returned result is: '%s'" % str(res)) # res is a tuple
print("Now, the user can select files and directories")
res = system.ui.open_file_dialog("Choose multiple files:", filter="Text files (*.txt)|*.txt|Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*", filter_index = 0, multiselect=True)
print("The user did choose: '%s'" % str(res)) # res is a tuple as multiselect is true.
res = system.ui.save_file_dialog("Choose a file to save:", filter="Text files (*.txt)|*.txt|Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*", filter_index = 0)
print("The user did choose: '%s'" % res)
res = system.ui.browse_directory_dialog("Choose a directory", path="C:\\")
print("The user did choose: '%s'" % res)
print("Now we query a single line string")
res = system.ui.query_string("What's your name?")
print("Nice to meet you, dear %s." % res)
print("Now we query a multi line string")
res = system.ui.query_string("Please tell me a nice story about your life!", multi_line=True)
if (res):
print("Huh, that has been a long text, at least %s characters!" % len(res))
else:
print("Hey, don't be lazy!")
Beispiel: Objekt Projektinformationen manipulieren
Im Skript ProjectInfoExample.py setzen wir einige Informationen im Objekt Projektinformationen. Die wichtigsten Informationsteile (wie Titel und Version) haben explizite Eigenschaften. Aber beliebige andere Informationsfelder können Sie mit der dictionary-Syntax lesen und schreiben. Beispielsweise diejenigen, die für die Eigenschaften eines Bibliotheksprojekts empfohlen werden
Das unten gezeigte Beispiel mag etwas realitätsfern erscheinen, ähnlicher Code wird aber durchaus in Buildservern verwendet, die automatisch Bibliotheks- und andere Projekte erstellen, testen und gegebenenfalls auch freigeben. Die ScriptEngine ist eines der Schlüsselelemente beim Erstellen von CI-(Continous Integration)- und CD-(Continous Delivery)-Systemen.
ProjectInfoExample.py# encoding:utf-8
from __future__ import print_function
proj = projects.load("D:\Some.library")
info = proj.get_project_info()
# Set some values
info.company = "Test Library Ltd"
info.title = "Script Test Project"
info.version = (0, 8, 15, 4711)
info.default_namespace = "testlibrary"
info.author = "Python von Scriptinger"
# some values recommended in the library toolchain
info.values["DefaultNamespace"] = "testlibrary"
info.values["Placeholder"] = "testlibrary"
info.values["DocFormat"] = "reStructuredText"
# now we set a custom / vendor specific value.
info.values["SpecialDeviceId"] = "PLC0815_4711"
# Enable generation of Accessor functions, so the IEC
# application can display the version in an info screen.
info.change_accessor_generation(True)
# And set the library to released
info.released = True;
proj.save()Beispiel: Externe Befehle ausführen und PLCopenXML-Dateien importieren
Das Beispielskript DeviceImportFromSVN.py holt eine PLCopenXML-Datei von einem externen Programm (in diesem Fall ein SVN.-Client) und importiert es in ein neu angelegtes CODESYS-Projekt.
Wenn Sie das Skript verwenden wollen, müssen Sie die Pfade an Ihre Umgebung anpassen.
DeviceImportFromSVN.py# encoding:utf-8
# Imports a Device in PLCopenXML from Subversion via command line svn client.
# We enable the new python 3 print syntax
from __future__ import print_function
import sys, os
# some variable definitions:
SVNEXE = r"C:\Program Files\Subversion\bin\svn.exe"
XMLURL = "file:///D:/testrepo/testfolder/TestExport.xml"
PROJECT = r"D:\test.project"
# clean up any open project:
if projects.primary:
projects.primary.close()
# Fetch the plcopenxml data from subversion.
# We'll catch the output of the program into the xmldata variable.
# The 'with' construct automatically closes the open pipe for us.
with os.popen('"' + SVNEXE + '" cat ' + XMLURL, 'r') as pipe:
xmldata = pipe.read()
# create a new project:
proj = projects.create(PROJECT)
# import the data into the project.
proj.import_xml(xmldata, False)
# and finally save. :-)
proj.save()
print("--- Script finished. ---")Beispiele: Projektarchivierung
Die Funktionen open_archive() und save_archive() sind Scripting-Funktionen zum Extrahieren eines Projektarchivs beziehungsweise zum Speichern eines Projekts als Projektarchiv. Bei beiden Funktionen können ArchiveCategories angegeben werden.
open_archive()akzeptiert hierfür den Parametercategories_to_extractsave_archive()akzeptiert hierfür den Parameter:additional_categories
ArchiveCategories sind Projektarchivkategorien, die bei open_archive() beim Extrahieren des Projektarchivs mitextrahiert und bei save_archive() in das Projektarchiv mitarchiviert werden.
Beispiele open_archive()
Im Folgenden werden verschiedene Beispiele von open_archive() mit dem Parameter categories_to_extract vorgestellt:
Default-Verhalten: categories_to_extract=None
Wenn categories_to_extract nicht explizit angegeben werden, ist der Parameter auf None gesetzt. Hierbei werden alle Kategorien aus dem Projektarchiv gezogen und extrahiert.
# Default projects.open_archive(stArchiveFile=r"D:\Tests\Test.projectarchive", stProjectPath=r"D:\Tests", bOverwrite=True)
Leere Liste: categoriese_to_extract=[]
Wenn der Parameter categories_to_extract explizit mit einer leeren Liste angegeben wird, werden keine Kategorien aus dem Projektarchiv gezogen.
# Passing empty list projects.open_archive(stArchiveFile=r"D:\Tests\Test.projectarchive", stProjectPath=r"D:\Tests", bOverwrite=True, categories_to_extract=[])
Mehrere Kategorien
Es können mehrere Kategorien in dem Parameter categories_to_extract angegeben werden. Hierbei werden nur die angegebenen Kategorien mitextrahiert.
# Multiple categories projects.open_archive(stArchiveFile=r"D:\Tests\Test.projectarchive", stProjectPath=r"D:\Tests", bOverwrite=True, categories_to_extract=[ArchiveCategories.libraries, ArchiveCategories.devices])
Mögliche Fehler
Wenn in categories_to_extract Kategorien angegeben werden, die nicht in der aktuellen CODESYS-Installation vorhanden sind, wird eine Fehlermeldung ausgegeben. Das Projektarchiv kann nicht extrahiert werden bis die fehlende Kategorie installiert wird oder die Kategorie aus dem Parameter im Skript entfernt wird.
Beispiele save_archive()
Im Folgenden werden verschiedene Beispiele von save_archive() mit dem Parameter additional_categories vorgestellt:
Default-Verhalten
Wenn additional_categories nicht explizit gesetzt sind, werden die Default-Kategorien der CODESYS-Oberfläche mitarchiviert.
Keine Kategorien: additional_categories=None
Wenn explizit additional_categories im Skript auf None gesetzt wird, werden keine Kategorien mitarchiviert.
Mehrere Kategorien
Es können mehrere Kategorien in dem Parameter additional_categories angegeben werden. Hierbei werden nur die angegebenen Kategorien mitarchiviert.
# Muliple categories projects.save_archive(path=r"D:\Tests", comment="A new project", additional_files=[], additional_categories=[ArchiveCategories.libraries, ArchiveCategories.devices])
Fortgeschrittenes Beispiel: Bibliothek aus SVN abrufen und in CODESYS installieren
Das folgende Beispielskript kann als Teil einer CT-(Continuous Testing)-Umgebung das Abrufen und Installieren einer Bibliothek übernehmen, damit diese dann getestet werden kann. Zusätzlich zum Standard-CODESYS muss dafür auch das AddOn CODESYS SVN mit einer gültigen Lizenz installiert sein.
import tempfile
if projects.primary:
projects.primary.close()
tempdir = tempfile.mkdtemp()
URL = "svn://localhost/testrepo/trunk/SvnTestLibrary/"
proj = svn.checkout(URL, tempdir, "testlibrary", as_library=True)
proj.save()
repo = librarymanager.repositories[0]
librarymanager.install_library(proj.path, repo, True)
proj.close()