Merged in Andrey's branch
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Wed, 12 Sep 2018 22:59:30 +0200
changeset 2305 56f1d8aca886
parent 2273 a0efe3d9c853 (diff)
parent 2304 68e6649039a5 (current diff)
child 2308 4d7cee25a474
Merged in Andrey's branch
BeremizIDE.py
Beremiz_service.py
IDEFrame.py
PLCGenerator.py
connectors/PYRO/__init__.py
connectors/__init__.py
dialogs/AboutDialog.py
dialogs/BlockPreviewDialog.py
dialogs/ConnectionDialog.py
dialogs/FBDBlockDialog.py
modbus/mb_runtime.h
runtime/ServicePublisher.py
runtime/WampClient.py
runtime_files.list
svgui/svguilib.py
targets/Generic/XSD
targets/Generic/__init__.py
targets/Linux/XSD
targets/Linux/__init__.py
targets/Win32/XSD
targets/Win32/__init__.py
targets/XSD_toolchain_gcc
targets/XSD_toolchain_makefile
targets/Xenomai/XSD
targets/Xenomai/__init__.py
targets/__init__.py
targets/toolchain_gcc.py
targets/toolchain_makefile.py
tests/ethercat/wago_higen/beremiz.xml
tests/ethercat/wago_higen/ethercat@etherlab/baseconfnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/confnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/baseconfnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/config.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/confnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/coupler@EthercatSlave/baseconfnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/coupler@EthercatSlave/confnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/higen@EthercatCIA402Slave/baseconfnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/higen@EthercatCIA402Slave/confnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/inputs@EthercatSlave/baseconfnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/inputs@EthercatSlave/confnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/outputs@EthercatSlave/baseconfnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/outputs@EthercatSlave/confnode.xml
tests/ethercat/wago_higen/ethercat@etherlab/master@EthercatNode/process_variables.xml
tests/ethercat/wago_higen/ethercat@etherlab/modules/Beckhoff EKxxxx.xml
tests/ethercat/wago_higen/ethercat@etherlab/modules/Beckhoff EL1xxx.xml
tests/ethercat/wago_higen/ethercat@etherlab/modules/Beckhoff EL2xxx.xml
tests/ethercat/wago_higen/ethercat@etherlab/modules/Higen_EDA7000_CoE_ver16_with_variablePDO_rv3.xml
tests/ethercat/wago_higen/ethercat@etherlab/modules/modules_extra_params.cfg
tests/ethercat/wago_higen/plc.xml
tests/ethercat/wago_sanyo/beremiz.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/baseconfnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/confnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/baseconfnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/config.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/confnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/coupler@EthercatSlave/baseconfnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/coupler@EthercatSlave/confnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/inputs@EthercatSlave/baseconfnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/inputs@EthercatSlave/confnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/outputs@EthercatSlave/baseconfnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/outputs@EthercatSlave/confnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/sanyo@EthercatCIA402Slave/baseconfnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/master@EthercatNode/sanyo@EthercatCIA402Slave/confnode.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/modules/Beckhoff EKxxxx.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/modules/Beckhoff EL1xxx.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/modules/Beckhoff EL2xxx.xml
tests/ethercat/wago_sanyo/ethercat@etherlab/modules/sanyo.xml
tests/ethercat/wago_sanyo/plc.xml
tests/svgui/beremiz.xml
tests/svgui/plc.xml
tests/svgui/svgui@svgui/gui.svg
tests/wamp/.crossbar/config.json
tests/wamp/README
tests/wamp/py_ext_0@py_ext/baseconfnode.xml
tests/wamp/py_ext_0@py_ext/pyfile.xml
tests/wxHMI/HMI@wxglade_hmi/baseconfnode.xml
tests/wxHMI/HMI@wxglade_hmi/hmi.wxg
tests/wxHMI/HMI@wxglade_hmi/hmi.wxg.bak
tests/wxHMI/HMI@wxglade_hmi/pyfile.xml
--- a/Beremiz_service.py	Fri Sep 07 20:58:13 2018 +0000
+++ b/Beremiz_service.py	Wed Sep 12 22:59:30 2018 +0200
@@ -31,13 +31,12 @@
 import getopt
 import threading
 from threading import Thread, Semaphore, Lock
