merge
authormjsousa
Wed, 21 Oct 2015 15:00:32 +0100
changeset 1475 de4ee16f7c6c
parent 1474 28e9d479aa65 (current diff)
parent 1468 7df108e8cb18 (diff)
child 1476 49f1763a5613
child 1909 bb883e063175
merge
py_ext/PythonFileCTNMixin.py
tests/wxGlade/HMIFrame@wxglade_hmi/py_ext.xml
--- a/Beremiz.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/Beremiz.py	Wed Oct 21 15:00:32 2015 +0100
@@ -25,19 +25,23 @@
 
 updateinfo_url = None
 
-import os, sys, getopt, wx
+import os, sys, getopt
 import __builtin__
-from wx.lib.agw.advancedsplash import AdvancedSplash
 import tempfile
 import shutil
 import random
 import time
 from types import ListType
 
-CWD = os.path.split(os.path.realpath(__file__))[0]
+beremiz_dir = os.path.dirname(os.path.realpath(__file__))
+
+import wxversion
+wxversion.select('2.8')
+import wx
+from wx.lib.agw.advancedsplash import AdvancedSplash
 
 def Bpath(*args):
-    return os.path.join(CWD,*args)
+    return os.path.join(beremiz_dir,*args)
 
 if __name__ == '__main__':
     def usage():
@@ -111,7 +115,7 @@
         wx.Yield()
 
     from util.misc import InstallLocalRessources
-    InstallLocalRessources(CWD)
+    InstallLocalRessources(beremiz_dir)
 
     # Load extensions
     for extfilename in extensions:
@@ -634,6 +638,13 @@
         else:
             return IDEFrame.LoadTab(self, notebook, page_infos)
 
+    # Strange hack required by WAMP connector, using twisted.
+    # Twisted reactor needs to be stopped only before quit,
+    # since it cannot be restarted
+    ToDoBeforeQuit = []
+    def AddToDoBeforeQuit(self, Thing):
+        self.ToDoBeforeQuit.append(Thing)
+
     def OnCloseFrame(self, event):
         for evt_type in [wx.EVT_SET_FOCUS,
                          wx.EVT_KILL_FOCUS,
@@ -646,6 +657,10 @@
 
             self.SaveLastState()
 
+            for Thing in self.ToDoBeforeQuit :
+                Thing()
+            self.ToDoBeforeQuit = []
+
             event.Skip()
         else:
             event.Veto()
--- a/Beremiz_service.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/Beremiz_service.py	Wed Oct 21 15:00:32 2015 +0100
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 #This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
 #
 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
 #
@@ -36,12 +36,15 @@
            -a        - autostart PLC (0:disable 1:enable) (default:0)
            -x        - enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1)
            -t        - enable/disable Twisted web interface (0:disable 1:enable) (default:1)
-           
+           -w        - web server port or "off" (default:8009)
+           -c        - WAMP client config file or "off" (default:wampconf.json)
+           -e        - python extension (absolute path .py)
+
            working_dir - directory where are stored PLC files
 """%sys.argv[0]
 
 try:
-    opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:h")
+    opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:h")
 except getopt.GetoptError, err:
     # print help information and exit:
     print str(err) # will print something like "option -a not recognized"
@@ -51,6 +54,8 @@
 # default values
 given_ip = None
 port = 3000
+webport = 8009
+wampconf = "wampconf.json"
 servicename = None
 autostart = False
 enablewx = True
@@ -58,6 +63,8 @@
 enabletwisted = True
 havetwisted = False
 
+extensions=[]
+
 for o, a in opts:
     if o == "-h":
         usage()
@@ -79,10 +86,18 @@
         enabletwisted = int(a)
     elif o == "-a":
         autostart = int(a)
+    elif o == "-w":
+        webport = None if a == "off" else int(a)
+    elif o == "-c":
+        wampconf = None if a == "off" else a
+    elif o == "-e":
+        extensions.append(a)
     else:
         usage()
         sys.exit()
 
+beremiz_dir = os.path.dirname(os.path.realpath(__file__))
+
 if len(argv) > 1:
     usage()
     sys.exit()
@@ -99,26 +114,28 @@
 
 if enablewx:
     try:
-        import wx, re
-        from threading import Thread, currentThread
-        from types import *
+        import wxversion
+        wxversion.select('2.8')
+        import wx
         havewx = True
     except:
         print "Wx unavailable !"
         havewx = False
 
     if havewx:
+        import re
+        from threading import Thread, currentThread
+        from types import *
         app=wx.App(redirect=False)
-        
+
         # Import module for internationalization
         import gettext
-        
-        CWD = os.path.split(os.path.realpath(__file__))[0]
+
         def Bpath(*args):
-            return os.path.join(CWD,*args)
-        
+            return os.path.join(beremiz_dir,*args)
+
         # Get folder containing translation files
-        localedir = os.path.join(CWD,"locale")
+        localedir = os.path.join(beremiz_dir,"locale")
         # Get the default language
         langid = wx.LANGUAGE_DEFAULT
         # Define translation domain (name of translation files)
@@ -139,11 +156,11 @@
 
         if __name__ == '__main__':
             __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation
-        
+
         defaulticon = wx.Image(Bpath("images", "brz.png"))
         starticon = wx.Image(Bpath("images", "icoplay24.png"))
         stopicon = wx.Image(Bpath("images", "icostop24.png"))
-        
+
         class ParamsEntryDialog(wx.TextEntryDialog):
             if wx.VERSION < (2, 6, 0):
                 def Bind(self, event, function, id = None):
@@ -151,12 +168,12 @@
                         event(self, id, function)
                     else:
                         event(self, function)
-            
-            
-            def __init__(self, parent, message, caption = "Please enter text", defaultValue = "", 
+
+
+            def __init__(self, parent, message, caption = "Please enter text", defaultValue = "",
                                style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition):
                 wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos)
-                
+
                 self.Tests = []
                 if wx.VERSION >= (2, 8, 0):
                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
@@ -164,7 +181,7 @@
                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetAffirmativeButton().GetId())
                 else:
                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId())
-            
+
             def OnOK(self, event):
                 value = self.GetValue()
                 texts = {"value" : value}
@@ -176,13 +193,13 @@
                         return
                 self.EndModal(wx.ID_OK)
                 event.Skip()
-            
+
             def GetValue(self):
                 return self.GetSizer().GetItem(1).GetWindow().GetValue()
-            
+
             def SetTests(self, tests):
                 self.Tests = tests
-        
+
         class BeremizTaskBarIcon(wx.TaskBarIcon):
             TBMENU_START = wx.NewId()
             TBMENU_STOP = wx.NewId()
@@ -193,14 +210,14 @@
             TBMENU_WXINSPECTOR = wx.NewId()
             TBMENU_CHANGE_WD = wx.NewId()
             TBMENU_QUIT = wx.NewId()
-            
+
             def __init__(self, pyroserver, level):
                 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)
                 self.Bind(wx.EVT_MENU, self.OnTaskBarStopPLC, id=self.TBMENU_STOP)
@@ -211,7 +228,7 @@
                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT)
                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeWorkingDir, id=self.TBMENU_CHANGE_WD)
                 self.Bind(wx.EVT_MENU, self.OnTaskBarQuit, id=self.TBMENU_QUIT)
-            
+
             def CreatePopupMenu(self):
                 """
                 This method is called by the base class when it needs to popup
@@ -234,7 +251,7 @@
                 menu.AppendSeparator()
                 menu.Append(self.TBMENU_QUIT, _("Quit"))
                 return menu
-            
+
             def MakeIcon(self, img):
                 """
                 The various platforms have different requirements for the
@@ -247,15 +264,15 @@
                 # wxMac can be any size upto 128x128, so leave the source img alone....
                 icon = wx.IconFromBitmap(img.ConvertToBitmap() )
                 return icon
-            
+
             def OnTaskBarStartPLC(self, evt):
-                if self.pyroserver.plcobj is not None: 
+                if self.pyroserver.plcobj is not None:
                     self.pyroserver.plcobj.StartPLC()
-            
+
             def OnTaskBarStopPLC(self, evt):
                 if self.pyroserver.plcobj is not None:
                     Thread(target=self.pyroserver.plcobj.StopPLC).start()
-            
+
             def OnTaskBarChangeInterface(self, evt):
                 dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=self.pyroserver.ip_addr)
                 dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, _("IP is not valid!")),
@@ -264,38 +281,38 @@
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.ip_addr = dlg.GetValue()
                     self.pyroserver.Stop()
-            
+
             def OnTaskBarChangePort(self, evt):
                 dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port))
                 dlg.SetTests([(UnicodeType.isdigit, _("Port number must be an integer!")), (lambda port : 0 <= int(port) <= 65535 , _("Port number must be 0 <= port <= 65535!"))])
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.port = int(dlg.GetValue())
                     self.pyroserver.Stop()
-            
+
             def OnTaskBarChangeWorkingDir(self, evt):
                 dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON)
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.workdir = dlg.GetPath()
                     self.pyroserver.Stop()
-            
+
             def OnTaskBarChangeName(self, evt):
                 dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=self.pyroserver.name)
                 dlg.SetTests([(lambda name : len(name) is not 0 , _("Name must not be null!"))])
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.name = dlg.GetValue()
                     self.pyroserver.Restart()
-            
+
             def _LiveShellLocals(self):
                 if self.pyroserver.plcobj is not None:
                     return {"locals":self.pyroserver.plcobj.python_runtime_vars}
                 else:
                     return {}
-            
+
             def OnTaskBarLiveShell(self, evt):
                 from wx import py
                 frame = py.crust.CrustFrame(**self._LiveShellLocals())
                 frame.Show()
-            
+
             def OnTaskBarWXInspector(self, evt):
                 # Activate the widget inspection tool
                 from wx.lib.inspection import InspectionTool
@@ -304,13 +321,13 @@
 
                 wnd = wx.GetApp()
                 InspectionTool().Show(wnd, True)
-            
+
             def OnTaskBarQuit(self, evt):
                 if wx.Platform == '__WXMSW__':
                     Thread(target=self.pyroserver.Quit).start()
                 self.RemoveIcon()
                 wx.CallAfter(wx.GetApp().ExitMainLoop)
-            
+
             def UpdateIcon(self, plcstatus):
                 if plcstatus is "Started" :
                     currenticon = self.MakeIcon(starticon)
@@ -334,7 +351,10 @@
     return res
 
 class Server():
-    def __init__(self, servicename, ip_addr, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator, website=None):
+    def __init__(self, servicename, ip_addr, port,
+                 workdir, argv, autostart=False,
+                 statuschange=None, evaluator=default_evaluator,
+                 pyruntimevars=None):
         self.continueloop = True
         self.daemon = None
         self.servicename = servicename
@@ -347,12 +367,12 @@
         self.autostart = autostart
         self.statuschange = statuschange
         self.evaluator = evaluator
-        self.website = website
-    
+        self.pyruntimevars = pyruntimevars
+
     def Loop(self):
         while self.continueloop:
             self.Start()
-        
+
     def Restart(self):
         self.Stop()
 
@@ -365,30 +385,34 @@
     def Start(self):
         pyro.initServer()
         self.daemon=pyro.Daemon(host=self.ip_addr, port=self.port)
-        self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator, self.website)
+        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
         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 
+        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.autostart and self.plcobj.GetPLCstatus()[0] != "Empty":
-            self.plcobj.StartPLC()
-        
+
+        if self.autostart :
+            self.plcobj.AutoLoad()
+            if self.plcobj.GetPLCstatus()[0] != "Empty":
+                self.plcobj.StartPLC()
+
         sys.stdout.flush()
-        
+
         self.daemon.requestLoop()
-    
+
     def Stop(self):
         if self.plcobj is not None:
             self.plcobj.StopPLC()
@@ -406,205 +430,57 @@
             if havewx:
                 from twisted.internet import wxreactor
                 wxreactor.install()
-            from twisted.internet import reactor, task
-            from twisted.python import log, util
-            from nevow import rend, appserver, inevow, tags, loaders, athena
-            from nevow.page import renderer
-            
+            from twisted.internet import reactor
+
             havetwisted = True
         except:
-            print "Twisted unavailable !"
+            print "Twisted unavailable."
             havetwisted = False
 
+pyruntimevars = {}
+statuschange = []
+
 if havetwisted:
-    
-    xhtml_header = '''<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
-"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-'''
-
-    class PLCHMI(athena.LiveElement):
-    
-        initialised = False
-    
-        def HMIinitialised(self, result):
-            self.initialised = True
-        
-        def HMIinitialisation(self):
-            self.HMIinitialised(None)
-    
-    class DefaultPLCStartedHMI(PLCHMI):
-        docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[                                    
-                                             tags.h1["PLC IS NOW STARTED"],
-                                             ])
-        
-    class PLCStoppedHMI(PLCHMI):
-        docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[
-                                             tags.h1["PLC IS STOPPED"],
-                                             ])
-    
-    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')),
-                                                        ]])
-        
-        def __init__(self, *a, **kw):
-            athena.LiveElement.__init__(self, *a, **kw)
-            self.pcl_state = False
-            self.HMI = None
-            self.resetPLCStartedHMI()
-        
-        def setPLCState(self, state):
-            self.pcl_state = state
-            if self.HMI is not None:
-                self.callRemote('updateHMI')
-        
-        def setPLCStartedHMI(self, hmi):
-            self.PLCStartedHMIClass = hmi
-        
-        def resetPLCStartedHMI(self):
-            self.PLCStartedHMIClass = DefaultPLCStartedHMI
-        
-        def getHMI(self):
-            return self.HMI
-        
-        def HMIexec(self, function, *args, **kwargs):
-            if self.HMI is not None:
-                getattr(self.HMI, function, lambda:None)(*args, **kwargs)
-        athena.expose(HMIexec)
-        
-        def resetHMI(self):
-            self.HMI = None
-        
-        def PLCElement(self, ctx, data):
-            return self.getPLCElement()
-        renderer(PLCElement)
-        
-        def getPLCElement(self):
-            self.detachFragmentChildren()
-            if self.pcl_state:
-                f = self.PLCStartedHMIClass()
-            else:
-                f = PLCStoppedHMI()
-            f.setFragmentParent(self)
-            self.HMI = f
-            return f
-        athena.expose(getPLCElement)
-
-        def detachFragmentChildren(self):
-            for child in self.liveFragmentChildren[:]:
-                child.detach()
-    
-    class WebInterface(athena.LivePage):
-
-        docFactory = loaders.stan([tags.raw(xhtml_header),
-                                   tags.html(xmlns="http://www.w3.org/1999/xhtml")[
-                                       tags.head(render=tags.directive('liveglue')),
-                                       tags.body[
-                                           tags.div[
-                                                   tags.div( render = tags.directive( "MainPage" ))
-                                                   ]]]])
-        MainPage = MainPage()
-        PLCHMI = PLCHMI
-        
-        def __init__(self, plcState=False, *a, **kw):
-            super(WebInterface, self).__init__(*a, **kw)
-            self.jsModules.mapping[u'WebInterface'] = util.sibpath(__file__, os.path.join('runtime', 'webinterface.js'))
-            self.plcState = plcState
-            self.MainPage.setPLCState(plcState)
-
-        def getHMI(self):
-            return self.MainPage.getHMI()
-        
-        def LoadHMI(self, hmi, jsmodules):
-            for name, path in jsmodules.iteritems():
-                self.jsModules.mapping[name] = os.path.join(WorkingDir, path)
-            self.MainPage.setPLCStartedHMI(hmi)
-        
-        def UnLoadHMI(self):
-            self.MainPage.resetPLCStartedHMI()
-        
-        def PLCStarted(self):
-            self.plcState = True
-            self.MainPage.setPLCState(True)
-        
-        def PLCStopped(self):
-            self.plcState = False
-            self.MainPage.setPLCState(False)
-            
-        def renderHTTP(self, ctx):
-            """
-            Force content type to fit with SVG
-            """
-            req = inevow.IRequest(ctx)
-            req.setHeader('Content-type', 'application/xhtml+xml')
-            return super(WebInterface, self).renderHTTP(ctx)
-
-        def render_MainPage(self, ctx, data):
-            f = self.MainPage
-            f.setFragmentParent(self)
-            return ctx.tag[f]
-
-        def child_(self, ctx):
-            self.MainPage.detachFragmentChildren()
-            return WebInterface(plcState=self.plcState)
-            
-        def beforeRender(self, ctx):
-            d = self.notifyOnDisconnect()
-            d.addErrback(self.disconnected)
-        
-        def disconnected(self, reason):
-            self.MainPage.resetHMI()
-            #print reason
-            #print "We will be called back when the client disconnects"
-        
+
     if havewx:
         reactor.registerWxApp(app)
-    website = WebInterface()
-    site = appserver.NevowSite(website)
-    
-    website_port = 8009
-    listening = False
-    while not listening:
-        try:
-            reactor.listenTCP(website_port, site)
-            listening = True
-        except:
-            website_port += 1
-    print "Http interface port :",website_port
-else:
-    website = None
 
 if havewx:
     from threading import Semaphore
     wx_eval_lock = Semaphore(0)
     main_thread = currentThread()
 
-    def statuschange(status):
+    def statuschangeTskBar(status):
         wx.CallAfter(taskbar_instance.UpdateIcon,status)
-        
+
+    statuschange.append(statuschangeTskBar)
+
     def wx_evaluator(obj, *args, **kwargs):
         tocall,args,kwargs = obj.call
         obj.res = default_evaluator(tocall, *args, **kwargs)
         wx_eval_lock.release()
-        
+
     def evaluator(tocall, *args, **kwargs):
         global main_thread
         if(main_thread == currentThread()):
-            # avoid dead lock if called from the wx mainloop 
+            # avoid dead lock if called from the wx mainloop
             return default_evaluator(tocall, *args, **kwargs)
         else:
             o=type('',(object,),dict(call=(tocall, args, kwargs), res=None))
             wx.CallAfter(wx_evaluator,o)
             wx_eval_lock.acquire()
             return o.res
-    
-    pyroserver = Server(servicename, given_ip, port, WorkingDir, argv, autostart, statuschange, evaluator, website)
+
+    pyroserver = Server(servicename, given_ip, port,
+                        WorkingDir, argv, autostart,
+                        statuschange, evaluator, pyruntimevars)
+
     taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
 else:
-    pyroserver = Server(servicename, given_ip, port, WorkingDir, argv, autostart, website=website)
+    pyroserver = Server(servicename, given_ip, port,
+                        WorkingDir, argv, autostart,
+                        statuschange, pyruntimevars=pyruntimevars)
+
 
 # Exception hooks s
 import threading, traceback
@@ -631,6 +507,46 @@
     threading.Thread.__init__ = init
 installThreadExcepthook()
 
+if havetwisted:
+    if webport is not None :
+        try:
+            import runtime.NevowServer as NS
+        except Exception, e:
+            print "Nevow/Athena import failed :", e
+            webport = None
+        NS.WorkingDir = WorkingDir
+
+    if wampconf is not None :
+        try:
+            import runtime.WampClient as WC
+        except Exception, e:
+            print "WAMP import failed :", e
+            wampconf = None
+
+# Load extensions
+for extfilename in extensions:
+    extension_folder = os.path.split(os.path.realpath(extfilename))[0]
+    sys.path.append(extension_folder)
+    execfile(extfilename, locals())
+
+if havetwisted:
+    if webport is not None :
+        try:
+            website = NS.RegisterWebsite(webport)
+            pyruntimevars["website"] = website
+            statuschange.append(NS.website_statuslistener_factory(website))
+        except Exception, e:
+            print "Nevow Web service failed.", e
+
+    if wampconf is not None :
+        try:
+            WC.RegisterWampClient(wampconf)
+            pyruntimevars["wampsession"] = WC.GetSession
+            WC.SetServer(pyroserver)
+        except Exception, e:
+            print "WAMP client startup failed.", e
+
+
 if havetwisted or havewx:
     pyro_thread=Thread(target=pyroserver.Loop)
     pyro_thread.start()
--- a/CodeFileTreeNode.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/CodeFileTreeNode.py	Wed Oct 21 15:00:32 2015 +0100
@@ -31,6 +31,9 @@
                     </xsd:simpleType>
                   </xsd:attribute>
                   <xsd:attribute name="initial" type="xsd:string" use="optional" default=""/>
+                  <xsd:attribute name="desc" type="xsd:string" use="optional" default=""/>
+                  <xsd:attribute name="onchange" type="xsd:string" use="optional" default=""/>
+                  <xsd:attribute name="opts" type="xsd:string" use="optional" default=""/>
                 </xsd:complexType>
               </xsd:element>
             </xsd:sequence>
@@ -119,6 +122,9 @@
             variable.setname(var["Name"])
             variable.settype(var["Type"])
             variable.setinitial(var["Initial"])
+            variable.setdesc(var["Description"])
+            variable.setonchange(var["OnChange"])
+            variable.setopts(var["Options"])
             self.CodeFile.variables.appendvariable(variable)
     
     def GetVariables(self):
@@ -126,7 +132,11 @@
         for var in self.CodeFileVariables(self.CodeFile):
             datas.append({"Name" : var.getname(), 
                           "Type" : var.gettype(), 
-                          "Initial" : var.getinitial()})
+                          "Initial" : var.getinitial(),
+                          "Description" : var.getdesc(),
+                          "OnChange"    : var.getonchange(),
+                          "Options"     : var.getopts(),
+                         })
         return datas
 
     def SetTextParts(self, parts):
@@ -157,11 +167,15 @@
         return True
 
     def CTNGlobalInstances(self):
-        current_location = self.GetCurrentLocation()
-        return [(variable.getname(),
+        variables = self.CodeFileVariables(self.CodeFile)
+        ret =  [(variable.getname(),
                  variable.gettype(),
-                 variable.getinitial())
-                for variable in self.CodeFileVariables(self.CodeFile)]
+                 variable.getinitial()) 
+                for variable in variables]
+        ret.extend([("On"+variable.getname()+"Change", "python_poll", "")
+                for variable in variables
+                if variable.getonchange()])
+        return ret
 
 #-------------------------------------------------------------------------------
 #                      Current Buffering Management Functions
--- a/PLCGenerator.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/PLCGenerator.py	Wed Oct 21 15:00:32 2015 +0100
@@ -1480,10 +1480,10 @@
                 self.TagName = self.ParentGenerator.Controler.ComputePouTransitionName(self.Name, transitionValues["value"])
                 if transitionType == "IL":
                     transition_infos["content"] = [(":\n", ()),
-                                                   (ReIndentText(transitionBody.getanyText(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))]
+                                                   (ReIndentText(transitionBody.getcontent().getanyText(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))]
                 elif transitionType == "ST":
                     transition_infos["content"] = [("\n", ()),
-                                                   (ReIndentText(transitionBody.getanyText(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))]
+                                                   (ReIndentText(transitionBody.getcontent().getanyText(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))]
                 else:
                     for instance in transitionBody.getcontentInstances():
                         if isinstance(instance, OutVariableClass) and instance.getexpression() == transitionValues["value"]\
--- a/PLCOpenEditor.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/PLCOpenEditor.py	Wed Oct 21 15:00:32 2015 +0100
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
-#based on the plcopen standard. 
+#based on the plcopen standard.
 #
 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
 #
@@ -25,12 +25,12 @@
 import wx
 import os, sys, platform, time, traceback, getopt
 
-CWD = os.path.split(os.path.realpath(__file__))[0]
+beremiz_dir = os.path.dirname(os.path.realpath(__file__))
 
 __version__ = "$Revision: 1.130 $"
 
 if __name__ == '__main__':
-    # Usage message displayed when help request or when error detected in 
+    # Usage message displayed when help request or when error detected in
     # command line
     def usage():
         print "\nUsage of PLCOpenEditor.py :"
@@ -43,13 +43,13 @@
         # print help information and exit:
         usage()
         sys.exit(2)
-    
+
     # Extract if help has been requested
     for o, a in opts:
         if o in ("-h", "--help"):
             usage()
             sys.exit()
-    
+
     # Extract the optional filename to open
     fileOpen = None
     if len(args) > 1:
@@ -57,13 +57,13 @@
         sys.exit()
     elif len(args) == 1:
         fileOpen = args[0]
-    
+
     # Create wxApp (Need to create App before internationalization because of
-    # Windows) 
+    # Windows)
     app = wx.PySimpleApp()
 
     from util.misc import InstallLocalRessources
-    InstallLocalRessources(CWD)
+    InstallLocalRessources(beremiz_dir)
 
 from docutil import *
 from IDEFrame import IDEFrame, AppendMenu
@@ -78,7 +78,7 @@
 #-------------------------------------------------------------------------------
 
 # Define PLCOpenEditor FileMenu extra items id
-[ID_PLCOPENEDITORFILEMENUGENERATE, 
+[ID_PLCOPENEDITORFILEMENUGENERATE,
 ] = [wx.NewId() for _init_coll_FileMenu_Items in range(1)]
 
 class PLCOpenEditor(IDEFrame):
@@ -120,7 +120,7 @@
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_EXIT,
               kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
-        
+
         self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
         self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
         self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE)
@@ -134,15 +134,15 @@
         self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
         self.Bind(wx.EVT_MENU, self.OnPropertiesMenu, id=wx.ID_PROPERTIES)
         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
-        
+
         self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
                                (wx.ID_OPEN, "open", _(u'Open'), None),
                                (wx.ID_SAVE, "save", _(u'Save'), None),
                                (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None),
                                (wx.ID_PRINT, "print", _(u'Print'), None)])
-            
+
     def _init_coll_HelpMenu_Items(self, parent):
-        AppendMenu(parent, help='', id=wx.ID_HELP, 
+        AppendMenu(parent, help='', id=wx.ID_HELP,
             kind=wx.ITEM_NORMAL, text=_(u'PLCOpenEditor') + '\tF1')
         #AppendMenu(parent, help='', id=wx.ID_HELP_CONTENTS,
         #      kind=wx.ITEM_NORMAL, text=u'PLCOpen\tF2')
@@ -161,9 +161,9 @@
     #  @param debug The filepath to open if no controler defined (default: False).
     def __init__(self, parent, fileOpen = None):
         IDEFrame.__init__(self, parent)
-        
+
         result = None
-        
+
         # Open the filepath if defined
         if fileOpen is not None:
             fileOpen = DecodeFileSystemPath(fileOpen, False)
@@ -176,14 +176,14 @@
                 self.ProjectTree.Enable(True)
                 self.PouInstanceVariablesPanel.SetController(controler)
                 self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
-        
+
         # Define PLCOpenEditor icon
-        self.SetIcon(wx.Icon(os.path.join(CWD, "images", "poe.ico"),wx.BITMAP_TYPE_ICO))
+        self.SetIcon(wx.Icon(os.path.join(beremiz_dir, "images", "poe.ico"),wx.BITMAP_TYPE_ICO))
 
         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
-        
+
         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
-        
+
         if result is not None:
             self.ShowErrorMessage(
                 _("PLC syntax error at line %d:\n%s") % result)
@@ -191,9 +191,9 @@
     def OnCloseFrame(self, event):
         if self.Controler is None or self.CheckSaveBeforeClosing(_("Close Application")):
             self.AUIManager.UnInit()
-            
+
             self.SaveLastState()
-            
+
             event.Skip()
         else:
             event.Veto()
@@ -266,7 +266,7 @@
             self.Controler.CreateNewProject(properties)
             self.LibraryPanel.SetController(self.Controler)
             self.ProjectTree.Enable(True)
-            self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, 
+            self._Refresh(TITLE, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL,
                           LIBRARYTREE)
 
     def OnOpenProjectMenu(self, event):
@@ -279,9 +279,9 @@
             directory = os.path.dirname(filepath)
         else:
             directory = os.getcwd()
-        
+
         result = None
-        
+
         dialog = wx.FileDialog(self, _("Choose a file"), directory, "",  _("PLCOpen files (*.xml)|*.xml|All files|*.*"), wx.OPEN)
         if dialog.ShowModal() == wx.ID_OK:
             filepath = dialog.GetPath()
@@ -296,11 +296,11 @@
                 self._Refresh(PROJECTTREE, LIBRARYTREE)
             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
         dialog.Destroy()
-        
+
         if result is not None:
             self.ShowErrorMessage(
                 _("PLC syntax error at line %d:\n%s") % result)
-    
+
     def OnCloseProjectMenu(self, event):
         if not self.CheckSaveBeforeClosing():
             return
@@ -338,12 +338,12 @@
 
     def OnPLCOpenEditorMenu(self, event):
         wx.MessageBox(_("No documentation available.\nComing soon."))
-        
+
     def OnPLCOpenMenu(self, event):
-        open_pdf(os.path.join(CWD, "plcopen", "TC6_XML_V101.pdf"))
-    
+        open_pdf(os.path.join(beremiz_dir, "plcopen", "TC6_XML_V101.pdf"))
+
     def OnAboutMenu(self, event):
-        OpenHtmlFrame(self,_("About PLCOpenEditor"), os.path.join(CWD, "doc", "plcopen_about.html"), wx.Size(350, 350))
+        OpenHtmlFrame(self,_("About PLCOpenEditor"), os.path.join(beremiz_dir, "doc", "plcopen_about.html"), wx.Size(350, 350))
 
     def SaveProject(self):
         result = self.Controler.SaveXMLFile()
@@ -351,7 +351,7 @@
             self.SaveProjectAs()
         else:
             self._Refresh(TITLE, FILEMENU, PAGETITLES)
-        
+
     def SaveProjectAs(self):
         filepath = self.Controler.GetFilePath()
         if filepath != "":
@@ -386,13 +386,13 @@
             trcbck += _("file : ") + str(line[0][len(os.getcwd()):]) + _(",   ")
         trcbck += _("line : ") + str(line[1]) + _(",   ") + _("function : ") + str(line[2])
         trcbck_lst.append(trcbck)
-        
+
     # Allow clicking....
     cap = wx.Window_GetCapture()
     if cap:
         cap.ReleaseMouse()
 
-    dlg = wx.SingleChoiceDialog(None, 
+    dlg = wx.SingleChoiceDialog(None,
         _("""
 An error has occurred.
 
