Mit default zusammenführen

This commit is contained in:
2017-07-06 15:44:47 +02:00
11 changed files with 753 additions and 223 deletions

View File

@@ -5,8 +5,8 @@
# Required-Stop: $remote_fs $syslog $piControl # Required-Stop: $remote_fs $syslog $piControl
# Default-Start: 2 3 4 5 # Default-Start: 2 3 4 5
# Default-Stop: 0 1 6 # Default-Stop: 0 1 6
# Short-Description: Start RevPiPyLoad to execute python plc program # Short-Description: RevPiPyLoad to execute python plc program
# Description: This file starts the RevPiPyLoad on system # Description: This file manages the RevPiPyLoad on system
# boot. The Loader starts your python plc program and # boot. The Loader starts your python plc program and
# check whether it is running. # check whether it is running.
### END INIT INFO ### END INIT INFO

26
doc/index.html Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html><head>
<title>Table of contents</title>
<meta charset="UTF-8">
</head>
<body style="background-color:#FFFFFF;color:#000000">
<h1 style="background-color:#FFFFFF;color:#0000FF">
Table of contents</h1>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Modules</h3>
<table>
<tr>
<td><a style="color:#0000FF" href="procimgserver.html">procimgserver</a></td>
<td>Stellt Funktionen bereit um das Prozessabbild zu ueberwachen.</td>
</tr><tr>
<td><a style="color:#0000FF" href="proginit.html">proginit</a></td>
<td>Main functions of our program.</td>
</tr><tr>
<td><a style="color:#0000FF" href="revpipyload.html">revpipyload</a></td>
<td>Revolution Pi Python PLC Loader.</td>
</tr>
</table>
</body></html>

207
doc/procimgserver.html Normal file
View File

@@ -0,0 +1,207 @@
<!DOCTYPE html>
<html><head>
<title>procimgserver</title>
<meta charset="UTF-8">
</head>
<body style="background-color:#FFFFFF;color:#000000"><a NAME="top" ID="top"></a>
<h1 style="background-color:#FFFFFF;color:#0000FF">
procimgserver</h1>
<p>
Stellt Funktionen bereit um das Prozessabbild zu ueberwachen.
</p><p>
Bei ausreichend Rechten koennen Ausgaenge auch gesetzt werden um einen
IO-Check bei Inbetriebname durchzufuehren.
</p>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Global Attributes</h3>
<table>
<tr><td>None</td></tr>
</table>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Classes</h3>
<table>
<tr>
<td><a style="color:#0000FF" href="#ProcimgServer">ProcimgServer</a></td>
<td>Serverkomponente fuer zusaetzliche XML-RPC Funktionen.</td>
</tr>
</table>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Functions</h3>
<table>
<tr><td>None</td></tr>
</table>
<hr /><hr />
<a NAME="ProcimgServer" ID="ProcimgServer"></a>
<h2 style="background-color:#FFFFFF;color:#0000FF">ProcimgServer</h2>
<p>
Serverkomponente fuer zusaetzliche XML-RPC Funktionen.
</p><p>
Diese Klasse registriert zusaetzliche Funktionen an einem besthenden
XML-RPC-Server. Der Funktionsumfang wird erweitert um zyklisch das
Prozessabbild zu empfangen und bei ausreichend Rechten Ausgaenge zu
setzen.
</p><p>
</p>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Derived from</h3>
None
<h3 style="background-color:#FFFFFF;color:#FF0000">
Class Attributes</h3>
<table>
<tr><td>None</td></tr>
</table>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Class Methods</h3>
<table>
<tr><td>None</td></tr>
</table>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Methods</h3>
<table>
<tr>
<td><a style="color:#0000FF" href="#ProcimgServer.__init__">ProcimgServer</a></td>
<td>Instantiiert RevPiCheckServer()-Klasse.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#ProcimgServer.devices">devices</a></td>
<td>Generiert Deviceliste mit Position und Namen.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#ProcimgServer.ios">ios</a></td>
<td>Generiert ein dict() der Devices und IOs.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#ProcimgServer.loadrevpimodio">loadrevpimodio</a></td>
<td>Instantiiert das RevPiModIO Modul.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#ProcimgServer.setvalue">setvalue</a></td>
<td>Setzt einen Wert auf dem RevPi.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#ProcimgServer.start">start</a></td>
<td>Registriert XML Funktionen.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#ProcimgServer.stop">stop</a></td>
<td>Entfernt XML-Funktionen.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#ProcimgServer.values">values</a></td>
<td>Liefert Prozessabbild an Client.</td>
</tr>
</table>
<h3 style="background-color:#FFFFFF;color:#FF0000">
Static Methods</h3>
<table>
<tr><td>None</td></tr>
</table>
<a NAME="ProcimgServer.__init__" ID="ProcimgServer.__init__"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer (Constructor)</h3>
<b>ProcimgServer</b>(<i>logger, xmlserver, configrsc, procimg, aclmode</i>)
<p>
Instantiiert RevPiCheckServer()-Klasse.
</p><dl>
<dt><i>xmlserver</i></dt>
<dd>
XML-RPC Server
</dd><dt><i>procimg</i></dt>
<dd>
Pfad zum Prozessabbild
</dd><dt><i>configrsc</i></dt>
<dd>
Pfad zur piCtory Konfigurationsdatei
</dd><dt><i>logger</i></dt>
<dd>
Loggerinstanz
</dd><dt><i>aclmode</i></dt>
<dd>
Zugriffsrechte
</dd>
</dl><a NAME="ProcimgServer.devices" ID="ProcimgServer.devices"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer.devices</h3>
<b>devices</b>(<i></i>)
<p>
Generiert Deviceliste mit Position und Namen.
</p><dl>
<dt>Returns:</dt>
<dd>
list() mit Tuple (pos, name)
</dd>
</dl><a NAME="ProcimgServer.ios" ID="ProcimgServer.ios"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer.ios</h3>
<b>ios</b>(<i>type</i>)
<p>
Generiert ein dict() der Devices und IOs.
</p><dl>
<dt><i>type</i></dt>
<dd>
IO Typ inp/out
</dd>
</dl><dl>
<dt>Returns:</dt>
<dd>
pickled dict()
</dd>
</dl><a NAME="ProcimgServer.loadrevpimodio" ID="ProcimgServer.loadrevpimodio"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer.loadrevpimodio</h3>
<b>loadrevpimodio</b>(<i></i>)
<p>
Instantiiert das RevPiModIO Modul.
</p><dl>
<dt>Returns:</dt>
<dd>
True, wenn erfolgreich, sonst False
</dd>
</dl><a NAME="ProcimgServer.setvalue" ID="ProcimgServer.setvalue"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer.setvalue</h3>
<b>setvalue</b>(<i>device, io, value</i>)
<p>
Setzt einen Wert auf dem RevPi.
</p><dl>
<dt><i>device</i></dt>
<dd>
Device Position oder Name
</dd><dt><i>io</i></dt>
<dd>
IO Name fuer neuen Wert
</dd><dt><i>value</i></dt>
<dd>
Neuer Wert
</dd>
</dl><dl>
<dt>Returns:</dt>
<dd>
list() [device, io, status, msg]
</dd>
</dl><a NAME="ProcimgServer.start" ID="ProcimgServer.start"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer.start</h3>
<b>start</b>(<i></i>)
<p>
Registriert XML Funktionen.
</p><dl>
<dt>Returns:</dt>
<dd>
True, wenn erfolgreich
</dd>
</dl><a NAME="ProcimgServer.stop" ID="ProcimgServer.stop"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer.stop</h3>
<b>stop</b>(<i></i>)
<p>
Entfernt XML-Funktionen.
</p><a NAME="ProcimgServer.values" ID="ProcimgServer.values"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
ProcimgServer.values</h3>
<b>values</b>(<i></i>)
<p>
Liefert Prozessabbild an Client.
</p><dl>
<dt>Returns:</dt>
<dd>
Binary() bytes or None
</dd>
</dl>
<div align="right"><a style="color:#0000FF" href="#top">Up</a></div>
<hr />
</body></html>

View File