-import traceback
 import __builtin__
-import Pyro
-import Pyro.core as pyro
-
-from runtime import PLCObject, ServicePublisher, MainWorker
+
+import runtime
+from runtime.PyroServer import Server
 from runtime.xenomai import TryPreloadXenomai
+from runtime import LogMessageAndException
 import util.paths as paths
 
 
@@ -252,12 +251,11 @@
             TBMENU_CHANGE_WD = wx.NewId()
             TBMENU_QUIT = wx.NewId()
 
-            def __init__(self, pyroserver, level):
+            def __init__(self, pyroserver):
                 wx.TaskBarIcon.__init__(self)
                 self.pyroserver = pyroserver
                 # Set the image
                 self.UpdateIcon(None)
-                self.level = level
 
                 # bind some events
                 self.Bind(wx.EVT_MENU, self.OnTaskBarStartPLC, id=self.TBMENU_START)
@@ -280,15 +278,14 @@
                 menu = wx.Menu()
                 menu.Append(self.TBMENU_START, _("Start PLC"))
                 menu.Append(self.TBMENU_STOP, _("Stop PLC"))
-                if self.level == 1:
-                    menu.AppendSeparator()
-                    menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name"))
-                    menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind"))
-                    menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number"))
-                    menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory"))
-                    menu.AppendSeparator()
-                    menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell"))
-                    menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector"))
+                menu.AppendSeparator()
+                menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name"))
+                menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind"))
+                menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number"))
+                menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory"))
+                menu.AppendSeparator()
+                menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell"))
+                menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector"))
                 menu.AppendSeparator()
                 menu.Append(self.TBMENU_QUIT, _("Quit"))
                 return menu
@@ -307,19 +304,10 @@
                 return icon
 
             def OnTaskBarStartPLC(self, evt):
-                if self.pyroserver.plcobj is not None:
-                    plcstatus = self.pyroserver.plcobj.GetPLCstatus()[0]
-                    if plcstatus is "Stopped":
-                        self.pyroserver.plcobj.StartPLC()
-                    else:
-                        print(_("PLC is empty or already started."))
+                runtime.GetPLCObjectSingleton().StartPLC()
 
             def OnTaskBarStopPLC(self, evt):
-                if self.pyroserver.plcobj is not None:
-                    if self.pyroserver.plcobj.GetPLCstatus()[0] == "Started":
-                        Thread(target=self.pyroserver.plcobj.StopPLC).start()
-                    else:
-                        print(_("PLC is not started."))
+                runtime.GetPLCObjectSingleton().StopPLC()
 
             def OnTaskBarChangeInterface(self, evt):
                 ip_addr = self.pyroserver.ip_addr
@@ -355,10 +343,7 @@
                     self.pyroserver.Restart()
 
             def _LiveShellLocals(self):
-                if self.pyroserver.plcobj is not None:
-                    return {"locals": self.pyroserver.plcobj.python_runtime_vars}
-                else:
-                    return {}
+                return {"locals": runtime.GetPLCObjectSingleton().python_runtime_vars}
 
             def OnTaskBarLiveShell(self, evt):
                 from wx import py
@@ -401,90 +386,6 @@
         res = (None, sys.exc_info())
     return res
 