@@ -403,7 +403,7 @@
 
 Error:
 """) +
-        str(e_type) + _(" : ") + str(e_value), 
+        str(e_type) + _(" : ") + str(e_value),
         _("Error"),
         trcbck_lst)
     try:
@@ -431,7 +431,7 @@
 ignored_exceptions = [] # a problem with a line in a module is only reported once per session
 
 def AddExceptHook(path, app_version='[No version]'):#, ignored_exceptions=[]):
-    
+
     def handle_exception(e_type, e_value, e_traceback):
         traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func
         last_tb = get_last_traceback(e_traceback)
@@ -461,7 +461,7 @@
                     info['locals'] = format_namespace(exception_locals)
                     if 'self' in exception_locals:
                         info['self'] = format_namespace(exception_locals['self'].__dict__)
-                
+
                 output = open(path+os.sep+"bug_report_"+info['date'].replace(':','-').replace(' ','_')+".txt",'w')
                 lst = info.keys()
                 lst.sort()
@@ -473,12 +473,12 @@
 
 if __name__ == '__main__':
     wx.InitAllImageHandlers()
-    
+
     # Install a exception handle for bug reports
     AddExceptHook(os.getcwd(),__version__)
-    
+
     frame = PLCOpenEditor(None, fileOpen=fileOpen)
 
     frame.Show()
     app.MainLoop()
-    
+
--- a/ProjectController.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/ProjectController.py	Wed Oct 21 15:00:32 2015 +0100
@@ -13,6 +13,7 @@
 from time import localtime
 from datetime import datetime
 from weakref import WeakKeyDictionary
+from itertools import izip
 
 import targets
 import connectors
@@ -28,15 +29,13 @@
 from PLCControler import PLCControler
 from plcopen.structures import IEC_KEYWORDS
 from targets.typemapping import DebugTypesSize, LogLevelsCount, LogLevels
+from targets.typemapping import UnpackDebugBuffer
 from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage
 
-base_folder = os.path.split(sys.path[0])[0]
+base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
 
 MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$")
 
-DEBUG_RETRIES_WARN = 3
-DEBUG_RETRIES_REREGISTER = 4
-
 ITEM_CONFNODE = 25
 
 def ExtractChildrenTypesFromCatalog(catalog):
@@ -609,17 +608,21 @@
     def _Compile_ST_to_SoftPLC(self):
         self.logger.write(_("Compiling IEC Program into C code...\n"))
         buildpath = self._getBuildPath()
-
-        # Now compile IEC code into many C files
-        # files are listed to stdout, and errors to stderr.
-        status, result, err_result = ProcessLogger(
-               self.logger,
-               "\"%s\" -f -l -p -I \"%s\" -T \"%s\" \"%s\""%(
+        buildcmd = "\"%s\" -f -l -p -I \"%s\" -T \"%s\" \"%s\""%(
                          self.iec2c_path,
                          self.ieclib_path,
                          buildpath,
-                         self._getIECcodepath()),
-               no_stdout=True, no_stderr=True).spin()
+                         self._getIECcodepath())
+
+        try:
+            # Invoke compiler. Output files are listed to stdout, errors to stderr
+            status, result, err_result = ProcessLogger(self.logger, buildcmd,
+                no_stdout=True, no_stderr=True).spin()
+        except Exception,e:
+            self.logger.write_error(buildcmd + "\n")
+            self.logger.write_error(repr(e) + "\n")
+            return False
+
         if status:
             # Failed !
 
@@ -732,9 +735,11 @@
         """
         self._ProgramList = None
         self._VariablesList = None
+        self._DbgVariablesList = None
         self._IECPathToIdx = {}
         self._Ticktime = 0
         self.TracedIECPath = []
+        self.TracedIECTypes = []
 
     def GetIECProgramsAndVariables(self):
         """
@@ -750,6 +755,7 @@
                 VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"]
                 self._ProgramList = []
                 self._VariablesList = []
+                self._DbgVariablesList = []
                 self._IECPathToIdx = {}
 
                 # Separate sections
@@ -774,6 +780,7 @@
 
                 # second section contains all variables
                 config_FBs = {}
+                Idx = 0
                 for line in ListGroup[1]:
                     # Split and Maps each field to dictionnary entries
                     attrs = dict(zip(VariablesListAttributeName,line.strip().split(';')))
@@ -790,12 +797,17 @@
                         attrs["C_path"] = '__'.join(parts)
                         if attrs["vartype"] == "FB":
                             config_FBs[tuple(parts)] = attrs["C_path"]
-                    # Push this dictionnary into result.
+                    if attrs["vartype"] != "FB":
+                        # Push this dictionnary into result.
+                        self._DbgVariablesList.append(attrs)
+                        # Fill in IEC<->C translation dicts
+                        IEC_path=attrs["IEC_path"]
+                        self._IECPathToIdx[IEC_path]=(Idx, attrs["type"])
+                        # Ignores numbers given in CSV file
+                        # Idx=int(attrs["num"])
+                        # Count variables only, ignore FBs
+                        Idx+=1
                     self._VariablesList.append(attrs)
-                    # Fill in IEC<->C translation dicts
-                    IEC_path=attrs["IEC_path"]
-                    Idx=int(attrs["num"])
-                    self._IECPathToIdx[IEC_path]=(Idx, attrs["type"])
 
                 # third section contains ticktime
                 if len(ListGroup) > 2:
@@ -816,8 +828,21 @@
         self.GetIECProgramsAndVariables()
 
         # prepare debug code
-        debug_code = targets.GetCode("plc_debug") % {
-           "buffer_size": reduce(lambda x, y: x + y, [DebugTypesSize.get(v["type"], 0) for v in self._VariablesList], 0),
+        variable_decl_array = []
+        bofs = 0
+        for v in self._DbgVariablesList :
+            sz = DebugTypesSize.get(v["type"], 0)
+            variable_decl_array += [
+                "{&(%(C_path)s), "%v+
+                {"EXT":"%(type)s_P_ENUM",
+                 "IN":"%(type)s_P_ENUM",
+                 "MEM":"%(type)s_O_ENUM",
+                 "OUT":"%(type)s_O_ENUM",
+                 "VAR":"%(type)s_ENUM"}[v["vartype"]]%v +
+                 "}"]
+            bofs += sz
+        debug_code = targets.GetCode("plc_debug.c") % {
+           "buffer_size":bofs,
            "programs_declarations":
                "\n".join(["extern %(type)s %(C_path)s;"%p for p in self._ProgramList]),
            "extern_variables_declarations":"\n".join([
@@ -828,22 +853,8 @@
                "VAR":"extern __IEC_%(type)s_t %(C_path)s;",
                "FB":"extern %(type)s %(C_path)s;"}[v["vartype"]]%v
                for v in self._VariablesList if v["C_path"].find('.')<0]),
-           "for_each_variable_do_code":"\n".join([
-               {"EXT":"    (*fp)((void*)&(%(C_path)s),%(type)s_P_ENUM);\n",
-                "IN":"    (*fp)((void*)&(%(C_path)s),%(type)s_P_ENUM);\n",
-                "MEM":"    (*fp)((void*)&(%(C_path)s),%(type)s_O_ENUM);\n",
-                "OUT":"    (*fp)((void*)&(%(C_path)s),%(type)s_O_ENUM);\n",
-                "VAR":"    (*fp)((void*)&(%(C_path)s),%(type)s_ENUM);\n"}[v["vartype"]]%v
-                for v in self._VariablesList if v["vartype"] != "FB" and v["type"] in DebugTypesSize ]),
-           "find_variable_case_code":"\n".join([
-               "    case %(num)s:\n"%v+
-               "        *varp = (void*)&(%(C_path)s);\n"%v+
-               {"EXT":"        return %(type)s_P_ENUM;\n",
-                "IN":"        return %(type)s_P_ENUM;\n",
-                "MEM":"        return %(type)s_O_ENUM;\n",
-                "OUT":"        return %(type)s_O_ENUM;\n",
-                "VAR":"        return %(type)s_ENUM;\n"}[v["vartype"]]%v
-                for v in self._VariablesList if v["vartype"] != "FB" and v["type"] in DebugTypesSize ])}
+           "variable_decl_array": ",\n".join(variable_decl_array)
+           }
 
         return debug_code
 
@@ -859,7 +870,7 @@
 
         # Generate main, based on template
         if not self.BeremizRoot.getDisable_Extensions():
-            plc_main_code = targets.GetCode("plc_main_head") % {
+            plc_main_code = targets.GetCode("plc_main_head.c") % {
                 "calls_prototypes":"\n".join([(
                       "int __init_%(s)s(int argc,char **argv);\n"+
                       "void __cleanup_%(s)s(void);\n"+
@@ -879,7 +890,7 @@
                       "__cleanup_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)])
                 }
         else:
-            plc_main_code = targets.GetCode("plc_main_head") % {
+            plc_main_code = targets.GetCode("plc_main_head.c") % {
                 "calls_prototypes":"\n",
                 "retrieve_calls":"\n",
                 "publish_calls":"\n",
@@ -887,7 +898,7 @@
                 "cleanup_calls":"\n"
                 }
         plc_main_code += targets.GetTargetCode(self.GetTarget().getcontent().getLocalTag())
-        plc_main_code += targets.GetCode("plc_main_tail")
+        plc_main_code += targets.GetCode("plc_main_tail.c")
         return plc_main_code
 
 
@@ -975,7 +986,7 @@
             self.ResetBuildMD5()
             return False
 
-        self.LocationCFilesAndCFLAGS =  CTNLocationCFilesAndCFLAGS + LibCFilesAndCFLAGS
+        self.LocationCFilesAndCFLAGS =  LibCFilesAndCFLAGS + CTNLocationCFilesAndCFLAGS
         self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS
         ExtraFiles = CTNExtraFiles + LibExtraFiles
 
@@ -1206,7 +1217,7 @@
 
     def SnapshotAndResetDebugValuesBuffers(self):
         buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers,
-            [list() for iec_path in self.TracedIECPath])
+            [list() for n in xrange(len(self.TracedIECPath))])
         ticks, self.DebugTicks = self.DebugTicks, []
         return ticks, buffers
 
@@ -1214,6 +1225,7 @@
         self.DebugTimer=None
         Idxs = []
         self.TracedIECPath = []
+        self.TracedIECTypes = []
         if self._connector is not None:
             self.IECdebug_lock.acquire()
             IECPathsToPop = []
@@ -1238,8 +1250,10 @@
 
             if Idxs:
                 Idxs.sort()
-                self.TracedIECPath = zip(*Idxs)[3]
-                self._connector.SetTraceVariablesList(zip(*zip(*Idxs)[0:3]))
+                IdxsT = zip(*Idxs)
+                self.TracedIECPath = IdxsT[3]
+                self.TracedIECTypes = IdxsT[1]
+                self._connector.SetTraceVariablesList(zip(*IdxsT[0:3]))
             else:
                 self.TracedIECPath = []
                 self._connector.SetTraceVariablesList([])
@@ -1267,11 +1281,11 @@
         Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None))
         return IEC_Type
 
-    def SubscribeDebugIECVariable(self, IECPath, callableobj, buffer_list=False, *args, **kwargs):
+    def SubscribeDebugIECVariable(self, IECPath, callableobj, buffer_list=False):
         """
         Dispatching use a dictionnary linking IEC variable paths
         to a WeakKeyDictionary linking
-        weakly referenced callables to optionnal args
+        weakly referenced callables
         """
         if IECPath != "__tick__" and not self._IECPathToIdx.has_key(IECPath):
             return None
@@ -1290,7 +1304,7 @@
         else:
             IECdebug_data[4] |= buffer_list
 
-        IECdebug_data[0][callableobj]=(buffer_list, args, kwargs)
+        IECdebug_data[0][callableobj]=buffer_list
 
         self.IECdebug_lock.release()
 
@@ -1308,8 +1322,7 @@
             else:
                 IECdebug_data[4] = reduce(
                     lambda x, y: x|y,
-                    [buffer_list for buffer_list,args,kwargs
-                     in IECdebug_data[0].itervalues()],
+                    IECdebug_data[0].itervalues(),
                     False)
         self.IECdebug_lock.release()
 
@@ -1357,13 +1370,13 @@
         if data_tuple is not None:
             WeakCallableDict, data_log, status, fvalue, buffer_list = data_tuple
             #data_log.append((debug_tick, value))
-            for weakcallable,(buffer_list,args,kwargs) in WeakCallableDict.iteritems():
+            for weakcallable,buffer_list in WeakCallableDict.iteritems():
                 function = getattr(weakcallable, function_name, None)
                 if function is not None:
                     if buffer_list:
-                        function(*(cargs + args), **kwargs)
+                        function(*cargs)
                     else:
-                        function(*(tuple([lst[-1] for lst in cargs]) + args), **kwargs)
+                        function(*tuple([lst[-1] for lst in cargs]))
 
     def GetTicktime(self):
         return self._Ticktime
@@ -1380,38 +1393,34 @@
         self.debug_break = False
         debug_getvar_retry = 0
         while (not self.debug_break) and (self._connector is not None):
-            Trace = self._connector.GetTraceVariables()
-            if(Trace):
-                plc_status, debug_tick, debug_vars = Trace
-            else:
-                plc_status = None
+            plc_status, Traces = self._connector.GetTraceVariables()
             debug_getvar_retry += 1
             #print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()]
-            if plc_status == "Started":
-                self.IECdebug_lock.acquire()
-                if (debug_tick is not None and
-                    len(debug_vars) == len(self.DebugValuesBuffers) and
-                    len(debug_vars) == len(self.TracedIECPath)):
-                    if debug_getvar_retry > DEBUG_RETRIES_WARN:
-                        self.logger.write(_("... debugger recovered\n"))
-                    debug_getvar_retry = 0
-                    for IECPath, values_buffer, value in zip(self.TracedIECPath, self.DebugValuesBuffers, debug_vars):
-                        IECdebug_data = self.IECdebug_datas.get(IECPath, None)
-                        if IECdebug_data is not None and value is not None:
-                            forced = IECdebug_data[2:4] == ["Forced", value]
-                            if not IECdebug_data[4] and len(values_buffer) > 0:
-                                values_buffer[-1] = (value, forced)
-                            else:
-                                values_buffer.append((value, forced))
-                    self.DebugTicks.append(debug_tick)
-                self.IECdebug_lock.release()
-                if debug_getvar_retry == DEBUG_RETRIES_WARN:
-                    self.logger.write(_("Waiting debugger to recover...\n"))
-                if debug_getvar_retry == DEBUG_RETRIES_REREGISTER:
-                    # re-register debug registry to PLC
-                    wx.CallAfter(self.RegisterDebugVarToConnector)
+            if plc_status == "Started" :
+                if len(Traces) > 0:
+                    Failed = False
+                    self.IECdebug_lock.acquire()
+                    for debug_tick, debug_buff in Traces :
+                        debug_vars = UnpackDebugBuffer(debug_buff, self.TracedIECTypes)
+                        if (debug_vars is not None and
+                            len(debug_vars) == len(self.TracedIECPath)):
+                            for IECPath, values_buffer, value in izip(
+                                    self.TracedIECPath,
+                                    self.DebugValuesBuffers,
+                                    debug_vars):
+                                IECdebug_data = self.IECdebug_datas.get(IECPath, None) #FIXME get
+                                if IECdebug_data is not None and value is not None:
+                                    forced = IECdebug_data[2:4] == ["Forced", value]
+                                    if not IECdebug_data[4] and len(values_buffer) > 0:
+                                        values_buffer[-1] = (value, forced)
+                                    else:
+                                        values_buffer.append((value, forced))
+                            self.DebugTicks.append(debug_tick)
+                            debug_getvar_retry = 0
+                    self.IECdebug_lock.release()
+
                 if debug_getvar_retry != 0:
-                    # Be patient, tollerate PLC to come up before debugging
+                    # Be patient, tollerate PLC to come with fresh samples
                     time.sleep(0.1)
             else:
                 self.debug_break = True
@@ -1426,7 +1435,7 @@
         self.IECdebug_lock.release()
         start_time = time.time()
         if len(self.TracedIECPath) == len(buffers):
-            for IECPath, values in zip(self.TracedIECPath, buffers):
+            for IECPath, values in izip(self.TracedIECPath, buffers):
                 if len(values) > 0:
                     self.CallWeakcallables(IECPath, "NewValues", debug_ticks, values)
             if len(debug_ticks) > 0:
--- a/canfestival/canfestival.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/canfestival/canfestival.py	Wed Oct 21 15:00:32 2015 +0100
@@ -1,6 +1,6 @@
 import os, sys, shutil
 
-base_folder = os.path.split(sys.path[0])[0]
+base_folder = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
 CanFestivalPath = os.path.join(base_folder, "CanFestival-3")
 sys.path.append(os.path.join(CanFestivalPath, "objdictgen"))
 
--- a/canfestival/cf_runtime.c	Sat Dec 06 19:31:51 2014 +0000
+++ b/canfestival/cf_runtime.c	Wed Oct 21 15:00:32 2015 +0100
@@ -14,7 +14,7 @@
 /* Keep track of init level to cleanup correctly */
 static int init_level=0;
 /* Retrieve PLC cycle time */
-extern int common_ticktime__;
+extern unsigned long long common_ticktime__;
 
 /* Per master node slavebootup callbacks. Checks that
  * every node have booted before calling Master_post_SlaveBootup */
@@ -34,7 +34,7 @@
     nodename##_Data.CurrentCommunicationState.csSYNC = -1;\
     /* Force sync period to common_ticktime__ so that other node can read it*/\
     *nodename##_Data.COB_ID_Sync = 0x40000080;\
-    *nodename##_Data.Sync_Cycle_Period = common_ticktime__ * 1000;
+    *nodename##_Data.Sync_Cycle_Period = common_ticktime__ / 1000;
 
 static void DeferedInitAlarm(CO_Data* d, UNS32 id){
     /* Node will start beeing active on the network after this */
--- a/connectors/PYRO/__init__.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/connectors/PYRO/__init__.py	Wed Oct 21 15:00:32 2015 +0100
@@ -1,32 +1,33 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 #
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
 #
-#See COPYING file for copyrights details.
+# See COPYING file for copyrights details.
 #
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
 #
-#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
-#General Public License for more details.
+# 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
+# General Public License for more details.
 #
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-import Pyro.core as pyro
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+import Pyro
+import Pyro.core
+import Pyro.util
 from Pyro.errors import PyroError
-import Pyro.util
 import traceback
 from time import sleep
 import copy
 import socket
 service_type = '_PYRO._tcp.local.'
-
+import os.path
 # this module attribute contains a list of DNS-SD (Zeroconf) service types
 # supported by this connector confnode.
 #
@@ -37,67 +38,99 @@
     """
     This returns the connector to Pyro style PLCobject
     """
-    confnodesroot.logger.write(_("Connecting to URI : %s\n")%uri)
+    confnodesroot.logger.write(_("PYRO connecting to URI : %s\n") % uri)
 
     servicetype, location = uri.split("://")
+    if servicetype == "PYROS":
+        schemename = "PYROLOCSSL"
+        # Protect against name->IP substitution in Pyro3
+        Pyro.config.PYRO_DNS_URI = True
+        # Beware Pyro lib need str path, not unicode
+        # don't rely on PYRO_STORAGE ! see documentation
+        Pyro.config.PYROSSL_CERTDIR = os.path.abspath(str(confnodesroot.ProjectPath) + '/certs')
+        if not os.path.exists(Pyro.config.PYROSSL_CERTDIR):
+            confnodesroot.logger.write_error(
+                'Error : the directory %s is missing for SSL certificates (certs_dir).'
+                'Please fix it in your project.\n' % Pyro.config.PYROSSL_CERTDIR)
+            return None
+        else:
+            confnodesroot.logger.write(_("PYRO using certificates in '%s' \n")
+                                       % (Pyro.config.PYROSSL_CERTDIR))
+        Pyro.config.PYROSSL_CERT = "client.crt"
+        Pyro.config.PYROSSL_KEY = "client.key"
+        # Ugly Monkey Patching
+        def _gettimeout(self):
+            return self.timeout
+
+        def _settimeout(self, timeout):
+            self.timeout = timeout
+        from M2Crypto.SSL import Connection
+        Connection.timeout = None
+        Connection.gettimeout = _gettimeout
+        Connection.settimeout = _settimeout
+        # M2Crypto.SSL.Checker.WrongHost: Peer certificate commonName does not
+        # match host, expected 127.0.0.1, got server
+        Connection.clientPostConnectionCheck = None
+    else:
+        schemename = "PYROLOC"
     if location.find(service_type) != -1:
-        try :
+        try:
             from util.Zeroconf import Zeroconf
             r = Zeroconf()
-            i=r.getServiceInfo(service_type, location)
-            if i is None : raise Exception, "'%s' not found"%location
+            i = r.getServiceInfo(service_type, location)
+            if i is None:
+                raise Exception("'%s' not found" % location)
             ip = str(socket.inet_ntoa(i.getAddress()))
             port = str(i.getPort())
-            newlocation = ip+':'+port
-            confnodesroot.logger.write(_("'%s' is located at %s\n")%(location, newlocation))
+            newlocation = ip + ':' + port
+            confnodesroot.logger.write(_("'%s' is located at %s\n") % (location, newlocation))
             location = newlocation
             r.close()
         except Exception, msg:
-            confnodesroot.logger.write_error(_("MDNS resolution failure for '%s'\n")%location)
+            confnodesroot.logger.write_error(_("MDNS resolution failure for '%s'\n") % location)
             confnodesroot.logger.write_error(traceback.format_exc())
             return None
-    
+
     # Try to get the proxy object
-    try :
-        RemotePLCObjectProxy = pyro.getAttrProxyForURI("PYROLOC://"+location+"/PLCObject")
+    try:
+        RemotePLCObjectProxy = Pyro.core.getAttrProxyForURI(schemename + "://" + location + "/PLCObject")
     except Exception, msg:
-        confnodesroot.logger.write_error(_("Connection to '%s' failed.\n")%location)
+        confnodesroot.logger.write_error(_("Connection to '%s' failed.\n") % location)
         confnodesroot.logger.write_error(traceback.format_exc())
         return None
 
     def PyroCatcher(func, default=None):
         """