@@ -32,7 +32,7 @@ begrenzt werden!
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
Global Attributes</h3> Global Attributes</h3>
<table> <table>
<tr><td>configrsc</td></tr><tr><td>picontrolreset</td></tr><tr><td>procimg</td></tr><tr><td>pyloadverion</td></tr> <tr><td>configrsc</td></tr><tr><td>picontrolreset</td></tr><tr><td>procimg</td></tr><tr><td>pyloadverion</td></tr><tr><td>rapcatalog</td></tr>
</table> </table>
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
Classes</h3> Classes</h3>
@@ -64,8 +64,6 @@ Ermoeglicht den Zugriff auf die Logdateien.
</p><p> </p><p>
Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das
RevPiPyLoad-System und die Logdatei der PLC-Anwendung. RevPiPyLoad-System und die Logdatei der PLC-Anwendung.
Ausserdem koennen nur neue Zeilen abgerufen werden, um eine dynamische
Logansicht zu ermoeglichen.
</p><p> </p><p>
</p> </p>
@@ -92,17 +90,11 @@ Methods</h3>
<td><a style="color:#0000FF" href="#LogReader.closeall">closeall</a></td> <td><a style="color:#0000FF" href="#LogReader.closeall">closeall</a></td>
<td>Fuehrt close auf File Handler durch.</td> <td>Fuehrt close auf File Handler durch.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#LogReader.get_applines">get_applines</a></td> <td><a style="color:#0000FF" href="#LogReader.load_applog">load_applog</a></td>
<td>Gibt neue Zeilen ab letzen Aufruf zurueck.</td> <td>Uebertraegt Logdaten des PLC Programms Binaer.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#LogReader.get_applog">get_applog</a></td> <td><a style="color:#0000FF" href="#LogReader.load_plclog">load_plclog</a></td>
<td>Gibt die gesamte Logdatei zurueck.</td> <td>Uebertraegt Logdaten des Loaders Binaer.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#LogReader.get_plclines">get_plclines</a></td>
<td>Gibt neue Zeilen ab letzen Aufruf zurueck.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#LogReader.get_plclog">get_plclog</a></td>
<td>Gibt die gesamte Logdatei zurueck.</td>
</tr> </tr>
</table> </table>
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
@@ -122,49 +114,43 @@ LogReader.closeall</h3>
<b>closeall</b>(<i></i>) <b>closeall</b>(<i></i>)
<p> <p>
Fuehrt close auf File Handler durch. Fuehrt close auf File Handler durch.
</p><a NAME="LogReader.get_applines" ID="LogReader.get_applines"></a> </p><a NAME="LogReader.load_applog" ID="LogReader.load_applog"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
LogReader.get_applines</h3> LogReader.load_applog</h3>
<b>get_applines</b>(<i></i>) <b>load_applog</b>(<i>start, count</i>)
<p> <p>
Gibt neue Zeilen ab letzen Aufruf zurueck. Uebertraegt Logdaten des PLC Programms Binaer.
</p><dl> </p><dl>
<dt>Returns:</dt> <dt><i>start</i></dt>
<dd> <dd>
list() mit neuen Zeilen Startbyte
</dd><dt><i>count</i></dt>
<dd>
Max. Byteanzahl zum uebertragen
</dd> </dd>
</dl><a NAME="LogReader.get_applog" ID="LogReader.get_applog"></a> </dl><dl>
<h3 style="background-color:#FFFFFF;color:#FF0000">
LogReader.get_applog</h3>
<b>get_applog</b>(<i></i>)
<p>
Gibt die gesamte Logdatei zurueck.
</p><dl>
<dt>Returns:</dt> <dt>Returns:</dt>
<dd> <dd>
str() mit Logdaten Binary() der Logdatei
</dd> </dd>
</dl><a NAME="LogReader.get_plclines" ID="LogReader.get_plclines"></a> </dl><a NAME="LogReader.load_plclog" ID="LogReader.load_plclog"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
LogReader.get_plclines</h3> LogReader.load_plclog</h3>
<b>get_plclines</b>(<i></i>) <b>load_plclog</b>(<i>start, count</i>)
<p> <p>
Gibt neue Zeilen ab letzen Aufruf zurueck. Uebertraegt Logdaten des Loaders Binaer.
</p><dl> </p><dl>
<dt>Returns:</dt> <dt><i>start</i></dt>
<dd> <dd>
list() mit neuen Zeilen Startbyte
</dd><dt><i>count</i></dt>
<dd>
Max. Byteanzahl zum uebertragen
</dd> </dd>
</dl><a NAME="LogReader.get_plclog" ID="LogReader.get_plclog"></a> </dl><dl>
<h3 style="background-color:#FFFFFF;color:#FF0000">
LogReader.get_plclog</h3>
<b>get_plclog</b>(<i></i>)
<p>
Gibt die gesamte Logdatei zurueck.
</p><dl>
<dt>Returns:</dt> <dt>Returns:</dt>
<dd> <dd>
str() mit Logdaten Binary() der Logdatei
</dd> </dd>
</dl> </dl>
<div align="right"><a style="color:#0000FF" href="#top">Up</a></div> <div align="right"><a style="color:#0000FF" href="#top">Up</a></div>
@@ -232,7 +218,7 @@ PipeLogwriter (Constructor)</h3>
<p> <p>
Instantiiert PipeLogwriter-Klasse. Instantiiert PipeLogwriter-Klasse.
</p><dl> </p><dl>
<dt><i>logfilename:</i></dt> <dt><i>logfilename</i></dt>
<dd> <dd>
Dateiname fuer Logdatei Dateiname fuer Logdatei
</dd> </dd>
@@ -260,7 +246,7 @@ PipeLogwriter.logline</h3>
<p> <p>
Schreibt eine Zeile in die Logdatei oder stdout. Schreibt eine Zeile in die Logdatei oder stdout.
</p><dl> </p><dl>
<dt><i>message:</i></dt> <dt><i>message</i></dt>
<dd> <dd>
Logzeile zum Schreiben Logzeile zum Schreiben
</dd> </dd>
@@ -374,7 +360,7 @@ RevPiPlc._spopen</h3>
<p> <p>
Startet das PLC Programm. Startet das PLC Programm.
</p><dl> </p><dl>
<dt><i>lst_proc:</i></dt> <dt><i>lst_proc</i></dt>
<dd> <dd>
Prozessliste Prozessliste
</dd> </dd>
@@ -456,13 +442,13 @@ Methods</h3>
<td>Signal handler to start new logfile.</td> <td>Signal handler to start new logfile.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.packapp">packapp</a></td> <td><a style="color:#0000FF" href="#RevPiPyLoad.packapp">packapp</a></td>
<td>Erzeugt aus dem PLC-Programm ein TAR-File.</td> <td>Erzeugt aus dem PLC-Programm ein TAR/Zip-File.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.start">start</a></td> <td><a style="color:#0000FF" href="#RevPiPyLoad.start">start</a></td>
<td>Start plcload and PLC python program.</td> <td>Start revpipyload.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.stop">stop</a></td> <td><a style="color:#0000FF" href="#RevPiPyLoad.stop">stop</a></td>
<td>Stop PLC python program and plcload.</td> <td>Stop revpipyload.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.xml_getconfig">xml_getconfig</a></td> <td><a style="color:#0000FF" href="#RevPiPyLoad.xml_getconfig">xml_getconfig</a></td>
<td>Uebertraegt die RevPiPyLoad Konfiguration.</td> <td>Uebertraegt die RevPiPyLoad Konfiguration.</td>
@@ -492,11 +478,17 @@ Methods</h3>
<td>Stoppt das PLC Programm.</td> <td>Stoppt das PLC Programm.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.xml_plcupload">xml_plcupload</a></td> <td><a style="color:#0000FF" href="#RevPiPyLoad.xml_plcupload">xml_plcupload</a></td>
<td>Empfaengt Dateien fuer das PLC Programm.</td> <td>Empfaengt Dateien fuer das PLC Programm einzeln.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.xml_plcuploadclean">xml_plcuploadclean</a></td> <td><a style="color:#0000FF" href="#RevPiPyLoad.xml_plcuploadclean">xml_plcuploadclean</a></td>
<td>Loescht das gesamte plcworkdir Verzeichnis.</td> <td>Loescht das gesamte plcworkdir Verzeichnis.</td>
</tr><tr> </tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.xml_psstart">xml_psstart</a></td>
<td>Startet den Prozessabbildserver.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.xml_psstop">xml_psstop</a></td>
<td>Stoppt den Prozessabbildserver.</td>
</tr><tr>
<td><a style="color:#0000FF" href="#RevPiPyLoad.xml_reload">xml_reload</a></td> <td><a style="color:#0000FF" href="#RevPiPyLoad.xml_reload">xml_reload</a></td>
<td>Startet RevPiPyLoad neu und verwendet neue Konfiguraiton.</td> <td>Startet RevPiPyLoad neu und verwendet neue Konfiguraiton.</td>
</tr><tr> </tr><tr>
@@ -558,12 +550,12 @@ Signal handler to start new logfile.
RevPiPyLoad.packapp</h3> RevPiPyLoad.packapp</h3>
<b>packapp</b>(<i>mode="tar", pictory=False</i>) <b>packapp</b>(<i>mode="tar", pictory=False</i>)
<p> <p>
Erzeugt aus dem PLC-Programm ein TAR-File. Erzeugt aus dem PLC-Programm ein TAR/Zip-File.
</p><dl> </p><dl>
<dt><i>mode:</i></dt> <dt><i>mode</i></dt>
<dd> <dd>
Packart 'tar' oder 'zip' Packart 'tar' oder 'zip'
</dd><dt><i>pictory:</i></dt> </dd><dt><i>pictory</i></dt>
<dd> <dd>
piCtory Konfiguration mit einpacken piCtory Konfiguration mit einpacken
</dd> </dd>
@@ -577,13 +569,13 @@ Dateinamen des Archivs
RevPiPyLoad.start</h3> RevPiPyLoad.start</h3>
<b>start</b>(<i></i>) <b>start</b>(<i></i>)
<p> <p>
Start plcload and PLC python program. Start revpipyload.
</p><a NAME="RevPiPyLoad.stop" ID="RevPiPyLoad.stop"></a> </p><a NAME="RevPiPyLoad.stop" ID="RevPiPyLoad.stop"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
RevPiPyLoad.stop</h3> RevPiPyLoad.stop</h3>
<b>stop</b>(<i></i>) <b>stop</b>(<i></i>)
<p> <p>
Stop PLC python program and plcload. Stop revpipyload.
</p><a NAME="RevPiPyLoad.xml_getconfig" ID="RevPiPyLoad.xml_getconfig"></a> </p><a NAME="RevPiPyLoad.xml_getconfig" ID="RevPiPyLoad.xml_getconfig"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
RevPiPyLoad.xml_getconfig</h3> RevPiPyLoad.xml_getconfig</h3>
@@ -635,10 +627,10 @@ RevPiPyLoad.xml_plcdownload</h3>
<p> <p>
Uebertraegt ein Archiv vom plcworkdir. Uebertraegt ein Archiv vom plcworkdir.
</p><dl> </p><dl>
<dt><i>mode:</i></dt> <dt><i>mode</i></dt>
<dd> <dd>
Archivart 'tar' 'zip' Archivart 'tar' 'zip'
</dd><dt><i>pictory:</i></dt> </dd><dt><i>pictory</i></dt>
<dd> <dd>
piCtory Konfiguraiton mit einpacken piCtory Konfiguraiton mit einpacken
</dd> </dd>
@@ -682,6 +674,7 @@ Startet das PLC Programm.
<dt>Returns:</dt> <dt>Returns:</dt>
<dd> <dd>
int() Status: int() Status:
-0 Erfolgreich
-1 Programm lauft noch -1 Programm lauft noch
-2 Datei nicht gefunden -2 Datei nicht gefunden
</dd> </dd>
@@ -695,6 +688,7 @@ Stoppt das PLC Programm.
<dt>Returns:</dt> <dt>Returns:</dt>
<dd> <dd>
int() Exitcode vom PLC Programm int() Exitcode vom PLC Programm
-0 Erfolgreich
-1 PLC Programm lief nicht -1 PLC Programm lief nicht
</dd> </dd>
</dl><a NAME="RevPiPyLoad.xml_plcupload" ID="RevPiPyLoad.xml_plcupload"></a> </dl><a NAME="RevPiPyLoad.xml_plcupload" ID="RevPiPyLoad.xml_plcupload"></a>
@@ -702,12 +696,12 @@ int() Exitcode vom PLC Programm
RevPiPyLoad.xml_plcupload</h3> RevPiPyLoad.xml_plcupload</h3>
<b>xml_plcupload</b>(<i>filedata, filename</i>) <b>xml_plcupload</b>(<i>filedata, filename</i>)
<p> <p>
Empfaengt Dateien fuer das PLC Programm. Empfaengt Dateien fuer das PLC Programm einzeln.
</p><dl> </p><dl>
<dt><i>filedata:</i></dt> <dt><i>filedata</i></dt>
<dd> <dd>
GZIP Binary data der datei GZIP Binary data der datei
</dd><dt><i>filename:</i></dt> </dd><dt><i>filename</i></dt>
<dd> <dd>
Name inkl. Unterverzeichnis der Datei Name inkl. Unterverzeichnis der Datei
</dd> </dd>
@@ -727,6 +721,28 @@ Loescht das gesamte plcworkdir Verzeichnis.
<dd> <dd>
True, wenn erfolgreich True, wenn erfolgreich
</dd> </dd>
</dl><a NAME="RevPiPyLoad.xml_psstart" ID="RevPiPyLoad.xml_psstart"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
RevPiPyLoad.xml_psstart</h3>
<b>xml_psstart</b>(<i></i>)
<p>
Startet den Prozessabbildserver.
</p><dl>
<dt>Returns:</dt>
<dd>
True, wenn start erfolgreich
</dd>
</dl><a NAME="RevPiPyLoad.xml_psstop" ID="RevPiPyLoad.xml_psstop"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000">
RevPiPyLoad.xml_psstop</h3>
<b>xml_psstop</b>(<i></i>)
<p>
Stoppt den Prozessabbildserver.
</p><dl>
<dt>Returns:</dt>
<dd>
True, wenn stop erfolgreich
</dd>
</dl><a NAME="RevPiPyLoad.xml_reload" ID="RevPiPyLoad.xml_reload"></a> </dl><a NAME="RevPiPyLoad.xml_reload" ID="RevPiPyLoad.xml_reload"></a>
<h3 style="background-color:#FFFFFF;color:#FF0000"> <h3 style="background-color:#FFFFFF;color:#FF0000">
RevPiPyLoad.xml_reload</h3> RevPiPyLoad.xml_reload</h3>
@@ -751,10 +767,10 @@ RevPiPyLoad.xml_setpictoryrsc</h3>
<p> <p>
Schreibt die config.rsc Datei von piCotry. Schreibt die config.rsc Datei von piCotry.
</p><dl> </p><dl>
<dt><i>filebytes:</i></dt> <dt><i>filebytes</i></dt>
<dd> <dd>
xmlrpc.client.Binary()-Objekt xmlrpc.client.Binary()-Objekt
</dd><dt><i>reset:</i></dt> </dd><dt><i>reset</i></dt>
<dd> <dd>
Reset piControl Device Reset piControl Device
</dd> </dd>
@@ -762,10 +778,12 @@ Reset piControl Device
<dt>Returns:</dt> <dt>Returns:</dt>
<dd> <dd>
Statuscode: Statuscode:
0 Alles erfolgreich -0 Alles erfolgreich
-1 Kann JSON-Datei nicht laden -1 Kann JSON-Datei nicht laden
-2 piCtory Elemente in JSON-Datei nicht gefunden -2 piCtory Elemente in JSON-Datei nicht gefunden
-3 Konnte Konfiguraiton nicht schreiben -3 Konnte Konfiguraiton nicht schreiben
-4 Module in Konfiguration enthalten, die es nicht gibt
-5 Kein RAP Katalog zur Ueberpruefung gefunden
Positive Zahl ist exitcode von piControlReset Positive Zahl ist exitcode von piControlReset
</dd> </dd>
</dl> </dl>

