beremiz

merge

2015-10-21, mjsousa
de4ee16f7c6c
merge
  • +20 -5
    Beremiz.py
  • +139 -223
    Beremiz_service.py
  • +19 -5
    CodeFileTreeNode.py
  • +2 -2
    PLCGenerator.py
  • +39 -39
    PLCOpenEditor.py
  • +89 -80
    ProjectController.py
  • +1 -1
    canfestival/canfestival.py
  • +2 -2
    canfestival/cf_runtime.c
  • +79 -49
    connectors/PYRO/__init__.py
  • +153 -0
    connectors/WAMP/__init__.py
  • +36 -28
    connectors/__init__.py
  • +5 -5
    controls/DebugVariablePanel/DebugVariablePanel.py
  • +127 -126
    controls/LogViewer.py
  • +107 -0
    doc/manual/connectors.rst
  • +1 -3
    doc/manual/index.rst
  • +8 -5
    editors/CodeFileEditor.py
  • +114 -119
    editors/ConfTreeNodeEditor.py
  • +11 -11
    editors/DebugViewer.py
  • +1 -1
    editors/Viewer.py
  • +98 -54
    py_ext/PythonFileCTNMixin.py
  • +173 -0
    runtime/NevowServer.py
  • +135 -78
    runtime/PLCObject.py
  • +112 -0
    runtime/WampClient.py
  • +9 -1
    targets/Linux/plc_Linux_main.c
  • +22 -4
    targets/Win32/plc_Win32_main.c
  • +1 -22
    targets/Xenomai/plc_Xenomai_main.c
  • +20 -0
    targets/Xenomai/plc_Xenomai_noretain.c
  • +8 -3
    targets/__init__.py
  • +53 -34
    targets/plc_debug.c
  • +4 -5
    targets/plc_main_head.c
  • +7 -7
    targets/plc_main_tail.c
  • +25 -23
    targets/typemapping.py
  • +2 -0
    tests/python/c_code@c_ext/cfile.xml
  • +2 -2
    tests/python/plc.xml
  • +2 -0
    tests/python/py_ext_0@py_ext/baseconfnode.xml
  • +29 -0
    tests/python/py_ext_0@py_ext/pyfile.xml
  • +1 -1
    tests/svgui/plc.xml
  • +43 -0
    tests/wamp/.crossbar/config.json
  • +12 -0
    tests/wamp/README
  • +4 -0
    tests/wamp/beremiz.xml
  • +118 -0
    tests/wamp/plc.xml
  • +7 -0
    tests/wamp/project_files/wampconf.json
  • +2 -0
    tests/wamp/py_ext_0@py_ext/baseconfnode.xml
  • +22 -0
    tests/wamp/py_ext_0@py_ext/pyfile.xml
  • +0 -4
    tests/wxGlade/HMIFrame@wxglade_hmi/py_ext.xml
  • +19 -0
    tests/wxGlade/HMIFrame@wxglade_hmi/pyfile.xml
  • +1 -1
    tests/wxGlade/plc.xml
  • +2 -0
    tests/wxHMI/HMI@wxglade_hmi/baseconfnode.xml
  • +99 -0
    tests/wxHMI/HMI@wxglade_hmi/hmi.wxg
  • +71 -0
    tests/wxHMI/HMI@wxglade_hmi/hmi.wxg.bak
  • +134 -0
    tests/wxHMI/HMI@wxglade_hmi/pyfile.xml
  • +5 -0
    tests/wxHMI/beremiz.xml
  • +592 -0
    tests/wxHMI/plc.xml
  • +0 -0
    tests/wxHMI/project_files/Detect_Circle.png
  • +0 -0
    tests/wxHMI/project_files/DrawEscher.png
  • +0 -0
    tests/wxHMI/project_files/DrawLogo.png
  • +0 -0
    tests/wxHMI/project_files/DrawTest.png
  • +0 -0
    tests/wxHMI/project_files/Power_OFF.png
  • +0 -0
    tests/wxHMI/project_files/Power_ON.png
  • +0 -0
    tests/wxHMI/project_files/TaxisMinus.png
  • +0 -0
    tests/wxHMI/project_files/TaxisPlus.png
  • +0 -0
    tests/wxHMI/project_files/XaxisMinus.png
  • +0 -0
    tests/wxHMI/project_files/XaxisPlus.png
  • +0 -0
    tests/wxHMI/project_files/YaxisMinus.png
  • +0 -0
    tests/wxHMI/project_files/YaxisPlus.png
  • +0 -0
    tests/wxHMI/project_files/ZaxisMinus.png
  • +0 -0
    tests/wxHMI/project_files/ZaxisPlus.png
  • --- 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)
    + DebugViewer.RefreshNewData(self)
    - def NewDataAvailable(self, ticks, *args, **kwargs):
    + 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)
    + self.StartLastRefreshTimer()
    - def StartRefreshing(self, *args, **kwargs):
    + 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)
    + wx.CallAfter(self.RefreshNewData)
    - def StartLastRefreshTimer(self, *args, **kwargs):
    + 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);
    + }
    +"""
    +
    + 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"]])
    - 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 = ""
    -
    + # 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 self.PLCStatus, self._TracesSwap()
    +
    + def TraceThreadProc(self):
    """
    - Return a list of variables, corresponding to the list of required idx
    + Return a list of traces, corresponding to the list of required idx
    """
    - if self.PLCStatus == "Started":
    + 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, []
    + if TraceBuffer is not None:
    + self._TracesPush((tick.value, TraceBuffer))
    + self._TracesAutoSuspend()
    + self._TracesFlush()
    - def RemoteExec(self, script, **kwargs):
    +
    + 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 DebugIterator(dbgvardsc_t *dsc){
    + BufferIterator(dsc, 1);
    }
    -void RetainIterator(void* varp, __IEC_types_enum vartype){
    - BufferIterator(varp, vartype, 0);
    +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