-        A function that catch a pyro exceptions, write error to logger
-        and return defaul value when it happen
+        A function that catch a Pyro exceptions, write error to logger
+        and return default value when it happen
         """
-        def catcher_func(*args,**kwargs):
+        def catcher_func(*args, **kwargs):
             try:
-                return func(*args,**kwargs)
+                return func(*args, **kwargs)
             except Pyro.errors.ConnectionClosedError, e:
                 confnodesroot.logger.write_error("Connection lost!\n")
                 confnodesroot._SetConnector(None)
             except Pyro.errors.ProtocolError, e:
-                confnodesroot.logger.write_error("Pyro exception: "+str(e)+"\n")
-            except Exception,e:
-                #confnodesroot.logger.write_error(traceback.format_exc())
+                confnodesroot.logger.write_error("Pyro exception: " + str(e) + "\n")
+            except Exception, e:
+                # confnodesroot.logger.write_error(traceback.format_exc())
                 errmess = ''.join(Pyro.util.getPyroTraceback(e))
-                confnodesroot.logger.write_error(errmess+"\n")
+                confnodesroot.logger.write_error(errmess + "\n")
                 print errmess
                 confnodesroot._SetConnector(None)
             return default
         return catcher_func
 
-    # Check connection is effective. 
+    # Check connection is effective.
     # lambda is for getattr of GetPLCstatus to happen inside catcher
-    if PyroCatcher(lambda:RemotePLCObjectProxy.GetPLCstatus())() is None:
+    if PyroCatcher(lambda: RemotePLCObjectProxy.GetPLCstatus())() is None:
         confnodesroot.logger.write_error(_("Cannot get PLC status - connection failed.\n"))
         return None
 
-
-    class PyroProxyProxy:
+    class PyroProxyProxy(object):
         """
         A proxy proxy class to handle Beremiz Pyro interface specific behavior.
-        And to put pyro exception catcher in between caller and pyro proxy
+        And to put Pyro exception catcher in between caller and Pyro proxy
         """
         def __init__(self):
             # for safe use in from debug thread, must create a copy
@@ -112,9 +145,9 @@
 
         def _PyroStartPLC(self, *args, **kwargs):
             """
-            confnodesroot._connector.GetPyroProxy() is used 
+            confnodesroot._connector.GetPyroProxy() is used
             rather than RemotePLCObjectProxy because
-            object is recreated meanwhile, 
+            object is recreated meanwhile,
             so we must not keep ref to it here
             """
             current_status, log_count = confnodesroot._connector.GetPyroProxy().GetPLCstatus()
@@ -133,7 +166,6 @@
             return confnodesroot._connector.GetPyroProxy().StartPLC(*args, **kwargs)
         StartPLC = PyroCatcher(_PyroStartPLC, False)
 
-
         def _PyroGetTraceVariables(self):
             """
             for safe use in from debug thread, must use the copy
@@ -141,11 +173,11 @@
             if self.RemotePLCObjectProxyCopy is None:
                 self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy())
             return self.RemotePLCObjectProxyCopy.GetTraceVariables()
-        GetTraceVariables = PyroCatcher(_PyroGetTraceVariables,("Broken",None,None))
+        GetTraceVariables = PyroCatcher(_PyroGetTraceVariables, ("Broken", None))
 
         def _PyroGetPLCstatus(self):
             return RemotePLCObjectProxy.GetPLCstatus()
-        GetPLCstatus = PyroCatcher(_PyroGetPLCstatus, ("Broken",None))
+        GetPLCstatus = PyroCatcher(_PyroGetPLCstatus, ("Broken", None))
 
         def _PyroRemoteExec(self, script, **kwargs):
             return RemotePLCObjectProxy.RemoteExec(script, **kwargs)
@@ -154,12 +186,10 @@
         def __getattr__(self, attrName):
             member = self.__dict__.get(attrName, None)
             if member is None:
-                def my_local_func(*args,**kwargs):
-                    return RemotePLCObjectProxy.__getattr__(attrName)(*args,**kwargs)
+                def my_local_func(*args, **kwargs):
+                    return RemotePLCObjectProxy.__getattr__(attrName)(*args, **kwargs)
                 member = PyroCatcher(my_local_func, None)
                 self.__dict__[attrName] = member
             return member
 
     return PyroProxyProxy()
-    
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/WAMP/__init__.py	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2015: Edouard TISSERANT
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#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
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import sys, traceback, atexit
+#from twisted.python import log
+from twisted.internet import reactor, threads
+from autobahn.twisted import wamp
+from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
+from autobahn.wamp import types
+from autobahn.wamp.exception import TransportLost
+from autobahn.wamp.serializer import MsgPackSerializer
+from threading import Thread, Event
+
+_WampSession = None
+_WampConnection = None
+_WampSessionEvent = Event()
+
+class WampSession(wamp.ApplicationSession):
+    def onJoin(self, details):
+        global _WampSession, _WampSessionEvent
+        _WampSession = self
+        _WampSessionEvent.set()
+        print 'WAMP session joined for :', self.config.extra["ID"]
+
+    def onLeave(self, details):
+        global _WampSession, _WampSessionEvent
+        _WampSessionEvent.clear()
+        _WampSession = None
+        print 'WAMP session left'
+
+PLCObjDefaults = { "StartPLC": False,
+                   "GetTraceVariables" : ("Broken",None),
+                   "GetPLCstatus" : ("Broken",None),
+                   "RemoteExec" : (-1, "RemoteExec script failed!")}
+
+def WAMP_connector_factory(uri, confnodesroot):
+    """
+    WAMP://127.0.0.1:12345/path#realm#ID
+    WAMPS://127.0.0.1:12345/path#realm#ID
+    """
+    servicetype, location = uri.split("://")
+    urlpath, realm, ID = location.split('#')
+    urlprefix = {"WAMP":"ws",
+                 "WAMPS":"wss"}[servicetype]
+    url = urlprefix+"://"+urlpath
+
+    def RegisterWampClient():
+
+        ## start logging to console
+        # log.startLogging(sys.stdout)
+
+        # create a WAMP application session factory
+        component_config = types.ComponentConfig(
+            realm = realm,
+            extra = {"ID":ID})
+        session_factory = wamp.ApplicationSessionFactory(
+            config = component_config)
+        session_factory.session = WampSession
+
+        # create a WAMP-over-WebSocket transport client factory
+        transport_factory = WampWebSocketClientFactory(
+            session_factory,
+            url = url,
+            serializers = [MsgPackSerializer()],
+            debug = False,
+            debug_wamp = False)
+
+        # start the client from a Twisted endpoint
+        conn = connectWS(transport_factory)
+        confnodesroot.logger.write(_("WAMP connecting to URL : %s\n")%url)
+        return conn
+
+    AddToDoBeforeQuit = confnodesroot.AppFrame.AddToDoBeforeQuit
+    def ThreadProc():
+        global _WampConnection
+        _WampConnection = RegisterWampClient()
+        AddToDoBeforeQuit(reactor.stop)
+        reactor.run(installSignalHandlers=False)
+
+    def WampSessionProcMapper(funcname):
+        wampfuncname = '.'.join((ID,funcname))
+        def catcher_func(*args,**kwargs):
+            global _WampSession
+            if _WampSession is not None :
+                try:
+                    return threads.blockingCallFromThread(
+                        reactor, _WampSession.call, wampfuncname,
+                        *args,**kwargs)
+                except TransportLost, e:
+                    confnodesroot.logger.write_error("Connection lost!\n")
+                    confnodesroot._SetConnector(None)
+                except Exception,e:
+                    errmess = traceback.format_exc()
+                    confnodesroot.logger.write_error(errmess+"\n")
+                    print errmess
+                    #confnodesroot._SetConnector(None)
+            return PLCObjDefaults.get(funcname)
+        return catcher_func
+
+    class WampPLCObjectProxy(object):
+        def __init__(self):
+            global _WampSessionEvent, _WampConnection
+            if not reactor.running:
+                Thread(target=ThreadProc).start()
+            else:
+                _WampConnection = threads.blockingCallFromThread(
+                    reactor, RegisterWampClient)
+            if not _WampSessionEvent.wait(5):
+                _WampConnection = stopConnecting()
+                raise Exception, _("WAMP connection timeout")
+
+        def __del__(self):
+            global _WampConnection
+            _WampConnection.disconnect()
+            #
+            # reactor.stop()
+
+        def __getattr__(self, attrName):
+            member = self.__dict__.get(attrName, None)
+            if member is None:
+                member = WampSessionProcMapper(attrName)
+                self.__dict__[attrName] = member
+            return member
+
+    # Try to get the proxy object
+    try :
+        return WampPLCObjectProxy()
+    except Exception, msg:
+        confnodesroot.logger.write_error(_("WAMP connection to '%s' failed.\n")%location)
+        confnodesroot.logger.write_error(traceback.format_exc())
+        return None
+
+
+
+
--- a/connectors/__init__.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/connectors/__init__.py	Wed Oct 21 15:00:32 2015 +0100
@@ -1,23 +1,23 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 #
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
 #
-#See COPYING file for copyrights details.
+# See COPYING file for copyrights details.
 #
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
 #
-#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
-#General Public License for more details.
+# 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
+# General Public License for more details.
 #
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 # Package initialisation
 
@@ -28,27 +28,35 @@
 
 
 def _GetLocalConnectorClassFactory(name):
-    return lambda:getattr(__import__(name,globals(),locals()), name + "_connector_factory")
+    return lambda: getattr(__import__(name, globals(), locals()), name + "_connector_factory")
 