View File

@@ -1,3 +1,11 @@
procimgserver.ProcimgServer.devices?4()
procimgserver.ProcimgServer.ios?4(type)
procimgserver.ProcimgServer.loadrevpimodio?4()
procimgserver.ProcimgServer.setvalue?4(device, io, value)
procimgserver.ProcimgServer.start?4()
procimgserver.ProcimgServer.stop?4()
procimgserver.ProcimgServer.values?4()
procimgserver.ProcimgServer?1(logger, xmlserver, configrsc, procimg, aclmode)
proginit.cleanup?4() proginit.cleanup?4()
proginit.configure?4() proginit.configure?4()
proginit.forked?7 proginit.forked?7
@@ -8,10 +16,8 @@ proginit.logplc?7
proginit.pargs?7 proginit.pargs?7
proginit.startdir?7 proginit.startdir?7
revpipyload.LogReader.closeall?4() revpipyload.LogReader.closeall?4()
revpipyload.LogReader.get_applines?4() revpipyload.LogReader.load_applog?4(start, count)
revpipyload.LogReader.get_applog?4() revpipyload.LogReader.load_plclog?4(start, count)
revpipyload.LogReader.get_plclines?4()
revpipyload.LogReader.get_plclog?4()
revpipyload.LogReader?1() revpipyload.LogReader?1()
revpipyload.PipeLogwriter.__del__?6() revpipyload.PipeLogwriter.__del__?6()
revpipyload.PipeLogwriter._configurefh?5() revpipyload.PipeLogwriter._configurefh?5()
@@ -48,6 +54,8 @@ revpipyload.RevPiPyLoad.xml_plcstart?4()
revpipyload.RevPiPyLoad.xml_plcstop?4() revpipyload.RevPiPyLoad.xml_plcstop?4()
revpipyload.RevPiPyLoad.xml_plcupload?4(filedata, filename) revpipyload.RevPiPyLoad.xml_plcupload?4(filedata, filename)
revpipyload.RevPiPyLoad.xml_plcuploadclean?4() revpipyload.RevPiPyLoad.xml_plcuploadclean?4()
revpipyload.RevPiPyLoad.xml_psstart?4()
revpipyload.RevPiPyLoad.xml_psstop?4()
revpipyload.RevPiPyLoad.xml_reload?4() revpipyload.RevPiPyLoad.xml_reload?4()
revpipyload.RevPiPyLoad.xml_setconfig?4(dc, loadnow=False) revpipyload.RevPiPyLoad.xml_setconfig?4(dc, loadnow=False)
revpipyload.RevPiPyLoad.xml_setpictoryrsc?4(filebytes, reset=False) revpipyload.RevPiPyLoad.xml_setpictoryrsc?4(filebytes, reset=False)
@@ -56,3 +64,4 @@ revpipyload.configrsc?7
revpipyload.picontrolreset?7 revpipyload.picontrolreset?7
revpipyload.procimg?7 revpipyload.procimg?7
revpipyload.pyloadverion?7 revpipyload.pyloadverion?7
revpipyload.rapcatalog?7

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Project SYSTEM "Project-5.1.dtd"> <!DOCTYPE Project SYSTEM "Project-5.1.dtd">
<!-- eric project file for project revpipyload --> <!-- eric project file for project revpipyload -->
<!-- Saved: 2017-05-31, 15:02:13 --> <!-- Saved: 2017-07-06, 12:34:30 -->
<!-- Copyright (C) 2017 Sven Sager, akira@narux.de --> <!-- Copyright (C) 2017 Sven Sager, akira@narux.de -->
<Project version="5.1"> <Project version="5.1">
<Language>en_US</Language> <Language>en_US</Language>
@@ -9,7 +9,7 @@
<ProgLanguage mixed="0">Python3</ProgLanguage> <ProgLanguage mixed="0">Python3</ProgLanguage>
<ProjectType>Console</ProjectType> <ProjectType>Console</ProjectType>
<Description>Dieser Loader wird über das Init-System geladen und führt das angegebene Pythonprogramm aus. Es ist für den RevolutionPi gedacht um automatisch das SPS-Programm zu starten.</Description> <Description>Dieser Loader wird über das Init-System geladen und führt das angegebene Pythonprogramm aus. Es ist für den RevolutionPi gedacht um automatisch das SPS-Programm zu starten.</Description>
<Version>0.3.0</Version> <Version>0.4.2</Version>
<Author>Sven Sager</Author> <Author>Sven Sager</Author>
<Email>akira@narux.de</Email> <Email>akira@narux.de</Email>
<Eol index="-1"/> <Eol index="-1"/>
@@ -17,6 +17,7 @@
<Source>revpipyload/proginit.py</Source> <Source>revpipyload/proginit.py</Source>
<Source>setup.py</Source> <Source>setup.py</Source>
<Source>revpipyload/revpipyload.py</Source> <Source>revpipyload/revpipyload.py</Source>
<Source>revpipyload/procimgserver.py</Source>
</Sources> </Sources>
<Forms/> <Forms/>
<Translations/> <Translations/>
@@ -28,7 +29,9 @@
<Other>doc</Other> <Other>doc</Other>
<Other>debian</Other> <Other>debian</Other>
<Other>eric-revpipyload.api</Other> <Other>eric-revpipyload.api</Other>
<Other>stdeb.cfg</Other>
</Others> </Others>
<MainScript>revpipyload/revpipyload.py</MainScript>
<Vcs> <Vcs>
<VcsType>Mercurial</VcsType> <VcsType>Mercurial</VcsType>
<VcsOptions> <VcsOptions>
@@ -209,12 +212,6 @@
<string>setup.py</string> <string>setup.py</string>
</list> </list>
</value> </value>
<key>
<string>noindex</string>
</key>
<value>
<bool>True</bool>
</value>
<key> <key>
<string>outputDirectory</string> <string>outputDirectory</string>
</key> </key>

