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