Mit replace_ios zusammenführen

This commit is contained in:
2019-08-26 13:19:13 +02:00
17 changed files with 297 additions and 236 deletions

View File

@@ -5,7 +5,6 @@ __copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "GPLv3"
import os
import proginit
from configparser import ConfigParser
from re import match as rematch
from subprocess import Popen, PIPE
@@ -118,115 +117,6 @@ def _zeroprocimg():
)
def revpimodio_replaceio(revpi, filename):
"""Importiert und ersetzt IOs in RevPiModIO.
@param revpi RevPiModIO Instanz
@param filename Dateiname der Ersetzungsdatei
@return True, wenn alle IOs ersetzt werden konnten
"""
cp = ConfigParser()
try:
with open(filename, "r") as fh:
cp.read_file(fh)
except Exception as e:
proginit.logger.error(
"could not read replace_io file '{0}' | {1}".format(filename, e)
)
return False
# Pre-check
lst_replace = []
rc = True
for io in cp:
if io == "DEFAULT":
continue
dict_replace = {
"replace": cp[io].get("replace", ""),
"frm": cp[io].get("frm"),
"bmk": cp[io].get("bmk", ""),
"byteorder": cp[io].get("byteorder", "little"),
}
if dict_replace["replace"] in revpi.io:
# Byteorder prüfen
if not (dict_replace["byteorder"] == "little" or
dict_replace["byteorder"] == "big"):
proginit.logger.error(
"byteorder of '{0}' must be 'little' or 'big'".format(io)
)
rc = False
continue
if dict_replace["frm"] == "?":
# Convert defaultvalue from config file
try:
dict_replace["default"] = cp[io].getboolean("defaultvalue")
except Exception:
proginit.logger.error(
"could not convert '{0}' defaultvalue '{1}' to boolean"
"".format(io, cp[io].get("defaultvalue"))
)
rc = False
continue
# Get bitaddress
try:
dict_replace["bitaddress"] = cp[io].getint("bitaddress", 0)
except Exception:
proginit.logger.error(
"could not convert '{0}' bitaddress '{1}' to integer"
"".format(io, cp[io].get("bitaddress"))
)
rc = False
continue
else:
# Convert defaultvalue from config file
try:
dict_replace["default"] = cp[io].getint("defaultvalue")
except Exception:
proginit.logger.error(
"could not convert '{0}' defaultvalue '{1}' to integer"
"".format(io, cp[io].get("defaultvalue"))
)
rc = False
continue
else:
proginit.logger.error(
"can not find io '{0}' to replace with '{1}'"
"".format(dict_replace["replace"], io)
)
rc = False
continue
# Replace_IO übernehmen
lst_replace.append(dict_replace)
if not rc:
# Abbrechen, wenn IO-Verarbeitung einen Fehler hatte
return False
# Replace IOs
for dict_replace in lst_replace:
# FIXME: Hier können Fehler auftreten !!!
revpi.io[dict_replace["replace"]].replace_io(
io,
frm=dict_replace["frm"],
bmk=dict_replace["bmk"],
bit=dict_replace["bitaddress"],
byteorder=dict_replace["byteorder"],
defaultvalue=dict_replace["default"]
)
def refullmatch(regex, string):
"""re.fullmatch wegen alter python version aus wheezy nachgebaut.

View File

@@ -74,7 +74,6 @@ class MqttServer(Thread):
self._reloadmodio = False
self._replace_ios = replace_ios
self._rpi = None
self._rpi_write = None
self._send_events = send_events
self._sendinterval = sendinterval
self._write_outputs = write_outputs
@@ -130,26 +129,18 @@ class MqttServer(Thread):
# RevPiModIO-Modul Instantiieren
if self._rpi is not None:
self._rpi.cleanup()
if self._rpi_write is not None:
self._rpi_write.cleanup()
proginit.logger.debug("create revpimodio2 object for MQTT")
try:
# Lesend und Eventüberwachung
# Vollzugriff und Eventüberwachung
self._rpi = revpimodio2.RevPiModIO(
autorefresh=self._send_events,
monitoring=True,
monitoring=not self._write_outputs,
configrsc=proginit.pargs.configrsc,
procimg=proginit.pargs.procimg,
replace_io_file=self._replace_ios
replace_io_file=self._replace_ios,
direct_output=True,
)
# Schreibenen Zugriff
if self._write_outputs:
self._rpi_write = revpimodio2.RevPiModIO(
configrsc=proginit.pargs.configrsc,
procimg=proginit.pargs.procimg,
replace_io_file=self._replace_ios
)
if self._replace_ios:
proginit.logger.info("loaded replace_ios to MQTT")
@@ -159,16 +150,11 @@ class MqttServer(Thread):
# Lesend und Eventüberwachung
self._rpi = revpimodio2.RevPiModIO(
autorefresh=self._send_events,
monitoring=True,
monitoring=not self._write_outputs,
configrsc=proginit.pargs.configrsc,
procimg=proginit.pargs.procimg
procimg=proginit.pargs.procimg,
direct_output=True,
)
# Schreibenen Zugriff
if self._write_outputs:
self._rpi_write = revpimodio2.RevPiModIO(
configrsc=proginit.pargs.configrsc,
procimg=proginit.pargs.procimg
)
proginit.logger.warning(
"replace_ios_file not loadable for MQTT - using "
"defaults now | {0}".format(e)
@@ -176,7 +162,6 @@ class MqttServer(Thread):
except Exception:
self._rpi = None
self._rpi_write = None
proginit.logger.error(
"piCtory configuration not loadable for MQTT"
)
@@ -280,11 +265,11 @@ class MqttServer(Thread):
# IO holen
if coreio:
coreio = ioname.split(".")[-1]
io = getattr(self._rpi_write.core, coreio)
io = getattr(self._rpi.core, coreio)
if not isinstance(io, revpimodio2.io.IOBase):
raise RuntimeError()
else:
io = self._rpi_write.io[ioname]
io = self._rpi.io[ioname]
io_needbytes = type(io.value) == bytes
except Exception:
proginit.logger.error(
@@ -300,10 +285,8 @@ class MqttServer(Thread):
)
elif ioget:
# Daten je nach IO Type aus Prozessabbild laden
if io.type == revpimodio2.OUT:
io._parentdevice.syncoutputs()
else:
# Werte laden, wenn nicht autorefresh
if not self._send_events:
io._parentdevice.readprocimg()
# Publish Wert von IO
@@ -344,7 +327,6 @@ class MqttServer(Thread):
return
# Write Value to RevPi
io._parentdevice.syncoutputs()
try:
io.value = value
except Exception:
@@ -352,8 +334,6 @@ class MqttServer(Thread):
"could not write '{0}' to Output '{1}'"
"".format(value, ioname)
)
else:
io._parentdevice.writeprocimg()
elif ioreset:
# Counter zurücksetzen

View File

@@ -10,8 +10,13 @@ from shared.ipaclmanager import IpAclManager
from threading import Event, Thread
from timeit import default_timer
# Hashvalues
HASH_NULL = b'\x00' * 16
HASH_FAIL = b'\xff' * 16
HASH_PICT = HASH_FAIL
HASH_RPIO = HASH_NULL
# NOTE: Sollte dies als Process ausgeführt werden?
class RevPiSlave(Thread):
"""RevPi PLC-Server.
@@ -70,6 +75,19 @@ class RevPiSlave(Thread):
)
dev._acl = level
def disconnect_all(self):
"""Close all device connection."""
# Alle Threads beenden
for th in self._th_dev:
th.stop()
def disconnect_replace_ios(self):
"""Close all device with loaded replace_ios file."""
# Alle Threads beenden die Replace_IOs emfpangen haben
for th in self._th_dev:
if th.got_replace_ios:
th.stop()
def newlogfile(self):
"""Konfiguriert die FileHandler auf neue Logdatei."""
pass
@@ -80,6 +98,7 @@ class RevPiSlave(Thread):
# Socket öffnen und konfigurieren bis Erfolg oder Ende
self.so = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.so.settimeout(2)
while not self._evt_exit.is_set():
try:
self.so.bind((self._bindip, self._port))
@@ -97,9 +116,11 @@ class RevPiSlave(Thread):
self.exitcode = -1
# Verbindung annehmen
proginit.logger.info("accept new connection for revpinetio")
try:
tup_sock = self.so.accept()
proginit.logger.info("accepted new connection for revpinetio")
except socket.timeout:
continue
except Exception:
if not self._evt_exit.is_set():
proginit.logger.exception("accept exception")
@@ -174,6 +195,7 @@ class RevPiSlaveDev(Thread):
self._deadtime = None
self._devcon, self._addr = devcon
self._evt_exit = Event()
self.got_replace_ios = False
self._writeerror = False
# Sicherheitsbytes
@@ -381,10 +403,47 @@ class RevPiSlaveDev(Thread):
)
break
else:
continue
finally:
# End-of-Transmission character immer senden
self._devcon.send(b'\x04')
continue
elif cmd == b'PH':
# piCtory md5 Hashwert senden (16 Byte)
proginit.logger.debug(
"send pictory hashvalue: {0}".format(HASH_PICT)
)
self._devcon.sendall(HASH_PICT)
elif cmd == b'RP':
# Replace_IOs Konfiguration senden, wenn hash existiert
proginit.logger.debug(
"transfair replace_io configuration: {0}"
"".format(proginit.pargs.configrsc)
)
replace_ios = proginit.conf["DEFAULT"].get("replace_ios", "")
try:
if HASH_RPIO != HASH_NULL and replace_ios:
with open(replace_ios, "rb") as fh:
# Komplette replace_io Datei senden
self._devcon.sendall(fh.read())
except Exception as e:
proginit.logger.error(
"error on replace_io transfair: {0}".format(e)
)
break
else:
# End-of-Transmission character immer senden
self._devcon.send(b'\x04')
continue
elif cmd == b'RH':
# Replace_IOs md5 Hashwert senden (16 Byte)
self.got_replace_ios = True
proginit.logger.debug(
"send replace_ios hashvalue: {0}".format(HASH_RPIO)
)
self._devcon.sendall(HASH_RPIO)
elif cmd == b'EX':
# Sauber Verbindung verlassen
@@ -416,6 +475,7 @@ class RevPiSlaveDev(Thread):
)
else:
# Simulation
# TODO: IOCTL für Dateien implementieren
proginit.logger.warning(
"ioctl {0} with {1} simulated".format(request, arg)
)