View File

@@ -0,0 +1,207 @@
#
# RevPiPyLoad
#
# Webpage: https://revpimodio.org/revpipyplc/
# (c) Sven Sager, License: LGPLv3
#
# -*- coding: utf-8 -*-
"""Stellt Funktionen bereit um das Prozessabbild zu ueberwachen.
Bei ausreichend Rechten koennen Ausgaenge auch gesetzt werden um einen
IO-Check bei Inbetriebname durchzufuehren.
"""
import pickle
import revpimodio
from xmlrpc.client import Binary
class ProcimgServer():
"""Serverkomponente fuer zusaetzliche XML-RPC Funktionen.
Diese Klasse registriert zusaetzliche Funktionen an einem besthenden
XML-RPC-Server. Der Funktionsumfang wird erweitert um zyklisch das
Prozessabbild zu empfangen und bei ausreichend Rechten Ausgaenge zu
setzen.
"""
def __init__(self, logger, xmlserver, configrsc, procimg, aclmode):
"""Instantiiert RevPiCheckServer()-Klasse.
@param xmlserver XML-RPC Server
@param procimg Pfad zum Prozessabbild
@param configrsc Pfad zur piCtory Konfigurationsdatei
@param logger Loggerinstanz
@param aclmode Zugriffsrechte
"""
# Logger übernehmen
self.logger = logger
self.logger.debug("enter ProcimgServer.__init__()")
self.acl = aclmode
self.configrsc = configrsc
self.procimg = procimg
self.rpi = None
# XML-Server übernehmen
self.xmlsrv = xmlserver
self.xmlreadfuncs = {
"ps_devices": self.devices,
"ps_inps": lambda: self.ios("inp"),
"ps_outs": lambda: self.ios("out"),
"ps_mems": lambda: self.ios("mem"),
"ps_values": self.values,
}
self.xmlwritefuncs = {
"ps_setvalue": self.setvalue,
}
self.loadrevpimodio()
self.logger.debug("leave ProcimgServer.__init__()")
def devices(self):
"""Generiert Deviceliste mit Position und Namen.
@return list() mit Tuple (pos, name)"""
return [
(dev.position, dev.name) for dev in self.rpi.devices
]
def ios(self, type):
"""Generiert ein dict() der Devices und IOs.
@param type IO Typ inp/out
@return pickled dict()"""
dict_ios = {}
for dev in self.rpi.devices:
dict_ios[dev.position] = []
# IO Typen auswerten
if type == "inp":
lst_io = dev.get_inps()
elif type == "out":
lst_io = dev.get_outs()
elif type == "mem":
lst_io = dev.get_mems()
else:
lst_io = []
for io in lst_io:
dict_ios[dev.position].append([
io.name,
1 if io._bitlength == 1 else int(io._bitlength / 8),
io.slc_address.start + dev.offset,
io.bmk,
io._bitaddress,
])
return Binary(pickle.dumps(dict_ios))
def loadrevpimodio(self):
"""Instantiiert das RevPiModIO Modul.
@return True, wenn erfolgreich, sonst False"""
# RevPiModIO-Modul Instantiieren
if self.rpi is not None:
self.rpi.cleanup()
self.logger.debug("create revpimodio class")
try:
self.rpi = revpimodio.RevPiModIO(
configrsc=self.configrsc,
procimg=self.procimg,
)
except:
self.rpi = None
self.logger.error("piCtory configuration not loadable")
return False
self.rpi.devices.syncoutputs(device=0)
self.logger.debug("created revpimodio class")
return True
def setvalue(self, device, io, value):
"""Setzt einen Wert auf dem RevPi.
@param device Device Position oder Name
@param io IO Name fuer neuen Wert
@param value Neuer Wert
@return list() [device, io, status, msg]
"""
# Zugriffsrechte prüfen
if self.acl < 3:
return [
device, io, False,
"not allowed in XML-RPC permission mode {}".format(self.acl)
]
# Binary() in bytes() umwandeln
if type(value) == Binary:
value = value.data
self.rpi.devices.syncoutputs(device=device)
try:
# Neuen Wert übernehmen
if type(value) == bytes or type(value) == bool:
self.rpi.devices[device][io].set_value(value)
else:
self.rpi.devices[device][io].set_value(
value.to_bytes(
self.rpi.devices[device][io].length, byteorder="little"
)
)
except Exception as e:
return [device, io, False, str(e)]
self.rpi.devices.writeprocimg(device=device)
return [device, io, True, ""]
def values(self):
"""Liefert Prozessabbild an Client.
@return Binary() bytes or None"""
if self.rpi.devices.readprocimg() and self.rpi.devices.syncoutputs():
bytebuff = b''
for dev in self.rpi.devices:
bytebuff += bytes(dev)
return Binary(bytebuff)
else:
return None
def start(self):
"""Registriert XML Funktionen.
@return True, wenn erfolgreich"""
self.logger.debug("enter ProcimgServer.start()")
ec = False
if self.rpi is not None:
# Registriere Funktionen
for xmlfunc in self.xmlreadfuncs:
self.xmlsrv.register_function(
self.xmlreadfuncs[xmlfunc], xmlfunc
)
if self.acl >= 3:
for xmlfunc in self.xmlwritefuncs:
self.xmlsrv.register_function(
self.xmlwritefuncs[xmlfunc], xmlfunc
)
ec = True
self.logger.debug("leave ProcimgServer.start()")
return ec
def stop(self):
"""Entfernt XML-Funktionen."""
self.logger.debug("enter ProcimgServer.stop()")
# Entferne Funktionen
for xmlfunc in self.xmlreadfuncs:
if xmlfunc in self.xmlsrv.funcs:
del self.xmlsrv.funcs[xmlfunc]
if self.acl >= 3:
for xmlfunc in self.xmlwritefuncs:
if xmlfunc in self.xmlsrv.funcs:
del self.xmlsrv.funcs[xmlfunc]
self.logger.debug("leave ProcimgServer.stop()")