-connectors = {name:_GetLocalConnectorClassFactory(name) 
-                  for name in listdir(_base_path) 
-                      if path.isdir(path.join(_base_path, name)) 
+connectors = {name:_GetLocalConnectorClassFactory(name)
+                  for name in listdir(_base_path)
+                      if path.isdir(path.join(_base_path, name))
                           and not name.startswith("__")}
 
+
 def ConnectorFactory(uri, confnodesroot):
     """
     Return a connector corresponding to the URI
     or None if cannot connect to URI
     """
-    servicetype = uri.split("://")[0]
-    if servicetype in connectors:
-        # import module according to uri type
-        connectorclass = connectors[servicetype]()
-    elif servicetype == "LOCAL":
-        from PYRO import PYRO_connector_factory as connectorclass
-        runtime_port = confnodesroot.AppFrame.StartLocalRuntime(taskbaricon=True)
-        uri="PYRO://127.0.0.1:"+str(runtime_port)
-    else :
-        return None    
+    servicetype = uri.split("://")[0].upper()
+    if servicetype == "LOCAL":
+        # Local is special case
+        # pyro connection to local runtime
+        # started on demand, listening on random port
+        servicetype = "PYRO"
+        runtime_port = confnodesroot.AppFrame.StartLocalRuntime(
+            taskbaricon=True)
+        uri = "PYROLOC://127.0.0.1:" + str(runtime_port)
+    elif servicetype in connectors:
+        pass
+    elif servicetype[-1] == 'S' and servicetype[:-1] in connectors:
+        servicetype = servicetype[:-1]
+    else:
+        return None
+
+    # import module according to uri type
+    connectorclass = connectors[servicetype]()
     return connectorclass(uri, confnodesroot)
-
--- a/controls/DebugVariablePanel/DebugVariablePanel.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/controls/DebugVariablePanel/DebugVariablePanel.py	Wed Oct 21 15:00:32 2015 +0100
@@ -319,7 +319,7 @@
         if self.DataProducer is not None:
             self.SetTickTime(self.DataProducer.GetTicktime())
     
-    def RefreshNewData(self, *args, **kwargs):
+    def RefreshNewData(self):
         """
         Called to refresh Panel according to values received by variables
         Can receive any parameters (not used here)
@@ -329,9 +329,9 @@
             self.HasNewData = False
             self.RefreshView()
         
-        DebugViewer.RefreshNewData(self, *args, **kwargs)
-    
-    def NewDataAvailable(self, ticks, *args, **kwargs):
+        DebugViewer.RefreshNewData(self)
+    
+    def NewDataAvailable(self, ticks):
         """
         Called by DataProducer for each tick captured or by panel to refresh
         graphs
@@ -363,14 +363,14 @@
             self.RefreshView()
             
         else:
-            DebugViewer.NewDataAvailable(self, ticks, *args, **kwargs)
+            DebugViewer.NewDataAvailable(self, ticks)
     
     def ForceRefresh(self):
         """
         Called to force refresh of graphs
         """
         self.Force = True
-        wx.CallAfter(self.NewDataAvailable, None, True)
+        wx.CallAfter(self.NewDataAvailable, None)
     
     def SetCursorTick(self, cursor_tick):
         """
--- a/controls/LogViewer.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/controls/LogViewer.py	Wed Oct 21 15:00:32 2015 +0100
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
-#based on the plcopen standard. 
+#based on the plcopen standard.
 #
 #Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
 #
@@ -32,6 +32,7 @@
 from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
 from targets.typemapping import LogLevelsCount, LogLevels
 from util.BitmapLibrary import GetBitmap
+from weakref import proxy
 
 THUMB_SIZE_RATIO = 1. / 8.
 
@@ -46,7 +47,7 @@
                 wx.Point(xoffset + width - 1, yoffset - height + 1)]
 
 class LogScrollBar(wx.Panel):
-    
+
     def __init__(self, parent, size):
         wx.Panel.__init__(self, parent, size=size)
         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
@@ -55,14 +56,14 @@
         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
         self.Bind(wx.EVT_PAINT, self.OnPaint)
         self.Bind(wx.EVT_SIZE, self.OnResize)
-        
+
         self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
         self.ThumbScrollingStartPos = None
-    
+
     def GetRangeRect(self):
         width, height = self.GetClientSize()
         return wx.Rect(0, width, width, height - 2 * width)
-    
+
     def GetThumbRect(self):
         width, height = self.GetClientSize()
         range_rect = self.GetRangeRect()
@@ -72,7 +73,7 @@
         thumb_start = int(thumb_center_position - thumb_size / 2.)
         thumb_end = int(thumb_center_position + thumb_size / 2.)
         return wx.Rect(0, range_rect.y + thumb_start, width, thumb_end - thumb_start)
-    
+
     def RefreshThumbPosition(self, thumb_position=None):
         if thumb_position is None:
             thumb_position = self.ThumbPosition
@@ -84,7 +85,7 @@
             self.ThumbPosition = thumb_position
             self.Parent.SetScrollSpeed(self.ThumbPosition)
         self.Refresh()
-    
+
     def OnLeftDown(self, event):
         self.CaptureMouse()
         posx, posy = event.GetPosition()
@@ -103,14 +104,14 @@
         elif posy > height - width:
             self.Parent.ScrollMessagePanelByPage(-1)
         event.Skip()
-        
+
     def OnLeftUp(self, event):
         self.ThumbScrollingStartPos = None
         self.RefreshThumbPosition(0.)
         if self.HasCapture():
             self.ReleaseMouse()
         event.Skip()
-        
+
     def OnMotion(self, event):
         if event.Dragging() and self.ThumbScrollingStartPos is not None:
             posx, posy = event.GetPosition()
@@ -121,32 +122,32 @@
             self.RefreshThumbPosition(
                 max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. / thumb_range, 1.)))
         event.Skip()
-    
+
     def OnResize(self, event):
         self.Refresh()
         event.Skip()
-    
+
     def OnEraseBackground(self, event):
         pass
-    
+
     def OnPaint(self, event):
         dc = wx.BufferedPaintDC(self)
         dc.Clear()
         dc.BeginDrawing()
-        
+
         gc = wx.GCDC(dc)
-        
+
         width, height = self.GetClientSize()
-        
+
         gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
         gc.SetBrush(wx.GREY_BRUSH)
-        
+
         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
-        
+
         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
-        
+
         thumb_rect = self.GetThumbRect()
         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
                                  thumb_rect.width, thumb_rect.height)
@@ -158,71 +159,71 @@
             colour = wx.NamedColour("LIGHT GREY")
             gc.SetPen(wx.Pen(colour))
             gc.SetBrush(wx.Brush(colour))
-        
-            gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y, 
+
+            gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y,
                              exclusion_rect.width, exclusion_rect.height)
-        
+
         gc.SetPen(wx.GREY_PEN)
         gc.SetBrush(wx.GREY_BRUSH)
-        
+
         gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
-        
+
         gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
-            
-        gc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
+
+        gc.DrawRectangle(thumb_rect.x, thumb_rect.y,
                          thumb_rect.width, thumb_rect.height)
-        
+
         dc.EndDrawing()
         event.Skip()
 
 BUTTON_SIZE = (30, 15)
 
 class LogButton():
-    
+
     def __init__(self, label, callback):
         self.Position = wx.Point(0, 0)
         self.Size = wx.Size(*BUTTON_SIZE)
         self.Label = label
         self.Shown = True
         self.Callback = callback
-    
+
     def __del__(self):
         self.callback = None
-    
+
     def GetSize(self):
         return self.Size
-    
+
     def SetPosition(self, x, y):
         self.Position = wx.Point(x, y)
-    
+
     def HitTest(self, x, y):
-        rect = wx.Rect(self.Position.x, self.Position.y, 
+        rect = wx.Rect(self.Position.x, self.Position.y,
                        self.Size.width, self.Size.height)
         if rect.InsideXY(x, y):
             return True
         return False
-    
+
     def ProcessCallback(self):
         if self.Callback is not None:
             wx.CallAfter(self.Callback)
-            
+
     def Draw(self, dc):
         dc.SetPen(wx.TRANSPARENT_PEN)
         dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
-        
-        dc.DrawRectangle(self.Position.x, self.Position.y, 
+
+        dc.DrawRectangle(self.Position.x, self.Position.y,
                          self.Size.width, self.Size.height)
-        
+
         w, h = dc.GetTextExtent(self.Label)
-        dc.DrawText(self.Label, 
-            self.Position.x + (self.Size.width - w) / 2, 
+        dc.DrawText(self.Label,
+            self.Position.x + (self.Size.width - w) / 2,
             self.Position.y + (self.Size.height - h) / 2)
 
 DATE_INFO_SIZE = 10
 MESSAGE_INFO_SIZE = 18
 
 class LogMessage:
-    
+
     def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
         self.Date = datetime.utcfromtimestamp(tv_sec)
         self.Seconds = self.Date.second + tv_nsec * 1e-9
@@ -232,12 +233,12 @@
         self.LevelBitmap = level_bitmap
         self.Message = msg
         self.DrawDate = True
-    
+
     def __cmp__(self, other):
         if self.Date == other.Date:
             return cmp(self.Seconds, other.Seconds)
         return cmp(self.Date, other.Date)
-    
+
     def GetFullText(self):
         date = self.Date.replace(second=int(self.Seconds))
         nsec = (self.Seconds % 1.) * 1e9
@@ -245,25 +246,25 @@
             LogLevels[self.Level],
             str(date), nsec,
             self.Message)
-    
+
     def Draw(self, dc, offset, width, draw_date):
         if draw_date:
             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
             dw, dh = dc.GetTextExtent(datetime_text)
             dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
             offset += DATE_INFO_SIZE
-        
+
         seconds_text = "%12.9f" % self.Seconds
         sw, sh = dc.GetTextExtent(seconds_text)
         dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
-        
+
         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
-        
+
         text = self.Message.replace("\n", " ")
         mw, mh = dc.GetTextExtent(text)
         dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
-        
+
     def GetHeight(self, draw_date):
         if draw_date:
             return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
@@ -280,18 +281,18 @@
                             (_("1s"), SECOND)]
 
 class LogViewer(DebugViewer, wx.Panel):
-    
+
     def __init__(self, parent, window):
         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
         DebugViewer.__init__(self, None, False, False)
-        
+
         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
         main_sizer.AddGrowableCol(0)
         main_sizer.AddGrowableRow(1)
-        
+
         filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
         main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
-        
+
         self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
         self.MessageFilter.Append(_("All"))
         levels = LogLevels[:3]
@@ -300,28 +301,28 @@
             self.MessageFilter.Append(_(level))
         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
         filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
-        
+
         self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
         self.SearchMessage.ShowSearchButton(True)
         self.SearchMessage.ShowCancelButton(True)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
-        self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
+        self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN,
               self.OnSearchMessageSearchButtonClick, self.SearchMessage)
-        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, 
+        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN,
               self.OnSearchMessageCancelButtonClick, self.SearchMessage)
         filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
-        
-        self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"), 
+
+        self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"),
               size=wx.Size(28, 28), style=wx.NO_BORDER)
         self.CleanButton.SetToolTipString(_("Clean log messages"))
         self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
         filter_sizer.AddWindow(self.CleanButton)
-        
+
         message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
         message_panel_sizer.AddGrowableCol(0)
         message_panel_sizer.AddGrowableRow(0)
         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
-        
+
         self.MessagePanel = wx.Panel(self)
         if wx.Platform == '__WXMSW__':
             self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
@@ -337,45 +338,45 @@
         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
-        
+
         self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
         message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
-        
+
         self.SetSizer(main_sizer)
-        
+
         self.LeftButtons = []
-        for label, callback in [("+" + text, self.GenerateOnDurationButton(duration)) 
+        for label, callback in [("+" + text, self.GenerateOnDurationButton(duration))
                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
             self.LeftButtons.append(LogButton(label, callback))
-        
+
         self.RightButtons = []
-        for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration)) 
+        for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration))
                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
             self.RightButtons.append(LogButton(label, callback))
-        
+
         self.MessageFilter.SetSelection(0)
         self.LogSource = None
         self.ResetLogMessages()
         self.ParentWindow = window
-    
+
         self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
         self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
         self.CurrentFilter = self.LevelFilters[0]
         self.CurrentSearchValue = ""
-        
+
         self.ScrollSpeed = 0.
         self.LastStartTime = None
         self.ScrollTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
-        
+
         self.LastMousePos = None
         self.MessageToolTip = None
         self.MessageToolTipTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
-    
+
     def __del__(self):
         self.ScrollTimer.Stop()
-    
+
     def ResetLogMessages(self):
         self.previous_log_count = [None]*LogLevelsCount
         self.OldestMessages = []
@@ -383,14 +384,14 @@
         self.LogMessagesTimestamp = numpy.array([])
         self.CurrentMessage = None
         self.HasNewData = False
-    
+
     def SetLogSource(self, log_source):
-        self.LogSource = log_source
+        self.LogSource = proxy(log_source) if log_source else None
         self.CleanButton.Enable(self.LogSource is not None)
         if log_source is not None:
             self.ResetLogMessages()
             self.RefreshView()
-    
+
     def GetLogMessageFromSource(self, msgidx, level):
         if self.LogSource is not None:
             answer = self.LogSource.GetLogMessage(level, msgidx)
@@ -398,7 +399,7 @@
                 msg, tick, tv_sec, tv_nsec = answer
                 return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
         return None
-    
+
     def SetLogCounters(self, log_count):
         new_messages = []
         for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
@@ -441,12 +442,12 @@
                 self.MessageToolTipTimer.Stop()
                 self.ParentWindow.SelectTab(self)
             self.NewDataAvailable(None)
-    
+
     def FilterLogMessage(self, message, timestamp=None):
-        return (message.Level in self.CurrentFilter and 
+        return (message.Level in self.CurrentFilter and
                 message.Message.find(self.CurrentSearchValue) != -1 and
                 (timestamp is None or message.Timestamp < timestamp))
-    
+
     def GetMessageByTimestamp(self, timestamp):
         if self.CurrentMessage is not None:
             msgidx = numpy.argmin(abs(self.LogMessagesTimestamp - timestamp))
@@ -455,7 +456,7 @@
                 return self.GetPreviousMessage(msgidx, timestamp)
             return message, msgidx
         return None, None
-    
+
     def GetNextMessage(self, msgidx):
         while msgidx < len(self.LogMessages) - 1:
             message = self.LogMessages[msgidx + 1]
@@ -463,7 +464,7 @@
                 return message, msgidx + 1
             msgidx += 1
         return None, None
-    
+
     def GetPreviousMessage(self, msgidx, timestamp=None):
         message = None
         while 0 < msgidx < len(self.LogMessages):
@@ -490,7 +491,7 @@
                     self.OldestMessages[level] = (-1, None)
                 if message is not None:
                     message_idx = 0
-                    while (message_idx < len(self.LogMessages) and 
+                    while (message_idx < len(self.LogMessages) and
                            self.LogMessages[message_idx] < message):
                         message_idx += 1
                     if len(self.LogMessages) > 0:
@@ -499,8 +500,8 @@
                         current_message = message
                     self.LogMessages.insert(message_idx, message)
                     self.LogMessagesTimestamp = numpy.insert(
-                            self.LogMessagesTimestamp, 
-                            [message_idx], 
+                            self.LogMessagesTimestamp,
+                            [message_idx],
                             [message.Timestamp])
                     self.CurrentMessage = self.LogMessages.index(current_message)
                     if message_idx == 0 and self.FilterLogMessage(message, timestamp):
@@ -509,27 +510,27 @@
                     if msg is not None and (message is None or msg > message):
                         message = msg
         return None, None
-    
+
     def RefreshNewData(self, *args, **kwargs):
         if self.HasNewData:
             self.HasNewData = False
             self.RefreshView()
         DebugViewer.RefreshNewData(self, *args, **kwargs)
-    
+
     def RefreshView(self):
         width, height = self.MessagePanel.GetClientSize()
         bitmap = wx.EmptyBitmap(width, height)
         dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
         dc.Clear()
         dc.BeginDrawing()
-        
+
         if self.CurrentMessage is not None:
-            
+
             dc.SetFont(self.Font)
-            
+
             for button in self.LeftButtons + self.RightButtons:
                 button.Draw(dc)
-            
+
             message_idx = self.CurrentMessage
             message = self.LogMessages[message_idx]
             draw_date = True
@@ -537,23 +538,23 @@
             while offset < height and message is not None:
                 message.Draw(dc, offset, width, draw_date)
                 offset += message.GetHeight(draw_date)
-                
+
                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
                 if previous_message is not None:
                     draw_date = message.Date != previous_message.Date
                 message = previous_message
-        
+
         dc.EndDrawing()
-        
+
         self.MessageScrollBar.RefreshThumbPosition()
-    
+
     def IsMessagePanelTop(self, message_idx=None):
         if message_idx is None:
             message_idx = self.CurrentMessage
         if message_idx is not None:
             return self.GetNextMessage(message_idx)[0] is None
         return True
-    
+
     def IsMessagePanelBottom(self, message_idx=None):
         if message_idx is None:
             message_idx = self.CurrentMessage
@@ -570,7 +571,7 @@
                 message = previous_message
             return offset < height
         return True
-    
+
     def ScrollMessagePanel(self, scroll):
         if self.CurrentMessage is not None:
             message = self.LogMessages[self.CurrentMessage]
@@ -585,13 +586,13 @@
                     self.CurrentMessage = msgidx
                     scroll += 1
             self.RefreshView()
-    
+
     def ScrollMessagePanelByPage(self, page):
         if self.CurrentMessage is not None:
             width, height = self.MessagePanel.GetClientSize()
             message_per_page = max(1, (height - DATE_INFO_SIZE) / MESSAGE_INFO_SIZE - 1)
             self.ScrollMessagePanel(page * message_per_page)
-    
+
     def ScrollMessagePanelByTimestamp(self, seconds):
         if self.CurrentMessage is not None:
             current_message = self.LogMessages[self.CurrentMessage]
@@ -603,7 +604,7 @@
                     msgidx += 1
                 self.CurrentMessage = msgidx
                 self.RefreshView()
-            
+
     def ResetMessagePanel(self):
         if len(self.LogMessages) > 0:
             self.CurrentMessage = len(self.LogMessages) - 1
@@ -611,45 +612,45 @@
             while message is not None and not self.FilterLogMessage(message):
                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
             self.RefreshView()
-    
+
     def OnMessageFilterChanged(self, event):
         self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
         self.ResetMessagePanel()
         event.Skip()
-    
+
     def OnSearchMessageChanged(self, event):
         self.CurrentSearchValue = self.SearchMessage.GetValue()
         self.ResetMessagePanel()
         event.Skip()
-        
+
     def OnSearchMessageSearchButtonClick(self, event):
         self.CurrentSearchValue = self.SearchMessage.GetValue()
         self.ResetMessagePanel()
         event.Skip()
-    
+
     def OnSearchMessageCancelButtonClick(self, event):
         self.CurrentSearchValue = ""
         self.SearchMessage.SetValue("")
         self.ResetMessagePanel()
         event.Skip()
-    
+
     def OnCleanButton(self, event):
         if self.LogSource is not None:
             self.LogSource.ResetLogCount()
         self.ResetLogMessages()
         self.RefreshView()
         event.Skip()
-    
+
     def GenerateOnDurationButton(self, duration):
         def OnDurationButton():
             self.ScrollMessagePanelByTimestamp(duration)
         return OnDurationButton
-    
+
     def GetCopyMessageToClipboardFunction(self, message):
         def CopyMessageToClipboardFunction(event):
             self.ParentWindow.SetCopyBuffer(message.GetFullText())
         return CopyMessageToClipboardFunction
-    
+
     def GetMessageByScreenPos(self, posx, posy):
         if self.CurrentMessage is not None:
             width, height = self.MessagePanel.GetClientSize()
@@ -657,22 +658,22 @@
             message = self.LogMessages[message_idx]
             draw_date = True
             offset = 5
-            
+
             while offset < height and message is not None:
                 if draw_date:
                     offset += DATE_INFO_SIZE
-    
+
                 if offset <= posy < offset + MESSAGE_INFO_SIZE:
                     return message
-        
+
                 offset += MESSAGE_INFO_SIZE
-                
+
                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
                 if previous_message is not None:
                     draw_date = message.Date != previous_message.Date
                 message = previous_message
         return None
-    
+
     def OnMessagePanelLeftUp(self, event):
         if self.CurrentMessage is not None:
             posx, posy = event.GetPosition()
@@ -681,32 +682,32 @@
                     button.ProcessCallback()
                     break
         event.Skip()
-    
+
     def OnMessagePanelRightUp(self, event):
         message = self.GetMessageByScreenPos(*event.GetPosition())
         if message is not None:
             menu = wx.Menu(title='')
-            
+
             new_id = wx.NewId()
             menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
             self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
-    
+
             self.MessagePanel.PopupMenu(menu)
             menu.Destroy()
         event.Skip()
-    
+
     def OnMessagePanelLeftDCLick(self, event):
         message = self.GetMessageByScreenPos(*event.GetPosition())
         if message is not None:
             self.SearchMessage.SetFocus()
             self.SearchMessage.SetValue(message.Message)
         event.Skip()
-    
+
     def ResetMessageToolTip(self):
         if self.MessageToolTip is not None:
             self.MessageToolTip.Destroy()
             self.MessageToolTip = None
-    
+
     def OnMessageToolTipTimer(self, event):
         if self.LastMousePos is not None:
             message = self.GetMessageByScreenPos(*self.LastMousePos)
@@ -719,31 +720,31 @@
                 self.MessageToolTip.SetToolTipPosition(tooltip_pos)
                 self.MessageToolTip.Show()
         event.Skip()
-    
+
     def OnMessagePanelMotion(self, event):
         if not event.Dragging():
             self.ResetMessageToolTip()
             self.LastMousePos = event.GetPosition()
             self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
         event.Skip()
-        
+
     def OnMessagePanelLeaveWindow(self, event):
         self.ResetMessageToolTip()
         self.LastMousePos = None
         self.MessageToolTipTimer.Stop()
         event.Skip()
-    
+
     def OnMessagePanelMouseWheel(self, event):
         self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
         event.Skip()
-    
+
     def OnMessagePanelEraseBackground(self, event):
         pass
-    
+
     def OnMessagePanelPaint(self, event):
         self.RefreshView()
         event.Skip()
-    
+
     def OnMessagePanelResize(self, event):
         width, height = self.MessagePanel.GetClientSize()
         offset = 2
@@ -761,7 +762,7 @@
         else:
             self.RefreshView()
         event.Skip()
-    
+
     def OnScrollTimer(self, event):
         if self.ScrollSpeed != 0.:
             speed_norm = abs(self.ScrollSpeed)
@@ -770,7 +771,7 @@
             self.LastStartTime = gettime()
             self.ScrollTimer.Start(int(period * 1000), True)
         event.Skip()
-    
+
     def SetScrollSpeed(self, speed):
         if speed == 0.:
             self.ScrollTimer.Stop()
@@ -788,8 +789,8 @@
             else:
                 self.LastStartTime = current_time
             self.ScrollTimer.Start(int(period * 1000), True)
-        self.ScrollSpeed = speed    
-    
+        self.ScrollSpeed = speed
+
     def ScrollToLast(self, refresh=True):
         if len(self.LogMessages) > 0:
             self.CurrentMessage = len(self.LogMessages) - 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/manual/connectors.rst	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,107 @@
+Beremiz and Beremiz_service connectors 
+======================================
+
+To connect a PLC, Beremiz provides 2 types of connectors :
+ * a Pyro connector
+ * a WAMP connector
+
+To configure the connection, you have to set the *URI_location* in your project Config tab according to this documentation. 
+
+The Pyro connector
+----------------------------
+
+Pyro is an advanced and powerful Distributed Object Technology system written entirely in Python.
+Beremiz_service spawns a Pyro server, serving a PLCObject (see runtime/PLCObject.py). Therefore, Beremiz acts as a Pyro client.
+
+TODO:: link to PLCObject API documentation
+
+URI_location :
+ * LOCAL:// is a facility that starts the PLC service locally and connect Beremiz to it via Pyro.
+   This is intended for use in development stage.
+ * PYRO://<ip:port> normal connection to a remote PLC. PLC default port is 3000.
+ * PYROS://<ip:port> SSL connection to a remote PLC, see below.
+
+more information about Pyro can be found on http://pythonhosted.org//Pyro/1-intro.html
+
+===========================
+Setup a Pyro SSL connection
+===========================
+
+Pyro v3 has a limited TLS/SSL support based on m2crypto. Pyro v4 had dropped it.
+In order to have a full and reliable SSL, we recommand to use a TLS/SSL wrapper as nginx, stub or stunnel.
+
+--------------------
+TLS-PSK with stunnel
+--------------------
+
+In this example, we setup a simple TLS-PSK connection according to rfc4279.
+This ciphersuite avoid the need for public key operations and certificate management.
+It is perfect for a performance-constrained environments with limited CPU power as a PLC.
+
+
+Needed :
+ * stunnel >= 5.09
+
+verify openssl support for PSK cipher::
+
+    openssl ciphers -v 'PSK'
+
+----------------------
+Client setup (Beremiz)
+----------------------
+
+You need to choose an identity for your client, here *client1*.
+generate a valid and strong key::
+
+    $ echo client1:$(openssl rand -base64 48) > pskclient1.txt
+
+write a stunnel client configuration file *stunnel-client.conf*::
+
+    output = stunnel-client.log
+    client = yes
+    
+    [beremiz]
+    accept = 3002
+    connect = [PLC]:3001
+    PSKidentity = client1
+    PSKsecrets = pskclient1.txt
+
+start stunnel client side::
+
+    stunnel stunnel-client.conf
+
+You could now connect beremiz with classic URI_location = PYRO://127.0.0.1:3002
+
+--------------------
+Server setup (PLC)
+--------------------
+
+import the client key in a keyfile psk.txt, concatening all client key.
+
+write a stunnel server  configuration file *stunnel-server.conf*::
+
+    output = stunnel-server.log
+    
+    [beremiz]
+    accept = 3001
+    connect = 127.0.0.1:3000
+    PSKsecrets = psk.txt
+
+start stunnel server side::
+
+    stunnel stunnel-server.conf
+    
+more documentation on stunnel http://www.stunnel.org/docs.html
+
+The WAMP connector
+------------------
+
+WAMP is an open standard WebSocket subprotocol that provides two application messaging 
+patterns in one unified protocol: Remote Procedure Calls + Publish & Subscribe.
+
+Beremiz WAMP connector implementation uses Autobahn and crossbar.
+
+URI_location :
+	* WAMP://127.0.0.1:8888#Automation#2534667845
+
+more information about WAMP can be found on http://wamp.ws/
--- a/doc/manual/index.rst	Sat Dec 06 19:31:51 2014 +0000
+++ b/doc/manual/index.rst	Wed Oct 21 15:00:32 2015 +0100
@@ -10,7 +10,5 @@
    start
    edit
    build
+   connectors
    debug
-
-
-
--- a/editors/CodeFileEditor.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/editors/CodeFileEditor.py	Wed Oct 21 15:00:32 2015 +0100
@@ -607,7 +607,7 @@
                 renderer = None
                 colname = self.GetColLabelValue(col, False)
 
-                if colname in ["Name", "Initial"]:
+                if colname in ["Name", "Initial", "Description", "OnChange", "Options"]:
                     editor = wx.grid.GridCellTextEditor()
                 elif colname == "Class":
                     editor = wx.grid.GridCellChoiceEditor()
@@ -658,10 +658,13 @@
         self.ParentWindow = window
         self.Controler = controler
 
-        self.VariablesDefaultValue = {"Name" : "", "Type" : DefaultType, "Initial": ""}
-        self.Table = VariablesTable(self, [], ["#", "Name", "Type", "Initial"])
-        self.ColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
-        self.ColSizes = [40, 200, 150, 150]
+        self.VariablesDefaultValue = {"Name" : "", "Type" : DefaultType, "Initial": "",
+                                      "Description":"", "OnChange":"", "Options":""}
+        self.Table = VariablesTable(self, [], ["#", "Name", "Type", "Initial",
+                                    "Description", "OnChange", "Options"])
+        self.ColAlignements = [wx.ALIGN_RIGHT] +  \
+                              [wx.ALIGN_LEFT]*(len(self.VariablesDefaultValue))
+        self.ColSizes = [20, 150] + [100]*(len(self.VariablesDefaultValue)-1)
         self.VariablesGrid.SetTable(self.Table)
         self.VariablesGrid.SetButtons({"Add": self.AddVariableButton,
                                        "Delete": self.DeleteVariableButton,
--- a/editors/ConfTreeNodeEditor.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/editors/ConfTreeNodeEditor.py	Wed Oct 21 15:00:32 2015 +0100
@@ -29,11 +29,6 @@
 
 SCROLLBAR_UNIT = 10
 
-CWD = os.path.split(os.path.realpath(__file__))[0]
-
-def Bpath(*args):
-    return os.path.join(CWD,*args)
-
 class GenBitmapTextButton(wx.lib.buttons.GenBitmapTextButton):
     def _GetLabelSize(self):
         """ used internally """
@@ -88,82 +83,82 @@
 
 
 class GenStaticBitmap(wx.StaticBitmap):
-    """ Customized GenStaticBitmap, fix transparency redraw bug on wx2.8/win32, 
+    """ Customized GenStaticBitmap, fix transparency redraw bug on wx2.8/win32,
     and accept image name as __init__ parameter, fail silently if file do not exist"""
     def __init__(self, parent, ID, bitmapname,
                  pos = wx.DefaultPosition, size = wx.DefaultSize,
                  style = 0,
                  name = "genstatbmp"):
-        
+
         bitmap = GetBitmap(bitmapname)
         if bitmap is None:
             bitmap = wx.EmptyBitmap(0, 0)
-        
-        wx.StaticBitmap.__init__(self, parent, ID, 
+
+        wx.StaticBitmap.__init__(self, parent, ID,
                  bitmap,
                  pos, size,
                  style,
                  name)
 
 class ConfTreeNodeEditor(EditorPanel):
-    
+
     SHOW_BASE_PARAMS = True
     SHOW_PARAMS = True
     CONFNODEEDITOR_TABS = []
-    
+
     def _init_Editor(self, parent):
         tabs_num = len(self.CONFNODEEDITOR_TABS)
         if self.SHOW_PARAMS and len(self.Controler.GetParamsAttributes()) > 0:
             tabs_num += 1
-            
+
         if tabs_num > 1 or self.SHOW_BASE_PARAMS:
-            self.Editor = wx.Panel(parent, 
+            self.Editor = wx.Panel(parent,
                 style=wx.SUNKEN_BORDER|wx.SP_3D)
-            
+
             self.MainSizer = wx.BoxSizer(wx.VERTICAL)
-            
+
             if self.SHOW_BASE_PARAMS:
                 baseparamseditor_sizer = wx.BoxSizer(wx.HORIZONTAL)
-                self.MainSizer.AddSizer(baseparamseditor_sizer, border=5, 
+                self.MainSizer.AddSizer(baseparamseditor_sizer, border=5,
                       flag=wx.GROW|wx.ALL)
-                
+
                 self.FullIECChannel = wx.StaticText(self.Editor, -1)
                 self.FullIECChannel.SetFont(
-                    wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL, 
+                    wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL,
                             wx.BOLD, faceName = faces["helv"]))
-                baseparamseditor_sizer.AddWindow(self.FullIECChannel, 
+                baseparamseditor_sizer.AddWindow(self.FullIECChannel,
                       flag=wx.ALIGN_CENTER_VERTICAL)
-                
+
                 updownsizer = wx.BoxSizer(wx.VERTICAL)
-                baseparamseditor_sizer.AddSizer(updownsizer, border=5, 
+                baseparamseditor_sizer.AddSizer(updownsizer, border=5,
                       flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
-                
-                self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.Editor, 
+
+                self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.Editor,
                       bitmap=GetBitmap('IECCDown'), size=wx.Size(16, 16), style=wx.NO_BORDER)
-                self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1), 
+                self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1),
                       self.IECCUpButton)
                 updownsizer.AddWindow(self.IECCUpButton, flag=wx.ALIGN_LEFT)
-                
-                self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.Editor, 
+
+                self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.Editor,
                       bitmap=GetBitmap('IECCUp'), size=wx.Size(16, 16), style=wx.NO_BORDER)
-                self.IECCDownButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(-1), 
+                self.IECCDownButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(-1),
                       self.IECCDownButton)
                 updownsizer.AddWindow(self.IECCDownButton, flag=wx.ALIGN_LEFT)
-                
-                self.ConfNodeName = wx.TextCtrl(self.Editor, 
+
+                self.ConfNodeName = wx.TextCtrl(self.Editor,
                       size=wx.Size(150, 25))
                 self.ConfNodeName.SetFont(
-                    wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL, 
+                    wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL,
                             wx.BOLD, faceName = faces["helv"]))
-                self.ConfNodeName.Bind(wx.EVT_TEXT, 
-                      self.GetTextCtrlCallBackFunction(self.ConfNodeName, "BaseParams.Name", True), 
+                self.ConfNodeName.Bind(wx.EVT_TEXT,
+                      self.GetTextCtrlCallBackFunction(self.ConfNodeName, "BaseParams.Name", True),
                       self.ConfNodeName)
-                baseparamseditor_sizer.AddWindow(self.ConfNodeName, border=5, 
+                baseparamseditor_sizer.AddWindow(self.ConfNodeName, border=5,
                       flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
-                
+
                 buttons_sizer = self.GenerateMethodButtonSizer()
                 baseparamseditor_sizer.AddSizer(buttons_sizer, flag=wx.ALIGN_CENTER)
-            
+
             if tabs_num > 1:
                 self.ConfNodeNoteBook = wx.Notebook(self.Editor)
                 parent = self.ConfNodeNoteBook
@@ -171,12 +166,12 @@
             else:
                 parent = self.Editor
                 self.ConfNodeNoteBook = None
-            
+
             self.Editor.SetSizer(self.MainSizer)
         else:
             self.ConfNodeNoteBook = None
             self.Editor = None
-        
+
         for title, create_func_name in self.CONFNODEEDITOR_TABS:
             editor = getattr(self, create_func_name)(parent)
             if self.ConfNodeNoteBook is not None:
@@ -185,28 +180,28 @@
                 self.MainSizer.AddWindow(editor, 1, flag=wx.GROW)
             else:
                 self.Editor = editor
-        
+
         if self.SHOW_PARAMS and len(self.Controler.GetParamsAttributes()) > 0:
-            
+
             panel_style = wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL
             if self.ConfNodeNoteBook is None and parent != self.Editor:
                 panel_style |= wx.SUNKEN_BORDER
-            self.ParamsEditor = wx.ScrolledWindow(parent, 
+            self.ParamsEditor = wx.ScrolledWindow(parent,
                   style=panel_style)
             self.ParamsEditor.Bind(wx.EVT_SIZE, self.OnParamsEditorResize)
             self.ParamsEditor.Bind(wx.EVT_SCROLLWIN, self.OnParamsEditorScroll)
-            
+
             self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=5)
             self.ParamsEditorSizer.AddGrowableCol(0)
             self.ParamsEditorSizer.AddGrowableRow(0)
             self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
-            
+
             self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
-            self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5, 
+            self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5,
                   flag=wx.LEFT|wx.RIGHT|wx.BOTTOM)
-            
+
             self.RefreshConfNodeParamsSizer()
-        
+
             if self.ConfNodeNoteBook is not None:
                 self.ConfNodeNoteBook.AddPage(self.ParamsEditor, _("Config"))
             elif self.SHOW_BASE_PARAMS:
@@ -215,40 +210,40 @@
                 self.Editor = self.ParamsEditor
         else:
             self.ParamsEditor = None
-    
+
     def __init__(self, parent, controler, window, tagname=""):
         EditorPanel.__init__(self, parent, tagname, window, controler)
-        
+
         icon_name = self.Controler.GetIconName()
         if icon_name is not None:
             self.SetIcon(GetBitmap(icon_name))
         else:
             self.SetIcon(GetBitmap("Extension"))
-        
+
     def __del__(self):
         self.Controler.OnCloseEditor(self)
-    
+
     def GetTagName(self):
         return self.Controler.CTNFullName()
-    
+
     def GetTitle(self):
         fullname = self.Controler.CTNFullName()
         if self.Controler.CTNTestModified():
             return "~%s~" % fullname
         return fullname
-    
+
     def HasNoModel(self):
         return False
-    
+
     def GetBufferState(self):
         return False, False
-    
+
     def Undo(self):
         pass
-    
+
     def Redo(self):
         pass
-    
+
     def RefreshView(self):
         EditorPanel.RefreshView(self)
         if self.SHOW_BASE_PARAMS:
@@ -257,33 +252,33 @@
         if self.ParamsEditor is not None:
             self.RefreshConfNodeParamsSizer()
             self.RefreshScrollbars()
-    
+
     def RefreshIECChannelControlsState(self):
         self.FullIECChannel.SetLabel(self.Controler.GetFullIEC_Channel())
         self.IECCDownButton.Enable(self.Controler.BaseParams.getIEC_Channel() > 0)
         self.MainSizer.Layout()
-    
+
     def RefreshConfNodeParamsSizer(self):
         self.Freeze()
         self.ConfNodeParamsSizer.Clear(True)
-        
+
         confnode_infos = self.Controler.GetParamsAttributes()
         if len(confnode_infos) > 0:
             self.GenerateSizerElements(self.ConfNodeParamsSizer, confnode_infos, None, False)
-        
+
         self.ParamsEditorSizer.Layout()
         self.Thaw()
-    
+
     def GenerateMethodButtonSizer(self):
         normal_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = faces["helv"])
         mouseover_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline=True, faceName = faces["helv"])
