--- a/Beremiz_service.py Thu Apr 12 22:32:12 2018 +0200
+++ b/Beremiz_service.py Thu Apr 12 22:32:43 2018 +0200
@@ -35,7 +35,7 @@
-from runtime import PLCObject, ServicePublisher
+from runtime import PLCObject, ServicePublisher, MainWorker import util.paths as paths
@@ -401,7 +401,7 @@
def __init__(self, servicename, ip_addr, port,
- workdir, argv, autostart=False,
statuschange=None, evaluator=default_evaluator,
@@ -413,12 +413,11 @@
self.servicepublisher = None
- self.autostart = autostart
self.statuschange = statuschange
self.evaluator = evaluator
self.pyruntimevars = pyruntimevars
self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
@@ -426,7 +425,27 @@
# taking too small timeout value may cause
# unwanted diconnection when IDE is kept busy for long periods
self.daemon.setTimeout(60)
+ uri = self.daemon.connect(self.plcobj, "PLCObject") + print(_("Pyro port :"), self.port) + print(_("Pyro object's uri :"), uri) + # Beremiz IDE detects daemon start by looking + # for self.workdir in the daemon's stdout. + # Therefore don't delete the following line + print(_("Current working directory :"), self.workdir) + # Configure and publish service + # Not publish service if localhost in address params + if self.servicename is not None and \ + self.ip_addr is not None and \ + self.ip_addr != "localhost" and \ + self.ip_addr != "127.0.0.1": + print(_("Publishing service on local network")) + self.servicepublisher = ServicePublisher.ServicePublisher() + self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port) self.daemon.requestLoop()
@@ -440,38 +459,8 @@
- self.plcobj = PLCObject(self.workdir, self.daemon, self.argv,
- self.statuschange, self.evaluator,
- uri = self.daemon.connect(self.plcobj, "PLCObject")
- print(_("Pyro port :"), self.port)
- print(_("Pyro object's uri :"), uri)
- # Beremiz IDE detects daemon start by looking
- # for self.workdir in the daemon's stdout.
- # Therefore don't delete the following line
- print(_("Current working directory :"), self.workdir)
- # Configure and publish service
- # Not publish service if localhost in address params
- if self.servicename is not None and \
- self.ip_addr is not None and \
- self.ip_addr != "localhost" and \
- self.ip_addr != "127.0.0.1":
- print(_("Publishing service on local network"))
- self.servicepublisher = ServicePublisher.ServicePublisher()
- self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
- if self.plcobj.GetPLCstatus()[0] != "Empty":
- self.plcobj.StatusChange()
+ def RegisterPLCObject(self, plcobj): if self.plcobj is not None:
@@ -529,13 +518,13 @@
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
statuschange, evaluator, pyruntimevars)
taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
statuschange, pyruntimevars=pyruntimevars)
@@ -631,19 +620,36 @@
LogMessageAndException(_("WAMP client startup failed. "))
+plcobj = PLCObject(pyroserver) +if plcobj.GetPLCstatus()[0] == "Stopped": +pyro_thread = Thread(target=pyroserver.PyroLoop) if havetwisted or havewx:
- pyro_thread = Thread(target=pyroserver.Loop)
+ # reactor._installSignalHandlersAgain() + def ui_thread_target(): + # FIXME: had to disable SignaHandlers install because + # signal not working in non-main thread + reactor.run(installSignalHandlers=False) + ui_thread_target = app.MainLoop
- except KeyboardInterrupt:
+ ui_thread = Thread(target = ui_thread_target) +except KeyboardInterrupt: --- a/canfestival/canfestival.py Thu Apr 12 22:32:12 2018 +0200
+++ b/canfestival/canfestival.py Thu Apr 12 22:32:43 2018 +0200
@@ -38,14 +38,11 @@
- from nodelist import NodeList
- base_folder = paths.AbsParentDir(__file__, 2)
- CanFestivalPath = os.path.join(base_folder, "CanFestival-3")
- sys.path.append(os.path.join(CanFestivalPath, "objdictgen"))
+base_folder = paths.AbsParentDir(__file__, 2) +CanFestivalPath = os.path.join(base_folder, "CanFestival-3") +sys.path.append(os.path.join(CanFestivalPath, "objdictgen")) - from nodelist import NodeList
+from nodelist import NodeList from nodemanager import NodeManager
--- a/connectors/PYRO/__init__.py Thu Apr 12 22:32:12 2018 +0200
+++ b/connectors/PYRO/__init__.py Thu Apr 12 22:32:43 2018 +0200
@@ -148,41 +148,17 @@
# for safe use in from debug thread, must create a copy
self.RemotePLCObjectProxyCopy = None
- def GetPyroProxy(self):
- This func returns the real Pyro Proxy.
- Use this if you musn't keep reference to it.
- return RemotePLCObjectProxy
def _PyroStartPLC(self, *args, **kwargs):
- confnodesroot._connector.GetPyroProxy() is used
- rather than RemotePLCObjectProxy because
- object is recreated meanwhile,
- so we must not keep ref to it here
- current_status, _log_count = confnodesroot._connector.GetPyroProxy().GetPLCstatus()
- if current_status == "Dirty":
- # Some bad libs with static symbols may polute PLC
- # ask runtime to suicide and come back again
- confnodesroot.logger.write(_("Force runtime reload\n"))
- confnodesroot._connector.GetPyroProxy().ForceReload()
- confnodesroot._Disconnect()
- # let remote PLC time to resurect.(freeze app)
- confnodesroot._Connect()
- self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy())
- return confnodesroot._connector.GetPyroProxy().StartPLC(*args, **kwargs)
+ return RemotePLCObjectProxy.StartPLC(*args, **kwargs) StartPLC = PyroCatcher(_PyroStartPLC, False)
def _PyroGetTraceVariables(self):
- for safe use in from debug thread, must use the copy
+ for use from debug thread, use a copy + pyro creates a new thread on server end proxy object is copied if self.RemotePLCObjectProxyCopy is None:
- self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy())
+ self.RemotePLCObjectProxyCopy = copy.copy(RemotePLCObjectProxy) return self.RemotePLCObjectProxyCopy.GetTraceVariables()
GetTraceVariables = PyroCatcher(_PyroGetTraceVariables, ("Broken", None))
--- a/runtime/PLCObject.py Thu Apr 12 22:32:12 2018 +0200
+++ b/runtime/PLCObject.py Thu Apr 12 22:32:43 2018 +0200
@@ -23,7 +23,8 @@
from __future__ import absolute_import
-from threading import Timer, Thread, Lock, Semaphore, Event
+from threading import Timer, Thread, Lock, Semaphore, Event, Condition @@ -60,28 +61,133 @@
+ job to be executed by a worker + def __init__(self,call,*args,**kwargs): + self.job = (call,args,kwargs) + do the job by executing the call, and deal with exceptions + call, args, kwargs = self.job + self.result = call(*args,**kwargs) + self.exc_info = sys.exc_info() + serialize main thread load/unload of PLC shared objects + # Only one job at a time + self.todo = Condition(self.mutex) + self.done = Condition(self.mutex) + meant to be called by worker thread (blocking) + self._threadID = thread.get_ident() + while not self._finish: + if self.job is not None: + def call(self, *args, **kwargs): + print("call", args, kwargs) + creates a job, execute it in worker thread, and deliver result. + if job execution raise exception, re-raise same exception + meant to be called by non-worker threads, but this is accepted. + blocking until job done + _job = job(*args,**kwargs) + if self._threadID == thread.get_ident(): + # if caller is worker thread execute immediately + # otherwise notify and wait for completion + while self.job is not None: + raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2] + unblocks main thread, and terminate execution of runloop() + def func_wrapper(*args,**kwargs): + return MainWorker.call(func, *args, **kwargs) class PLCObject(pyro.ObjBase):
- def __init__(self, workingdir, daemon, argv, statuschange, evaluator, pyruntimevars):
+ def __init__(self, server): pyro.ObjBase.__init__(self)
- self.evaluator = evaluator
- self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
- self.workingdir = workingdir
+ self.evaluator = server.evaluator + self.argv = [server.workdir] + server.argv # force argv[0] to be "path" to exec... + self.workingdir = server.workdir self.PLClibraryHandle = None
self.PLClibraryLock = Lock()
self.DummyIteratorLock = None
# Creates fake C funcs proxies
- self.statuschange = statuschange
+ self._InitPLCStubCalls() + self.daemon = server.daemon + self.statuschange = server.statuschange - self.pyruntimevars = pyruntimevars
+ self.pyruntimevars = server.pyruntimevars self._loading_error = None
self.python_runtime_vars = None
self.TraceWakeup = Event()
+ server.RegisterPLCObject(self) # Get the last transfered PLC if connector must be restart
@@ -145,6 +251,7 @@
def _GetLibFileName(self):
return os.path.join(self.workingdir, self.CurrentPLCFilename)
@@ -233,19 +340,18 @@
self._loading_error = traceback.format_exc()
PLCprint(self._loading_error)
self.PythonRuntimeCleanup()
+ def _InitPLCStubCalls(self):
- This is also called by __init__ to create dummy C func proxies
+ create dummy C func proxies - self.PLClibraryLock.acquire()
- # Forget all refs to library
self._startPLC = lambda x, y: None
self._stopPLC = lambda: None
self._ResetDebugVariables = lambda: None
@@ -259,11 +365,22 @@
self._GetLogMessage = None
+ self._PLClibraryHandle = None self.PLClibraryHandle = None
+ This is also called by __init__ to create dummy C func proxies + self.PLClibraryLock.acquire() # Unload library explicitely
if getattr(self, "_PLClibraryHandle", None) is not None:
dlclose(self._PLClibraryHandle)
- self._PLClibraryHandle = None
+ # Forget all refs to library + self._InitPLCStubCalls() self.PLClibraryLock.release()
@@ -397,18 +514,6 @@
- self.daemon.shutdown(True)
- self.daemon.sock.close()
- os.execv(sys.executable, [sys.executable]+sys.argv[:])
- # respawn python interpreter
- Timer(0.1, self._Reload).start()
return self.PLCStatus, map(self.GetLogCount, xrange(LogLevelsCount))
@@ -460,7 +565,6 @@
self.PLCStatus = "Stopped"
self.PLCStatus = "Broken"
return self.PLCStatus == "Stopped"
--- a/runtime/__init__.py Thu Apr 12 22:32:12 2018 +0200
+++ b/runtime/__init__.py Thu Apr 12 22:32:43 2018 +0200
@@ -24,5 +24,5 @@
from __future__ import absolute_import
-from runtime.PLCObject import PLCObject, PLCprint
+from runtime.PLCObject import PLCObject, PLCprint, MainWorker import runtime.ServicePublisher
--- a/targets/Xenomai/plc_Xenomai_main.c Thu Apr 12 22:32:12 2018 +0200
+++ b/targets/Xenomai/plc_Xenomai_main.c Thu Apr 12 22:32:43 2018 +0200
@@ -159,6 +159,15 @@
+#define _startPLCLog(text) \ + LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));\ +#define FO "Failed opening " #define max_val(a,b) ((a>b)?a:b)
int startPLC(int argc,char **argv)
@@ -171,49 +180,55 @@
/*** RT Pipes creation and opening ***/
- if(rt_pipe_create(&Debug_pipe, "Debug_pipe", DEBUG_PIPE_MINOR, PIPE_SIZE))
+ if(rt_pipe_create(&Debug_pipe, "Debug_pipe", DEBUG_PIPE_MINOR, PIPE_SIZE) < 0) + _startPLCLog(FO "Debug_pipe real-time end"); PLC_state |= PLC_STATE_DEBUG_PIPE_CREATED;
- if((Debug_pipe_fd = open(DEBUG_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((Debug_pipe_fd = open(DEBUG_PIPE_DEVICE, O_RDWR)) == -1) + _startPLCLog(FO DEBUG_PIPE_DEVICE); PLC_state |= PLC_STATE_DEBUG_FILE_OPENED;
- if(rt_pipe_create(&Python_pipe, "Python_pipe", PYTHON_PIPE_MINOR, PIPE_SIZE))
+ if(rt_pipe_create(&Python_pipe, "Python_pipe", PYTHON_PIPE_MINOR, PIPE_SIZE) < 0) + _startPLCLog(FO "Python_pipe real-time end"); PLC_state |= PLC_STATE_PYTHON_PIPE_CREATED;
- if((Python_pipe_fd = open(PYTHON_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((Python_pipe_fd = open(PYTHON_PIPE_DEVICE, O_RDWR)) == -1) + _startPLCLog(FO PYTHON_PIPE_DEVICE); PLC_state |= PLC_STATE_PYTHON_FILE_OPENED;
/* create WaitDebug_pipe */
- if(rt_pipe_create(&WaitDebug_pipe, "WaitDebug_pipe", WAITDEBUG_PIPE_MINOR, PIPE_SIZE))
+ if(rt_pipe_create(&WaitDebug_pipe, "WaitDebug_pipe", WAITDEBUG_PIPE_MINOR, PIPE_SIZE) < 0) + _startPLCLog(FO "WaitDebug_pipe real-time end"); PLC_state |= PLC_STATE_WAITDEBUG_PIPE_CREATED;
- if((WaitDebug_pipe_fd = open(WAITDEBUG_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((WaitDebug_pipe_fd = open(WAITDEBUG_PIPE_DEVICE, O_RDWR)) == -1) + _startPLCLog(FO WAITDEBUG_PIPE_DEVICE); PLC_state |= PLC_STATE_WAITDEBUG_FILE_OPENED;
/* create WaitPython_pipe */
- if(rt_pipe_create(&WaitPython_pipe, "WaitPython_pipe", WAITPYTHON_PIPE_MINOR, PIPE_SIZE))
+ if(rt_pipe_create(&WaitPython_pipe, "WaitPython_pipe", WAITPYTHON_PIPE_MINOR, PIPE_SIZE) < 0) + _startPLCLog(FO "WaitPython_pipe real-time end"); PLC_state |= PLC_STATE_WAITPYTHON_PIPE_CREATED;
/* open WaitPython_pipe*/
- if((WaitPython_pipe_fd = open(WAITPYTHON_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((WaitPython_pipe_fd = open(WAITPYTHON_PIPE_DEVICE, O_RDWR)) == -1) + _startPLCLog(FO WAITPYTHON_PIPE_DEVICE); PLC_state |= PLC_STATE_WAITPYTHON_FILE_OPENED;
/*** create PLC task ***/
- if(rt_task_create(&PLC_task, "PLC_task", 0, 50, T_JOINABLE)) goto error;
+ if(rt_task_create(&PLC_task, "PLC_task", 0, 50, T_JOINABLE)) + _startPLCLog("Failed creating PLC task"); PLC_state |= PLC_STATE_TASK_CREATED;
if(__init(argc,argv)) goto error;
- if(rt_task_start(&PLC_task, &PLC_task_proc, NULL)) goto error;
+ if(rt_task_start(&PLC_task, &PLC_task_proc, NULL)) + _startPLCLog("Failed starting PLC task"); --- a/targets/plc_main_head.c Thu Apr 12 22:32:12 2018 +0200
+++ b/targets/plc_main_head.c Thu Apr 12 22:32:43 2018 +0200
@@ -35,6 +35,9 @@
/* Help to quit cleanly when init fail at a certain level */
static int init_level = 0;
+/* Prototype for Logging to help spotting errors at init */ +int LogMessage(uint8_t level, char* buf, uint32_t size); * Prototypes of functions exported by plugins