mirror of
https://github.com/naruxde/revpipyload.git
synced 2025-11-09 07:28:03 +01:00
first checkin
This commit is contained in:
1
revpipyload/__init__.py
Normal file
1
revpipyload/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""just init file."""
|
||||
94
revpipyload/proginit.py
Normal file
94
revpipyload/proginit.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Main functions of our program."""
|
||||
import logging
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from configparser import ConfigParser
|
||||
from os import fork as osfork
|
||||
from os.path import exists as ospexists
|
||||
|
||||
|
||||
class ProgInit():
|
||||
|
||||
"""Programmfunktionen fuer Parameter und Logger."""
|
||||
|
||||
def __del__(self):
|
||||
"""Clean up program."""
|
||||
# Logging beenden
|
||||
logging.shutdown()
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize general program functions."""
|
||||
|
||||
# Command arguments
|
||||
parser = ArgumentParser(
|
||||
description="RevolutionPi Python3 Loader"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d", "--daemon", action="store_true", dest="daemon",
|
||||
help="Run program as a daemon in background"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--conffile", dest="conffile",
|
||||
default="/etc/revpipyload/revpipyload.conf",
|
||||
help="Application configuration file"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f", "--logfile", dest="logfile",
|
||||
help="Save log entries to this file"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="count", dest="verbose",
|
||||
help="Switch on verbose logging"
|
||||
)
|
||||
self.pargs = parser.parse_args()
|
||||
|
||||
# Prüfen ob als Daemon ausgeführt werden soll
|
||||
self.pidfile = "/var/run/revpipyload.pid"
|
||||
self.pid = 0
|
||||
if self.pargs.daemon:
|
||||
# Prüfen ob daemon schon läuft
|
||||
if ospexists(self.pidfile):
|
||||
raise SystemError(
|
||||
"program already running as daemon. check {}".format(
|
||||
self.pidfile
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.pid = osfork()
|
||||
if self.pid > 0:
|
||||
with open(self.pidfile, "w") as f:
|
||||
f.write(str(self.pid))
|
||||
exit(0)
|
||||
|
||||
# Ausgaben umhängen in Logfile
|
||||
sys.stdout = open("/var/log/revpipyload", "a")
|
||||
sys.stderr = sys.stdout
|
||||
|
||||
# Initialize configparser globalconfig
|
||||
self.globalconffile = self.pargs.conffile
|
||||
self.globalconfig = ConfigParser()
|
||||
self.globalconfig.read(self.pargs.conffile)
|
||||
|
||||
# Program logger
|
||||
self.logger = logging.getLogger()
|
||||
logformat = logging.Formatter(
|
||||
"{asctime} [{levelname:8}] {message}",
|
||||
datefmt="%Y-%m-%d %H:%M:%S", style="{"
|
||||
)
|
||||
lhandler = logging.StreamHandler(sys.stdout)
|
||||
lhandler.setFormatter(logformat)
|
||||
self.logger.addHandler(lhandler)
|
||||
if self.pargs.logfile is not None:
|
||||
lhandler = logging.FileHandler(filename=self.pargs.logfile)
|
||||
lhandler.setFormatter(logformat)
|
||||
self.logger.addHandler(lhandler)
|
||||
|
||||
# Loglevel auswerten
|
||||
if self.pargs.verbose is None:
|
||||
loglevel = logging.WARNING
|
||||
elif self.pargs.verbose == 1:
|
||||
loglevel = logging.INFO
|
||||
elif self.pargs.verbose > 1:
|
||||
loglevel = logging.DEBUG
|
||||
self.logger.setLevel(loglevel)
|
||||
263
revpipyload/revpipyload.py
Executable file
263
revpipyload/revpipyload.py
Executable file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# (c) Sven Sager, License: GPLv3
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
import proginit
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
from concurrent import futures
|
||||
from threading import Thread, Event
|
||||
from time import sleep
|
||||
from xmlrpc.server import SimpleXMLRPCServer
|
||||
|
||||
|
||||
class RevPiPlc(Thread):
|
||||
|
||||
def __init__(self, logger, lst_proc):
|
||||
super().__init__()
|
||||
self.autoreload = False
|
||||
self._evt_exit = Event()
|
||||
self.exitcode = 0
|
||||
self._lst_proc = lst_proc
|
||||
self._logger = logger
|
||||
self._procplc = None
|
||||
self.zeroonexit = False
|
||||
|
||||
def run(self):
|
||||
# Prozess starten
|
||||
self._logger.info("start plc program")
|
||||
self._procplc = subprocess.Popen(self._lst_proc)
|
||||
|
||||
while not self._evt_exit.is_set():
|
||||
|
||||
# Auswerten
|
||||
self.exitcode = self._procplc.poll()
|
||||
|
||||
if self.exitcode is not None:
|
||||
|
||||
if self.exitcode > 0:
|
||||
self._logger.error(
|
||||
"plc program chrashed - exitcode: {}".format(
|
||||
self.exitcode
|
||||
)
|
||||
)
|
||||
if self.zeroonexit:
|
||||
f = open("/dev/piControl0", "w+b", 0)
|
||||
f.write(bytes(4096))
|
||||
self._logger.warning("set piControl0 to ZERO")
|
||||
else:
|
||||
self._logger.info("plc program did a clean exit")
|
||||
|
||||
if self.autoreload:
|
||||
# Prozess neu starten
|
||||
self._procplc = subprocess.Popen(self._lst_proc)
|
||||
if self.exitcode == 0:
|
||||
self._logger.warning(
|
||||
"restart plc program after clean exit"
|
||||
)
|
||||
else:
|
||||
self._logger.warning("restart plc program after crash")
|
||||
else:
|
||||
break
|
||||
|
||||
self._evt_exit.wait(1)
|
||||
|
||||
# Prozess beenden
|
||||
count = 0
|
||||
self._logger.info("term plc program")
|
||||
self._procplc.terminate()
|
||||
while self._procplc.poll() is None and count < 10:
|
||||
count += 1
|
||||
self._logger.debug(
|
||||
"wait term plc program {} seconds".format(count * 0.5)
|
||||
)
|
||||
sleep(0.5)
|
||||
if self._procplc.poll() is None:
|
||||
self._logger.warning("can not term plc program")
|
||||
self._procplc.kill()
|
||||
self._logger.warning("killed plc program")
|
||||
|
||||
self.exitcode = self._procplc.poll()
|
||||
|
||||
def stop(self):
|
||||
self.evt_exit.set()
|
||||
|
||||
|
||||
class RevPiPyLoad(proginit.ProgInit):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._exit = True
|
||||
self.evt_loadconfig = Event()
|
||||
|
||||
self.autoreload = None
|
||||
self.plc = None
|
||||
self.plcprog = None
|
||||
self.tpe = None
|
||||
self.xmlrpc = None
|
||||
self.xsrv = None
|
||||
|
||||
# Load config
|
||||
self._loadconfig()
|
||||
|
||||
# Signal events
|
||||
signal.signal(signal.SIGINT, self._sigexit)
|
||||
signal.signal(signal.SIGTERM, self._sigexit)
|
||||
signal.signal(signal.SIGHUP, self._sigloadconfig)
|
||||
|
||||
def _loadconfig(self):
|
||||
"""Load configuration file and setup modul."""
|
||||
self.evt_loadconfig.clear()
|
||||
pauseproc = False
|
||||
if not self._exit:
|
||||
self.logger.info(
|
||||
"shutdown python plc program while getting new config"
|
||||
)
|
||||
self.stop()
|
||||
pauseproc = True
|
||||
|
||||
# Konfigurationsdatei laden
|
||||
self.logger.info(
|
||||
"loading config file: {}".format(self.globalconffile)
|
||||
)
|
||||
self.globalconfig.read(self.globalconffile)
|
||||
|
||||
# Konfiguration verarbeiten
|
||||
self.autoreload = int(self.globalconfig["DEFAULT"].get("autoreload", 1))
|
||||
self.autostart = int(self.globalconfig["DEFAULT"].get("autostart", 0))
|
||||
self.plcprog = self.globalconfig["DEFAULT"].get("plcprogram", None)
|
||||
self.xmlrpc = int(self.globalconfig["DEFAULT"].get("xmlrpc", 1))
|
||||
self.zeroonexit = int(self.globalconfig["DEFAULT"].get("zeroonexit", 1))
|
||||
|
||||
# PLC Thread konfigurieren
|
||||
self.logger.debug("create PLC watcher")
|
||||
self.plc = RevPiPlc(
|
||||
self.logger, shlex.split("/usr/bin/env python3 " + self.plcprog)
|
||||
)
|
||||
self.plc.autoreload = self.autoreload
|
||||
self.plc.zeroonexit = self.zeroonexit
|
||||
self.logger.debug("created PLC watcher")
|
||||
|
||||
# XMLRPC-Server Instantiieren und konfigurieren
|
||||
if self.xmlrpc:
|
||||
self.logger.debug("create xmlrpc server")
|
||||
self.xsrv = SimpleXMLRPCServer(
|
||||
(
|
||||
"",
|
||||
int(self.globalconfig["DEFAULT"].get("xmlrpcport", 55123))
|
||||
),
|
||||
logRequests=False,
|
||||
allow_none=True
|
||||
)
|
||||
self.xsrv.register_introspection_functions()
|
||||
|
||||
self.xsrv.register_function(self.xml_plcexitcode, "plcexitcode")
|
||||
self.xsrv.register_function(self.xml_plcrestart, "plcrestart")
|
||||
self.xsrv.register_function(self.xml_plcrunning, "plcrunning")
|
||||
self.xsrv.register_function(self.xml_plcstart, "plcstart")
|
||||
self.xsrv.register_function(self.xml_plcstop, "plcstop")
|
||||
self.xsrv.register_function(self.xml_reload, "reload")
|
||||
self.logger.debug("created xmlrpc server")
|
||||
|
||||
if pauseproc:
|
||||
self.logger.info(
|
||||
"start python plc program after getting new config"
|
||||
)
|
||||
self.start()
|
||||
|
||||
def _sigexit(self, signum, frame):
|
||||
"""Signal handler to clean an exit program."""
|
||||
self.logger.info("got exit signal")
|
||||
self.stop()
|
||||
|
||||
def _sigloadconfig(self, signum, frame):
|
||||
self.logger.info("got reload config signal")
|
||||
self.evt_loadconfig.set()
|
||||
|
||||
def start(self):
|
||||
"""Start python program and watching it."""
|
||||
self.logger.info("starting revpipyload")
|
||||
self._exit = False
|
||||
|
||||
if self.xmlrpc:
|
||||
self.logger.info("start xmlrpc-server")
|
||||
self.tpe = futures.ThreadPoolExecutor(max_workers=1)
|
||||
self.tpe.submit(self.xsrv.serve_forever)
|
||||
|
||||
if self.autostart:
|
||||
self.logger.info("starting plc program {}".format(self.plcprog))
|
||||
self.plc.start()
|
||||
|
||||
while not self._exit \
|
||||
and not self.evt_loadconfig.is_set() \
|
||||
and self.plc.is_alive():
|
||||
self.evt_loadconfig.wait(1)
|
||||
|
||||
if not self._exit:
|
||||
self.logger.info("exit python plc program to reload config")
|
||||
self._loadconfig()
|
||||
|
||||
def stop(self):
|
||||
"""Stop python program."""
|
||||
self.logger.info("stopping revpipyload")
|
||||
self._exit = True
|
||||
|
||||
self.logger.info("stopping plc program {}".format(self.plcprog))
|
||||
self.plc.stop()
|
||||
self.plc.join()
|
||||
|
||||
if self.xmlrpc:
|
||||
self.logger.info("shutting down xmlrpc-server")
|
||||
self.xsrv.shutdown()
|
||||
self.tpe.shutdown()
|
||||
self.xsrv.server_close()
|
||||
|
||||
def xml_plcexitcode(self):
|
||||
self.logger.debug("xmlrpc get plcexitcode")
|
||||
return -1 if self.plc.is_alive() else self.plc.exitcode
|
||||
|
||||
def xml_plcrestart(self):
|
||||
self.logger.debug("xmlrpc get plcrestart")
|
||||
self.plc.stop()
|
||||
self.plc.join()
|
||||
exitcode = self.plc.exitcode
|
||||
self.plc = RevPiPlc(
|
||||
self.logger, shlex.split("/usr/bin/env python3 " + self.plcprog)
|
||||
)
|
||||
self.plc.autoreload = self.autoreload
|
||||
self.plc.zeroonexit = self.zeroonexit
|
||||
self.plc.start()
|
||||
return (exitcode, self.plc.exitcode)
|
||||
|
||||
def xml_plcrunning(self):
|
||||
self.logger.debug("xmlrpc get plcrunning")
|
||||
return self.plc.is_alive()
|
||||
|
||||
def xml_plcstart(self):
|
||||
if self.plc.is_alive():
|
||||
return -1
|
||||
else:
|
||||
self.plc = RevPiPlc(
|
||||
self.logger, shlex.split("/usr/bin/env python3 " + self.plcprog)
|
||||
)
|
||||
self.plc.autoreload = self.autoreload
|
||||
self.plc.zeroonexit = self.zeroonexit
|
||||
self.plc.start()
|
||||
return self.plc.exitcode
|
||||
|
||||
def xml_plcstop(self):
|
||||
self.logger.debug("xmlrpc get plcstop")
|
||||
self.plc.stop()
|
||||
self.plc.join()
|
||||
return self.plc.exitcode
|
||||
|
||||
def xml_reload(self):
|
||||
self.logger.info("xmlrpc reload configuration")
|
||||
self.evt_loadconfig.set()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = RevPiPyLoad()
|
||||
root.start()
|
||||
Reference in New Issue
Block a user