-        
+
         msizer = wx.BoxSizer(wx.HORIZONTAL)
-        
+
         for confnode_method in self.Controler.ConfNodeMethods:
             if "method" in confnode_method and confnode_method.get("shown",True):
                 button = GenBitmapTextButton(self.Editor,
-                    bitmap=GetBitmap(confnode_method.get("bitmap", "Unknown")), 
+                    bitmap=GetBitmap(confnode_method.get("bitmap", "Unknown")),
                     label=confnode_method["name"], style=wx.NO_BORDER)
                 button.SetFont(normal_bt_font)
                 button.SetToolTipString(confnode_method["tooltip"])
@@ -305,7 +300,7 @@
                     button.Disable()
                 msizer.AddWindow(button, flag=wx.ALIGN_CENTER)
         return msizer
-    
+
     def GenerateSizerElements(self, sizer, elements, path, clean = True):
         if clean:
             sizer.Clear(True)
@@ -321,44 +316,44 @@
                 label = _(name)
                 if value is not None:
                     label += " - %s" % _(value)
-                staticbox = wx.StaticBox(self.ParamsEditor, 
+                staticbox = wx.StaticBox(self.ParamsEditor,
                       label=_(label), size=wx.Size(10, 0))
                 staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
                 if first:
-                    sizer.AddSizer(staticboxsizer, border=5, 
+                    sizer.AddSizer(staticboxsizer, border=5,
                           flag=wx.GROW|wx.TOP|wx.BOTTOM)
                 else:
-                    sizer.AddSizer(staticboxsizer, border=5, 
+                    sizer.AddSizer(staticboxsizer, border=5,
                           flag=wx.GROW|wx.BOTTOM)
-                self.GenerateSizerElements(staticboxsizer, 
-                                           element_infos["children"], 
+                self.GenerateSizerElements(staticboxsizer,
+                                           element_infos["children"],
                                            element_path)
             else:
                 boxsizer = wx.FlexGridSizer(cols=3, rows=1)
                 boxsizer.AddGrowableCol(1)
                 if first:
-                    sizer.AddSizer(boxsizer, border=5, 
+                    sizer.AddSizer(boxsizer, border=5,
                           flag=wx.GROW|wx.ALL)
                 else:
-                    sizer.AddSizer(boxsizer, border=5, 
+                    sizer.AddSizer(boxsizer, border=5,
                           flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
-                
+
                 staticbitmap = GenStaticBitmap(ID=-1, bitmapname=element_infos["name"],
                     name="%s_bitmap"%element_infos["name"], parent=self.ParamsEditor,
                     pos=wx.Point(0, 0), size=wx.Size(24, 24), style=0)
                 boxsizer.AddWindow(staticbitmap, border=5, flag=wx.RIGHT)
-                
-                statictext = wx.StaticText(self.ParamsEditor, 
+
+                statictext = wx.StaticText(self.ParamsEditor,
                       label="%s:"%_(element_infos["name"]))
-                boxsizer.AddWindow(statictext, border=5, 
+                boxsizer.AddWindow(statictext, border=5,
                       flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT)
-                
+
                 if isinstance(element_infos["type"], types.ListType):
                     if isinstance(element_infos["value"], types.TupleType):
                         browse_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
                         boxsizer.AddSizer(browse_boxsizer)
-                        
-                        textctrl = wx.TextCtrl(self.ParamsEditor, 
+
+                        textctrl = wx.TextCtrl(self.ParamsEditor,
                               size=wx.Size(275, -1), style=wx.TE_READONLY)
                         if element_infos["value"] is not None:
                             textctrl.SetValue(element_infos["value"][0])
@@ -366,19 +361,19 @@
                         else:
                             value_infos = None
                         browse_boxsizer.AddWindow(textctrl)
-                        
-                        button = wx.Button(self.ParamsEditor, 
+
+                        button = wx.Button(self.ParamsEditor,
                               label="...", size=wx.Size(25, 25))
                         browse_boxsizer.AddWindow(button)
-                        button.Bind(wx.EVT_BUTTON, 
-                                    self.GetBrowseCallBackFunction(element_infos["name"], textctrl, element_infos["type"], 
-                                                                   value_infos, element_path), 
+                        button.Bind(wx.EVT_BUTTON,
+                                    self.GetBrowseCallBackFunction(element_infos["name"], textctrl, element_infos["type"],
+                                                                   value_infos, element_path),
                                     button)
                     else:
-                        combobox = wx.ComboBox(self.ParamsEditor, 
+                        combobox = wx.ComboBox(self.ParamsEditor,
                               size=wx.Size(300, -1), style=wx.CB_READONLY)
                         boxsizer.AddWindow(combobox)
-                        
+
                         if element_infos["use"] == "optional":
                             combobox.Append("")
                         if len(element_infos["type"]) > 0 and isinstance(element_infos["type"][0], types.TupleType):
@@ -386,8 +381,8 @@
                                 combobox.Append(choice)
                             name = element_infos["name"]
                             value = element_infos["value"]
-                        
-                            staticbox = wx.StaticBox(self.ParamsEditor, 
+
+                            staticbox = wx.StaticBox(self.ParamsEditor,
                                   label="%s - %s"%(_(name), _(value)), size=wx.Size(10, 0))
                             staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
                             sizer.AddSizer(staticboxsizer, border=5, flag=wx.GROW|wx.BOTTOM)
@@ -402,7 +397,7 @@
                         else:
                             combobox.SetStringSelection(element_infos["value"])
                         combobox.Bind(wx.EVT_COMBOBOX, callback, combobox)
-                
+
                 elif isinstance(element_infos["type"], types.DictType):
                     scmin = -(2**31)
                     scmax = 2**31-1
@@ -410,50 +405,50 @@
                         scmin = element_infos["type"]["min"]
                     if "max" in element_infos["type"]:
                         scmax = element_infos["type"]["max"]
-                    spinctrl = wx.SpinCtrl(self.ParamsEditor, 
+                    spinctrl = wx.SpinCtrl(self.ParamsEditor,
                           size=wx.Size(300, -1), style=wx.SP_ARROW_KEYS|wx.ALIGN_RIGHT)
                     spinctrl.SetRange(scmin, scmax)
                     boxsizer.AddWindow(spinctrl)
                     if element_infos["value"] is not None:
                         spinctrl.SetValue(element_infos["value"])
-                    spinctrl.Bind(wx.EVT_SPINCTRL, 
+                    spinctrl.Bind(wx.EVT_SPINCTRL,
                                   self.GetTextCtrlCallBackFunction(spinctrl, element_path),
                                   spinctrl)
-                
+
                 else:
                     if element_infos["type"] == "boolean":
                         checkbox = wx.CheckBox(self.ParamsEditor, size=wx.Size(17, 25))
                         boxsizer.AddWindow(checkbox)
                         if element_infos["value"] is not None:
                             checkbox.SetValue(element_infos["value"])
-                        checkbox.Bind(wx.EVT_CHECKBOX, 
-                                      self.GetCheckBoxCallBackFunction(checkbox, element_path), 
+                        checkbox.Bind(wx.EVT_CHECKBOX,
+                                      self.GetCheckBoxCallBackFunction(checkbox, element_path),
                                       checkbox)
-                    
+
                     elif element_infos["type"] in ["unsignedLong", "long","integer"]:
                         if element_infos["type"].startswith("unsigned"):
                             scmin = 0
                         else:
                             scmin = -(2**31)
                         scmax = 2**31-1
-                        spinctrl = wx.SpinCtrl(self.ParamsEditor, 
+                        spinctrl = wx.SpinCtrl(self.ParamsEditor,
                               size=wx.Size(300, -1), style=wx.SP_ARROW_KEYS|wx.ALIGN_RIGHT)
                         spinctrl.SetRange(scmin, scmax)
                         boxsizer.AddWindow(spinctrl)
                         if element_infos["value"] is not None:
                             spinctrl.SetValue(element_infos["value"])
-                        spinctrl.Bind(wx.EVT_SPINCTRL, 
-                                      self.GetTextCtrlCallBackFunction(spinctrl, element_path), 
+                        spinctrl.Bind(wx.EVT_SPINCTRL,
+                                      self.GetTextCtrlCallBackFunction(spinctrl, element_path),
                                       spinctrl)
-                    
+
                     else:
                         choices = self.ParentWindow.GetConfigEntry(element_path, [""])
-                        textctrl = TextCtrlAutoComplete(name=element_infos["name"], 
-                                                        parent=self.ParamsEditor, 
-                                                        choices=choices, 
+                        textctrl = TextCtrlAutoComplete(name=element_infos["name"],
+                                                        parent=self.ParamsEditor,
+                                                        choices=choices,
                                                         element_path=element_path,
                                                         size=wx.Size(300, -1))
-                        
+
                         boxsizer.AddWindow(textctrl)
                         if element_infos["value"] is not None:
                             textctrl.ChangeValue(str(element_infos["value"]))
@@ -461,8 +456,8 @@
                         textctrl.Bind(wx.EVT_TEXT_ENTER, callback)
                         textctrl.Bind(wx.EVT_KILL_FOCUS, callback)
             first = False
-    
-    
+
+
     def GetItemChannelChangedFunction(self, dir):
         def OnConfNodeTreeItemChannelChanged(event):
             confnode_IECChannel = self.Controler.BaseParams.getIEC_Channel()
@@ -471,28 +466,28 @@
             wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE)
             event.Skip()
         return OnConfNodeTreeItemChannelChanged
-    
+
     def SetConfNodeParamsAttribute(self, *args, **kwargs):
         res, StructChanged = self.Controler.SetParamsAttribute(*args, **kwargs)
         if StructChanged and self.ParamsEditor is not None:
             wx.CallAfter(self.RefreshConfNodeParamsSizer)
         wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU)
         return res
-    
+
     def GetButtonCallBackFunction(self, method, push=False):
         """ Generate the callbackfunc for a given confnode method"""
         def OnButtonClick(event):
-            # Disable button to prevent re-entrant call 
+            # Disable button to prevent re-entrant call
             event.GetEventObject().Disable()
             # Call
             getattr(self.Controler,method)()
-            # Re-enable button 
+            # Re-enable button
             event.GetEventObject().Enable()
-            
+
             if not push:
                 event.Skip()
         return OnButtonClick
-    
+
     def GetChoiceCallBackFunction(self, choicectrl, path):
         def OnChoiceChanged(event):
             res = self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
@@ -501,14 +496,14 @@
             choicectrl.SetStringSelection(res)
             event.Skip()
         return OnChoiceChanged
-    
+
     def GetChoiceContentCallBackFunction(self, choicectrl, staticboxsizer, path):
         def OnChoiceContentChanged(event):
             res = self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
             wx.CallAfter(self.RefreshConfNodeParamsSizer)
             event.Skip()
         return OnChoiceContentChanged
-    
+
     def GetTextCtrlCallBackFunction(self, textctrl, path, refresh=False):
         def OnTextCtrlChanged(event):
             res = self.SetConfNodeParamsAttribute(path, textctrl.GetValue())
@@ -522,14 +517,14 @@
                 wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
             event.Skip()
         return OnTextCtrlChanged
-    
+
     def GetCheckBoxCallBackFunction(self, chkbx, path):
         def OnCheckBoxChanged(event):
             res = self.SetConfNodeParamsAttribute(path, chkbx.IsChecked())
             chkbx.SetValue(res)
             event.Skip()
         return OnCheckBoxChanged
-    
+
     def GetBrowseCallBackFunction(self, name, textctrl, library, value_infos, path):
         infos = [value_infos]
         def OnBrowseButton(event):
@@ -541,7 +536,7 @@
             dialog.Destroy()
             event.Skip()
         return OnBrowseButton
-    
+
     def RefreshScrollbars(self):
         self.ParamsEditor.GetBestSize()
         xstart, ystart = self.ParamsEditor.GetViewStart()
@@ -550,17 +545,17 @@
         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
         self.ParamsEditor.Scroll(posx, posy)
-        self.ParamsEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
+        self.ParamsEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
-    
+
     def OnParamsEditorResize(self, event):
         self.RefreshScrollbars()
         event.Skip()
-    
+
     def OnParamsEditorScroll(self, event):
         control = self.ParamsEditor.FindFocus()
         if isinstance(control, TextCtrlAutoComplete):
             control.DismissListBox()
             self.Refresh()
         event.Skip()
-    
+
--- a/editors/DebugViewer.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/editors/DebugViewer.py	Wed Oct 21 15:00:32 2015 +0100
@@ -246,7 +246,7 @@
         if self.DataProducer is not None:
             self.DataProducer.ReleaseDebugIECVariable(iec_path)
     
-    def NewDataAvailable(self, ticks, *args, **kwargs):
+    def NewDataAvailable(self, ticks):
         """
         Called by DataProducer for each tick captured
         @param tick: PLC tick captured
@@ -267,19 +267,19 @@
             # two refresh has expired
             if gettime() - self.LastRefreshTime > REFRESH_PERIOD and \
                DEBUG_REFRESH_LOCK.acquire(False):
-                self.StartRefreshing(*args, **kwargs)
+                self.StartRefreshing()
             
             # If common lock wasn't acquired for any reason, restart last
             # refresh timer
             else:
-                self.StartLastRefreshTimer(*args, **kwargs)
+                self.StartLastRefreshTimer()
         
         # In the case that DebugViewer isn't visible on screen and has already
         # acquired common refresh lock, reset DebugViewer
         elif not self.IsShown() and self.HasAcquiredLock:
             DebugViewer.RefreshNewData(self)
     
-    def ShouldRefresh(self, *args, **kwargs):
+    def ShouldRefresh(self):
         """
         Callback function called when last refresh timer expired
         All parameters are passed to refresh function
@@ -289,13 +289,13 @@
             
             # Try to acquire common refresh lock
             if DEBUG_REFRESH_LOCK.acquire(False):
-                self.StartRefreshing(*args, **kwargs)
+                self.StartRefreshing()
             
             # Restart last refresh timer if common refresh lock acquired failed
             else:
-                self.StartLastRefreshTimer(*args, **kwargs)
-    
-    def StartRefreshing(self, *args, **kwargs):
+                self.StartLastRefreshTimer()
+    
+    def StartRefreshing(self):
         """
         Called to initiate a refresh of DebugViewer
         All parameters are passed to refresh function
@@ -311,9 +311,9 @@
         self.Inhibit(True)
         
         # Initiate DebugViewer refresh
-        wx.CallAfter(self.RefreshNewData, *args, **kwargs)
-    
-    def StartLastRefreshTimer(self, *args, **kwargs):
+        wx.CallAfter(self.RefreshNewData)
+    
+    def StartLastRefreshTimer(self):
         """
         Called to start last refresh timer for the minimum time between 2
         refresh
@@ -321,11 +321,11 @@
         """
         self.TimerAccessLock.acquire()
         self.LastRefreshTimer = Timer(
-            REFRESH_PERIOD, self.ShouldRefresh, args, kwargs)
+            REFRESH_PERIOD, self.ShouldRefresh)
         self.LastRefreshTimer.start()
         self.TimerAccessLock.release()
     
-    def RefreshNewData(self, *args, **kwargs):
+    def RefreshNewData(self):
         """
         Called to refresh DebugViewer according to values received by data
         consumers
--- a/editors/Viewer.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/editors/Viewer.py	Wed Oct 21 15:00:32 2015 +0100
@@ -1114,7 +1114,7 @@
         self.ElementRefreshList.append(element)
         self.ElementRefreshList_lock.release()
 
-    def NewDataAvailable(self, ticks, *args, **kwargs):
+    def NewDataAvailable(self, ticks):
         if self.IsShown():
             refresh_rect = None
             self.ElementRefreshList_lock.acquire()
--- a/py_ext/PythonFileCTNMixin.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/py_ext/PythonFileCTNMixin.py	Wed Oct 21 15:00:32 2015 +0100
@@ -7,7 +7,7 @@
 from PythonEditor import PythonEditor
 
 class PythonFileCTNMixin(CodeFile):
-    
+
     CODEFILE_NAME = "PyFile"
     SECTIONS_NAMES = [
         "globals",
@@ -16,45 +16,45 @@
         "start",
         "stop"]
     EditorType = PythonEditor
-    
+
     def __init__(self):
         CodeFile.__init__(self)
-        
+
         filepath = self.PythonFileName()
-        
+
         if os.path.isfile(filepath):
             PythonParser = GenerateParserFromXSD(
-                os.path.join(os.path.dirname(__file__), "py_ext_xsd.xsd")) 
-            
+                os.path.join(os.path.dirname(__file__), "py_ext_xsd.xsd"))
+
             xmlfile = open(filepath, 'r')
             pythonfile_xml = xmlfile.read()
             xmlfile.close()
-            
+
             pythonfile_xml = pythonfile_xml.replace(
-                'xmlns="http://www.w3.org/2001/XMLSchema"', 
+                'xmlns="http://www.w3.org/2001/XMLSchema"',
                 'xmlns:xhtml="http://www.w3.org/1999/xhtml"')
             for cre, repl in [
                 (re.compile("(?<!<xhtml:p>)(?:<!\[CDATA\[)"), "<xhtml:p><![CDATA["),
                 (re.compile("(?:]]>)(?!</xhtml:p>)"), "]]></xhtml:p>")]:
                 pythonfile_xml = cre.sub(repl, pythonfile_xml)
-            
+
             try:
                 python_code, error = PythonParser.LoadXMLString(pythonfile_xml)
-                if error is None:    
+                if error is None:
                     self.CodeFile.globals.setanyText(python_code.getanyText())
                     os.remove(filepath)
                     self.CreateCodeFileBuffer(False)
                     self.OnCTNSave()
             except Exception, exc:
                 error = unicode(exc)
-            
+
             if error is not None:
                 self.GetCTRoot().logger.write_error(
-                    _("Couldn't import old %s file.") % CTNName)
-    
+                    _("Couldn't import old %s file.") % self.CTNName())
+
     def CodeFileName(self):
         return os.path.join(self.CTNPath(), "pyfile.xml")
-    
+
     def PythonFileName(self):
         return os.path.join(self.CTNPath(), "py_ext.xml")
 
@@ -64,30 +64,46 @@
         return self.PreSectionsTexts.get(section,"") + "\n" + \
                getattr(self.CodeFile, section).getanyText() + "\n" + \
                self.PostSectionsTexts.get(section,"")
-        
 
     def CTNGenerate_C(self, buildpath, locations):
-        # location string for that CTN 
-        location_str = "_".join(map(lambda x:str(x), 
+        # location string for that CTN
+        location_str = "_".join(map(lambda x:str(x),
                                 self.GetCurrentLocation()))
         configname = self.GetCTRoot().GetProjectConfigNames()[0]
-        
-        
+
+        pyextname = self.CTNName()
+        varinfos = map(lambda variable : {
+                    "name": variable.getname(),
+                    "desc" : repr(variable.getdesc()),
+                    "onchangecode" : '"'+variable.getonchange()+\
+                                         "('"+variable.getname()+"')\"" \
+                                     if variable.getonchange() else '""',
+                    "onchange" : repr(variable.getonchange()) \
+                                 if variable.getonchange() else None,
+                    "opts" : repr(variable.getopts()),
+                    "configname" : configname.upper(),
+                    "uppername" : variable.getname().upper(),
+                    "IECtype" : variable.gettype(),
+                    "pyextname" :pyextname},
+                    self.CodeFile.variables.variable)
         # python side PLC global variables access stub
         globalstubs = "\n".join(["""\
 _%(name)s_ctype, _%(name)s_unpack, _%(name)s_pack = \\
     TypeTranslator["%(IECtype)s"]
 _PySafeGetPLCGlob_%(name)s = PLCBinary.__SafeGetPLCGlob_%(name)s
 _PySafeGetPLCGlob_%(name)s.restype = None
-_PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)] 
+_PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)]
 _PySafeSetPLCGlob_%(name)s = PLCBinary.__SafeSetPLCGlob_%(name)s
 _PySafeSetPLCGlob_%(name)s.restype = None
 _PySafeSetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)]
-""" % { "name": variable.getname(),
-        "configname": configname.upper(),
-        "uppername": variable.getname().upper(),
-        "IECtype": variable.gettype()}
-            for variable in self.CodeFile.variables.variable])
+_%(pyextname)sGlobalsDesc.append((
+    "%(name)s",
+    "%(IECtype)s",
+    %(desc)s,
+    %(onchange)s,
+    %(opts)s))
+""" % varinfo
+      for varinfo in varinfos])
 
         # Runtime calls (start, stop, init, and cleanup)
         rtcalls = ""
@@ -101,36 +117,41 @@
                 else:
                     rtcalls += "    pass\n\n"
 
-        globalsection = self.GetSection("globals")      
+        globalsection = self.GetSection("globals")
 
         PyFileContent = """\
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 ## Code generated by Beremiz python mixin confnode
-##        
-        
+##
+
 ## Code for PLC global variable access
 from targets.typemapping import TypeTranslator
-import ctypes 
+import ctypes
+_%(pyextname)sGlobalsDesc = []
+__ext_name__ = "%(pyextname)s"
+PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc ))
 %(globalstubs)s
-        
+
 ## User code in "global" scope
 %(globalsection)s
 
 ## Beremiz python runtime calls
 %(rtcalls)s
 
+del __ext_name__
+
 """ % locals()
 
         # write generated content to python file
-        runtimefile_path = os.path.join(buildpath, 
+        runtimefile_path = os.path.join(buildpath,
             "runtime_%s.py"%location_str)
         runtimefile = open(runtimefile_path, 'w')
         runtimefile.write(PyFileContent.encode('utf-8'))
         runtimefile.close()
 
         # C code for safe global variables access
-        
+
         vardecfmt = """\
 extern  __IEC_%(IECtype)s_t %(configname)s__%(uppername)s;
 IEC_%(IECtype)s __%(name)s_rbuffer = __INIT_%(IECtype)s;
@@ -151,6 +172,11 @@
 }
 
 """
+
+        vardeconchangefmt = """\
+PYTHON_POLL* __%(name)s_notifier;
+"""
+
         varretfmt = """\
     if(!AtomicCompareExchange(&__%(name)s_wlock, 0, 1)){
         if(__%(name)s_wbuffer_written == 1){
@@ -159,34 +185,51 @@
         }
         AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
     }
-""" 
+"""
         varpubfmt = """\
     if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
-        __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+        __%(name)s_rbuffer = __GET_VAR(%(configname)s__%(uppername)s);
         AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
     }
-""" 
-
-        var_str = map("\n".join, zip(*[
-            map(lambda f : f % varinfo,
-                (vardecfmt, varretfmt, varpubfmt))
-                for varinfo in map(lambda variable : {
-                    "name": variable.getname(),
-                    "configname": configname.upper(),
-                    "uppername": variable.getname().upper(),
-                    "IECtype": variable.gettype()},
-                    self.CodeFile.variables.variable)]))
-        if len(var_str) > 0:
-            vardec, varret, varpub = var_str
-        else:
-            vardec = varret = varpub = ""
-        
+"""
+
+        varpubonchangefmt = """\
+    if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
+        IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s);
+        if(__%(name)s_rbuffer != tmp){
+            __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+            PYTHON_POLL_body__(__%(name)s_notifier);
+        }
+        AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
+    }
+"""
+        varinitonchangefmt = """\
+    __%(name)s_notifier = __GET_GLOBAL_ON%(uppername)sCHANGE();
+    __SET_VAR(__%(name)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE));
+    __SET_VAR(__%(name)s_notifier->,CODE,,__STRING_LITERAL(%(onchangelen)d,%(onchangecode)s));
+"""
+        vardec = "\n".join([(vardecfmt + vardeconchangefmt
+                             if varinfo["onchange"] else vardecfmt)% varinfo
+                            for varinfo in varinfos])
+        varret = "\n".join([varretfmt % varinfo for varinfo in varinfos])
+        varpub = "\n".join([(varpubonchangefmt if varinfo["onchange"] else
+                             varpubfmt) % varinfo
+                            for varinfo in varinfos])
+        varinit = "\n".join([varinitonchangefmt % dict(
+                                onchangelen = len(varinfo["onchangecode"]),**varinfo)
+                            for varinfo in varinfos if varinfo["onchange"]])
+
+        # TODO : use config name obtained from model instead of default
+        # "config.h". User cannot change config name, but project imported
+        # or created in older beremiz vesion could use different name.
         PyCFileContent = """\
-/* 
- * Code generated by Beremiz py_ext confnode 
+/*
+ * Code generated by Beremiz py_ext confnode
  * for safe global variables access
  */
 #include "iec_types_all.h"
+#include "POUS.h"
+#include "config.h"
 #include "beremiz.h"
 
 /* User variables reference */
@@ -194,6 +237,7 @@
 
 /* Beremiz confnode functions */
 int __init_%(location_str)s(int argc,char **argv){
+%(varinit)s
     return 0;
 }
 
@@ -208,15 +252,15 @@
 %(varpub)s
 }
 """ % locals()
-        
+
         Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c"%location_str)
         pycfile = open(Gen_PyCfile_path,'w')
         pycfile.write(PyCFileContent)
         pycfile.close()
-        
+
         matiec_flags = '"-l -p -I%s"'%os.path.abspath(
             self.GetCTRoot().GetIECLibPath())
-        
+
         return ([(Gen_PyCfile_path, matiec_flags)],
                 "",
                 True,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/NevowServer.py	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,173 @@
+import os
+from nevow import rend, appserver, inevow, tags, loaders, athena
+from nevow.page import renderer
+from twisted.python import util
+from twisted.internet import reactor
+
+xhtml_header = '''<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+'''
+
+WorkingDir = None
+
+class PLCHMI(athena.LiveElement):
+
+    initialised = False
+
+    def HMIinitialised(self, result):
+        self.initialised = True
+
+    def HMIinitialisation(self):
+        self.HMIinitialised(None)
+
+class DefaultPLCStartedHMI(PLCHMI):
+    docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[
+                                            tags.h1["PLC IS NOW STARTED"],
+                                            ])
+
+class PLCStoppedHMI(PLCHMI):
+    docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[
+                                            tags.h1["PLC IS STOPPED"],
+                                            ])
+
+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')),
+                                                    ]])
+
+    def __init__(self, *a, **kw):
+        athena.LiveElement.__init__(self, *a, **kw)
+        self.pcl_state = False
+        self.HMI = None
+        self.resetPLCStartedHMI()
+
+    def setPLCState(self, state):
+        self.pcl_state = state
+        if self.HMI is not None:
+            self.callRemote('updateHMI')
+
+    def setPLCStartedHMI(self, hmi):
+        self.PLCStartedHMIClass = hmi
+
+    def resetPLCStartedHMI(self):
+        self.PLCStartedHMIClass = DefaultPLCStartedHMI
+
+    def getHMI(self):
+        return self.HMI
+
+    def HMIexec(self, function, *args, **kwargs):
+        if self.HMI is not None:
+            getattr(self.HMI, function, lambda:None)(*args, **kwargs)
+    athena.expose(HMIexec)
+
+    def resetHMI(self):
+        self.HMI = None
+
+    def PLCElement(self, ctx, data):
+        return self.getPLCElement()
+    renderer(PLCElement)
+
+    def getPLCElement(self):
+        self.detachFragmentChildren()
+        if self.pcl_state:
+            f = self.PLCStartedHMIClass()
+        else:
+            f = PLCStoppedHMI()
+        f.setFragmentParent(self)
+        self.HMI = f
+        return f
+    athena.expose(getPLCElement)
+
+    def detachFragmentChildren(self):
+        for child in self.liveFragmentChildren[:]:
+            child.detach()
+
+class WebInterface(athena.LivePage):
+
+    docFactory = loaders.stan([tags.raw(xhtml_header),
+                                tags.html(xmlns="http://www.w3.org/1999/xhtml")[
+                                    tags.head(render=tags.directive('liveglue')),
+                                    tags.body[
+                                        tags.div[
+                                                tags.div( render = tags.directive( "MainPage" ))
+                                                ]]]])
+    MainPage = MainPage()
+    PLCHMI = PLCHMI
+
+    def __init__(self, plcState=False, *a, **kw):
+        super(WebInterface, self).__init__(*a, **kw)
+        self.jsModules.mapping[u'WebInterface'] = util.sibpath(__file__, 'webinterface.js')
+        self.plcState = plcState
+        self.MainPage.setPLCState(plcState)
+
+    def getHMI(self):
+        return self.MainPage.getHMI()
+
+    def LoadHMI(self, hmi, jsmodules):
+        for name, path in jsmodules.iteritems():
+            self.jsModules.mapping[name] = os.path.join(WorkingDir, path)
+        self.MainPage.setPLCStartedHMI(hmi)
+
+    def UnLoadHMI(self):
+        self.MainPage.resetPLCStartedHMI()
+
+    def PLCStarted(self):
+        self.plcState = True
+        self.MainPage.setPLCState(True)
+
+    def PLCStopped(self):
+        self.plcState = False
+        self.MainPage.setPLCState(False)
+
+    def renderHTTP(self, ctx):
+        """
+        Force content type to fit with SVG
+        """
+        req = inevow.IRequest(ctx)
+        req.setHeader('Content-type', 'application/xhtml+xml')
+        return super(WebInterface, self).renderHTTP(ctx)
+
+    def render_MainPage(self, ctx, data):
+        f = self.MainPage
+        f.setFragmentParent(self)
+        return ctx.tag[f]
+
+    def child_(self, ctx):
+        self.MainPage.detachFragmentChildren()
+        return WebInterface(plcState=self.plcState)
+
+    def beforeRender(self, ctx):
+        d = self.notifyOnDisconnect()
+        d.addErrback(self.disconnected)
+
+    def disconnected(self, reason):
+        self.MainPage.resetHMI()
+        #print reason
+        #print "We will be called back when the client disconnects"
+
+def RegisterWebsite(port):
+    website = WebInterface()
+    site = appserver.NevowSite(website)
+
+    listening = False
+    reactor.listenTCP(port, site)
+    print "Http interface port :",port
+    return website
+
+class statuslistener:
+    def __init__(self, site):
+        self.oldstate = None
+        self.site = site
+
+    def listen(self, state):
+        if state != self.oldstate:
+            action = {'Started': self.site.PLCStarted,
+                      'Stopped': self.site.PLCStopped}.get(state, None)
+            if action is not None: action ()
+            self.oldstate = state
+
+def website_statuslistener_factory(site):
+    return statuslistener(site).listen
--- a/runtime/PLCObject.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/runtime/PLCObject.py	Wed Oct 21 15:00:32 2015 +0100
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 #This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
+#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
 #
 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
 #
@@ -23,9 +23,10 @@
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 import Pyro.core as pyro
-from threading import Timer, Thread, Lock, Semaphore
+from threading import Timer, Thread, Lock, Semaphore, Event
 import ctypes, os, commands, types, sys
 from targets.typemapping import LogLevelsDefault, LogLevelsCount, TypeTranslator, UnpackDebugBuffer
+from time import time
 
 
 if os.name in ("nt", "ce"):
@@ -50,13 +51,12 @@
     sys.stdout.flush()
 
 class PLCObject(pyro.ObjBase):
-    _Idxs = []
-    def __init__(self, workingdir, daemon, argv, statuschange, evaluator, website):
+    def __init__(self, workingdir, daemon, argv, statuschange, evaluator, pyruntimevars):
         pyro.ObjBase.__init__(self)
         self.evaluator = evaluator
         self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
         self.workingdir = workingdir
-        self.PLCStatus = "Stopped"
+        self.PLCStatus = "Empty"
         self.PLClibraryHandle = None
         self.PLClibraryLock = Lock()
         self.DummyIteratorLock = None
@@ -65,10 +65,15 @@
         self.daemon = daemon
         self.statuschange = statuschange
         self.hmi_frame = None
-        self.website = website
+        self.pyruntimevars = pyruntimevars
         self._loading_error = None
         self.python_runtime_vars = None
-        
+        self.TraceThread = None
+        self.TraceLock = Lock()
+        self.TraceWakeup = Event()
+        self.Traces = []
+
+    def AutoLoad(self):
         # Get the last transfered PLC if connector must be restart
         try:
             self.CurrentPLCFilename=open(
@@ -81,7 +86,8 @@
 
     def StatusChange(self):
         if self.statuschange is not None:
-            self.statuschange(self.PLCStatus)
+            for callee in self.statuschange:
+                callee(self.PLCStatus)
 
     def LogMessage(self, *args):
         if len(args) == 2:
@@ -107,7 +113,7 @@
         tv_nsec = ctypes.c_uint32()
         if self._GetLogMessage is not None:
             maxsz = len(self._log_read_buffer)-1
-            sz = self._GetLogMessage(level, msgid, 
+            sz = self._GetLogMessage(level, msgid,
                 self._log_read_buffer, maxsz,
                 ctypes.byref(tick),
                 ctypes.byref(tv_sec),
@@ -131,52 +137,56 @@
         Load PLC library
         Declare all functions, arguments and return values
         """
+        md5 = open(self._GetMD5FileName(), "r").read()
         try:
             self._PLClibraryHandle = dlopen(self._GetLibFileName())
             self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
-    
+
+            self.PLC_ID = ctypes.c_char_p.in_dll(self.PLClibraryHandle, "PLC_ID")
+            if len(md5) == 32 : 
+                self.PLC_ID.value = md5 
+
             self._startPLC = self.PLClibraryHandle.startPLC
             self._startPLC.restype = ctypes.c_int
             self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
-            
+
             self._stopPLC_real = self.PLClibraryHandle.stopPLC
             self._stopPLC_real.restype = None
-            
+
             self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None)
             if self._PythonIterator is not None:
                 self._PythonIterator.restype = ctypes.c_char_p
                 self._PythonIterator.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
-                
+
                 self._stopPLC = self._stopPLC_real
             else:
                 # If python confnode is not enabled, we reuse _PythonIterator
-                # as a call that block pythonthread until StopPLC 
-                self.PythonIteratorLock = Lock()
-                self.PythonIteratorLock.acquire()
+                # as a call that block pythonthread until StopPLC
+                self.PlcStopping = Event()
                 def PythonIterator(res, blkid):
-                    self.PythonIteratorLock.acquire()
-                    self.PythonIteratorLock.release()
+                    self.PlcStopping.clear()
+                    self.PlcStopping.wait()
                     return None
                 self._PythonIterator = PythonIterator
-                
+
                 def __StopPLC():
                     self._stopPLC_real()
-                    self.PythonIteratorLock.release()
+                    self.PlcStopping.set()
                 self._stopPLC = __StopPLC
-            
-    
+
+
             self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables
             self._ResetDebugVariables.restype = None
-    
+
             self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
             self._RegisterDebugVariable.restype = None
             self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p]
-    
+
             self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
             self._FreeDebugData.restype = None
-            
+
             self._GetDebugData = self.PLClibraryHandle.GetDebugData
-            self._GetDebugData.restype = ctypes.c_int  
+            self._GetDebugData.restype = ctypes.c_int
             self._GetDebugData.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_void_p)]
 
             self._suspendDebug = self.PLClibraryHandle.suspendDebug
@@ -233,7 +243,7 @@
         self._suspendDebug = lambda x:-1
         self._resumeDebug = lambda:None
         self._PythonIterator = lambda:""
-        self._GetLogCount = None 
+        self._GetLogCount = None
         self._LogMessage = lambda l,m,s:PLCprint("OFF LOG :"+m)
         self._GetLogMessage = None
         self.PLClibraryHandle = None
@@ -241,29 +251,25 @@
         if getattr(self,"_PLClibraryHandle",None) is not None:
             dlclose(self._PLClibraryHandle)
             self._PLClibraryHandle = None
-        
+
         self.PLClibraryLock.release()
         return False
 
     def PythonRuntimeCall(self, methodname):
-        """ 
-        Calls init, start, stop or cleanup method provided by 
+        """
+        Calls init, start, stop or cleanup method provided by
         runtime python files, loaded when new PLC uploaded
         """
         for method in self.python_runtime_vars.get("_runtime_%s"%methodname, []):
             res,exp = self.evaluator(method)
-            if exp is not None: 
+            if exp is not None:
                 self.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
 
     def PythonRuntimeInit(self):
         MethodNames = ["init", "start", "stop", "cleanup"]
         self.python_runtime_vars = globals().copy()
-        self.python_runtime_vars["WorkingDir"] = self.workingdir
-        self.python_runtime_vars["website"] = self.website
-        for methodname in MethodNames :
-            self.python_runtime_vars["_runtime_%s"%methodname] = []
-        self.python_runtime_vars["PLCObject"] = self
-        self.python_runtime_vars["PLCBinary"] = self.PLClibraryHandle
+        self.python_runtime_vars.update(self.pyruntimevars)
+
         class PLCSafeGlobals:
             def __getattr__(_self, name):
                 try :
@@ -280,46 +286,50 @@
                     raise KeyError("Try to set unknown shared global variable : %s"%name)
                 v = self.python_runtime_vars["_"+name+"_pack"](t,value)
                 self.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v))
-        self.python_runtime_vars["PLCGlobals"] = PLCSafeGlobals()
+
+        self.python_runtime_vars.update({
+            "PLCGlobals" : PLCSafeGlobals(),
+            "WorkingDir" : self.workingdir,
+            "PLCObject"  : self,
+            "PLCBinary"  : self.PLClibraryHandle,
+            "PLCGlobalsDesc" : []})
+
+        for methodname in MethodNames :
+            self.python_runtime_vars["_runtime_%s"%methodname] = []
+
         try:
-            for filename in os.listdir(self.workingdir):
+            filenames = os.listdir(self.workingdir)
+            filenames.sort()
+            for filename in filenames:
                 name, ext = os.path.splitext(filename)
                 if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
                     execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars)
-                    for methodname in MethodNames: 
+                    for methodname in MethodNames:
                         method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
                         if method is not None:
                             self.python_runtime_vars["_runtime_%s"%methodname].append(method)
         except:
             self.LogMessage(0,traceback.format_exc())
             raise
-            
+
         self.PythonRuntimeCall("init")
 
-        if self.website is not None:
-            self.website.PLCStarted()
 
 
     def PythonRuntimeCleanup(self):
         if self.python_runtime_vars is not None:
             self.PythonRuntimeCall("cleanup")
 
-        if self.website is not None:
-            self.website.PLCStopped()
-
         self.python_runtime_vars = None
 
     def PythonThreadProc(self):
-        self.PLCStatus = "Started"
-        self.StatusChange()
         self.StartSem.release()
-        self.PythonRuntimeCall("start")
         res,cmd,blkid = "None","None",ctypes.c_void_p()
         compile_cache={}
         while True:
             # print "_PythonIterator(", res, ")",
             cmd = self._PythonIterator(res,blkid)
-            FBID = blkid.value 
+            FBID = blkid.value
             # print " -> ", cmd, blkid
             if cmd is None:
                 break
@@ -330,7 +340,7 @@
                     AST = compile(cmd, '<plc>', 'eval')
                     compile_cache[FBID]=(cmd,AST)
                 result,exp = self.evaluator(eval,AST,self.python_runtime_vars)
-                if exp is not None: 
+                if exp is not None:
                     res = "#EXCEPTION : "+str(exp[1])
                     self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,
                         '\n'.join(traceback.format_exception(*exp))))
@@ -340,16 +350,16 @@
             except Exception,e:
                 res = "#EXCEPTION : "+str(e)
                 self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,str(e)))
-        self.PLCStatus = "Stopped"
-        self.StatusChange()
-        self.PythonRuntimeCall("stop")
-    
+
     def StartPLC(self):
         if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
             c_argv = ctypes.c_char_p * len(self.argv)
             error = None
             res = self._startPLC(len(self.argv),c_argv(*self.argv))
             if res == 0:
+                self.PLCStatus = "Started"
+                self.StatusChange()
+                self.PythonRuntimeCall("start")
                 self.StartSem=Semaphore(0)
                 self.PythonThread = Thread(target=self.PythonThreadProc)
                 self.PythonThread.start()
@@ -359,12 +369,19 @@
                 self.LogMessage(0,_("Problem starting PLC : error %d" % res))
                 self.PLCStatus = "Broken"
                 self.StatusChange()
-            
+
     def StopPLC(self):
         if self.PLCStatus == "Started":
             self.LogMessage("PLC stopped")
             self._stopPLC()
             self.PythonThread.join()
+            self.PLCStatus = "Stopped"
+            self.StatusChange()
+            self.PythonRuntimeCall("stop")
+            if self.TraceThread is not None :
+                self.TraceWakeup.set()
+                self.TraceThread.join()
+                self.TraceThread = None
             return True
         return False
 
@@ -382,7 +399,7 @@
 
     def GetPLCstatus(self):
         return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
-    
+
     def NewPLC(self, md5sum, data, extrafiles):
         if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
             NewFileName = md5sum + lib_ext
@@ -403,15 +420,15 @@
                         pass
             except:
                 pass
-                        
+
             try:
                 # Create new PLC file
                 open(os.path.join(self.workingdir,NewFileName),
                      'wb').write(data)
-        
+
                 # Store new PLC filename based on md5 key
                 open(self._GetMD5FileName(), "w").write(md5sum)
-        
+
                 # Then write the files
                 log = file(extra_files_log, "w")
                 for fname,fdata in extrafiles:
@@ -442,62 +459,102 @@
             last_md5 = open(self._GetMD5FileName(), "r").read()
             return last_md5 == MD5
         except:
-            return False
-    
-
-    
+            pass
+        return False
+
     def SetTraceVariablesList(self, idxs):
         """
-        Call ctype imported function to append 
+        Call ctype imported function to append
         these indexes to registred variables in PLC debugger
         """
         if idxs:
             # suspend but dont disable
             if self._suspendDebug(False) == 0:
                 # keep a copy of requested idx
-                self._Idxs = idxs[:]
                 self._ResetDebugVariables()
                 for idx,iectype,force in idxs:
                     if force !=None:
                         c_type,unpack_func, pack_func = \
                             TypeTranslator.get(iectype,
                                                     (None,None,None))
-                        force = ctypes.byref(pack_func(c_type,force)) 
+                        force = ctypes.byref(pack_func(c_type,force))
                     self._RegisterDebugVariable(idx, force)
+                self._TracesSwap()
                 self._resumeDebug()
         else:
             self._suspendDebug(True)
-            self._Idxs =  []
+
+    def _TracesPush(self, trace):
+        self.TraceLock.acquire()
+        lT = len(self.Traces)
+        if lT != 0 and lT * len(self.Traces[0]) > 1024 * 1024 :
+            self.Traces.pop(0)
+        self.Traces.append(trace)
+        self.TraceLock.release()
+
+    def _TracesSwap(self):
+        self.LastSwapTrace = time()
+        if self.TraceThread is None and self.PLCStatus == "Started":
+            self.TraceThread = Thread(target=self.TraceThreadProc)
+            self.TraceThread.start()
+        self.TraceLock.acquire()
+        Traces = self.Traces
+        self.Traces = []
+        self.TraceLock.release()
+        self.TraceWakeup.set()
+        return Traces
+
+    def _TracesAutoSuspend(self):
+        # TraceProc stops here if Traces not polled for 3 seconds
+        traces_age = time() - self.LastSwapTrace
+        if traces_age > 3:
+            self.TraceLock.acquire()
+            self.Traces = []
+            self.TraceLock.release()
+            self._suspendDebug(True) # Disable debugger
+            self.TraceWakeup.clear()
+            self.TraceWakeup.wait()
+            self._resumeDebug() # Re-enable debugger
+
+    def _TracesFlush(self):
+        self.TraceLock.acquire()
+        self.Traces = []
+        self.TraceLock.release()
 
     def GetTraceVariables(self):
-        """
-        Return a list of variables, corresponding to the list of required idx
-        """
-        if self.PLCStatus == "Started":
+        return self.PLCStatus, self._TracesSwap()
+
+    def TraceThreadProc(self):
+        """
+        Return a list of traces, corresponding to the list of required idx
+        """
+        while self.PLCStatus == "Started" :
             tick = ctypes.c_uint32()
             size = ctypes.c_uint32()
             buff = ctypes.c_void_p()
-            TraceVariables = None
+            TraceBuffer = None
             if self.PLClibraryLock.acquire(False):
                 if self._GetDebugData(ctypes.byref(tick),
                                       ctypes.byref(size),
                                       ctypes.byref(buff)) == 0:
                     if size.value:
-                        TraceVariables = UnpackDebugBuffer(buff, size.value, self._Idxs)
+                        TraceBuffer = ctypes.string_at(buff.value, size.value)
                     self._FreeDebugData()
                 self.PLClibraryLock.release()
-            if TraceVariables is not None:
-                return self.PLCStatus, tick.value, TraceVariables
-        return self.PLCStatus, None, []
-
-    def RemoteExec(self, script, **kwargs):
+            if TraceBuffer is not None:
+                self._TracesPush((tick.value, TraceBuffer))
+            self._TracesAutoSuspend()
+        self._TracesFlush()
+
+
+    def RemoteExec(self, script, *kwargs):
         try:
             exec script in kwargs
         except:
             e_type, e_value, e_traceback = sys.exc_info()
             line_no = traceback.tb_lineno(get_last_traceback(e_traceback))
-            return (-1, "RemoteExec script failed!\n\nLine %d: %s\n\t%s" % 
+            return (-1, "RemoteExec script failed!\n\nLine %d: %s\n\t%s" %
                         (line_no, e_value, script.splitlines()[line_no - 1]))
         return (0, kwargs.get("returnVal", None))
-    
-        
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/WampClient.py	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+#from twisted.python import log
+from autobahn.twisted import wamp
+from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
+from twisted.internet.defer import inlineCallbacks
+from autobahn.wamp import types
+from autobahn.wamp.serializer import MsgPackSerializer
+from twisted.internet.protocol import ReconnectingClientFactory
+import json
+
+_WampSession = None
+_PySrv = None
+
+ExposedCalls = ["StartPLC",
+                "StopPLC",
+                "ForceReload",
+                "GetPLCstatus",
+                "NewPLC",
+                "MatchMD5",
+                "SetTraceVariablesList",
+                "GetTraceVariables",
+                "RemoteExec",
+                "GetLogMessage",
+                "ResetLogCount",
+                ]
+
+SubscribedEvents = []
+
+DoOnJoin = []
+
+def GetCallee(name):
+    """ Get Callee or Subscriber corresponding to '.' spearated object path """
+    global _PySrv
+    names = name.split('.')
+    obj = _PySrv.plcobj
+    while names: obj = getattr(obj, names.pop(0))
+    return obj
+
+class WampSession(wamp.ApplicationSession):
+
+    @inlineCallbacks
+    def onJoin(self, details):
+        global _WampSession
+        _WampSession = self
+        ID = self.config.extra["ID"]
+        print 'WAMP session joined by :', ID
+        for name in ExposedCalls:
+            reg = yield self.register(GetCallee(name), '.'.join((ID,name)))
+
+        for name in SubscribedEvents:
+            reg = yield self.subscribe(GetCallee(name), name)
+
+        for func in DoOnJoin:
+            yield func(self)
+
+    def onLeave(self, details):
+        global _WampSession
+        _WampSession = None
+        print 'WAMP session left'
+
+class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
+    def clientConnectionFailed(self, connector, reason):
+        print("WAMP Client connection failed .. retrying ..")
+        self.retry(connector)
+    def clientConnectionLost(self, connector, reason):
+        print("WAMP Client connection lost .. retrying ..")
+        self.retry(connector)
+
+def LoadWampClientConf(wampconf):
+
+    WSClientConf = json.load(open(wampconf))
+    return WSClientConf
+
+def RegisterWampClient(wampconf):
+
+    WSClientConf = LoadWampClientConf(wampconf)
+
+    ## start logging to console
+    # log.startLogging(sys.stdout)
+
+    # create a WAMP application session factory
+    component_config = types.ComponentConfig(
+        realm = WSClientConf["realm"],
+        extra = {"ID":WSClientConf["ID"]})
+    session_factory = wamp.ApplicationSessionFactory(
+        config = component_config)
+    session_factory.session = WampSession
+
+    # create a WAMP-over-WebSocket transport client factory
+    transport_factory = ReconnectingWampWebSocketClientFactory(
+        session_factory,
+        url = WSClientConf["url"],
+        serializers = [MsgPackSerializer()],
+        debug = False,
+        debug_wamp = False)
+
+    # start the client from a Twisted endpoint
+    conn = connectWS(transport_factory)
+    print "WAMP client connecting to :",WSClientConf["url"]
+    return conn
+
+def GetSession():
+    global _WampSession
+    return _WampSession
+
+def SetServer(pysrv):
+    global _PySrv
+    _PySrv = pysrv
+
--- a/targets/Linux/plc_Linux_main.c	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/Linux/plc_Linux_main.c	Wed Oct 21 15:00:32 2015 +0100
@@ -119,7 +119,7 @@
 
     timer_create (CLOCK_REALTIME, &sigev, &PLC_timer);
     if(  __init(argc,argv) == 0 ){
-        PLC_SetTimer(Ttick,Ttick);
+        PLC_SetTimer(common_ticktime__,common_ticktime__);
 
         /* install signal handler for manual break */
         signal(SIGINT, catch_signal);
@@ -232,6 +232,14 @@
     pthread_mutex_lock(&python_mutex);
 }
 
+void InitRetain(void)
+{
+}
+
+void CleanupRetain(void)
+{
+}
+
 int CheckRetainBuffer(void)
 {
 	return 1;
--- a/targets/Win32/plc_Win32_main.c	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/Win32/plc_Win32_main.c	Wed Oct 21 15:00:32 2015 +0100
@@ -76,8 +76,6 @@
     BOOL tmp;
     setlocale(LC_NUMERIC, "C");
 
-    InitializeCriticalSection(&Atomic64CS);
-
     debug_sem = CreateSemaphore(
                             NULL,           // default security attributes
                             1,  			// initial count
@@ -136,7 +134,7 @@
     }
     if( __init(argc,argv) == 0 )
     {
-        PLC_SetTimer(Ttick,Ttick);
+        PLC_SetTimer(common_ticktime__,common_ticktime__);
         PLC_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlcLoop, NULL, 0, &thread_id);
     }
     else{
@@ -170,7 +168,6 @@
     CloseHandle(PLC_timer);
     WaitForSingleObject(PLC_thread, INFINITE);
     __cleanup();
-    DeleteCriticalSection(&Atomic64CS);
     CloseHandle(debug_wait_sem);
     CloseHandle(debug_sem);
     CloseHandle(python_wait_sem);
@@ -244,6 +241,14 @@
 	WaitForSingleObject(python_sem, INFINITE);
 }
 
+void InitRetain(void)
+{
+}
+
+void CleanupRetain(void)
+{
+}
+
 int CheckRetainBuffer(void)
 {
 	return 1;
@@ -271,3 +276,16 @@
 {
 }
 
+static void __attribute__((constructor))
+beremiz_dll_init(void)
+{
+    InitializeCriticalSection(&Atomic64CS);
+
+}
+
+static void __attribute__((destructor))
+beremiz_dll_destroy(void)
+{
+    DeleteCriticalSection(&Atomic64CS);
+}
+
--- a/targets/Xenomai/plc_Xenomai_main.c	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/Xenomai/plc_Xenomai_main.c	Wed Oct 21 15:00:32 2015 +0100
@@ -13,7 +13,6 @@
 
 #include <native/task.h>
 #include <native/timer.h>
-#include <native/mutex.h>
 #include <native/sem.h>
 #include <native/pipe.h>
 
@@ -75,7 +74,7 @@
 
 void PLC_task_proc(void *arg)
 {
-    PLC_SetTimer(Ttick, Ttick);
+    PLC_SetTimer(common_ticktime__, common_ticktime__);
 
     while (!PLC_shutdown) {
         PLC_GetTime(&__CURRENT_TIME);
@@ -364,23 +363,3 @@
     }    /* as plc does not wait for lock. */
 }
 
-int CheckRetainBuffer(void)
-{
-	return 1;
-}
-
-void ValidateRetainBuffer(void)
-{
-}
-
-void InValidateRetainBuffer(void)
-{
-}
-
-void Retain(unsigned int offset, unsigned int count, void *p)
-{
-}
-
-void Remind(unsigned int offset, unsigned int count, void *p)
-{
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/Xenomai/plc_Xenomai_noretain.c	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,20 @@
+int CheckRetainBuffer(void)
+{
+	return 1;
+}
+
+void ValidateRetainBuffer(void)
+{
+}
+
+void InValidateRetainBuffer(void)
+{
+}
+
+void Retain(unsigned int offset, unsigned int count, void *p)
+{
+}
+
+void Remind(unsigned int offset, unsigned int count, void *p)
+{
+}
--- a/targets/__init__.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/__init__.py	Wed Oct 21 15:00:32 2015 +0100
@@ -38,7 +38,10 @@
 
 targets = dict([(name, {"xsd":path.join(_base_path, name, "XSD"), 
                         "class":_GetLocalTargetClassFactory(name),
-                        "code": path.join(path.split(__file__)[0],name,"plc_%s_main.c"%name)})
+                        "code": { fname: path.join(_base_path, name, fname) 
+                           for fname in listdir(path.join(_base_path, name))
+                             if fname.startswith("plc_%s_main"%name) and
+                               fname.endswith(".c")}})
                 for name in listdir(_base_path) 
                     if path.isdir(path.join(_base_path, name)) 
                        and not name.startswith("__")])
@@ -67,13 +70,15 @@
     return targetchoices
 
 def GetTargetCode(targetname):
-    return open(targets[targetname]["code"]).read()
+    codedesc = targets[targetname]["code"]
+    code = "\n".join([open(fpath).read() for fname, fpath in sorted(codedesc.items())])
+    return code
 
 def GetHeader():
     filename = path.join(path.split(__file__)[0],"beremiz.h")
     return open(filename).read()
 
 def GetCode(name):
-    filename = path.join(path.split(__file__)[0],name + ".c")
+    filename = path.join(path.split(__file__)[0],name)
     return open(filename).read()
 
--- a/targets/plc_debug.c	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/plc_debug.c	Wed Oct 21 15:00:32 2015 +0100
@@ -39,19 +39,23 @@
  **/
 %(extern_variables_declarations)s
 
-typedef void(*__for_each_variable_do_fp)(void*, __IEC_types_enum);
+typedef const struct {
+    void *ptr;
+    __IEC_types_enum type;
+} dbgvardsc_t;
+
+static dbgvardsc_t dbgvardsc[] = {
+%(variable_decl_array)s
+};
+
+typedef void(*__for_each_variable_do_fp)(dbgvardsc_t*);
 void __for_each_variable_do(__for_each_variable_do_fp fp)
 {
-%(for_each_variable_do_code)s
-}
-
-__IEC_types_enum __find_variable(unsigned int varindex, void ** varp)
-{
-    switch(varindex){
-%(find_variable_case_code)s
-     default:
-      *varp = NULL;
-      return UNKNOWN_ENUM;
+    int i;
+    for(i = 0; i < sizeof(dbgvardsc)/sizeof(dbgvardsc_t); i++){
+        dbgvardsc_t *dsc = &dbgvardsc[i];
+        if(dsc->type != UNKNOWN_ENUM) 
+            (*fp)(dsc);
     }
 }
 
@@ -70,12 +74,13 @@
             forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\
             break;
 
-void* UnpackVar(void* varp, __IEC_types_enum vartype, void **real_value_p, char *flags)
-{
+void* UnpackVar(dbgvardsc_t *dsc, void **real_value_p, char *flags)
+{
+    void *varp = dsc->ptr;
     void *forced_value_p = NULL;
     *flags = 0;
     /* find data to copy*/
-    switch(vartype){
+    switch(dsc->type){
         __ANY(__Unpack_case_t)
         __ANY(__Unpack_case_p)
     default:
@@ -88,14 +93,14 @@
 
 void Remind(unsigned int offset, unsigned int count, void * p);
 
-void RemindIterator(void* varp, __IEC_types_enum vartype)
+void RemindIterator(dbgvardsc_t *dsc)
 {
     void *real_value_p = NULL;
     char flags = 0;
-    UnpackVar(varp, vartype, &real_value_p, &flags);
+    UnpackVar(dsc, &real_value_p, &flags);
 
     if(flags & __IEC_RETAIN_FLAG){
-        USINT size = __get_type_enum_size(vartype);
+        USINT size = __get_type_enum_size(dsc->type);
         /* compute next cursor positon*/
         unsigned int next_retain_offset = retain_offset + size;
         /* if buffer not full */
@@ -106,6 +111,7 @@
 }
 
 extern int CheckRetainBuffer(void);
+extern void InitRetain(void);
 
 void __init_debug(void)
 {
@@ -113,13 +119,19 @@
     buffer_cursor = debug_buffer;
     retain_offset = 0;
     buffer_state = BUFFER_FREE;
+    InitRetain();
     /* Iterate over all variables to fill debug buffer */
-    if(CheckRetainBuffer())
+    if(CheckRetainBuffer()){
     	__for_each_variable_do(RemindIterator);
+    }else{
+    	char mstr[] = "RETAIN memory invalid - defaults used";
+        LogMessage(LOG_WARNING, mstr, sizeof(mstr));
+    }
     retain_offset = 0;
 }
 
 extern void InitiateDebugTransfer(void);
+extern void CleanupRetain(void);
 
 extern unsigned long __tick;
 
@@ -127,6 +139,7 @@
 {
     buffer_cursor = debug_buffer;
     InitiateDebugTransfer();
+    CleanupRetain();
 }
 
 void __retrieve_debug(void)
@@ -136,23 +149,23 @@
 
 void Retain(unsigned int offset, unsigned int count, void * p);
 
-inline void BufferIterator(void* varp, __IEC_types_enum vartype, int do_debug)
+inline void BufferIterator(dbgvardsc_t *dsc, int do_debug)
 {
     void *real_value_p = NULL;
     void *visible_value_p = NULL;
     char flags = 0;
 
-    visible_value_p = UnpackVar(varp, vartype, &real_value_p, &flags);
+    visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
 
     if(flags & ( __IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG)){
-        USINT size = __get_type_enum_size(vartype);
+        USINT size = __get_type_enum_size(dsc->type);
         if(flags & __IEC_DEBUG_FLAG){
             /* copy visible variable to buffer */;
             if(do_debug){
                 /* compute next cursor positon.
                    No need to check overflow, as BUFFER_SIZE
                    is computed large enough */
-                if(vartype == STRING_ENUM){
+                if(dsc->type == STRING_ENUM){
                     /* optimization for strings */
                     size = ((STRING*)visible_value_p)->len + 1;
                 }
@@ -178,12 +191,12 @@
     }
 }
 
-void DebugIterator(void* varp, __IEC_types_enum vartype){
-    BufferIterator(varp, vartype, 1);
-}
-
-void RetainIterator(void* varp, __IEC_types_enum vartype){
-    BufferIterator(varp, vartype, 0);
+void DebugIterator(dbgvardsc_t *dsc){
+    BufferIterator(dsc, 1);
+}
+
+void RetainIterator(dbgvardsc_t *dsc){
+    BufferIterator(dsc, 0);
 }
 
 extern void PLC_GetTime(IEC_TIME*);
@@ -251,13 +264,18 @@
             break;
 void RegisterDebugVariable(int idx, void* force)
 {
-    void *varp = NULL;
-    unsigned char flags = force ? __IEC_DEBUG_FLAG | __IEC_FORCE_FLAG : __IEC_DEBUG_FLAG;
-    switch(__find_variable(idx, &varp)){
-        __ANY(__RegisterDebugVariable_case_t)
-        __ANY(__RegisterDebugVariable_case_p)
-    default:
-        break;
+    if(idx  < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){
+        unsigned char flags = force ?
+            __IEC_DEBUG_FLAG | __IEC_FORCE_FLAG :
+            __IEC_DEBUG_FLAG;
+        dbgvardsc_t *dsc = &dbgvardsc[idx];
+        void *varp = dsc->ptr;
+        switch(dsc->type){
+            __ANY(__RegisterDebugVariable_case_t)
+            __ANY(__RegisterDebugVariable_case_p)
+        default:
+            break;
+        }
     }
 }
 
@@ -272,10 +290,11 @@
             ((__IEC_##TYPENAME##_p *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
             break;
 
-void ResetDebugVariablesIterator(void* varp, __IEC_types_enum vartype)
+void ResetDebugVariablesIterator(dbgvardsc_t *dsc)
 {
     /* force debug flag to 0*/
-    switch(vartype){
+    void *varp = dsc->ptr;
+    switch(dsc->type){
         __ANY(__ResetDebugVariablesIterator_case_t)
         __ANY(__ResetDebugVariablesIterator_case_p)
     default:
--- a/targets/plc_main_head.c	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/plc_main_head.c	Wed Oct 21 15:00:32 2015 +0100
@@ -25,15 +25,13 @@
 IEC_TIME __CURRENT_TIME;
 IEC_BOOL __DEBUG = 0;
 unsigned long __tick = 0;
+char *PLC_ID = NULL;
 
 /*
  *  Variable generated by C softPLC and plugins
  **/
 extern unsigned long greatest_tick_count__;
 
-/* Effective tick time with 1ms default value */
-static long long Ttick = 1000000;
-
 /* Help to quit cleanly when init fail at a certain level */
 static int init_level = 0;
 
@@ -72,8 +70,9 @@
     int res = 0;
     init_level = 0;
     
-    if(common_ticktime__)
-        Ttick = common_ticktime__;
+    /* Effective tick time with 1ms default value */
+    if(!common_ticktime__)
+        common_ticktime__ = 1000000;
 
     config_init__();
     __init_debug();
--- a/targets/plc_main_tail.c	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/plc_main_tail.c	Wed Oct 21 15:00:32 2015 +0100
@@ -175,10 +175,10 @@
 			/* compute mean of Tsync, over calibration period */
 			Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
 					(cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
-			if( (Nticks = (Tsync / Ttick)) > 0){
-				FreqCorr = (Tsync % Ttick); /* to be divided by Nticks */
+			if( (Nticks = (Tsync / common_ticktime__)) > 0){
+				FreqCorr = (Tsync % common_ticktime__); /* to be divided by Nticks */
 			}else{
-				FreqCorr = Tsync - (Ttick % Tsync);
+				FreqCorr = Tsync - (common_ticktime__ % Tsync);
 			}
 			/*
 			printf("Tsync = %ld\n", Tsync);
@@ -197,19 +197,19 @@
 			PLC_GetTime(&now);
 			elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
 			if(Nticks > 0){
-				PhaseCorr = elapsed - (Ttick + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
-				Tcorr = Ttick + (PhaseCorr + FreqCorr) / Nticks;
+				PhaseCorr = elapsed - (common_ticktime__ + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
+				Tcorr = common_ticktime__ + (PhaseCorr + FreqCorr) / Nticks;
 				if(Nticks < 2){
 					/* When Sync source period is near Tick time */
 					/* PhaseCorr may not be applied to Periodic time given to timer */
-					PeriodicTcorr = Ttick + FreqCorr / Nticks;
+					PeriodicTcorr = common_ticktime__ + FreqCorr / Nticks;
 				}else{
 					PeriodicTcorr = Tcorr;
 				}
 			}else if(__tick > last_tick){
 				last_tick = __tick;
 				PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
-				PeriodicTcorr = Tcorr = Ttick + PhaseCorr + FreqCorr;
+				PeriodicTcorr = Tcorr = common_ticktime__ + PhaseCorr + FreqCorr;
 			}else{
 				/*PLC did not run meanwhile. Nothing to do*/
 				return;
--- a/targets/typemapping.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/targets/typemapping.py	Wed Oct 21 15:00:32 2015 +0100
@@ -19,6 +19,11 @@
 #License along with this library; if not, write to the Free Software
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+import ctypes
+ctypes.pythonapi.PyString_AsString.argtypes = (ctypes.c_void_p,)
+ctypes.pythonapi.PyString_AsString.restype = ctypes.POINTER(ctypes.c_char)
+
+
 from ctypes import *
 from datetime import timedelta as td
 
@@ -27,7 +32,7 @@
     Must be changed according to changes in iec_types.h
     """
     _fields_ = [("len", c_uint8),
-                ("body", c_char * 126)] 
+                ("body", c_char * 126)]
 
 class IEC_TIME(Structure):
     """
@@ -37,8 +42,8 @@
                 ("ns", c_long)] #tv_nsec
 
 def _t(t, u=lambda x:x.value, p=lambda t,x:t(x)): return  (t, u, p)
-def _ttime(): return (IEC_TIME, 
-                      lambda x:td(0, x.s, x.ns/1000), 
+def _ttime(): return (IEC_TIME,
+                      lambda x:td(0, x.s, x.ns/1000),
                       lambda t,x:t(x.days * 24 * 3600 + x.seconds, x.microseconds*1000))
 
 SameEndianessTypeTranslator = {
@@ -49,8 +54,8 @@
     "SINT" :       _t(c_int8),
     "USINT" :      _t(c_uint8),
     "BYTE" :       _t(c_uint8),
-    "STRING" :     (IEC_STRING, 
-                      lambda x:x.body[:x.len], 
+    "STRING" :     (IEC_STRING,
+                      lambda x:x.body[:x.len],
                       lambda t,x:t(len(x),x)),
     "INT" :        _t(c_int16),
     "UINT" :       _t(c_uint16),
@@ -67,38 +72,35 @@
     "TOD" :        _ttime(),
     "DATE" :       _ttime(),
     "DT" :         _ttime(),
-    } 
+    }
 
 SwapedEndianessTypeTranslator = {
     #TODO
-    } 
+    }
 
 TypeTranslator=SameEndianessTypeTranslator
 
 # Construct debugger natively supported types
 DebugTypesSize =  dict([(key,sizeof(t)) for key,(t,p,u) in SameEndianessTypeTranslator.iteritems() if t is not None])
 
-def UnpackDebugBuffer(buff, size, indexes):
-    res = []
-    offset = 0
-    for idx, iectype, forced in indexes:
-        cursor = c_void_p(buff.value + offset)
+def UnpackDebugBuffer(buff, indexes):
+    res =  []
+    buffoffset = 0
+    buffsize = len(buff)
+    buffptr = cast(ctypes.pythonapi.PyString_AsString(id(buff)),c_void_p).value
+    for iectype in indexes:
         c_type,unpack_func, pack_func = \
             TypeTranslator.get(iectype,
                                     (None,None,None))
-        if c_type is not None and offset < size:
-            res.append(unpack_func(
-                        cast(cursor,
-                         POINTER(c_type)).contents))
-            offset += sizeof(c_type) if iectype != "STRING" else len(res[-1])+1
+        if c_type is not None and buffoffset < buffsize:
+            cursor = c_void_p( buffptr + buffoffset)
+            value = unpack_func( cast(cursor,
+                         POINTER(c_type)).contents)
+            buffoffset += sizeof(c_type) if iectype != "STRING" else len(value)+1
+            res.append(value)
         else:
-            #if c_type is None:
-            #    PLCprint("Debug error - " + iectype +
-            #             " not supported !")
-            #if offset >= size:
-            #    PLCprint("Debug error - buffer too small ! %d != %d"%(offset, size))
             break
-    if offset and offset == size:
+    if buffoffset and buffoffset == buffsize:
         return res
     return None
 
--- a/tests/python/c_code@c_ext/cfile.xml	Sat Dec 06 19:31:51 2014 +0000
+++ b/tests/python/c_code@c_ext/cfile.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -14,6 +14,7 @@
 volatile char PtoC=1,CtoP=2;
 
 extern long AtomicCompareExchange(long*,long, long);
+extern char *PLC_ID;
 
 int Simple_C_Call(int val){
   return val+1;
@@ -34,6 +35,7 @@
     res=1;
   }
   printf("C code called by Python: toC %d fromC %d\n",toC,*fromC);
+  printf("PLC_ID id %s\n",PLC_ID);
   return res;
 }
 
--- a/tests/python/plc.xml	Sat Dec 06 19:31:51 2014 +0000
+++ b/tests/python/plc.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:21:19"/>
-  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2014-06-12T17:48:28">
+  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2015-03-13T22:06:10">
     <coordinateInfo>
       <pageSize x="1024" y="1024"/>
       <fbd>
@@ -1297,7 +1297,7 @@
   </types>
   <instances>
     <configurations>
-      <configuration name="conf_pytest">
+      <configuration name="config">
         <resource name="res_pytest">
           <task name="pytest_task" interval="T#1ms" priority="0"/>
           <globalVars>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/python/py_ext_0@py_ext/baseconfnode.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="2" Name="py_ext_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/python/py_ext_0@py_ext/pyfile.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <variables>
+    <variable name="SomeVarName" type="DINT"/>
+    <variable name="Grumpf" type="STRING"/>
+  </variables>
+  <globals>
+    <xhtml:p><![CDATA[
+print "All python PLC globals variables :", PLCGlobalsDesc
+print "Current extention name :", __ext_name__
+]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </stop>
+</PyFile>
--- a/tests/svgui/plc.xml	Sat Dec 06 19:31:51 2014 +0000
+++ b/tests/svgui/plc.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -497,7 +497,7 @@
   </types>
   <instances>
     <configurations>
-      <configuration name="conf_pytest">
+      <configuration name="config">
         <resource name="res_pytest">
           <task name="pytest_task" interval="t#100ms" priority="0"/>
           <pouInstance name="pytest_instance" typeName="main_pytest"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wamp/.crossbar/config.json	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,43 @@
+
+{
+   "controller": {
+   },
+   "workers": [
+      {
+         "type": "router",
+         "options": {
+            "pythonpath": [".."]
+         },
+         "realms": [
+            {
+               "name": "Automation",
+               "roles": [
+                  {
+                     "name": "anonymous",
+                     "permissions": [
+                        {
+                           "uri": "*",
+                           "publish": true,
+                           "subscribe": true,
+                           "call": true,
+                           "register": true
+                        }
+                     ]
+                  }
+               ]
+            }
+         ],
+         "transports": [
+            {
+               "type": "websocket",
+               "endpoint": {
+                  "type": "tcp",
+                  "port": 8888
+               },
+               "url": "ws://127.0.0.1:8888/",
+               "serializers" : ["msgpack"]
+            }
+         ]
+      }
+   ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wamp/README	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,12 @@
+This project contains wamp client config to be loaded at runtime startup.  
+
+project_files/wampconf.json
+
+wampconf.json is in "Project Files", so it is copied to runtime's working directory, and then loaded after program transfer + runtime restart.
+
+Otherwise, wamp config file path can be forced :
+./Beremiz_service.py -c  /path/to/my/wampconf.json /working/dir
+
+Crossbar test router configuration is available in .crossbar directory. Start with :
+crossbar -d start
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wamp/beremiz.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="WAMP://127.0.0.1:8888#Automation#2534667845">
+  <TargetType/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wamp/plc.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,118 @@
+<?xml version='1.0' encoding='utf-8'?>
+<project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
+  <fileHeader companyName="Beremiz" productName="Beremiz" productVersion="1" creationDateTime="2015-02-05T11:44:55" contentDescription=" &#10;&#10;"/>
+  <contentHeader name="WAMPTest" modificationDateTime="2015-02-18T23:59:50">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="0" y="0"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="program0" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="LocalVar0">
+              <type>
+                <DINT/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="PyVar0">
+              <type>
+                <DINT/>
+              </type>
+            </variable>
+            <variable name="PyVar1">
+              <type>
+                <DINT/>
+              </type>
+            </variable>
+          </externalVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="1" executionOrderId="0" height="25" width="55" negated="false">
+              <position x="144" y="70"/>
+              <connectionPointOut>
+                <relPosition x="55" y="12"/>
+              </connectionPointOut>
+              <expression>PyVar0</expression>
+            </inVariable>
+            <block localId="3" typeName="ADD" executionOrderId="0" height="60" width="65">
+              <position x="245" y="52"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="245" y="82"/>
+                      <position x="199" y="82"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="4">
+                      <position x="245" y="102"/>
+                      <position x="228" y="102"/>
+                      <position x="228" y="113"/>
+                      <position x="198" y="113"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="65" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="4" executionOrderId="0" height="25" width="73" negated="false">
+              <position x="125" y="101"/>
+              <connectionPointOut>
+                <relPosition x="73" y="12"/>
+              </connectionPointOut>
+              <expression>LocalVar0</expression>
+            </inVariable>
+            <outVariable localId="2" executionOrderId="0" height="25" width="55" negated="false">
+              <position x="344" y="70"/>
+              <connectionPointIn>
+                <relPosition x="0" y="12"/>
+                <connection refLocalId="3" formalParameter="OUT">
+                  <position x="344" y="82"/>
+                  <position x="310" y="82"/>
+                </connection>
+              </connectionPointIn>
+              <expression>PyVar1</expression>
+            </outVariable>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="Task0" priority="0" interval="T#100ms">
+            <pouInstance name="Instance0" typeName="program0"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wamp/project_files/wampconf.json	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,7 @@
+{
+    "url":"ws://127.0.0.1:8888",
+    "realm":"Automation",
+    "ID":"wamptest",
+    "password":"1234567890",
+    "key":"ABCDEFGHIJ"
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wamp/py_ext_0@py_ext/baseconfnode.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="py_ext_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wamp/py_ext_0@py_ext/pyfile.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <variables>
+    <variable name="PyVar0" type="DINT"/>
+    <variable name="PyVar1" type="DINT"/>
+  </variables>
+  <globals>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </stop>
+</PyFile>
--- a/tests/wxGlade/HMIFrame@wxglade_hmi/py_ext.xml	Sat Dec 06 19:31:51 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<Python xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="python_xsd.xsd">
-<![CDATA[]]>
-</Python>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wxGlade/HMIFrame@wxglade_hmi/pyfile.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <variables/>
+  <globals>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[]]></xhtml:p>
+  </stop>
+</PyFile>
--- a/tests/wxGlade/plc.xml	Sat Dec 06 19:31:51 2014 +0000
+++ b/tests/wxGlade/plc.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -419,7 +419,7 @@
   </types>
   <instances>
     <configurations>
-      <configuration name="conf_pytest">
+      <configuration name="config">
         <resource name="res_pytest">
           <task name="pytest_task" interval="t#100ms" priority="0"/>
           <pouInstance name="pytest_instance" typeName="main_pytest"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wxHMI/HMI@wxglade_hmi/baseconfnode.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams Name="HMI" IEC_Channel="0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wxHMI/HMI@wxglade_hmi/hmi.wxg	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!-- generated by wxGlade 0.6.8 on Thu Jun 18 15:19:02 2015 -->
+
+<application path="" name="" class="" option="0" language="python" top_window="wxglade_hmi" encoding="UTF-8" use_gettext="1" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0" indent_amount="4" indent_symbol="space" source_extension=".cpp" header_extension=".h">
+    <object class="Class_wxglade_hmi" name="wxglade_hmi" base="EditFrame">
+        <style>wxCAPTION|wxCLOSE_BOX|wxMINIMIZE_BOX|wxMAXIMIZE|wxMAXIMIZE_BOX|wxSYSTEM_MENU|wxRESIZE_BORDER|wxCLIP_CHILDREN</style>
+        <title>frame_1</title>
+        <object class="wxFlexGridSizer" name="grid_sizer_1" base="EditFlexGridSizer">
+            <hgap>0</hgap>
+            <growable_rows>0</growable_rows>
+            <rows>1</rows>
+            <growable_cols>0</growable_cols>
+            <cols>4</cols>
+            <vgap>0</vgap>
+            <object class="sizeritem">
+                <flag>wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL</flag>
+                <border>0</border>
+                <option>1</option>
+                <object class="ThreeDee" name="window_1" base="CustomWidget">
+                    <extracode># WHERE IS THAT ?\nprint "hello"\n</extracode>
+                    <arguments>
+                        <argument>$parent</argument>
+                        <argument>$id</argument>
+                    </arguments>
+                    <size>400,400</size>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>1</option>
+                <object class="wxFlexGridSizer" name="sizer_1" base="EditFlexGridSizer">
+                    <hgap>0</hgap>
+                    <growable_rows>0</growable_rows>
+                    <rows>2</rows>
+                    <cols>1</cols>
+                    <vgap>0</vgap>
+                    <object class="sizeritem">
+                        <flag>wxEXPAND</flag>
+                        <border>0</border>
+                        <option>1</option>
+                        <object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
+                            <orient>wxVERTICAL</orient>
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxEXPAND</flag>
+                        <border>0</border>
+                        <option>1</option>
+                        <object class="wxGridSizer" name="sizer_3" base="EditGridSizer">
+                            <hgap>0</hgap>
+                            <rows>4</rows>
+                            <cols>2</cols>
+                            <vgap>0</vgap>
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                        </object>
+                    </object>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <border>0</border>
+                <option>0</option>
+                <object class="wxToggleButton" name="DrawTestBt" base="EditToggleButton">
+                    <label>Please Draw !</label>
+                    <events>
+                        <handler event="EVT_TOGGLEBUTTON">SetPLCGlobalVar</handler>
+                    </events>
+                    <extraproperties>
+                        <property name="Name">"DrawTest"</property>
+                    </extraproperties>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <border>0</border>
+                <option>0</option>
+                <object class="wxToggleButton" name="DrawTestBt_copy" base="EditToggleButton">
+                    <label>Please Draw copy !</label>
+                    <events>
+                        <handler event="EVT_TOGGLEBUTTON">SetPLCGlobalVar</handler>
+                    </events>
+                    <extraproperties>
+                        <property name="Name">"DrawEscher"</property>
+                    </extraproperties>
+                </object>
+            </object>
+        </object>
+    </object>
+</application>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wxHMI/HMI@wxglade_hmi/hmi.wxg.bak	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+<!-- generated by wxGlade HG on Wed Oct 02 23:48:24 2013 -->
+
+<application path="" name="" class="" option="0" language="python" top_window="wxglade_hmi" encoding="UTF-8" use_gettext="0" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0" indent_amount="4" indent_symbol="space" source_extension=".cpp" header_extension=".h">
+    <object class="Class_wxglade_hmi" name="wxglade_hmi" base="EditFrame">
+        <style>wxCAPTION|wxCLOSE_BOX|wxMINIMIZE_BOX|wxMAXIMIZE|wxMAXIMIZE_BOX|wxSYSTEM_MENU|wxRESIZE_BORDER|wxCLIP_CHILDREN</style>
+        <title>frame_1</title>
+        <object class="wxFlexGridSizer" name="grid_sizer_1" base="EditFlexGridSizer">
+            <hgap>0</hgap>
+            <growable_rows>0</growable_rows>
+            <rows>1</rows>
+            <growable_cols>0</growable_cols>
+            <cols>2</cols>
+            <vgap>0</vgap>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>1</option>
+                <object class="ThreeDee" name="window_1" base="CustomWidget">
+                    <arguments>
+                        <argument>$parent</argument>
+                        <argument>$id</argument>
+                    </arguments>
+                    <size>400,400</size>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>1</option>
+                <object class="wxFlexGridSizer" name="sizer_1" base="EditFlexGridSizer">
+                    <hgap>0</hgap>
+                    <rows>2</rows>
+                    <cols>1</cols>
+                    <vgap>0</vgap>
+                    <object class="sizeritem">
+                        <flag>wxEXPAND</flag>
+                        <border>0</border>
+                        <option>1</option>
+                        <object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer">
+                            <orient>wxVERTICAL</orient>
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                        </object>
+                    </object>
+                    <object class="sizeritem">
+                        <flag>wxEXPAND</flag>
+                        <border>0</border>
+                        <option>1</option>
+                        <object class="wxGridSizer" name="sizer_3" base="EditGridSizer">
+                            <hgap>0</hgap>
+                            <rows>4</rows>
+                            <cols>2</cols>
+                            <vgap>0</vgap>
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                            <object class="sizerslot" />
+                        </object>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+</application>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wxHMI/HMI@wxglade_hmi/pyfile.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,134 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml">
+  <variables>
+    <variable name="Power_ON" type="BOOL"/>
+    <variable name="Power_OFF" type="BOOL"/>
+    <variable name="DrawTest" type="BOOL"/>
+    <variable name="DrawLogo" type="BOOL"/>
+    <variable name="DrawEscher" type="BOOL"/>
+    <variable name="Detect_Circle" type="BOOL"/>
+    <variable name="XaxisPos" type="INT"/>
+    <variable name="YaxisPos" type="INT"/>
+    <variable name="ZaxisPos" type="INT"/>
+    <variable name="TaxisPos" type="INT"/>
+    <variable name="XaxisMinus" type="BOOL"/>
+    <variable name="YaxisMinus" type="BOOL"/>
+    <variable name="ZaxisMinus" type="BOOL"/>
+    <variable name="TaxisMinus" type="BOOL"/>
+    <variable name="XaxisPlus" type="BOOL"/>
+    <variable name="YaxisPlus" type="BOOL"/>
+    <variable name="ZaxisPlus" type="BOOL"/>
+    <variable name="TaxisPlus" type="BOOL"/>
+  </variables>
+  <globals>
+    <xhtml:p><![CDATA[
+import ctypes
+import wx, sys
+
+AxisList = ["X","Y","Z","T"]
+
+PwrButtons = ['Power_ON',
+              'Power_OFF']
+
+ActionButtons = ['Detect_Circle',
+                 'DrawTest',
+                 'DrawLogo',
+                 'DrawEscher']
+
+class ThreeDee(wx.StaticText):
+    def __init__(self, *args, **kwargs):
+        self.initialized = False
+        kwargs["style"] = wx.ALIGN_CENTRE_HORIZONTAL
+        super(ThreeDee, self).__init__(*args, **kwargs)
+
+        self.SetFont(wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD))
+        
+        self.positions = [0.]*4
+
+        self.Message = None
+        self.NegLimits = None
+        self.Disk = None
+
+
+    def UpdatePositions(self, positions):
+        # get globals from PLC
+        self.positions = positions
+        self.SetLabel(
+            ((self.Message  +'\n\n') if self.Message else '' )+
+            " ".join(["%s %+.2f"%(axis,self.positions[i])
+                          for i,axis in enumerate(AxisList)]))
+
+def MakeButtonFunc(window, sizer, btname):
+    def ButtonDown(event):
+        setattr(PLCGlobals,btname,1)
+        event.Skip()
+    def ButtonUp(event):
+        setattr(PLCGlobals,btname,0)
+        event.Skip()
+    obj = wx.BitmapButton(window, -1, wx.Bitmap('%s.png'%btname))
+    sizer.Add(obj, 2, wx.EXPAND, 0)
+    obj.Bind(wx.EVT_LEFT_DOWN, ButtonDown)
+    obj.Bind(wx.EVT_LEFT_UP, ButtonUp)
+    return obj
+
+def UpdPos(self):
+    positions = [getattr(PLCGlobals,axname+"axisPos") for axname in AxisList]
+
+    self.window_1.UpdatePositions(positions)
+
+Class_wxglade_hmi.UpdPos = UpdPos
+
+
+#def UpdatePositions(self, event):
+#
+#    positions = [getattr(PLCGlobals,axname+"axisPos") for axname in AxisList]
+#
+#    self.window_1.UpdatePositions(positions)
+#
+#    event.Skip()
+
+#Class_wxglade_hmi.UpdatePositions = UpdatePositions
+
+initorig = Class_wxglade_hmi.__init__
+def Init(self,*args,**kargs):
+    initorig(self,*args,**kargs)
+    sizer = self.GetSizer().GetItem(1).GetSizer().GetItem(0).GetSizer()
+    self.main_buttons = map(
+        lambda btname: MakeButtonFunc(self, sizer, btname), PwrButtons)
+    sizer = self.GetSizer().GetItem(1).GetSizer().GetItem(1).GetSizer()
+    self.main_buttons = map(
+        lambda btname: MakeButtonFunc(self, sizer, btname), ActionButtons)
+    self.axis_buttons = map(
+        lambda axis:( MakeButtonFunc(self, sizer, axis+"axisMinus"), 
+                        MakeButtonFunc(self, sizer, axis+"axisPlus")),
+        AxisList)
+    # self.timer = wx.Timer(self, -1)
+    # self.Bind(wx.EVT_TIMER, self.UpdatePositions, self.timer)
+    # self.ShowFullScreen(True,wx.FULLSCREEN_ALL)
+    # wx.CallAfter(self.timer.Start,200)
+    
+Class_wxglade_hmi.__init__ = Init
+
+def SetPLCGlobalVar(self, evt):
+    tglbtname = evt.GetEventObject().GetName()
+    setattr(PLCGlobals, tglbtname, evt.GetEventObject().GetValue())
+
+]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[
+]]></xhtml:p>
+  </stop>
+</PyFile>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wxHMI/beremiz.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot URI_location="LOCAL://">
+  <TargetType/>
+  <Libraries Enable_SVGUI_Library="false"/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wxHMI/plc.xml	Wed Oct 21 15:00:32 2015 +0100
@@ -0,0 +1,592 @@
+<?xml version='1.0' encoding='utf-8'?>
+<project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
+  <fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2012-09-12T23:30:19"/>
+  <contentHeader name="Unnamed" modificationDateTime="2015-07-01T22:17:12">
+    <coordinateInfo>
+      <pageSize x="1050" y="1485"/>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="main" pouType="program">
+        <interface>
+          <externalVars>
+            <variable name="Power_ON">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="Power_OFF">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="power">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="DrawTest">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="DrawLogo">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="DrawEscher">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="Detect_Circle">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+          </externalVars>
+          <localVars>
+            <variable name="RS0">
+              <type>
+                <derived name="RS"/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="ZaxisPos">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+          </externalVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="286" executionOrderId="0" height="25" width="65" negated="false">
+              <position x="230" y="205"/>
+              <connectionPointOut>
+                <relPosition x="65" y="10"/>
+              </connectionPointOut>
+              <expression>Power_ON</expression>
+            </inVariable>
+            <block localId="287" typeName="RS" instanceName="RS0" executionOrderId="0" height="65" width="45">
+              <position x="395" y="190"/>
+              <inputVariables>
+                <variable formalParameter="S">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="286">
+                      <position x="395" y="220"/>
+                      <position x="315" y="220"/>
+                      <position x="315" y="215"/>
+                      <position x="295" y="215"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="R1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="55"/>
+                    <connection refLocalId="288">
+                      <position x="395" y="245"/>
+                      <position x="320" y="245"/>
+                      <position x="320" y="260"/>
+                      <position x="310" y="260"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q1">
+                  <connectionPointOut>
+                    <relPosition x="45" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="288" executionOrderId="0" height="25" width="70" negated="false">
+              <position x="240" y="250"/>
+              <connectionPointOut>
+                <relPosition x="70" y="10"/>
+              </connectionPointOut>
+              <expression>Power_OFF</expression>
+            </inVariable>
+            <outVariable localId="289" executionOrderId="0" height="25" width="45" negated="false">
+              <position x="510" y="220"/>
+              <connectionPointIn>
+                <relPosition x="0" y="10"/>
+                <connection refLocalId="287" formalParameter="Q1">
+                  <position x="510" y="230"/>
+                  <position x="475" y="230"/>
+                  <position x="475" y="220"/>
+                  <position x="440" y="220"/>
+                </connection>
+              </connectionPointIn>
+              <expression>power</expression>
+            </outVariable>
+            <inVariable localId="290" executionOrderId="0" height="25" width="60" negated="false">
+              <position x="75" y="55"/>
+              <connectionPointOut>
+                <relPosition x="60" y="10"/>
+              </connectionPointOut>
+              <expression>DrawTest</expression>
+            </inVariable>
+            <block localId="292" typeName="ADD" executionOrderId="0" height="65" width="60">
+              <position x="350" y="50"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="291">
+                      <position x="350" y="80"/>
+                      <position x="340" y="80"/>
+                      <position x="340" y="25"/>
+                      <position x="530" y="25"/>
+                      <position x="530" y="45"/>
+                      <position x="520" y="45"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="55"/>
+                    <connection refLocalId="293" formalParameter="OUT">
+                      <position x="350" y="105"/>
+                      <position x="287" y="105"/>
+                      <position x="287" y="65"/>
+                      <position x="225" y="65"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="60" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inOutVariable localId="291" executionOrderId="0" height="25" width="55" negatedOut="false" negatedIn="false">
+              <position x="465" y="35"/>
+              <connectionPointIn>
+                <relPosition x="0" y="10"/>
+                <connection refLocalId="292" formalParameter="OUT">
+                  <position x="465" y="45"/>
+                  <position x="437" y="45"/>
+                  <position x="437" y="80"/>
+                  <position x="410" y="80"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="55" y="10"/>
+              </connectionPointOut>
+              <expression>ZaxisPos</expression>
+            </inOutVariable>
+            <block localId="293" typeName="SEL" executionOrderId="0" height="85" width="60">
+              <position x="165" y="35"/>
+              <inputVariables>
+                <variable formalParameter="G">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="290">
+                      <position x="165" y="65"/>
+                      <position x="135" y="65"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN0">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="295">
+                      <position x="165" y="85"/>
+                      <position x="135" y="85"/>
+                      <position x="135" y="90"/>
+                      <position x="105" y="90"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="294">
+                      <position x="165" y="105"/>
+                      <position x="135" y="105"/>
+                      <position x="135" y="115"/>
+                      <position x="105" y="115"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="60" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="294" executionOrderId="0" height="25" width="20" negated="false">
+              <position x="85" y="105"/>
+              <connectionPointOut>
+                <relPosition x="20" y="10"/>
+              </connectionPointOut>
+              <expression>1</expression>
+            </inVariable>
+            <inVariable localId="295" executionOrderId="0" height="25" width="20" negated="false">
+              <position x="85" y="80"/>
+              <connectionPointOut>
+                <relPosition x="20" y="10"/>
+              </connectionPointOut>
+              <expression>0</expression>
+            </inVariable>
+          </FBD>
+        </body>
+        <documentation>
+          <xhtml:p><![CDATA[]]></xhtml:p>
+        </documentation>
+      </pou>
+      <pou name="ReadGUIdata" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="tmp">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="power">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="XaxisPos">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="YaxisPos">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="ZaxisPos">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="TaxisPos">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+          </externalVars>
+          <localVars>
+            <variable name="python_poll0">
+              <type>
+                <derived name="python_poll"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <outVariable localId="211" executionOrderId="0" height="25" width="60" negated="false">
+              <position x="565" y="195"/>
+              <connectionPointIn>
+                <relPosition x="0" y="10"/>
+                <connection refLocalId="215">
+                  <position x="565" y="205"/>
+                  <position x="370" y="205"/>
+                  <position x="370" y="195"/>
+                  <position x="330" y="195"/>
+                </connection>
+              </connectionPointIn>
+              <expression>XaxisPos</expression>
+            </outVariable>
+            <block localId="213" typeName="ADD" executionOrderId="0" height="65" width="60">
+              <position x="220" y="295"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="212">
+                      <position x="220" y="325"/>
+                      <position x="210" y="325"/>
+                      <position x="210" y="270"/>
+                      <position x="380" y="270"/>
+                      <position x="380" y="325"/>
+                      <position x="365" y="325"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="55"/>
+                    <connection refLocalId="217" formalParameter="OUT">
+                      <position x="220" y="350"/>
+                      <position x="180" y="350"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="60" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inOutVariable localId="212" executionOrderId="0" height="25" width="30" negatedOut="false" negatedIn="false">
+              <position x="335" y="315"/>
+              <connectionPointIn>
+                <relPosition x="0" y="10"/>
+                <connection refLocalId="213" formalParameter="OUT">
+                  <position x="335" y="325"/>
+                  <position x="280" y="325"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="30" y="10"/>
+              </connectionPointOut>
+              <expression>tmp</expression>
+            </inOutVariable>
+            <inVariable localId="214" executionOrderId="0" height="25" width="20" negated="false">
+              <position x="65" y="385"/>
+              <connectionPointOut>
+                <relPosition x="20" y="10"/>
+              </connectionPointOut>
+              <expression>1</expression>
+            </inVariable>
+            <inVariable localId="215" executionOrderId="0" height="25" width="30" negated="false">
+              <position x="300" y="185"/>
+              <connectionPointOut>
+                <relPosition x="30" y="10"/>
+              </connectionPointOut>
+              <expression>tmp</expression>
+            </inVariable>
+            <outVariable localId="216" executionOrderId="0" height="25" width="60" negated="false">
+              <position x="540" y="310"/>
+              <connectionPointIn>
+                <relPosition x="0" y="10"/>
+                <connection refLocalId="215">
+                  <position x="540" y="320"/>
+                  <position x="435" y="320"/>
+                  <position x="435" y="195"/>
+                  <position x="330" y="195"/>
+                </connection>
+              </connectionPointIn>
+              <expression>YaxisPos</expression>
+            </outVariable>
+            <block localId="217" typeName="SEL" executionOrderId="0" height="85" width="60">
+              <position x="120" y="320"/>
+              <inputVariables>
+                <variable formalParameter="G">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="219">
+                      <position x="120" y="350"/>
+                      <position x="97" y="350"/>
+                      <position x="97" y="345"/>
+                      <position x="85" y="345"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN0">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="218">
+                      <position x="120" y="370"/>
+                      <position x="85" y="370"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="214">
+                      <position x="120" y="390"/>
+                      <position x="102" y="390"/>
+                      <position x="102" y="395"/>
+                      <position x="85" y="395"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="60" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="218" executionOrderId="0" height="25" width="20" negated="false">
+              <position x="65" y="360"/>
+              <connectionPointOut>
+                <relPosition x="20" y="10"/>
+              </connectionPointOut>
+              <expression>0</expression>
+            </inVariable>
+            <inVariable localId="219" executionOrderId="0" height="25" width="45" negated="false">
+              <position x="40" y="335"/>
+              <connectionPointOut>
+                <relPosition x="45" y="10"/>
+              </connectionPointOut>
+              <expression>power</expression>
+            </inVariable>
+            <block localId="220" typeName="python_poll" instanceName="python_poll0" executionOrderId="0" height="65" width="85">
+              <position x="640" y="370"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="221">
+                      <position x="640" y="400"/>
+                      <position x="595" y="400"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="55"/>
+                    <connection refLocalId="222">
+                      <position x="640" y="425"/>
+                      <position x="590" y="425"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="85" y="30"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="85" y="55"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="221" executionOrderId="0" height="25" width="75" negated="false">
+              <position x="520" y="390"/>
+              <connectionPointOut>
+                <relPosition x="75" y="10"/>
+              </connectionPointOut>
+              <expression>BOOL#TRUE</expression>
+            </inVariable>
+            <inVariable localId="222" executionOrderId="0" height="25" width="135" negated="false">
+              <position x="455" y="415"/>
+              <connectionPointOut>
+                <relPosition x="135" y="10"/>
+              </connectionPointOut>
+              <expression>'wxglade_hmi.UpdPos()'</expression>
+            </inVariable>
+          </FBD>
+        </body>
+        <documentation>
+          <xhtml:p><![CDATA[]]></xhtml:p>
+        </documentation>
+      </pou>
+      <pou name="Declarations" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="LocalVar0">
+              <type>
+                <DINT/>
+              </type>
+            </variable>
+            <variable name="LocalVar1">
+              <type>
+                <DINT/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="127" executionOrderId="0" height="25" width="60" negated="false">
+              <position x="235" y="205"/>
+              <connectionPointOut>
+                <relPosition x="60" y="10"/>
+              </connectionPointOut>
+              <expression>LocalVar0</expression>
+            </inVariable>
+            <outVariable localId="128" executionOrderId="0" height="25" width="60" negated="false">
+              <position x="450" y="190"/>
+              <connectionPointIn>
+                <relPosition x="0" y="10"/>
+                <connection refLocalId="127">
+                  <position x="450" y="200"/>
+                  <position x="372" y="200"/>
+                  <position x="372" y="215"/>
+                  <position x="295" y="215"/>
+                </connection>
+              </connectionPointIn>
+              <expression>LocalVar1</expression>
+            </outVariable>
+          </FBD>
+        </body>
+        <documentation>
+          <xhtml:p><![CDATA[]]></xhtml:p>
+        </documentation>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="InitOneShot" priority="0" single="Initialize">
+            <pouInstance name="Initializer" typeName="Declarations"/>
+          </task>
+          <task name="ControlTask" priority="0" interval="T#2ms">
+            <pouInstance name="MainInstance" typeName="main"/>
+          </task>
+          <task name="GUIupdate" priority="0" interval="T#200ms">
+            <pouInstance name="PosReader" typeName="ReadGUIdata"/>
+          </task>
+          <globalVars>
+            <variable name="Initialize">
+              <type>
+                <BOOL/>
+              </type>
+              <initialValue>
+                <simpleValue value="TRUE"/>
+              </initialValue>
+            </variable>
+            <variable name="power">
+              <type>
+                <BOOL/>
+              </type>
+              <initialValue>
+                <simpleValue value="TRUE"/>
+              </initialValue>
+            </variable>
+          </globalVars>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
Binary file tests/wxHMI/project_files/Detect_Circle.png has changed
Binary file tests/wxHMI/project_files/DrawEscher.png has changed
Binary file tests/wxHMI/project_files/DrawLogo.png has changed
Binary file tests/wxHMI/project_files/DrawTest.png has changed
Binary file tests/wxHMI/project_files/Power_OFF.png has changed
Binary file tests/wxHMI/project_files/Power_ON.png has changed
Binary file tests/wxHMI/project_files/TaxisMinus.png has changed
Binary file tests/wxHMI/project_files/TaxisPlus.png has changed
Binary file tests/wxHMI/project_files/XaxisMinus.png has changed
Binary file tests/wxHMI/project_files/XaxisPlus.png has changed
Binary file tests/wxHMI/project_files/YaxisMinus.png has changed
Binary file tests/wxHMI/project_files/YaxisPlus.png has changed
Binary file tests/wxHMI/project_files/ZaxisMinus.png has changed
Binary file tests/wxHMI/project_files/ZaxisPlus.png has changed