From d77fea0b486e962573cea9f6f84385f269ae9b90 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Thu, 30 Apr 2020 22:11:23 +0200 Subject: [PATCH] "Reset Driver" of piCtory can now restart your plc program You can set reset_driver_action in revpipyload.conf to 0, 1, 2 0=do nothing 1=Restart PLC program only if piCtory config was changed 2=Always restart PLC program after pressing "Reset Driver" (default) (will not work on old wheezy) close #1 --- .idea/codeStyles/Project.xml | 6 ++ .idea/codeStyles/codeStyleConfig.xml | 5 ++ .idea/dictionaries/akira.xml | 7 ++ data/etc/revpipyload/revpipyload.conf | 2 + revpipyload/revpipyload.py | 103 ++++++++++++++++---------- revpipyload/watchdogs.py | 75 +++++++++++++++++++ 6 files changed, 160 insertions(+), 38 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/dictionaries/akira.xml create mode 100644 revpipyload/watchdogs.py diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..48d4e66 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dictionaries/akira.xml b/.idea/dictionaries/akira.xml new file mode 100644 index 0000000..743ad92 --- /dev/null +++ b/.idea/dictionaries/akira.xml @@ -0,0 +1,7 @@ + + + + ctory + + + \ No newline at end of file diff --git a/data/etc/revpipyload/revpipyload.conf b/data/etc/revpipyload/revpipyload.conf index 186cfd0..a42d80a 100644 --- a/data/etc/revpipyload/revpipyload.conf +++ b/data/etc/revpipyload/revpipyload.conf @@ -5,11 +5,13 @@ autostart = 1 plcworkdir = /var/lib/revpipyload plcworkdir_set_uid = 0 plcprogram = program.py +plcprogram_watchdog = 0 plcarguments = plcuid = 1000 plcgid = 1000 pythonversion = 3 replace_ios = /etc/revpipyload/replace_ios.conf +reset_driver_action = 2 rtlevel = 0 zeroonerror = 0 zeroonexit = 0 diff --git a/revpipyload/revpipyload.py b/revpipyload/revpipyload.py index 8c7cc89..20f50ad 100755 --- a/revpipyload/revpipyload.py +++ b/revpipyload/revpipyload.py @@ -29,32 +29,34 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2018 Sven Sager" __license__ = "GPLv3" __version__ = "0.8.5" + import gzip -import logsystem -import picontrolserver -import plcsystem -import proginit import os 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 from shutil import rmtree from tempfile import mkstemp from threading import Event from time import asctime from xmlrpc.client import Binary + +import logsystem +import picontrolserver +import plcsystem +import proginit +from helper import refullmatch +from shared.ipaclmanager import IpAclManager +from watchdogs import ResetDriverWatchdog from xrpcserver import SaveXMLRPCServer min_revpimodio = "2.4.5" class RevPiPyLoad(): - """Hauptklasse, die alle Funktionen zur Verfuegung stellt. Hier wird die gesamte Konfiguraiton eingelesen und der ggf. aktivierte @@ -207,34 +209,38 @@ class RevPiPyLoad(): restart_plcprogram = self._check_mustrestart_plcprogram() # Konfiguration verarbeiten [DEFAULT] - self.autoreload = \ - self.globalconfig["DEFAULT"].getboolean("autoreload", True) - self.autoreloaddelay = \ - self.globalconfig["DEFAULT"].getint("autoreloaddelay", 5) - self.autostart = \ - self.globalconfig["DEFAULT"].getboolean("autostart", False) - self.plcworkdir = \ - self.globalconfig["DEFAULT"].get("plcworkdir", ".") - self.plcprogram = \ - self.globalconfig["DEFAULT"].get("plcprogram", "none.py") - self.plcarguments = \ - self.globalconfig["DEFAULT"].get("plcarguments", "") + self.autoreload = self.globalconfig["DEFAULT"].getboolean( + "autoreload", True) + self.autoreloaddelay = self.globalconfig["DEFAULT"].getint( + "autoreloaddelay", 5) + self.autostart = self.globalconfig["DEFAULT"].getboolean( + "autostart", False) + self.plcworkdir = self.globalconfig["DEFAULT"].get( + "plcworkdir", ".") + self.plcprogram = self.globalconfig["DEFAULT"].get( + "plcprogram", "none.py") + self.plcprogram_watchdog = self.globalconfig["DEFAULT"].getboolean( + "plcprogram_watchdog", False) + 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 = \ - self.globalconfig["DEFAULT"].getint("plcgid", 65534) - self.pythonversion = \ - self.globalconfig["DEFAULT"].getint("pythonversion", 3) - self.replace_ios_config = \ - self.globalconfig["DEFAULT"].get("replace_ios", "") - self.rtlevel = \ - self.globalconfig["DEFAULT"].getint("rtlevel", 0) - self.zeroonerror = \ - self.globalconfig["DEFAULT"].getboolean("zeroonerror", True) - self.zeroonexit = \ - self.globalconfig["DEFAULT"].getboolean("zeroonexit", True) + self.plcuid = self.globalconfig["DEFAULT"].getint( + "plcuid", 65534) + self.plcgid = self.globalconfig["DEFAULT"].getint( + "plcgid", 65534) + self.pythonversion = self.globalconfig["DEFAULT"].getint( + "pythonversion", 3) + self.replace_ios_config = self.globalconfig["DEFAULT"].get( + "replace_ios", "") + self.rtlevel = self.globalconfig["DEFAULT"].getint( + "rtlevel", 0) + self.reset_driver_action = self.globalconfig["DEFAULT"].getint( + "reset_driver_action", 2) + self.zeroonerror = self.globalconfig["DEFAULT"].getboolean( + "zeroonerror", True) + self.zeroonexit = self.globalconfig["DEFAULT"].getboolean( + "zeroonexit", True) # Dateiveränderungen prüfen file_changed = False @@ -336,9 +342,9 @@ class RevPiPyLoad(): # Workdirectory owner setzen try: if self.plcworkdir_set_uid: - os.chown(self.plcworkdir, self.plcuid, -1) + os.chown(self.plcworkdir, self.plcuid, -1) else: - os.chown(self.plcworkdir, 0, -1) + os.chown(self.plcworkdir, 0, -1) except Exception: proginit.logger.warning( "could not set user id on working directory" @@ -748,17 +754,21 @@ class RevPiPyLoad(): if self.autostart and self.plc is not None: self.plc.start() + # Watchdog to detect the reset_driver event + pictory_reset_driver = ResetDriverWatchdog() + # 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() + file_changed = False + reset_driver_detected = pictory_reset_driver.triggered + # Dateiveränderungen prüfen mit beiden Funktionen! - if self.check_pictory_changed(): + if reset_driver_detected and self.check_pictory_changed(): file_changed = True # Alle Verbindungen von ProcImgServer trennen @@ -789,6 +799,18 @@ class RevPiPyLoad(): self.xml_ps.loadrevpimodio() # Kein psstart um Reload im Client zu erzeugen + # Restart plc program after piCtory change + if self.plc is not None and self.plc.is_alive() and ( + self.reset_driver_action == 1 and file_changed or + self.reset_driver_action == 2 and reset_driver_detected): + # Plc program is running and we have to restart it + proginit.logger.warning( + "restart plc program after 'reset driver' was requested" + ) + self.stop_plcprogram() + self.plc = self._plcthread() + self.plc.start() + # MQTT Publisher Thread prüfen if self.mqtt and self.th_plcmqtt is not None \ and not self.th_plcmqtt.is_alive(): @@ -890,12 +912,14 @@ class RevPiPyLoad(): dc["plcworkdir"] = self.plcworkdir dc["plcworkdir_set_uid"] = self.plcworkdir_set_uid dc["plcprogram"] = self.plcprogram + dc["plcprogram_watchdog"] = self.plcprogram_watchdog dc["plcarguments"] = self.plcarguments dc["plcuid"] = self.plcuid dc["plcgid"] = self.plcgid dc["pythonversion"] = self.pythonversion dc["replace_ios"] = self.replace_ios_config.replace( self.plcworkdir + "/", "") + dc["reset_driver_action"] = self.reset_driver_action dc["rtlevel"] = self.rtlevel dc["zeroonerror"] = int(self.zeroonerror) dc["zeroonexit"] = int(self.zeroonexit) @@ -1127,12 +1151,14 @@ class RevPiPyLoad(): "autoreloaddelay": "[0-9]+", "autostart": "[01]", "plcprogram": ".+", + "plcprogram_watchdog": "[01]", "plcarguments": ".*", "plcworkdir_set_uid": "[01]", # "plcuid": "[0-9]{,5}", # "plcgid": "[0-9]{,5}", "pythonversion": "[23]", "replace_ios": ".*", + "reset_driver_action": "[0-2]", "rtlevel": "[0-1]", "zeroonerror": "[01]", "zeroonexit": "[01]", @@ -1351,6 +1377,7 @@ if __name__ == "__main__": if proginit.pargs.test: from testsystem import TestSystem + root = TestSystem() else: root = RevPiPyLoad() diff --git a/revpipyload/watchdogs.py b/revpipyload/watchdogs.py new file mode 100644 index 0000000..5b729a6 --- /dev/null +++ b/revpipyload/watchdogs.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +"""Watchdog systems to monitor plc program and reset_driver of piCtory.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2020 Sven Sager" +__license__ = "GPLv3" + +import os +from fcntl import ioctl +from threading import Thread + +import proginit as pi + + +class ResetDriverWatchdog(Thread): + """Watchdog to catch a piCtory reset_driver action.""" + + def __init__(self): + super(ResetDriverWatchdog, self).__init__() + self.daemon = True + self._exit = False + self._fh = None + self._triggered = False + self.start() + + def run(self) -> None: + """ + Mainloop of watchdog for reset_driver. + + If the thread can not open the process image or the IOCTL is not + implemented (wheezy), the thread function will stop. The trigger + property will always return True. + """ + pi.logger.debug("enter ResetDriverWatchdog.run()") + + try: + self._fh = os.open(pi.pargs.procimg, os.O_RDONLY) + except Exception: + pi.logger.error( + "can not open process image at '{0}' for piCtory " + "reset_driver watchdog".format(pi.pargs.procimg) + ) + return + + # The ioctl will return 2 byte (c-type int) + byte_buff = bytearray(2) + while not self._exit: + try: + rc = ioctl(self._fh, 19250, byte_buff) + if rc == 0 and byte_buff[0] == 1: + self._triggered = True + pi.logger.debug("piCtory reset_driver detected") + except Exception: + os.close(self._fh) + self._fh = None + pi.logger.warning("IOCTL KB_WAIT_FOR_EVENT is not implemented") + return + + pi.logger.debug("leave ResetDriverWatchdog.run()") + + def stop(self) -> None: + """Stop watchdog for piCtory reset_driver.""" + pi.logger.debug("enter ResetDriverWatchdog.stop()") + + self._exit = True + if self._fh is not None: + os.close(self._fh) + self._fh = None + + pi.logger.debug("leave ResetDriverWatchdog.stop()") + + @property + def triggered(self) -> bool: + rc = self._triggered or not self.is_alive() + self._triggered = False + return rc