View File

@@ -102,6 +102,7 @@ def configure():
# Program logger # Program logger
global logger global logger
if logger is None:
logger = logging.getLogger() logger = logging.getLogger()
# Alle handler entfernen # Alle handler entfernen

View File

@@ -33,7 +33,6 @@ begrenzt werden!
import gzip import gzip
import proginit import proginit
import os import os
import pickle
import shlex import shlex
import signal import signal
import socket import socket
@@ -47,7 +46,7 @@ from json import loads as jloads
from re import match as rematch from re import match as rematch
from shutil import rmtree from shutil import rmtree
from sys import stdout as sysstdout from sys import stdout as sysstdout
from tempfile import mktemp from tempfile import mkstemp
from threading import Thread, Event, Lock from threading import Thread, Event, Lock
from time import sleep, asctime from time import sleep, asctime
from timeit import default_timer from timeit import default_timer
@@ -57,7 +56,8 @@ from xmlrpc.server import SimpleXMLRPCServer
configrsc = None configrsc = None
picontrolreset = "/opt/KUNBUS/piControlReset" picontrolreset = "/opt/KUNBUS/piControlReset"
procimg = "/dev/piControl0" procimg = "/dev/piControl0"
pyloadverion = "0.3.0" pyloadverion = "0.4.2"
rapcatalog = None
def _zeroprocimg(): def _zeroprocimg():
@@ -73,17 +73,15 @@ class LogReader():
Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das
RevPiPyLoad-System und die Logdatei der PLC-Anwendung. RevPiPyLoad-System und die Logdatei der PLC-Anwendung.
Ausserdem koennen nur neue Zeilen abgerufen werden, um eine dynamische
Logansicht zu ermoeglichen.
""" """
def __init__(self): def __init__(self):
"""Instantiiert LogReader-Klasse.""" """Instantiiert LogReader-Klasse."""
self.fhapp = None self.fhapp = None
self.posapp = 0 self.fhapplk = Lock()
self.fhplc = None self.fhplc = None
self.posplc = 0 self.fhplclk = Lock()
def closeall(self): def closeall(self):
"""Fuehrt close auf File Handler durch.""" """Fuehrt close auf File Handler durch."""
@@ -92,84 +90,45 @@ class LogReader():
if self.fhplc is not None: if self.fhplc is not None:
self.fhplc.close() self.fhplc.close()
def get_applines(self): def load_applog(self, start, count):
"""Gibt neue Zeilen ab letzen Aufruf zurueck. """Uebertraegt Logdaten des PLC Programms Binaer.
@returns: list() mit neuen Zeilen"""
@param start Startbyte
@param count Max. Byteanzahl zum uebertragen
@return Binary() der Logdatei
"""
if not os.access(proginit.logapp, os.R_OK): if not os.access(proginit.logapp, os.R_OK):
return Binary(pickle.dumps([])) return Binary(b'\x16') # 
elif start > os.path.getsize(proginit.logapp):
return Binary(b'\x19') # 
else: else:
with self.fhapplk:
if self.fhapp is None or self.fhapp.closed: if self.fhapp is None or self.fhapp.closed:
self.fhapp = open(proginit.logapp) self.fhapp = open(proginit.logapp, "rb")
lst_new = [] self.fhapp.seek(start)
while True: return Binary(self.fhapp.read(count))
self.posapp = self.fhapp.tell()
line = self.fhapp.readline()
if line:
lst_new.append(line)
else:
self.fhapp.seek(self.posapp)
break
proginit.logger.debug( def load_plclog(self, start, count):
"got {} new app log lines".format(len(lst_new)) """Uebertraegt Logdaten des Loaders Binaer.
)
return Binary(pickle.dumps(lst_new))
def get_applog(self): @param start Startbyte
"""Gibt die gesamte Logdatei zurueck. @param count Max. Byteanzahl zum uebertragen
@returns: str() mit Logdaten""" @return Binary() der Logdatei
if not os.access(proginit.logapp, os.R_OK):
proginit.logger.error(
"can not access logfile {}".format(proginit.logapp)
)
return Binary(pickle.dumps(""))
else:
if self.fhapp is None or self.fhapp.closed:
self.fhapp = open(proginit.logapp)
self.fhapp.seek(0)
return Binary(pickle.dumps(self.fhapp.read()))
def get_plclines(self): """
"""Gibt neue Zeilen ab letzen Aufruf zurueck.
@returns: list() mit neuen Zeilen"""
if not os.access(proginit.logplc, os.R_OK): if not os.access(proginit.logplc, os.R_OK):
proginit.logger.error( return Binary(b'\x16') # 
"can not access logfile {}".format(proginit.logplc) elif start > os.path.getsize(proginit.logplc):
) return Binary(b'\x19') # 
return Binary(pickle.dumps([]))
else: else:
with self.fhplclk:
if self.fhplc is None or self.fhplc.closed: if self.fhplc is None or self.fhplc.closed:
self.fhplc = open(proginit.logplc) self.fhplc = open(proginit.logplc, "rb")
lst_new = [] self.fhplc.seek(start)
while True: return Binary(self.fhplc.read(count))
self.posplc = self.fhplc.tell()
line = self.fhplc.readline()
if line:
lst_new.append(line)
else:
self.fhplc.seek(self.posplc)
break
proginit.logger.debug(
"got {} new pyloader log lines".format(len(lst_new))
)
return Binary(pickle.dumps(lst_new))
def get_plclog(self):
"""Gibt die gesamte Logdatei zurueck.
@returns: str() mit Logdaten"""
if not os.access(proginit.logplc, os.R_OK):
proginit.logger.error(
"can not access logfile {}".format(proginit.logplc)
)
return Binary(pickle.dumps(""))
else:
if self.fhplc is None or self.fhplc.closed:
self.fhplc = open(proginit.logplc)
self.fhplc.seek(0)
return Binary(pickle.dumps(self.fhplc.read()))
class PipeLogwriter(Thread): class PipeLogwriter(Thread):
@@ -185,7 +144,7 @@ class PipeLogwriter(Thread):
def __init__(self, logfilename): def __init__(self, logfilename):
"""Instantiiert PipeLogwriter-Klasse. """Instantiiert PipeLogwriter-Klasse.
@param logfilename: Dateiname fuer Logdatei""" @param logfilename Dateiname fuer Logdatei"""
super().__init__() super().__init__()
self._exit = Event() self._exit = Event()
self._fh = None self._fh = None
@@ -208,11 +167,10 @@ class PipeLogwriter(Thread):
def _configurefh(self): def _configurefh(self):
"""Konfiguriert den FileHandler fuer Ausgaben der PLCAPP. """Konfiguriert den FileHandler fuer Ausgaben der PLCAPP.
@returns: FileHandler-Objekt""" @return FileHandler-Objekt"""
proginit.logger.debug("enter PipeLogwriter._configurefh()") proginit.logger.debug("enter PipeLogwriter._configurefh()")
dirname = os.path.dirname(self.logfile) dirname = os.path.dirname(self.logfile)
proginit.logger.debug("dirname = {}".format(os.path.abspath(dirname))) proginit.logger.debug("dirname = {}".format(os.path.abspath(dirname)))
if os.access(dirname, os.R_OK | os.W_OK): if os.access(dirname, os.R_OK | os.W_OK):
@@ -225,7 +183,7 @@ class PipeLogwriter(Thread):
def logline(self, message): def logline(self, message):
"""Schreibt eine Zeile in die Logdatei oder stdout. """Schreibt eine Zeile in die Logdatei oder stdout.
@param message: Logzeile zum Schreiben""" @param message Logzeile zum Schreiben"""
with self._lckfh: with self._lckfh:
self._fh.write("{}\n".format(message)) self._fh.write("{}\n".format(message))
self._fh.flush() self._fh.flush()
@@ -243,7 +201,6 @@ class PipeLogwriter(Thread):
proginit.logger.debug("enter PipeLogwriter.run()") proginit.logger.debug("enter PipeLogwriter.run()")
fhread = os.fdopen(self._fdr) fhread = os.fdopen(self._fdr)
proginit.logger.debug("enter logreader pipe loop")
while not self._exit.is_set(): while not self._exit.is_set():
line = fhread.readline() line = fhread.readline()
self._lckfh.acquire() self._lckfh.acquire()
@@ -251,7 +208,7 @@ class PipeLogwriter(Thread):
self._fh.write(line) self._fh.write(line)
self._fh.flush() self._fh.flush()
except: except:
proginit.logger.exception("PipeLogwriter write log line") proginit.logger.exception("PipeLogwriter in write log line")
finally: finally:
self._lckfh.release() self._lckfh.release()
proginit.logger.debug("leave logreader pipe loop") proginit.logger.debug("leave logreader pipe loop")
@@ -307,7 +264,7 @@ class RevPiPlc(Thread):
def _configureplw(self): def _configureplw(self):
"""Konfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP. """Konfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP.
@returns: PipeLogwriter()""" @return PipeLogwriter()"""
proginit.logger.debug("enter RevPiPlc._configureplw()") proginit.logger.debug("enter RevPiPlc._configureplw()")
logfile = None logfile = None
if proginit.pargs.daemon: if proginit.pargs.daemon:
@@ -324,15 +281,17 @@ class RevPiPlc(Thread):
def _setuppopen(self): def _setuppopen(self):
"""Setzt UID und GID fuer das PLC Programm.""" """Setzt UID und GID fuer das PLC Programm."""
proginit.logger.debug( proginit.logger.info(
"set uid {} and gid {}".format(self.uid, self.gid)) "set uid {} and gid {} for plc program".format(
self.uid, self.gid)
)
os.setgid(self.gid) os.setgid(self.gid)
os.setuid(self.uid) os.setuid(self.uid)
def _spopen(self, lst_proc): def _spopen(self, lst_proc):
"""Startet das PLC Programm. """Startet das PLC Programm.
@param lst_proc: Prozessliste @param lst_proc Prozessliste
@returns: subprocess""" @return subprocess"""
proginit.logger.debug("enter RevPiPlc._spopen({})".format(lst_proc)) proginit.logger.debug("enter RevPiPlc._spopen({})".format(lst_proc))
sp = subprocess.Popen( sp = subprocess.Popen(
lst_proc, lst_proc,
@@ -388,7 +347,7 @@ class RevPiPlc(Thread):
if self.exitcode > 0: if self.exitcode > 0:
# PLC Python Programm abgestürzt # PLC Python Programm abgestürzt
proginit.logger.error( proginit.logger.error(
"plc program chrashed - exitcode: {}".format( "plc program crashed - exitcode: {}".format(
self.exitcode self.exitcode
) )
) )
@@ -422,6 +381,12 @@ class RevPiPlc(Thread):
self._evt_exit.wait(1) self._evt_exit.wait(1)
if self._plw is not None:
self._plw.logline("-" * 55)
self._plw.logline("plc: {} stopped: {}".format(
os.path.basename(self._program), asctime()
))
proginit.logger.debug("leave RevPiPlc.run()") proginit.logger.debug("leave RevPiPlc.run()")
def stop(self): def stop(self):
@@ -434,9 +399,8 @@ class RevPiPlc(Thread):
if self._procplc is None: if self._procplc is None:
if self._plw is not None: if self._plw is not None:
self._plw.stop() self._plw.stop()
proginit.logger.debug("join after NONE pipe thread")
self._plw.join() self._plw.join()
proginit.logger.debug("joined after NONE pipe thread") proginit.logger.debug("log pipes successfully closed")
proginit.logger.debug("leave RevPiPlc.stop()") proginit.logger.debug("leave RevPiPlc.stop()")
return return
@@ -467,9 +431,8 @@ class RevPiPlc(Thread):
if self._plw is not None: if self._plw is not None:
self._plw.stop() self._plw.stop()
proginit.logger.debug("join pipe thread")
self._plw.join() self._plw.join()
proginit.logger.debug("joined pipe thread") proginit.logger.debug("log pipes successfully closed")
proginit.logger.debug("leave RevPiPlc.stop()") proginit.logger.debug("leave RevPiPlc.stop()")
@@ -675,12 +638,10 @@ class RevPiPyLoad():
def __init__(self): def __init__(self):
"""Instantiiert RevPiPyLoad-Klasse.""" """Instantiiert RevPiPyLoad-Klasse."""
proginit.configure() proginit.configure()
proginit.logger.debug("enter RevPiPyLoad.__init__()")
# Globale Werte anpassen
global configrsc
global picontrolreset
# piCtory Konfiguration an bekannten Stellen prüfen # piCtory Konfiguration an bekannten Stellen prüfen
global configrsc
lst_rsc = ["/etc/revpi/config.rsc", "/opt/KUNBUS/config.rsc"] lst_rsc = ["/etc/revpi/config.rsc", "/opt/KUNBUS/config.rsc"]
for rscfile in lst_rsc: for rscfile in lst_rsc:
if os.access(rscfile, os.F_OK | os.R_OK): if os.access(rscfile, os.F_OK | os.R_OK):
@@ -688,16 +649,28 @@ class RevPiPyLoad():
break break
if configrsc is None: if configrsc is None:
raise RuntimeError( raise RuntimeError(
"can not access known pictory configurations at {}" "can not find known pictory configurations at {}"
"".format(", ".join(lst_rsc)) "".format(", ".join(lst_rsc))
) )
# rap Katalog an bekannten Stellen prüfen und laden
global rapcatalog
lst_rap = [
"/opt/KUNBUS/pictory/resources/data/rap",
"/var/www/pictory/resources/data/rap"
]
for rapfolder in lst_rap:
if os.path.isdir(rapfolder):
rapcatalog = os.listdir(rapfolder)
# piControlReset suchen # piControlReset suchen
global picontrolreset
if not os.access(picontrolreset, os.F_OK | os.X_OK): if not os.access(picontrolreset, os.F_OK | os.X_OK):
picontrolreset = "/usr/bin/piTest -x" picontrolreset = "/usr/bin/piTest -x"
# Klassenattribute # Klassenattribute
self._exit = True self._exit = True
self.pictorymtime = os.path.getmtime(configrsc)
self.evt_loadconfig = Event() self.evt_loadconfig = Event()
self.globalconfig = ConfigParser() self.globalconfig = ConfigParser()
self.logr = LogReader() self.logr = LogReader()
@@ -705,6 +678,7 @@ class RevPiPyLoad():
self.tfile = {} self.tfile = {}
self.tpe = None self.tpe = None
self.xsrv = None self.xsrv = None
self.xml_ps = None
# Load config # Load config
self._loadconfig() self._loadconfig()
@@ -715,8 +689,12 @@ class RevPiPyLoad():
signal.signal(signal.SIGHUP, self._sigloadconfig) signal.signal(signal.SIGHUP, self._sigloadconfig)
signal.signal(signal.SIGUSR1, self._signewlogfile) signal.signal(signal.SIGUSR1, self._signewlogfile)
proginit.logger.debug("leave RevPiPyLoad.__init__()")
def _loadconfig(self): def _loadconfig(self):
"""Load configuration file and setup modul.""" """Load configuration file and setup modul."""
proginit.logger.debug("enter RevPiPyLoad._loadconfig()")
self.evt_loadconfig.clear() self.evt_loadconfig.clear()
pauseproc = False pauseproc = False
@@ -773,18 +751,34 @@ class RevPiPyLoad():
allow_none=True allow_none=True
) )
self.xsrv.register_introspection_functions() self.xsrv.register_introspection_functions()
self.xsrv.register_multicall_functions()
# XML Modus 1 Nur Logs lesen und PLC Programm neu starten # XML Modus 1 Nur Logs lesen und PLC Programm neu starten
self.xsrv.register_function(self.logr.get_applines, "get_applines") self.xsrv.register_function(self.logr.load_applog, "load_applog")
self.xsrv.register_function(self.logr.get_applog, "get_applog") self.xsrv.register_function(self.logr.load_plclog, "load_plclog")
self.xsrv.register_function(self.logr.get_plclines, "get_plclines")
self.xsrv.register_function(self.logr.get_plclog, "get_plclog")
self.xsrv.register_function(self.xml_plcexitcode, "plcexitcode") self.xsrv.register_function(self.xml_plcexitcode, "plcexitcode")
self.xsrv.register_function(self.xml_plcrunning, "plcrunning") self.xsrv.register_function(self.xml_plcrunning, "plcrunning")
self.xsrv.register_function(self.xml_plcstart, "plcstart") self.xsrv.register_function(self.xml_plcstart, "plcstart")
self.xsrv.register_function(self.xml_plcstop, "plcstop") self.xsrv.register_function(self.xml_plcstop, "plcstop")
self.xsrv.register_function(self.xml_reload, "reload") self.xsrv.register_function(self.xml_reload, "reload")
# Erweiterte Funktionen anmelden
try:
import procimgserver
self.xml_ps = procimgserver.ProcimgServer(
proginit.logger, self.xsrv, configrsc, procimg, self.xmlrpc
)
self.xsrv.register_function(self.xml_psstart, "psstart")
self.xsrv.register_function(self.xml_psstop, "psstop")
except:
self.xml_ps = None
proginit.logger.warning(
"can not load revpimodio module. maybe its not installed "
"or an old version (required at least 0.15.0). if you "
"like to use the process monitor feature, update/install "
"revpimodio: 'apt-get install python3-revpimodio'"
)
# XML Modus 2 Einstellungen lesen und Programm herunterladen # XML Modus 2 Einstellungen lesen und Programm herunterladen
if self.xmlrpc >= 2: if self.xmlrpc >= 2:
self.xsrv.register_function( self.xsrv.register_function(
@@ -821,9 +815,11 @@ class RevPiPyLoad():
) )
self.start() self.start()
proginit.logger.debug("leave RevPiPyLoad._loadconfig()")
def _plcthread(self): def _plcthread(self):
"""Konfiguriert den PLC-Thread fuer die Ausfuehrung. """Konfiguriert den PLC-Thread fuer die Ausfuehrung.
@returns: PLC-Thread Object or None""" @return PLC-Thread Object or None"""
th_plc = None th_plc = None
if self.plcslave: if self.plcslave:
@@ -851,28 +847,29 @@ class RevPiPyLoad():
th_plc.uid = int(self.globalconfig["DEFAULT"].get("plcuid", 65534)) th_plc.uid = int(self.globalconfig["DEFAULT"].get("plcuid", 65534))
th_plc.zeroonerror = self.zeroonerror th_plc.zeroonerror = self.zeroonerror
th_plc.zeroonexit = self.zeroonexit th_plc.zeroonexit = self.zeroonexit
proginit.logger.debug("created PLC watcher")
proginit.logger.debug("leave RevPiPyLoad._plcthread()")
return th_plc return th_plc
def _sigexit(self, signum, frame): def _sigexit(self, signum, frame):
"""Signal handler to clean and exit program.""" """Signal handler to clean and exit program."""
proginit.logger.debug("got exit signal") proginit.logger.debug("enter RevPiPyLoad._sigexit()")
self.stop()
# Programm aufräumen # Programm stoppen und aufräumen
self.stop()
proginit.cleanup() proginit.cleanup()
proginit.logger.debug("end revpipyload program") proginit.logger.debug("leave RevPiPyLoad._sigexit()")
def _sigloadconfig(self, signum, frame): def _sigloadconfig(self, signum, frame):
"""Signal handler to load configuration.""" """Signal handler to load configuration."""
proginit.logger.debug("got reload config signal") proginit.logger.debug("enter RevPiPyLoad._sigloadconfig()")
self.evt_loadconfig.set() self.evt_loadconfig.set()
proginit.logger.debug("leave RevPiPyLoad._sigloadconfig()")
def _signewlogfile(self, signum, frame): def _signewlogfile(self, signum, frame):
"""Signal handler to start new logfile.""" """Signal handler to start new logfile."""
proginit.logger.debug("got new logfile signal") proginit.logger.debug("enter RevPiPyLoad._signewlogfile()")
# Logger neu konfigurieren # Logger neu konfigurieren
proginit.configure() proginit.configure()
@@ -885,15 +882,20 @@ class RevPiPyLoad():
# Logreader schließen # Logreader schließen
self.logr.closeall() self.logr.closeall()
def packapp(self, mode="tar", pictory=False): proginit.logger.debug("leave RevPiPyLoad._signewlogfile()")
"""Erzeugt aus dem PLC-Programm ein TAR-File.
@param mode: Packart 'tar' oder 'zip' def packapp(self, mode="tar", pictory=False):
@param pictory: piCtory Konfiguration mit einpacken """Erzeugt aus dem PLC-Programm ein TAR/Zip-File.
@returns: Dateinamen des Archivs
@param mode Packart 'tar' oder 'zip'
@param pictory piCtory Konfiguration mit einpacken
@return Dateinamen des Archivs
""" """
filename = mktemp(suffix=".packed", prefix="plc") proginit.logger.debug("enter RevPiPyLoad.packapp()")
tup_file = mkstemp(suffix="_packed", prefix="plc_")
filename = tup_file[1]
if mode == "zip": if mode == "zip":
fh_pack = zipfile.ZipFile(filename, mode="w") fh_pack = zipfile.ZipFile(filename, mode="w")
@@ -928,10 +930,13 @@ class RevPiPyLoad():
finally: finally:
fh_pack.close() fh_pack.close()
proginit.logger.debug("leave RevPiPyLoad.packapp()")
return filename return filename
def start(self): def start(self):
"""Start plcload and PLC python program.""" """Start revpipyload."""
proginit.logger.debug("enter RevPiPyLoad.start()")
proginit.logger.info("starting revpipyload") proginit.logger.info("starting revpipyload")
self._exit = False self._exit = False
@@ -947,14 +952,28 @@ class RevPiPyLoad():
while not self._exit \ while not self._exit \
and not self.evt_loadconfig.is_set(): and not self.evt_loadconfig.is_set():
# piCtory auf Veränderung prüfen
if self.pictorymtime != os.path.getmtime(configrsc):
proginit.logger.warning("piCtory configuration was changed")
self.pictorymtime = os.path.getmtime(configrsc)
if self.xml_ps is not None:
self.xml_psstop()
self.xml_ps.loadrevpimodio()
self.evt_loadconfig.wait(1) self.evt_loadconfig.wait(1)
if not self._exit: if not self._exit:
proginit.logger.info("exit python plc program to reload config") proginit.logger.info("exit python plc program to reload config")
self._loadconfig() self._loadconfig()
proginit.logger.debug("leave RevPiPyLoad.start()")
def stop(self): def stop(self):
"""Stop PLC python program and plcload.""" """Stop revpipyload."""
proginit.logger.debug("enter RevPiPyLoad.stop()")
proginit.logger.info("stopping revpipyload") proginit.logger.info("stopping revpipyload")
self._exit = True self._exit = True
@@ -962,6 +981,7 @@ class RevPiPyLoad():
proginit.logger.debug("stopping revpiplc-thread") proginit.logger.debug("stopping revpiplc-thread")
self.plc.stop() self.plc.stop()
self.plc.join() self.plc.join()
proginit.logger.debug("revpiplc thread successfully closed")
if self.xmlrpc >= 1: if self.xmlrpc >= 1:
proginit.logger.info("shutting down xmlrpc-server") proginit.logger.info("shutting down xmlrpc-server")
@@ -969,9 +989,11 @@ class RevPiPyLoad():
self.tpe.shutdown() self.tpe.shutdown()
self.xsrv.server_close() self.xsrv.server_close()
proginit.logger.debug("leave RevPiPyLoad.stop()")
def xml_getconfig(self): def xml_getconfig(self):
"""Uebertraegt die RevPiPyLoad Konfiguration. """Uebertraegt die RevPiPyLoad Konfiguration.
@returns: dict() der Konfiguration""" @return dict() der Konfiguration"""
proginit.logger.debug("xmlrpc call getconfig") proginit.logger.debug("xmlrpc call getconfig")
dc = {} dc = {}
dc["autoreload"] = self.autoreload dc["autoreload"] = self.autoreload
@@ -990,7 +1012,7 @@ class RevPiPyLoad():
def xml_getfilelist(self): def xml_getfilelist(self):
"""Uebertraegt die Dateiliste vom plcworkdir. """Uebertraegt die Dateiliste vom plcworkdir.
@returns: list() mit Dateinamen""" @return list() mit Dateinamen"""
proginit.logger.debug("xmlrpc call getfilelist") proginit.logger.debug("xmlrpc call getfilelist")
lst_file = [] lst_file = []
wd = os.walk("./") wd = os.walk("./")
@@ -1001,7 +1023,7 @@ class RevPiPyLoad():
def xml_getpictoryrsc(self): def xml_getpictoryrsc(self):
"""Gibt die config.rsc Datei von piCotry zurueck. """Gibt die config.rsc Datei von piCotry zurueck.
@returns: xmlrpc.client.Binary()""" @return xmlrpc.client.Binary()"""
proginit.logger.debug("xmlrpc call getpictoryrsc") proginit.logger.debug("xmlrpc call getpictoryrsc")
with open(configrsc, "rb") as fh: with open(configrsc, "rb") as fh:
buff = fh.read() buff = fh.read()
@@ -1009,7 +1031,7 @@ class RevPiPyLoad():
def xml_getprocimg(self): def xml_getprocimg(self):
"""Gibt die Rohdaten aus piControl0 zurueck. """Gibt die Rohdaten aus piControl0 zurueck.
@returns: xmlrpc.client.Binary()""" @return xmlrpc.client.Binary()"""
proginit.logger.debug("xmlrpc call getprocimg") proginit.logger.debug("xmlrpc call getprocimg")
with open(procimg, "rb") as fh: with open(procimg, "rb") as fh:
buff = fh.read() buff = fh.read()
@@ -1018,9 +1040,9 @@ class RevPiPyLoad():
def xml_plcdownload(self, mode="tar", pictory=False): def xml_plcdownload(self, mode="tar", pictory=False):
"""Uebertraegt ein Archiv vom plcworkdir. """Uebertraegt ein Archiv vom plcworkdir.
@param mode: Archivart 'tar' 'zip' @param mode Archivart 'tar' 'zip'
@param pictory: piCtory Konfiguraiton mit einpacken @param pictory piCtory Konfiguraiton mit einpacken
@returns: Binary() mit Archivdatei @return Binary() mit Archivdatei
""" """
proginit.logger.debug("xmlrpc call plcdownload") proginit.logger.debug("xmlrpc call plcdownload")
@@ -1039,7 +1061,7 @@ class RevPiPyLoad():
def xml_plcexitcode(self): def xml_plcexitcode(self):
"""Gibt den aktuellen exitcode vom PLC Programm zurueck. """Gibt den aktuellen exitcode vom PLC Programm zurueck.
@returns: int() exitcode oder: @return int() exitcode oder:
-1 laeuft noch -1 laeuft noch
-2 Datei nicht gefunden -2 Datei nicht gefunden
-3 Lief nie -3 Lief nie
@@ -1055,14 +1077,15 @@ class RevPiPyLoad():
def xml_plcrunning(self): def xml_plcrunning(self):
"""Prueft ob das PLC Programm noch lauft. """Prueft ob das PLC Programm noch lauft.
@returns: True, wenn das PLC Programm noch lauft""" @return True, wenn das PLC Programm noch lauft"""
proginit.logger.debug("xmlrpc call plcrunning") proginit.logger.debug("xmlrpc call plcrunning")
return False if self.plc is None else self.plc.is_alive() return False if self.plc is None else self.plc.is_alive()
def xml_plcstart(self): def xml_plcstart(self):
"""Startet das PLC Programm. """Startet das PLC Programm.
@returns: int() Status: @return int() Status:
-0 Erfolgreich
-1 Programm lauft noch -1 Programm lauft noch
-2 Datei nicht gefunden -2 Datei nicht gefunden
@@ -1081,7 +1104,8 @@ class RevPiPyLoad():
def xml_plcstop(self): def xml_plcstop(self):
"""Stoppt das PLC Programm. """Stoppt das PLC Programm.
@returns: int() Exitcode vom PLC Programm @return int() Exitcode vom PLC Programm
-0 Erfolgreich
-1 PLC Programm lief nicht -1 PLC Programm lief nicht
""" """
@@ -1089,16 +1113,17 @@ class RevPiPyLoad():
if self.plc is not None and self.plc.is_alive(): if self.plc is not None and self.plc.is_alive():
self.plc.stop() self.plc.stop()
self.plc.join() self.plc.join()
proginit.logger.debug("revpiplc thread successfully closed")
return self.plc.exitcode return self.plc.exitcode
else: else:
return -1 return -1
def xml_plcupload(self, filedata, filename): def xml_plcupload(self, filedata, filename):
"""Empfaengt Dateien fuer das PLC Programm. """Empfaengt Dateien fuer das PLC Programm einzeln.
@param filedata: GZIP Binary data der datei @param filedata GZIP Binary data der datei
@param filename: Name inkl. Unterverzeichnis der Datei @param filename Name inkl. Unterverzeichnis der Datei
@returns: Ture, wenn Datei erfolgreich gespeichert wurde @return Ture, wenn Datei erfolgreich gespeichert wurde
""" """
proginit.logger.debug("xmlrpc call plcupload") proginit.logger.debug("xmlrpc call plcupload")
@@ -1128,7 +1153,7 @@ class RevPiPyLoad():
def xml_plcuploadclean(self): def xml_plcuploadclean(self):
"""Loescht das gesamte plcworkdir Verzeichnis. """Loescht das gesamte plcworkdir Verzeichnis.
@returns: True, wenn erfolgreich""" @return True, wenn erfolgreich"""
proginit.logger.debug("xmlrpc call plcuploadclean") proginit.logger.debug("xmlrpc call plcuploadclean")
try: try:
rmtree(".", ignore_errors=True) rmtree(".", ignore_errors=True)
@@ -1143,7 +1168,7 @@ class RevPiPyLoad():
def xml_setconfig(self, dc, loadnow=False): def xml_setconfig(self, dc, loadnow=False):
"""Empfaengt die RevPiPyLoad Konfiguration. """Empfaengt die RevPiPyLoad Konfiguration.
@returns: True, wenn erfolgreich angewendet""" @return True, wenn erfolgreich angewendet"""
proginit.logger.debug("xmlrpc call setconfig") proginit.logger.debug("xmlrpc call setconfig")
keys = { keys = {
"autoreload": "[01]", "autoreload": "[01]",
@@ -1186,13 +1211,15 @@ class RevPiPyLoad():
def xml_setpictoryrsc(self, filebytes, reset=False): def xml_setpictoryrsc(self, filebytes, reset=False):
"""Schreibt die config.rsc Datei von piCotry. """Schreibt die config.rsc Datei von piCotry.
@param filebytes: xmlrpc.client.Binary()-Objekt @param filebytes xmlrpc.client.Binary()-Objekt
@param reset: Reset piControl Device @param reset Reset piControl Device
@returns: Statuscode: @return Statuscode:
0 Alles erfolgreich -0 Alles erfolgreich
-1 Kann JSON-Datei nicht laden -1 Kann JSON-Datei nicht laden
-2 piCtory Elemente in JSON-Datei nicht gefunden -2 piCtory Elemente in JSON-Datei nicht gefunden
-3 Konnte Konfiguraiton nicht schreiben -3 Konnte Konfiguraiton nicht schreiben
-4 Module in Konfiguration enthalten, die es nicht gibt
-5 Kein RAP Katalog zur Ueberpruefung gefunden
Positive Zahl ist exitcode von piControlReset Positive Zahl ist exitcode von piControlReset
""" """
@@ -1210,6 +1237,23 @@ class RevPiPyLoad():
if chk not in jconfigrsc: if chk not in jconfigrsc:
return -2 return -2
# Prüfen ob Modulkatalog vorhanden ist
if rapcatalog is None:
return -5
else:
# piCtory Device in Katalog suchen
for picdev in jconfigrsc["Devices"]:
found = False
picdev = picdev["id"][7:-4]
for rapdev in rapcatalog:
if rapdev.find(picdev) >= 0:
found = True
# Device im Katalog nicht gefunden
if not found:
return -4
try: try:
with open(configrsc, "wb") as fh: with open(configrsc, "wb") as fh:
fh.write(filebytes.data) fh.write(filebytes.data)
@@ -1221,6 +1265,23 @@ class RevPiPyLoad():
else: else:
return 0 return 0
def xml_psstart(self):
"""Startet den Prozessabbildserver.
@return True, wenn start erfolgreich"""
if self.xml_ps is not None:
return self.xml_ps.start()
else:
return False
def xml_psstop(self):
"""Stoppt den Prozessabbildserver.
@return True, wenn stop erfolgreich"""
if self.xml_ps is not None:
self.xml_ps.stop()
return True
else:
return False
if __name__ == "__main__": if __name__ == "__main__":
root = RevPiPyLoad() root = RevPiPyLoad()

View File

@@ -27,10 +27,12 @@ setup(
license="LGPLv3", license="LGPLv3",
name="revpipyload", name="revpipyload",
version="0.3.0", version="0.4.2",
scripts=["data/revpipyload"], scripts=["data/revpipyload"],
install_requires=["revpimodio"],
data_files=[ data_files=[
("/etc/default", ["data/etc/default/revpipyload"]), ("/etc/default", ["data/etc/default/revpipyload"]),
("/etc/revpipyload", ["data/etc/revpipyload/revpipyload.conf"]), ("/etc/revpipyload", ["data/etc/revpipyload/revpipyload.conf"]),
@@ -40,12 +42,12 @@ setup(
description="PLC Loader für Python-Projekte auf den RevolutionPi", description="PLC Loader für Python-Projekte auf den RevolutionPi",
long_description="" long_description=""
"Dieses Programm startet beim Systemstart ein angegebenes Python PLC\n" "Dieses Programm startet beim Systemstart ein angegebenes Python PLC \n"
"Programm. Es überwacht das Programm und startet es im Fehlerfall neu.\n" "Programm. Es überwacht das Programm und startet es im Fehlerfall neu. \n"
"Bei Absturz kann das gesamte /dev/piControl0 auf 0x00 gesetzt werden.\n" "Bei Absturz kann das gesamte /dev/piControl0 auf 0x00 gesetzt werden. \n"
"Außerdem stellt es einen XML-RPC Server bereit, über den die Software\n" "Außerdem stellt es einen XML-RPC Server bereit, über den die Software \n"
"auf den RevPi geladen werden kann. Das Prozessabbild kann über ein Tool\n" "auf den RevPi geladen werden kann. Das Prozessabbild kann über ein \n"
"zur Laufzeit überwacht werden.", "Tool zur Laufzeit überwacht werden.",
classifiers=[ classifiers=[
"License :: OSI Approved :: " "License :: OSI Approved :: "

View File

@@ -1,4 +1,6 @@
[DEFAULT] [DEFAULT]
X-Python3-Version: 3.2- Debian-Version: 1
Depends3: python3-revpimodio (>= 0.11.0)
Package: revpipyload Package: revpipyload
Suite: stable Suite: stable
X-Python3-Version: >=3.2