--- 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 @@
import __builtin__
import Pyro.core as pyro
-from runtime import PLCObject, ServicePublisher
+from runtime import PLCObject, ServicePublisher, MainWorker
import util.paths as paths
@@ -401,7 +401,7 @@
class Server(object):
def __init__(self, servicename, ip_addr, port,
- workdir, argv, autostart=False,
+ workdir, argv,
statuschange=None, evaluator=default_evaluator,
pyruntimevars=None):
self.continueloop = True
@@ -413,12 +413,11 @@
self.argv = argv
self.plcobj = None
self.servicepublisher = None
- self.autostart = autostart
self.statuschange = statuschange
self.evaluator = evaluator
self.pyruntimevars = pyruntimevars
- def Loop(self):
+ def PyroLoop(self):
while self.continueloop:
pyro.initServer()
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)
- self.Start()
+
+ 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()
self.daemon.sock.close()
@@ -440,38 +459,8 @@
self.plcobj.UnLoadPLC()
self._stop()
- def Start(self):
- self.plcobj = PLCObject(self.workdir, self.daemon, self.argv,
- self.statuschange, self.evaluator,
- self.pyruntimevars)
-
- 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.plcobj.AutoLoad()
- if self.plcobj.GetPLCstatus()[0] != "Empty":
- if self.autostart:
- self.plcobj.StartPLC()
- self.plcobj.StatusChange()
-
- sys.stdout.flush()
+ def RegisterPLCObject(self, plcobj):
+ self.plcobj = plcobj
def _stop(self):
if self.plcobj is not None:
@@ -529,13 +518,13 @@
return o.res
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
+ WorkingDir, argv,
statuschange, evaluator, pyruntimevars)
taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
else:
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
+ WorkingDir, argv,
statuschange, pyruntimevars=pyruntimevars)
@@ -631,19 +620,36 @@
except Exception:
LogMessageAndException(_("WAMP client startup failed. "))
+plcobj = PLCObject(pyroserver)
+
+plcobj.AutoLoad()
+if plcobj.GetPLCstatus()[0] == "Stopped":
+ if autostart:
+ plcobj.StartPLC()
+plcobj.StatusChange()
+
+pyro_thread = Thread(target=pyroserver.PyroLoop)
+pyro_thread.start()
+
+sys.stdout.flush()
if havetwisted or havewx:
- pyro_thread = Thread(target=pyroserver.Loop)
- pyro_thread.start()
-
if havetwisted:
- reactor.run()
- elif havewx:
- app.MainLoop()
-else:
- try:
- pyroserver.Loop()
- except KeyboardInterrupt:
- pass
+ # reactor._installSignalHandlersAgain()
+ def ui_thread_target():
+ # FIXME: had to disable SignaHandlers install because
+ # signal not working in non-main thread
+ reactor.run(installSignalHandlers=False)
+ else :
+ ui_thread_target = app.MainLoop
+
+ ui_thread = Thread(target = ui_thread_target)
+ ui_thread.start()
+
+try:
+ MainWorker.runloop()
+except KeyboardInterrupt:
+ pass
+
pyroserver.Quit()
sys.exit(0)
--- 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 @@
LOCATION_CONFNODE, \
LOCATION_VAR_MEMORY
-try:
- from nodelist import NodeList
-except ImportError:
- 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
+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 nodemanager import NodeManager
import gen_cfile
--- 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)
- sleep(0.5)
- 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
+import thread
+from threading import Timer, Thread, Lock, Semaphore, Event, Condition
import ctypes
import os
import sys
@@ -60,28 +61,133 @@
sys.stdout.flush()
+class job(object):
+ """
+ job to be executed by a worker
+ """
+ def __init__(self,call,*args,**kwargs):
+ self.job = (call,args,kwargs)
+ self.result = None
+ self.success = False
+ self.exc_info = None
+
+ def do(self):
+ """
+ do the job by executing the call, and deal with exceptions
+ """
+ try :
+ call, args, kwargs = self.job
+ self.result = call(*args,**kwargs)
+ self.success = True
+ except Exception:
+ self.success = False
+ self.exc_info = sys.exc_info()
+
+
+class worker(object):
+ """
+ serialize main thread load/unload of PLC shared objects
+ """
+ def __init__(self):
+ # Only one job at a time
+ self._finish = False
+ self._threadID = None
+ self.mutex = Lock()
+ self.todo = Condition(self.mutex)
+ self.done = Condition(self.mutex)
+ self.job = None
+
+ def runloop(self):
+ """
+ meant to be called by worker thread (blocking)
+ """
+ self._threadID = thread.get_ident()
+ self.mutex.acquire()
+ while not self._finish:
+ self.todo.wait()
+ if self.job is not None:
+ self.job.do()
+ self.done.notify_all()
+ self.mutex.release()
+
+ 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
+ _job.do()
+ else:
+ # otherwise notify and wait for completion
+ self.mutex.acquire()
+
+ while self.job is not None:
+ self.done.wait()
+
+ self.job = _job
+ self.todo.notify()
+ self.done.wait()
+ _job = self.job
+ self.job = None
+ self.mutex.release()
+
+ if _job.success:
+ return _job.result
+ else:
+ raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2]
+
+ def quit(self):
+ """
+ unblocks main thread, and terminate execution of runloop()
+ """
+ # mark queue
+ self._finish = True
+ self.mutex.acquire()
+ self.job = None
+ self.todo.notify()
+ self.mutex.release()
+
+
+MainWorker = worker()
+
+
+def RunInMain(func):
+ def func_wrapper(*args,**kwargs):
+ return MainWorker.call(func, *args, **kwargs)
+ return func_wrapper
+
+
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.PLCStatus = "Empty"
self.PLClibraryHandle = None
self.PLClibraryLock = Lock()
self.DummyIteratorLock = None
# Creates fake C funcs proxies
- self._FreePLC()
- self.daemon = daemon
- self.statuschange = statuschange
+ self._InitPLCStubCalls()
+ self.daemon = server.daemon
+ self.statuschange = server.statuschange
self.hmi_frame = None
- self.pyruntimevars = pyruntimevars
+ self.pyruntimevars = server.pyruntimevars
self._loading_error = None
self.python_runtime_vars = None
self.TraceThread = None
self.TraceLock = Lock()
self.TraceWakeup = Event()
self.Traces = []
+ server.RegisterPLCObject(self)
def AutoLoad(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)
+ @RunInMain
def LoadPLC(self):
"""
Load PLC library
@@ -233,19 +340,18 @@
except Exception:
self._loading_error = traceback.format_exc()
PLCprint(self._loading_error)
+ self._FreePLC()
return False
+ @RunInMain
def UnLoadPLC(self):
self.PythonRuntimeCleanup()
self._FreePLC()
- def _FreePLC(self):
- """
- Unload PLC library.
- This is also called by __init__ to create dummy C func proxies
- """
- self.PLClibraryLock.acquire()
- # Forget all refs to library
+ def _InitPLCStubCalls(self):
+ """
+ create dummy C func proxies
+ """
self._startPLC = lambda x, y: None
self._stopPLC = lambda: None
self._ResetDebugVariables = lambda: None
@@ -259,11 +365,22 @@
self._GetLogCount = None
self._LogMessage = None
self._GetLogMessage = None
+ self._PLClibraryHandle = None
self.PLClibraryHandle = None
+
+ def _FreePLC(self):
+ """
+ Unload PLC library.
+ 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()
return False
@@ -397,18 +514,6 @@
return True
return False
- def _Reload(self):
- self.daemon.shutdown(True)
- self.daemon.sock.close()
- os.execv(sys.executable, [sys.executable]+sys.argv[:])
- # never reached
- return 0
-
- def ForceReload(self):
- # respawn python interpreter
- Timer(0.1, self._Reload).start()
- return True
-
def GetPLCstatus(self):
return self.PLCStatus, map(self.GetLogCount, xrange(LogLevelsCount))
@@ -460,7 +565,6 @@
self.PLCStatus = "Stopped"
else:
self.PLCStatus = "Broken"
- self._FreePLC()
self.StatusChange()
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
import os
-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 @@
exit(0);
}
+#define _startPLCLog(text) \
+ {\
+ char mstr[] = text;\
+ LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));\
+ goto error;\
+ }
+
+#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 ***/
/* create Debug_pipe */
- if(rt_pipe_create(&Debug_pipe, "Debug_pipe", DEBUG_PIPE_MINOR, PIPE_SIZE))
- goto error;
+ 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;
/* open Debug_pipe*/
- 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;
/* create Python_pipe */
- if(rt_pipe_create(&Python_pipe, "Python_pipe", PYTHON_PIPE_MINOR, PIPE_SIZE))
- goto error;
+ 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;
/* open Python_pipe*/
- 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))
- goto error;
+ 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;
/* open WaitDebug_pipe*/
- 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))
- goto error;
+ 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;
/* start PLC task */
- 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");
return 0;
--- 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
**/