View File

@@ -54,6 +54,11 @@ class ProcimgServer():
proginit.logger.debug("leave ProcimgServer.__init__()")
def __del__(self):
"""Clean up RevPiModIO."""
if self.rpi is not None:
self.rpi.cleanup()
def devices(self):
"""Generiert Deviceliste mit Position und Namen.
@return list() mit Tuple (pos, name)"""
@@ -99,7 +104,8 @@ class ProcimgServer():
self.rpi = revpimodio2.RevPiModIO(
configrsc=proginit.pargs.configrsc,
procimg=proginit.pargs.procimg,
replace_io_file=self.replace_ios
replace_io_file=self.replace_ios,
direct_output=True,
)
if self.replace_ios:
@@ -110,6 +116,7 @@ class ProcimgServer():
self.rpi = revpimodio2.RevPiModIO(
configrsc=proginit.pargs.configrsc,
procimg=proginit.pargs.procimg,
direct_output=True,
)
proginit.logger.warning(
"replace_ios_file not loadable for ProcimgServer - using "
@@ -122,9 +129,6 @@ class ProcimgServer():
)
return e
# NOTE: Warum das?
self.rpi.syncoutputs(device=0)
proginit.logger.debug("created revpimodio2 object")
def setvalue(self, device, io, value):
@@ -140,8 +144,6 @@ class ProcimgServer():
if type(value) == Binary:
value = value.data
self.rpi.syncoutputs(device=device)
try:
# Neuen Wert übernehmen
if type(value) == bytes or type(value) == bool:
@@ -156,13 +158,12 @@ class ProcimgServer():
except Exception as e:
return [device, io, False, str(e)]
self.rpi.writeprocimg(device=device)
return [device, io, True, ""]
def values(self):
"""Liefert Prozessabbild an Client.
@return Binary() bytes or None"""
if self.rpi.readprocimg() and self.rpi.syncoutputs():
if self.rpi.readprocimg():
bytebuff = bytearray()
for dev in self.rpi.device:
bytebuff += bytes(dev)

