"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
This commit is contained in:
2020-04-30 22:11:23 +02:00
parent 13d42acd67
commit d77fea0b48
6 changed files with 160 additions and 38 deletions

6
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" />
<option name="RIGHT_MARGIN" value="80" />
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

7
.idea/dictionaries/akira.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="akira">
<words>
<w>ctory</w>
</words>
</dictionary>
</component>

View File

@@ -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

View File

@@ -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()

75
revpipyload/watchdogs.py Normal file
View File

@@ -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