-
-class Server(object):
-    def __init__(self, servicename, ip_addr, port,
-                 workdir, argv,
-                 statuschange=None, evaluator=default_evaluator,
-                 pyruntimevars=None):
-        self.continueloop = True
-        self.daemon = None
-        self.servicename = servicename
-        self.ip_addr = ip_addr
-        self.port = port
-        self.workdir = workdir
-        self.argv = argv
-        self.servicepublisher = None
-        self.statuschange = statuschange
-        self.evaluator = evaluator
-        self.pyruntimevars = pyruntimevars
-        self.plcobj = PLCObject(self)
-
-    def _to_be_published(self):
-        return 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"
-
-    def PrintServerInfo(self):
-        print(_("Pyro port :"), self.port)
-
-        # Beremiz IDE detects LOCAL:// runtime is ready by looking
-        # for self.workdir in the daemon's stdout.
-        print(_("Current working directory :"), self.workdir)
-
-        if self._to_be_published():
-            print(_("Publishing service on local network"))
-
-        sys.stdout.flush()
-
-    def PyroLoop(self, when_ready):
-        while self.continueloop:
-            Pyro.config.PYRO_MULTITHREADED = 0
-            pyro.initServer()
-            self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
-
-            # pyro never frees memory after connection close if no timeout set
-            # taking too small timeout value may cause
-            # unwanted diconnection when IDE is kept busy for long periods
-            self.daemon.setTimeout(60)
-
-            self.daemon.connect(self.plcobj, "PLCObject")
-
-            if self._to_be_published():
-                self.servicepublisher = ServicePublisher.ServicePublisher()
-                self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
-
-            when_ready()
-            self.daemon.requestLoop()
-            self.daemon.sock.close()
-
-    def Restart(self):
-        self._stop()
-
-    def Quit(self):
-        self.continueloop = False
-        if self.plcobj is not None:
-            self.plcobj.StopPLC()
-            self.plcobj.UnLoadPLC()
-        self._stop()
-
-    def _stop(self):
-        if self.plcobj is not None:
-            self.plcobj.StopPLC()
-        if self.servicepublisher is not None:
-            self.servicepublisher.UnRegisterService()
-            self.servicepublisher = None
-        self.daemon.shutdown(True)
-
-    def AutoLoad(self):
-        self.plcobj.AutoLoad()
-        if self.plcobj.GetPLCstatus()[0] == "Stopped":
-            if autostart:
-                self.plcobj.StartPLC()
-        self.plcobj.StatusChange()
-
-
 if enabletwisted:
     import warnings
     with warnings.catch_warnings():
@@ -532,31 +433,11 @@
         wx.CallAfter(wx_evaluator, o)
         wx_eval_lock.acquire()
         return o.res
-
-    pyroserver = Server(servicename, given_ip, port,
-                        WorkingDir, argv,
-                        statuschange, evaluator, pyruntimevars)
-
-    taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
 else:
-    pyroserver = Server(servicename, given_ip, port,
-                        WorkingDir, argv,
-                        statuschange, pyruntimevars=pyruntimevars)
-
+    evaluator = default_evaluator
 
 # Exception hooks
 
-
-def LogMessageAndException(msg, exp=None):
-    if exp is None:
-        exp = sys.exc_info()
-    if pyroserver.plcobj is not None:
-        pyroserver.plcobj.LogMessage(0, msg + '\n'.join(traceback.format_exception(*exp)))
-    else:
-        print(msg)
-        traceback.print_exception(*exp)
-
-
 def LogException(*exp):
     LogMessageAndException("", exp)
 
@@ -606,6 +487,15 @@
     sys.path.append(extension_folder)
     execfile(os.path.join(extension_folder, extention_file), locals())
 
+
+runtime.CreatePLCObjectSingleton(
+    WorkingDir, argv, statuschange, evaluator, pyruntimevars)
+
+pyroserver = Server(servicename, given_ip, port)
+
+if havewx:
+    taskbar_instance = BeremizTaskBarIcon(pyroserver)
+
 if havetwisted:
     if webport is not None:
         try:
@@ -635,6 +525,11 @@
 
 pyroserver.PrintServerInfo()
 
+# Beremiz IDE detects LOCAL:// runtime is ready by looking
+# for self.workdir in the daemon's stdout.
+sys.stdout.write(_("Current working directory :") + WorkingDir + "\n")
+sys.stdout.flush()
+
 if havetwisted or havewx:
     ui_thread_started = Lock()
     ui_thread_started.acquire()
@@ -661,9 +556,15 @@
     print("UI thread started successfully.")
 
 try:
-    MainWorker.runloop(pyroserver.AutoLoad)
+    runtime.MainWorker.runloop(
+        runtime.GetPLCObjectSingleton().AutoLoad, autostart)
 except KeyboardInterrupt:
     pass
 
 pyroserver.Quit()
+
+plcobj = runtime.GetPLCObjectSingleton()
+plcobj.StopPLC()
+plcobj.UnLoadPLC()
+
 sys.exit(0)
--- a/ProjectController.py	Fri Sep 07 20:58:13 2018 +0000
+++ b/ProjectController.py	Wed Sep 12 22:59:30 2018 +0200
@@ -267,7 +267,7 @@
         self._setBuildPath(None)
         self.debug_break = False
         self.previous_plcstate = None
