--- 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()