View File

@@ -7,7 +7,9 @@ import logging
import os
import sys
from argparse import ArgumentParser
from configparser import ConfigParser
conf = ConfigParser()
forked = False
globalconffile = None
logapp = "revpipyloadapp.log"

View File

@@ -28,7 +28,7 @@ begrenzt werden!
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "GPLv3"
__version__ = "0.7.6"
__version__ = "0.8.0"
import gzip
import logsystem
import picontrolserver
@@ -39,6 +39,7 @@ import signal
import tarfile
import zipfile
from configparser import ConfigParser
from hashlib import md5
from helper import refullmatch
from json import loads as jloads
from shared.ipaclmanager import IpAclManager
@@ -49,7 +50,7 @@ from time import asctime
from xmlrpc.client import Binary
from xrpcserver import SaveXMLRPCServer
min_revpimodio = "2.3.3"
min_revpimodio = "2.4.1"
class RevPiPyLoad():
@@ -67,14 +68,18 @@ class RevPiPyLoad():
# Klassenattribute
self._exit = True
self.pictorymtime = os.path.getmtime(proginit.pargs.configrsc)
self.replaceiosmtime = 0
self.evt_loadconfig = Event()
self.globalconfig = ConfigParser()
proginit.conf = self.globalconfig
self.logr = logsystem.LogReader()
self.xsrv = None
self.xml_ps = None
# Dateimerker
self.pictorymtime = 0
self.replaceiosmtime = 0
self.replaceiofail = False
# Berechtigungsmanger
if proginit.pargs.developermode:
self.plcslaveacl = IpAclManager(minlevel=0, maxlevel=9)
@@ -194,6 +199,7 @@ class RevPiPyLoad():
"loading config file: {0}".format(proginit.globalconffile)
)
self.globalconfig.read(proginit.globalconffile)
proginit.conf = self.globalconfig
# Merker für Subsystem-Neustart nach laden, vor setzen
restart_plcmqtt = self._check_mustrestart_mqtt()
@@ -213,6 +219,8 @@ class RevPiPyLoad():
self.globalconfig["DEFAULT"].get("plcprogram", "none.py")
self.plcarguments = \
self.globalconfig["DEFAULT"].get("plcarguments", "")
self.plcworkdir_set_uid = self.globalconfig["DEFAULT"].getboolean(
"plcworkdir_set_uid", False)
self.plcuid = \
self.globalconfig["DEFAULT"].getint("plcuid", 65534)
self.plcgid = \
@@ -228,23 +236,17 @@ class RevPiPyLoad():
self.zeroonexit = \
self.globalconfig["DEFAULT"].getboolean("zeroonexit", True)
# MTime für replace io übernehmen
mtime = 0
if self.replace_ios_config:
if os.access(self.replace_ios_config, os.R_OK | os.W_OK):
mtime = os.path.getmtime(self.replace_ios_config)
else:
proginit.logger.error(
"can not access (r/w) the replace_ios file '{0}' "
"using defaults".format(self.replace_ios_config)
)
self.replace_ios_config = ""
if self.replaceiosmtime != mtime:
# MQTT reload erforderlich
# Dateiveränderungen prüfen
file_changed = False
# Beide Funktionen müssen einmal aufgerufen werden
if self.check_pictory_changed():
file_changed = True
if self.check_replace_ios_changed():
file_changed = True
if file_changed:
restart_plcmqtt = True
self.replaceiosmtime = mtime
restart_plcslave = True
restart_plcprogram = True
# Konfiguration verarbeiten [MQTT]
self.mqtt = 0
@@ -328,6 +330,10 @@ class RevPiPyLoad():
)
os.chdir(self.plcworkdir)
# Workdirectory owner setzen
if self.plcworkdir_set_uid:
os.chown(self.plcworkdir, self.plcuid, -1)
# MQTT konfigurieren
if restart_plcmqtt:
self.stop_plcmqtt()
@@ -419,17 +425,18 @@ class RevPiPyLoad():
"revpimodio2: 'apt-get install python3-revpimodio2'"
"".format(min_revpimodio)
)
try:
self.xml_ps = procimgserver.ProcimgServer(
self.xsrv,
None if not self.replace_ios_config
else self.replace_ios_config,
)
self.xsrv.register_function(1, self.xml_psstart, "psstart")
self.xsrv.register_function(1, self.xml_psstop, "psstop")
except Exception as e:
self.xml_ps = None
proginit.logger.error(e)
else:
try:
self.xml_ps = procimgserver.ProcimgServer(
self.xsrv,
None if not self.replace_ios_config
else self.replace_ios_config,
)
self.xsrv.register_function(1, self.xml_psstart, "psstart")
self.xsrv.register_function(1, self.xml_psstop, "psstop")
except Exception as e:
self.xml_ps = None
proginit.logger.error(e)
# XML Modus 2 Einstellungen lesen und Programm herunterladen
self.xsrv.register_function(
@@ -592,6 +599,67 @@ class RevPiPyLoad():
proginit.logger.debug("leave RevPiPyLoad._signewlogfile()")
def check_pictory_changed(self):
"""Prueft ob sich die piCtory Datei veraendert hat.
@return True, wenn veraendert wurde"""
mtime = os.path.getmtime(proginit.pargs.configrsc)
if self.pictorymtime == mtime:
return False
self.pictorymtime = mtime
# TODO: Nur "Devices" list vergleich
with open(proginit.pargs.configrsc, "rb") as fh:
file_hash = md5(fh.read()).digest()
if picontrolserver.HASH_PICT == file_hash:
return False
picontrolserver.HASH_PICT = file_hash
return True
def check_replace_ios_changed(self):
"""Prueft ob sich die replace_ios.conf Datei veraendert hat (oder del).
@return True, wenn veraendert wurde"""
# Zugriffsrechte prüfen (pre-check für unten)
if self.replace_ios_config \
and not os.access(self.replace_ios_config, os.R_OK):
if not self.replaceiofail:
proginit.logger.error(
"can not access (r/w) the replace_ios file '{0}' "
"using defaults".format(self.replace_ios_config)
)
self.replaceiofail = True
else:
self.replaceiofail = False
if not self.replace_ios_config or self.replaceiofail:
# Dateipfad leer, prüfen ob es vorher einen gab
if self.replaceiosmtime > 0 \
or picontrolserver.HASH_RPIO != picontrolserver.HASH_NULL:
self.replaceiosmtime = 0
picontrolserver.HASH_RPIO = picontrolserver.HASH_NULL
return True
else:
mtime = os.path.getmtime(self.replace_ios_config)
if self.replaceiosmtime == mtime:
return False
self.replaceiosmtime = mtime
# TODO: Instanz von ConfigParser vergleichen
with open(self.replace_ios_config, "rb") as fh:
file_hash = md5(fh.read()).digest()
if picontrolserver.HASH_RPIO == file_hash:
return False
picontrolserver.HASH_RPIO = file_hash
return True
return False
def packapp(self, mode="tar", pictory=False):
"""Erzeugt aus dem PLC-Programm ein TAR/Zip-File.
@@ -668,12 +736,43 @@ class RevPiPyLoad():
# mainloop
while not self._exit:
file_changed = False
# Neue Konfiguration laden
if self.evt_loadconfig.is_set():
proginit.logger.info("got reqeust to reload config")
self._loadconfig()
# Dateiveränderungen prüfen mit beiden Funktionen!
if self.check_pictory_changed():
file_changed = True
# Alle Verbindungen von ProcImgServer trennen
self.th_plcslave.disconnect_all()
proginit.logger.warning("piCtory configuration was changed")
if self.check_replace_ios_changed():
if not file_changed:
# Verbindungen von ProcImgServer trennen mit replace_ios
self.th_plcslave.disconnect_replace_ios()
file_changed = True
proginit.logger.warning("replace ios file was changed")
if file_changed:
# Auf Dateiveränderung reagieren
# MQTT Publisher neu laden
if self.mqtt and self.th_plcmqtt is not None:
self.th_plcmqtt.reload_revpimodio()
# XML Prozessabbildserver neu laden
if self.xml_ps is not None:
self.xml_psstop()
self.xml_ps.loadrevpimodio()
# Kein psstart um Reload im Client zu erzeugen
# MQTT Publisher Thread prüfen
if self.mqtt and self.th_plcmqtt is not None \
and not self.th_plcmqtt.is_alive():
@@ -687,27 +786,14 @@ class RevPiPyLoad():
# PLC Server Thread prüfen
if self.plcslave and self.th_plcslave is not None \
and not self.th_plcslave.is_alive():
proginit.logger.warning(
"restart plc slave after thread was not running"
)
if not file_changed:
proginit.logger.warning(
"restart plc slave after thread was not running"
)
self.th_plcslave = self._plcslave()
if self.th_plcslave is not None:
self.th_plcslave.start()
# piCtory auf Veränderung prüfen
if self.pictorymtime != os.path.getmtime(proginit.pargs.configrsc):
proginit.logger.warning("piCtory configuration was changed")
self.pictorymtime = os.path.getmtime(proginit.pargs.configrsc)
# MQTT Publisher neu laden
if self.mqtt and self.th_plcmqtt is not None:
self.th_plcmqtt.reload_revpimodio()
# XML Prozessabbildserver neu laden
if self.xml_ps is not None:
self.xml_psstop()
self.xml_ps.loadrevpimodio()
self.evt_loadconfig.wait(1)
proginit.logger.info("stopping revpipyload")
@@ -1082,6 +1168,7 @@ class RevPiPyLoad():
# conf-Datei schreiben
with open(proginit.globalconffile, "w") as fh:
self.globalconfig.write(fh)
proginit.conf = self.globalconfig
proginit.logger.info(
"got new config and wrote it to {0}"
"".format(proginit.globalconffile)

View File

@@ -41,7 +41,7 @@ class TestSystem:
rpi = revpimodio2.RevPiModIO(
configrsc=proginit.pargs.configrsc,
procimg=proginit.pargs.procimg,
monitoring=False,
monitoring=True,
debug=True,
replace_io_file=file,
)