-        # copy ConfNodeMethods so that it can be later customized
+        # copy StatusMethods so that it can be later customized
         self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
 
     def __del__(self):
--- a/bacnet/runtime/device.c	Fri Sep 07 20:58:13 2018 +0000
+++ b/bacnet/runtime/device.c	Wed Sep 12 22:59:30 2018 +0200
@@ -398,7 +398,7 @@
     PROP_OBJECT_TYPE,                      /* R  R ( 79) */
     PROP_SYSTEM_STATUS,                    /* R  R (112) */
     PROP_VENDOR_NAME,                      /* R  R (121) */
-    PROP_VENDOR_IDENTIFIER,                /* W  R (120) */
+    PROP_VENDOR_IDENTIFIER,                /* R  R (120) */
     PROP_MODEL_NAME,                       /* W  R ( 70) */
     PROP_FIRMWARE_REVISION,                /* R  R ( 44) */
     PROP_APPLICATION_SOFTWARE_VERSION,     /* R  R ( 12) */
@@ -1366,16 +1366,16 @@
                 apdu_timeout_set((uint16_t) value.type.Unsigned_Int);
             }
             break;
-        case PROP_VENDOR_IDENTIFIER:
-            status =
-                WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
-                &wp_data->error_class, &wp_data->error_code);
-            if (status) {
-                /* FIXME: bounds check? */
-                Device_Set_Vendor_Identifier((uint16_t) value.
-                    type.Unsigned_Int);
-            }
-            break;
+//         case PROP_VENDOR_IDENTIFIER:
+//             status =
+//                 WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+//                 &wp_data->error_class, &wp_data->error_code);
+//             if (status) {
+//                 /* FIXME: bounds check? */
+//                 Device_Set_Vendor_Identifier((uint16_t) value.
+//                     type.Unsigned_Int);
+//             }
+//             break;
 //       case PROP_SYSTEM_STATUS:
 //           status =
 //               WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
@@ -1453,6 +1453,7 @@
         case PROP_OBJECT_TYPE:
         case PROP_SYSTEM_STATUS:
         case PROP_VENDOR_NAME:
+        case PROP_VENDOR_IDENTIFIER:
         case PROP_FIRMWARE_REVISION:
         case PROP_APPLICATION_SOFTWARE_VERSION:
         case PROP_LOCAL_TIME:
--- a/bacnet/runtime/server.c	Fri Sep 07 20:58:13 2018 +0000
+++ b/bacnet/runtime/server.c	Wed Sep 12 22:59:30 2018 +0200
@@ -517,7 +517,7 @@
         if (elapsed_seconds) {
             last_seconds = current_seconds;
             dcc_timer_seconds(elapsed_seconds);
-            bvlc_maintenance_timer(elapsed_seconds);
+            //bvlc_maintenance_timer(elapsed_seconds); // already called by dlenv_maintenance_timer() => do _not_ call here!
             dlenv_maintenance_timer(elapsed_seconds);
             elapsed_milliseconds = elapsed_seconds * 1000;
             tsm_timer_milliseconds(elapsed_milliseconds);
--- a/py_ext/plc_python.c	Fri Sep 07 20:58:13 2018 +0000
+++ b/py_ext/plc_python.c	Wed Sep 12 22:59:30 2018 +0200
@@ -89,6 +89,10 @@
  */
 void __PythonEvalFB(int poll, PYTHON_EVAL* data__)
 {
+    if(!__GET_VAR(data__->TRIG)){
+        /* ACK is False when TRIG is false, except a pulse when receiving result */
+        __SET_VAR(data__->, ACK,, 0);
+    }
 	/* detect rising edge on TRIG to trigger evaluation */
 	if(((__GET_VAR(data__->TRIG) && !__GET_VAR(data__->TRIGM1)) ||
 	   /* polling is equivalent to trig on value rather than on rising edge*/
@@ -109,7 +113,7 @@
 		if(__GET_VAR(data__->STATE) == PYTHON_FB_ANSWERED){
 			/* Copy buffer content into result*/
 			__SET_VAR(data__->, RESULT,, __GET_VAR(data__->BUFFER));
-			/* signal result presece to PLC*/
+			/* signal result presence to PLC*/
 			__SET_VAR(data__->, ACK,, 1);
 			/* Mark as free */
 			__SET_VAR(data__->, STATE,, PYTHON_FB_FREE);
--- a/runtime/NevowServer.py	Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/NevowServer.py	Wed Sep 12 22:59:30 2018 +0200
@@ -30,6 +30,7 @@
 from zope.interface import implements
 from nevow import appserver, inevow, tags, loaders, athena, url, rend
 from nevow.page import renderer
+from nevow.static import File
 from formless import annotate
 from formless import webform
 from formless import configurable
@@ -77,10 +78,12 @@
 class MainPage(athena.LiveElement):
     jsClass = u"WebInterface.PLC"
     docFactory = loaders.stan(
-        tags.div(render=tags.directive('liveElement'))[
-            tags.div(id='content')[
-                tags.div(render=tags.directive('PLCElement'))]
-        ])
+        tags.invisible[
+            tags.div(render=tags.directive('liveElement'))[
+                tags.div(id='content')[
+                    tags.div(render=tags.directive('PLCElement'))]
+            ],
+            tags.a(href='settings')['Settings']])
 
     def __init__(self, *a, **kw):
         athena.LiveElement.__init__(self, *a, **kw)
@@ -135,11 +138,34 @@
     def __init__(self):
         configurable.Configurable.__init__(self, None)
         self.bindingsNames = []
+        self.infostringcount = 0
 
     def getBindingNames(self, ctx):
         return self.bindingsNames
 
-    def addExtension(self, name, desc, fields, btnlabel, callback):
+    def addInfoString(self, label, value, name=None):
+        if isinstance(value, str):
+            def default(*k):
+                return value
+        else:
+            def default(*k):
+                return value()
+
+        if name is None:
+            name = "_infostring_" + str(self.infostringcount)
+            self.infostringcount = self.infostringcount + 1
+
+        def _bind(ctx):
+            return annotate.Property(
+                name,
+                annotate.String(
+                    label=label,
+                    default=default,
+                    immutable=True))
+        setattr(self, 'bind_' + name, _bind)
+        self.bindingsNames.append(name)
+
+    def addSettings(self, name, desc, fields, btnlabel, callback):
         def _bind(ctx):
             return annotate.MethodBinding(
                 'action_' + name,
@@ -192,25 +218,27 @@
 
     # This makes webform_css url answer some default CSS
     child_webform_css = webform.defaultCSS
+    child_webinterface_css = File(paths.AbsNeighbourFile(__file__, 'webinterface.css'), 'text/css')
 
     implements(ISettings)
 
-    docFactory = loaders.stan([
-        tags.html[
-            tags.head[
-                tags.title[_("Beremiz Runtime Settings")],
-                tags.link(rel='stylesheet',
-                          type='text/css',
-                          href=url.here.child("webform_css"))
-            ],
-            tags.body[
-                tags.h1["Runtime settings:"],
-                webform.renderForms('staticSettings'),
-                tags.h2["Extensions settings:"],
-                webform.renderForms('dynamicSettings'),
-            ]
-        ]
-    ])
+    docFactory = loaders.stan([tags.html[
+        tags.head[
+            tags.title[_("Beremiz Runtime Settings")],
+            tags.link(rel='stylesheet',
+                      type='text/css',
+                      href=url.here.child("webform_css")),
+            tags.link(rel='stylesheet',
+                      type='text/css',
+                      href=url.here.child("webinterface_css"))
+        ],
+        tags.body[
+            tags.a(href='/')['Back'],
+            tags.h1["Runtime settings:"],
+            webform.renderForms('staticSettings'),
+            tags.h1["Extensions settings:"],
+            webform.renderForms('dynamicSettings'),
+        ]]])
 
     def configurable_staticSettings(self, ctx):
         return configurable.TypedInterfaceConfigurable(self)
--- a/runtime/PLCObject.py	Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/PLCObject.py	Wed Sep 12 22:59:30 2018 +0200
@@ -23,7 +23,6 @@
 
 
 from __future__ import absolute_import
-import thread
 from threading import Thread, Lock, Semaphore, Event, Condition
 import ctypes
 import os
@@ -31,10 +30,10 @@
 import traceback
 from time import time
 import _ctypes  # pylint: disable=wrong-import-order
-import Pyro.core as pyro
 
 from runtime.typemapping import TypeTranslator
 from runtime.loglevels import LogLevelsDefault, LogLevelsCount
+from runtime import MainWorker
 
 if os.name in ("nt", "ce"):
     dlopen = _ctypes.LoadLibrary
@@ -61,107 +60,6 @@
     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.free = Condition(self.mutex)
-        self.job = None
-
-    def runloop(self, *args, **kwargs):
-        """
-        meant to be called by worker thread (blocking)
-        """
-        self._threadID = thread.get_ident()
-        if args or kwargs:
-            job(*args, **kwargs).do()
-            # result is ignored
-        self.mutex.acquire()
-        while not self._finish:
-            self.todo.wait()
-            if self.job is not None:
-                self.job.do()
-                self.done.notify()
-            else:
-                self.free.notify()
-        self.mutex.release()
-
-    def call(self, *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() or self._threadID is None:
-            # 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.free.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):
@@ -169,22 +67,19 @@
     return func_wrapper
 
 
-class PLCObject(pyro.ObjBase):
-    def __init__(self, server):
-        pyro.ObjBase.__init__(self)
-        self.evaluator = server.evaluator
-        self.argv = [server.workdir] + server.argv  # force argv[0] to be "path" to exec...
-        self.workingdir = server.workdir
+class PLCObject(object):
+    def __init__(self, WorkingDir, argv, statuschange, evaluator, pyruntimevars):
+        self.workingdir = WorkingDir
+        # FIXME : is argv of any use nowadays ?
+        self.argv = [WorkingDir] + argv  # force argv[0] to be "path" to exec...
+        self.statuschange = statuschange
+        self.evaluator = evaluator
+        self.pyruntimevars = pyruntimevars
         self.PLCStatus = "Empty"
         self.PLClibraryHandle = None
         self.PLClibraryLock = Lock()
-        self.DummyIteratorLock = None
         # Creates fake C funcs proxies
         self._InitPLCStubCalls()
-        self.daemon = server.daemon
-        self.statuschange = server.statuschange
-        self.hmi_frame = None
-        self.pyruntimevars = server.pyruntimevars
         self._loading_error = None
         self.python_runtime_vars = None
         self.TraceThread = None
@@ -192,7 +87,7 @@
         self.Traces = []
 
     # First task of worker -> no @RunInMain
-    def AutoLoad(self):
+    def AutoLoad(self, autostart):
         # Get the last transfered PLC
         try:
             self.CurrentPLCFilename = open(
@@ -200,10 +95,15 @@
                 "r").read().strip() + lib_ext
             if self.LoadPLC():
                 self.PLCStatus = "Stopped"
+                if autostart:
+                    self.StartPLC()
+                    return
         except Exception:
             self.PLCStatus = "Empty"
             self.CurrentPLCFilename = None
 
+        self.StatusChange()
+
     def StatusChange(self):
         if self.statuschange is not None:
             for callee in self.statuschange:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/PyroServer.py	Wed Sep 12 22:59:30 2018 +0200
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz runtime.
+
+# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+# Copyright (C) 2017: Andrey Skvortsov
+# Copyright (C) 2018: Edouard TISSERANT
+
+# See COPYING file for copyrights details.
+
+import sys
+
+import Pyro
+import Pyro.core as pyro
+import runtime
+from runtime.ServicePublisher import ServicePublisher
+
+class Server(object):
+    def __init__(self, servicename, ip_addr, port):
+        self.continueloop = True
+        self.daemon = None
+        self.servicename = servicename
+        self.ip_addr = ip_addr
+        self.port = port
+        self.servicepublisher = None
+
+    def _to_be_published(self):
+        return 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"
+
+    def PrintServerInfo(self):
+        print(_("Pyro port :"), self.port)
+
+        if self._to_be_published():
+            print(_("Publishing service on local network"))
+
+        sys.stdout.flush()
+
+    def PyroLoop(self, when_ready):
+        while self.continueloop:
+            Pyro.config.PYRO_MULTITHREADED = 0
+            pyro.initServer()
+            self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
+
+            # pyro never frees memory after connection close if no timeout set
+            # taking too small timeout value may cause
+            # unwanted diconnection when IDE is kept busy for long periods
+            self.daemon.setTimeout(60)
+	
+            pyro_obj=Pyro.core.ObjBase()
+            pyro_obj.delegateTo(runtime.GetPLCObjectSingleton())
+
+            self.daemon.connect(pyro_obj, "PLCObject")
+
+            if self._to_be_published():
+                self.servicepublisher = ServicePublisher()
+                self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
+
+            when_ready()
+            self.daemon.requestLoop()
+            self.daemon.sock.close()
+
+    def Restart(self):
+        self._stop()
+
+    def Quit(self):
+        self.continueloop = False
+        self._stop()
+
+    def _stop(self):
+        if self.servicepublisher is not None:
+            self.servicepublisher.UnRegisterService()
+            self.servicepublisher = None
+        self.daemon.shutdown(True)
+
+
+
--- a/runtime/WampClient.py	Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/WampClient.py	Wed Sep 12 22:59:30 2018 +0200
@@ -427,7 +427,8 @@
 
 
 def RegisterWebSettings(NS):
-    NS.ConfigurableSettings.addExtension(
+
+    NS.ConfigurableSettings.addSettings(
         "wamp",
         _("Wamp Settings"),
         webFormInterface,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/Worker.py	Wed Sep 12 22:59:30 2018 +0200
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz runtime.
+#
+# Copyright (C) 2018: Edouard TISSERANT
+#
+# See COPYING.Runtime file for copyrights details.
+
+from __future__ import absolute_import
+import thread
+from threading import Lock, Condition
+
+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.free = Condition(self.mutex)
+        self.job = None
+
+    def runloop(self, *args, **kwargs):
+        """
+        meant to be called by worker thread (blocking)
+        """
+        self._threadID = thread.get_ident()
+        if args or kwargs:
+            job(*args, **kwargs).do()
+            # result is ignored
+        self.mutex.acquire()
+        while not self._finish:
+            self.todo.wait()
+            if self.job is not None:
+                self.job.do()
+                self.done.notify()
+            else:
+                self.free.notify()
+        self.mutex.release()
+
+    def call(self, *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.free.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()
--- a/runtime/__init__.py	Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime/__init__.py	Wed Sep 12 22:59:30 2018 +0200
@@ -1,28 +1,32 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# This file is part of Beremiz runtime.
-#
-# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#
-# See COPYING.Runtime file for copyrights details.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
+from __future__ import absolute_import
+import traceback
 
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
+from runtime.Worker import worker
+MainWorker = worker()
 
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+from runtime.PLCObject import PLCObject
 
-from __future__ import absolute_import
-import os
+_PLCObjectSingleton = None
 
-from runtime.PLCObject import PLCObject, PLCprint, MainWorker
-import runtime.ServicePublisher
+def GetPLCObjectSingleton():
+    global _PLCObjectSingleton
+    assert(_PLCObjectSingleton is not None)
+    return _PLCObjectSingleton
+
+
+def LogMessageAndException(msg, exp=None):
+    global _PLCObjectSingleton
+    if exp is None:
+        exp = sys.exc_info()
+    if _PLCObjectSingleton is not None:
+        _PLCObjectSingleton.LogMessage(0, msg + '\n'.join(traceback.format_exception(*exp)))
+    else:
+        print(msg)
+        traceback.print_exception(*exp)
+
+def CreatePLCObjectSingleton(*args):
+    global _PLCObjectSingleton
+    _PLCObjectSingleton = PLCObject(*args)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/webinterface.css	Wed Sep 12 22:59:30 2018 +0200
@@ -0,0 +1,6 @@
+
+.freeform-label {
+    float: left;
+    width: 30%;
+}
+
--- a/runtime_files.list	Fri Sep 07 20:58:13 2018 +0000
+++ b/runtime_files.list	Wed Sep 12 22:59:30 2018 +0200
@@ -10,6 +10,7 @@
 runtime/PLCObject.py
 runtime/NevowServer.py
 runtime/webinterface.js
+runtime/webinterface.css
 runtime/__init__.py
 runtime/ServicePublisher.py
 runtime/typemapping.py