# HG changeset patch # User Edouard Tisserant # Date 1321656843 -3600 # Node ID b4eae24d05bfcbbc16d23f54bdb976cb193c8034 # Parent d410fe057543d643d018983bcaff562f5bba0ad9 Fixed encoding of Beremiz_service.py diff -r d410fe057543 -r b4eae24d05bf Beremiz_service.py --- a/Beremiz_service.py Fri Nov 18 18:40:19 2011 +0100 +++ b/Beremiz_service.py Fri Nov 18 23:54:03 2011 +0100 @@ -1,717 +1,716 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#This file is part of Beremiz, a Integrated Development Environment for -#programming IEC 61131-3 automates supporting plcopen standard and CanFestival. -# -#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD -# -#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 os, sys, getopt - -def usage(): - print """ -Usage of Beremiz PLC execution service :\n -%s {[-n servicename] [-i ip] [-p port] [-x enabletaskbar] [-a autostart]|-h|--help} working_dir - -n - zeroconf service name (default:disabled) - -i - ip of interface to bind to (default:localhost) - -p - port number default:3000 - -h - print this help text and quit - -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) - - 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") -except getopt.GetoptError, err: - # print help information and exit: - print str(err) # will print something like "option -a not recognized" - usage() - sys.exit(2) - -# default values -ip = "" -port = 3000 -servicename = None -autostart = False -enablewx = True -havewx = False -enabletwisted = True -havetwisted = False - -for o, a in opts: - if o == "-h": - usage() - sys.exit() - elif o == "-i": - if len(a.split(".")) == 4 or a == "localhost": - ip = a - elif o == "-p": - # port: port that the service runs on - port = int(a) - elif o == "-n": - servicename = a - elif o == "-x": - enablewx = int(a) - elif o == "-t": - enabletwisted = int(a) - elif o == "-a": - autostart = int(a) - else: - usage() - sys.exit() - -if len(argv) > 1: - usage() - sys.exit() -elif len(argv) == 1: - WorkingDir = argv[0] - os.chdir(WorkingDir) -elif len(argv) == 0: - WorkingDir = os.getcwd() - argv=[WorkingDir] - -import __builtin__ -if __name__ == '__main__': - __builtin__.__dict__['_'] = lambda x: x - -if enablewx: - try: - import wx, re - from threading import Thread, currentThread - from types import * - havewx = True - except: - print "Wx unavailable !" - havewx = False - - if havewx: - app=wx.App(redirect=False) - - # Import module for internationalization - import gettext - - CWD = os.path.split(os.path.realpath(__file__))[0] - - # Get folder containing translation files - localedir = os.path.join(CWD,"locale") - # Get the default language - langid = wx.LANGUAGE_DEFAULT - # Define translation domain (name of translation files) - domain = "Beremiz" - - # Define locale for wx - loc = __builtin__.__dict__.get('loc', None) - if loc is None: - loc = wx.Locale(langid) - __builtin__.__dict__['loc'] = loc - # Define location for searching translation files - loc.AddCatalogLookupPathPrefix(localedir) - # Define locale domain - loc.AddCatalog(domain) - - def unicode_translation(message): - return wx.GetTranslation(message).encode("utf-8") - - if __name__ == '__main__': - __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation - - try: - from wx.lib.embeddedimage import PyEmbeddedImage - except: - import cStringIO - import base64 - - class PyEmbeddedImage: - def __init__(self, image_string): - stream = cStringIO.StringIO(base64.b64decode(image_string)) - self.Image = wx.ImageFromStream(stream) - def GetImage(self): - return self.Image - - defaulticon = PyEmbeddedImage( - "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") - - #---------------------------------------------------------------------- - starticon = PyEmbeddedImage( - "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" - "Dw/HnwT4P6CDl+TMvD0JAAAAAElFTkSuQmCC") - - #---------------------------------------------------------------------- - stopicon = PyEmbeddedImage( - "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABPRJ" - "REFUSImdlllsVGUUx3/f/e4sd5iZLjNt6XSFdtgkjWFRePABDaCBGgjamIg81CU0aoxbRHww" - "+EDkhWjEB5rYGEMUxQTCJg8EoQ2BbgrFCNJWltplgC63naEzd+bO50NLLVAq4STfwz3nfP/f" - "PSf3O98VSikmmQ94HFgDLDdNM1BfX5955swZX0tLi5FKpbSSkpJkaWlpIhQKdVdVVX2XkZFx" - "EpBAEGgHLgH/iSqlUEoJpVSBUqpaKXXYNM0r1dXVt6WUajx5ylVYWKi2bdvWY1nWUaXUgQ0b" - "NtRWVFQUjmuilEKMV5ALvAhsPHDgQFlNTU2gr69Pk1JSFMphTomfRXO8+A243i/oG9I5f6mX" - "K1evAxAOhykrKxs9duyYkZ+f/0lPT8/2OwXogBtYDKzYunVr0c6dO3Ns26akMIcdbxQyv0hy" - "rwmh8Bas5/eb89nxRR1NTU20t7cb4x1ZPjlXB2YBiw8ePJhdW1tb4DEMXng6xJtrPQhxn/Y4" - "QSM12o89fJnOjst3hXRdLxZCGEqpUQANmBuJRLK3bNmy1OFwUPVMPm9VTiMOqLRNYvg6+shv" - "rFoWwutxTcSklGEpZXDiXZRSr6xbt+6dtra2xUW5Lr7c7EUD3Flhwmu/nRKQGO7CvHaCwY7D" - "WNEeEmoGe0+PWnuOXHWmrBTl5eW7GxsbNwPoFy5ccHV2di7yzjD4uMqDNtFngZDOKQHurDLc" - "WWX4Qk/ScfRVXCLGoorU8J+z5gbjxyWGYbwshPhQKTWi7d+/P9/pdIp5xR5C2Q9uS1fDp3T+" - "8jo32uomfJ7cCtzZYQCOjKhYOmgxI+hBSumdOXPmegDt4sWLxU6nk3BIf7A6EB/sIBY5R/+l" - "nyd8yrZIRnvZ02tduxVwFQOojBQAfr9/tRBC103TLHe5XMwpSEwLKFj2EWk7gRGYOyaeTtJ4" - "pnZk+7UhM5FtlAhAIMYAESd+v78IyNWBYqfTSUF2fFqAJ7firufhRFSdTg36rIDhQ6XHnAI0" - "L1iWhWEYASBLl1L+JaWcfSuqk+u3AUikRer4ADffg/w7gt80fs35r34k3BYh2xNAarooAJ4d" - "vsHgaP8EWMR17GiaVo8r0+Fw6DrQDDzXO+RgQSjBUFIlPh+wB0vLZD6TrLWrkWRXB29fGAK6" - "pql1rNXVmrCklJYGtAgh6DXHDsuuG8k+O9M5895tq+atpSwwZ9o2TjZlWTGl1IAGNEsp1c1E" - "DiMqmI7nZRQJ7j/G6xZWMS/vsYcGkEzG4vF4RDt06FBfZmbmwR/27uOD3f1aVk+BljMjD6lp" - "/DN07a4VTYw8tL4rrQZgbNixadOm90+dOvX82cZmcbaxmWBukOVrlvJudw1R1xDp8a+kuPM6" - "Gx8S4LXtCIwNO1asWDGYl5dn3gneunGLc7/+gTttoAntQRrTmgMmpimAHQwGOycnlBaX4rUz" - "8LszMRweXLr7kWB35oMdCAT+1jRt0cqVK6Otra2+hvoGGuobWPLEEsoXzkbPkLhvR4CBRwJY" - "Xq/3SGVlZbq7u7utsrJyxDTNz06cOJHZ0tRCS1MLAKuRwNQT9v8AyV27dn1fXl7eqmlae11d" - "XXLfvn0/+Xy+l6LR6Gu2befFYjFfzrk2FzeHp7mK7jdxz2/LffGamhpvc3NzyLKsbFd3z1PG" - "aHyBTKdjum0POGzbFAp7qo0xVOtJZdf/C/wRDnL5FYGSAAAAAElFTkSuQmCC") - - class ParamsEntryDialog(wx.TextEntryDialog): - if wx.VERSION < (2, 6, 0): - def Bind(self, event, function, id = None): - if id is not None: - event(self, id, function) - else: - event(self, function) - - - 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()) - elif wx.VERSION >= (2, 6, 0): - 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} - for function, message in self.Tests: - if not function(value): - message = wx.MessageDialog(self, message%texts, _("Error"), wx.OK|wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - 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() - TBMENU_CHANGE_NAME = wx.NewId() - TBMENU_CHANGE_PORT = wx.NewId() - TBMENU_CHANGE_INTERFACE = wx.NewId() - TBMENU_LIVE_SHELL = wx.NewId() - TBMENU_WXINSPECTOR = wx.NewId() - TBMENU_CHANGE_WD = wx.NewId() - TBMENU_QUIT = wx.NewId() - - def __init__(self, pyroserver): - wx.TaskBarIcon.__init__(self) - self.pyroserver = pyroserver - # Set the image - self.UpdateIcon(None) - - # 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) - self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME) - self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE) - self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL) - self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR) - 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 - the menu for the default EVT_RIGHT_DOWN event. Just create - the menu how you want it and return it from this function, - the base class takes care of the rest. - """ - menu = wx.Menu() - menu.Append(self.TBMENU_START, _("Start PLC")) - menu.Append(self.TBMENU_STOP, _("Stop PLC")) - menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name")) - menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind")) - menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell")) - menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector")) - menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number")) - menu.AppendSeparator() - menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory")) - menu.Append(self.TBMENU_QUIT, _("Quit")) - return menu - - def MakeIcon(self, img): - """ - The various platforms have different requirements for the - icon size... - """ - if "wxMSW" in wx.PlatformInfo: - img = img.Scale(16, 16) - elif "wxGTK" in wx.PlatformInfo: - img = img.Scale(22, 22) - # 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: - self.pyroserver.plcobj.StartPLC() - evt.Skip() - - def OnTaskBarStopPLC(self, evt): - if self.pyroserver.plcobj is not None: - self.pyroserver.plcobj.StopPLC() - evt.Skip() - - def OnTaskBarChangeInterface(self, evt): - dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=self.pyroserver.ip) - dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, _("IP is not valid!")), - ( lambda ip :len([x for x in ip.split(".") if 0 <= int(x) <= 255]) == 4, _("IP is not valid!")) - ]) - if dlg.ShowModal() == wx.ID_OK: - self.pyroserver.ip = dlg.GetValue() - self.pyroserver.Stop() - evt.Skip() - - 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() - evt.Skip() - - 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() - evt.Skip() - - 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() - evt.Skip() - - def OnTaskBarLiveShell(self, evt): - if self.pyroserver.plcobj is not None and self.pyroserver.plcobj.python_threads_vars is not None: - from wx import py - #frame = py.shell.ShellFrame(locals=self.pyroserver.plcobj.python_threads_vars) - frame = py.crust.CrustFrame(locals=self.pyroserver.plcobj.python_threads_vars) - frame.Show() - else: - wx.MessageBox(_("No running PLC"), _("Error")) - evt.Skip() - - def OnTaskBarWXInspector(self, evt): - # Activate the widget inspection tool - from wx.lib.inspection import InspectionTool - if not InspectionTool().initialized: - InspectionTool().Init(locals=self.pyroserver.plcobj.python_threads_vars) - - # Find a widget to be selected in the tree. Use either the - # one under the cursor, if any, or this frame. - wnd = wx.FindWindowAtPointer() - if not wnd: - wnd = wx.GetApp() - InspectionTool().Show(wnd, True) - evt.Skip() - - def OnTaskBarQuit(self, evt): - self.pyroserver.Quit() - self.RemoveIcon() - wx.CallAfter(wx.GetApp().Exit) - evt.Skip() - - def UpdateIcon(self, plcstatus): - if plcstatus is "Started" : - currenticon = self.MakeIcon(starticon.GetImage()) - elif plcstatus is "Stopped": - currenticon = self.MakeIcon(stopicon.GetImage()) - else: - currenticon = self.MakeIcon(defaulticon.GetImage()) - self.SetIcon(currenticon, "Beremiz Service") - -from runtime import PLCObject, PLCprint, ServicePublisher -import Pyro.core as pyro - -if not os.path.isdir(WorkingDir): - os.mkdir(WorkingDir) - -def default_evaluator(callable, *args, **kwargs): - return callable(*args,**kwargs) - -class Server(): - def __init__(self, servicename, ip, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator, website=None): - self.continueloop = True - self.daemon = None - self.servicename = servicename - self.ip = ip - self.port = port - self.workdir = workdir - self.argv = argv - self.plcobj = None - self.servicepublisher = None - self.autostart = autostart - self.statuschange = statuschange - self.evaluator = evaluator - self.website = website - - def Loop(self): - while self.continueloop: - self.Start() - - def Restart(self): - self.Stop() - - def Quit(self): - self.continueloop = False - self.Stop() - - def Start(self): - pyro.initServer() - self.daemon=pyro.Daemon(host=self.ip, port=self.port) - self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator, self.website) - 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 != "localhost" and self.ip != "127.0.0.1": - print "Publishing service on local network" - self.servicepublisher = ServicePublisher.ServicePublisher() - self.servicepublisher.RegisterService(self.servicename, self.ip, self.port) - - if self.autostart: - self.plcobj.StartPLC() - - sys.stdout.flush() - - self.daemon.requestLoop() - - def Stop(self): - self.plcobj.StopPLC() - if self.servicepublisher is not None: - self.servicepublisher.UnRegisterService() - self.servicepublisher = None - self.daemon.shutdown(True) - -if enabletwisted: - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - try: - from threading import Thread, currentThread - 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 - - havetwisted = True - except: - print "Twisted unavailable !" - havetwisted = False - -if havetwisted: - - xhtml_header = ''' - -''' - - 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" - - 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) - mythread = currentThread() - - def statuschange(status): - wx.CallAfter(taskbar_instance.UpdateIcon,status) - - eval_res = None - def wx_evaluator(callable, *args, **kwargs): - global eval_res - try: - eval_res=callable(*args,**kwargs) - except Exception,e: - PLCprint("#EXCEPTION : "+str(e)) - finally: - wx_eval_lock.release() - - def evaluator(callable, *args, **kwargs): - # call directly the callable function if call from the wx mainloop (avoid dead lock) - if(mythread == currentThread()): - callable(*args,**kwargs) - else: - wx.CallAfter(wx_evaluator,callable,*args,**kwargs) - wx_eval_lock.acquire() - return eval_res - - pyroserver = Server(servicename, ip, port, WorkingDir, argv, autostart, statuschange, evaluator, website) - taskbar_instance = BeremizTaskBarIcon(pyroserver) -else: - pyroserver = Server(servicename, ip, port, WorkingDir, argv, autostart, website=website) - -if havetwisted or havewx: - pyro_thread=Thread(target=pyroserver.Loop) - pyro_thread.start() - - if havetwisted: - reactor.run() - elif havewx: - app.MainLoop() -else: - try : - pyroserver.Loop() - except KeyboardInterrupt,e: - pass -pyroserver.Quit() -sys.exit(0) - +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#This file is part of Beremiz, a Integrated Development Environment for +#programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# +#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 os, sys, getopt + +def usage(): + print """ +Usage of Beremiz PLC execution service :\n +%s {[-n servicename] [-i ip] [-p port] [-x enabletaskbar] [-a autostart]|-h|--help} working_dir + -n - zeroconf service name (default:disabled) + -i - ip of interface to bind to (default:localhost) + -p - port number default:3000 + -h - print this help text and quit + -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) + + 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") +except getopt.GetoptError, err: + # print help information and exit: + print str(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + +# default values +ip = "" +port = 3000 +servicename = None +autostart = False +enablewx = True +havewx = False +enabletwisted = True +havetwisted = False + +for o, a in opts: + if o == "-h": + usage() + sys.exit() + elif o == "-i": + if len(a.split(".")) == 4 or a == "localhost": + ip = a + elif o == "-p": + # port: port that the service runs on + port = int(a) + elif o == "-n": + servicename = a + elif o == "-x": + enablewx = int(a) + elif o == "-t": + enabletwisted = int(a) + elif o == "-a": + autostart = int(a) + else: + usage() + sys.exit() + +if len(argv) > 1: + usage() + sys.exit() +elif len(argv) == 1: + WorkingDir = argv[0] + os.chdir(WorkingDir) +elif len(argv) == 0: + WorkingDir = os.getcwd() + argv=[WorkingDir] + +import __builtin__ +if __name__ == '__main__': + __builtin__.__dict__['_'] = lambda x: x + +if enablewx: + try: + import wx, re + from threading import Thread, currentThread + from types import * + havewx = True + except: + print "Wx unavailable !" + havewx = False + + if havewx: + app=wx.App(redirect=False) + + # Import module for internationalization + import gettext + + CWD = os.path.split(os.path.realpath(__file__))[0] + + # Get folder containing translation files + localedir = os.path.join(CWD,"locale") + # Get the default language + langid = wx.LANGUAGE_DEFAULT + # Define translation domain (name of translation files) + domain = "Beremiz" + + # Define locale for wx + loc = __builtin__.__dict__.get('loc', None) + if loc is None: + loc = wx.Locale(langid) + __builtin__.__dict__['loc'] = loc + # Define location for searching translation files + loc.AddCatalogLookupPathPrefix(localedir) + # Define locale domain + loc.AddCatalog(domain) + + def unicode_translation(message): + return wx.GetTranslation(message).encode("utf-8") + + if __name__ == '__main__': + __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation + + try: + from wx.lib.embeddedimage import PyEmbeddedImage + except: + import cStringIO + import base64 + + class PyEmbeddedImage: + def __init__(self, image_string): + stream = cStringIO.StringIO(base64.b64decode(image_string)) + self.Image = wx.ImageFromStream(stream) + def GetImage(self): + return self.Image + + defaulticon = PyEmbeddedImage( + "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") + + #---------------------------------------------------------------------- + starticon = PyEmbeddedImage( + "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" + "Dw/HnwT4P6CDl+TMvD0JAAAAAElFTkSuQmCC") + + #---------------------------------------------------------------------- + stopicon = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABPRJ" + "REFUSImdlllsVGUUx3/f/e4sd5iZLjNt6XSFdtgkjWFRePABDaCBGgjamIg81CU0aoxbRHww" + "+EDkhWjEB5rYGEMUxQTCJg8EoQ2BbgrFCNJWltplgC63naEzd+bO50NLLVAq4STfwz3nfP/f" + "PSf3O98VSikmmQ94HFgDLDdNM1BfX5955swZX0tLi5FKpbSSkpJkaWlpIhQKdVdVVX2XkZFx" + "EpBAEGgHLgH/iSqlUEoJpVSBUqpaKXXYNM0r1dXVt6WUajx5ylVYWKi2bdvWY1nWUaXUgQ0b" + "NtRWVFQUjmuilEKMV5ALvAhsPHDgQFlNTU2gr69Pk1JSFMphTomfRXO8+A243i/oG9I5f6mX" + "K1evAxAOhykrKxs9duyYkZ+f/0lPT8/2OwXogBtYDKzYunVr0c6dO3Ns26akMIcdbxQyv0hy" + "rwmh8Bas5/eb89nxRR1NTU20t7cb4x1ZPjlXB2YBiw8ePJhdW1tb4DEMXng6xJtrPQhxn/Y4" + "QSM12o89fJnOjst3hXRdLxZCGEqpUQANmBuJRLK3bNmy1OFwUPVMPm9VTiMOqLRNYvg6+shv" + "rFoWwutxTcSklGEpZXDiXZRSr6xbt+6dtra2xUW5Lr7c7EUD3Flhwmu/nRKQGO7CvHaCwY7D" + "WNEeEmoGe0+PWnuOXHWmrBTl5eW7GxsbNwPoFy5ccHV2di7yzjD4uMqDNtFngZDOKQHurDLc" + "WWX4Qk/ScfRVXCLGoorU8J+z5gbjxyWGYbwshPhQKTWi7d+/P9/pdIp5xR5C2Q9uS1fDp3T+" + "8jo32uomfJ7cCtzZYQCOjKhYOmgxI+hBSumdOXPmegDt4sWLxU6nk3BIf7A6EB/sIBY5R/+l" + "nyd8yrZIRnvZ02tduxVwFQOojBQAfr9/tRBC103TLHe5XMwpSEwLKFj2EWk7gRGYOyaeTtJ4" + "pnZk+7UhM5FtlAhAIMYAESd+v78IyNWBYqfTSUF2fFqAJ7firufhRFSdTg36rIDhQ6XHnAI0" + "L1iWhWEYASBLl1L+JaWcfSuqk+u3AUikRer4ADffg/w7gt80fs35r34k3BYh2xNAarooAJ4d" + "vsHgaP8EWMR17GiaVo8r0+Fw6DrQDDzXO+RgQSjBUFIlPh+wB0vLZD6TrLWrkWRXB29fGAK6" + "pql1rNXVmrCklJYGtAgh6DXHDsuuG8k+O9M5895tq+atpSwwZ9o2TjZlWTGl1IAGNEsp1c1E" + "DiMqmI7nZRQJ7j/G6xZWMS/vsYcGkEzG4vF4RDt06FBfZmbmwR/27uOD3f1aVk+BljMjD6lp" + "/DN07a4VTYw8tL4rrQZgbNixadOm90+dOvX82cZmcbaxmWBukOVrlvJudw1R1xDp8a+kuPM6" + "Gx8S4LXtCIwNO1asWDGYl5dn3gneunGLc7/+gTttoAntQRrTmgMmpimAHQwGOycnlBaX4rUz" + "8LszMRweXLr7kWB35oMdCAT+1jRt0cqVK6Otra2+hvoGGuobWPLEEsoXzkbPkLhvR4CBRwJY" + "Xq/3SGVlZbq7u7utsrJyxDTNz06cOJHZ0tRCS1MLAKuRwNQT9v8AyV27dn1fXl7eqmlae11d" + "XXLfvn0/+Xy+l6LR6Gu2befFYjFfzrk2FzeHp7mK7jdxz2/LffGamhpvc3NzyLKsbFd3z1PG" + "aHyBTKdjum0POGzbFAp7qo0xVOtJZdf/C/wRDnL5FYGSAAAAAElFTkSuQmCC") + + class ParamsEntryDialog(wx.TextEntryDialog): + if wx.VERSION < (2, 6, 0): + def Bind(self, event, function, id = None): + if id is not None: + event(self, id, function) + else: + event(self, function) + + + 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()) + elif wx.VERSION >= (2, 6, 0): + 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} + for function, message in self.Tests: + if not function(value): + message = wx.MessageDialog(self, message%texts, _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + 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() + TBMENU_CHANGE_NAME = wx.NewId() + TBMENU_CHANGE_PORT = wx.NewId() + TBMENU_CHANGE_INTERFACE = wx.NewId() + TBMENU_LIVE_SHELL = wx.NewId() + TBMENU_WXINSPECTOR = wx.NewId() + TBMENU_CHANGE_WD = wx.NewId() + TBMENU_QUIT = wx.NewId() + + def __init__(self, pyroserver): + wx.TaskBarIcon.__init__(self) + self.pyroserver = pyroserver + # Set the image + self.UpdateIcon(None) + + # 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) + self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME) + self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE) + self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL) + self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR) + 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 + the menu for the default EVT_RIGHT_DOWN event. Just create + the menu how you want it and return it from this function, + the base class takes care of the rest. + """ + menu = wx.Menu() + menu.Append(self.TBMENU_START, _("Start PLC")) + menu.Append(self.TBMENU_STOP, _("Stop PLC")) + menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name")) + menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind")) + menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell")) + menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector")) + menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number")) + menu.AppendSeparator() + menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory")) + menu.Append(self.TBMENU_QUIT, _("Quit")) + return menu + + def MakeIcon(self, img): + """ + The various platforms have different requirements for the + icon size... + """ + if "wxMSW" in wx.PlatformInfo: + img = img.Scale(16, 16) + elif "wxGTK" in wx.PlatformInfo: + img = img.Scale(22, 22) + # 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: + self.pyroserver.plcobj.StartPLC() + evt.Skip() + + def OnTaskBarStopPLC(self, evt): + if self.pyroserver.plcobj is not None: + self.pyroserver.plcobj.StopPLC() + evt.Skip() + + def OnTaskBarChangeInterface(self, evt): + dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=self.pyroserver.ip) + dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, _("IP is not valid!")), + ( lambda ip :len([x for x in ip.split(".") if 0 <= int(x) <= 255]) == 4, _("IP is not valid!")) + ]) + if dlg.ShowModal() == wx.ID_OK: + self.pyroserver.ip = dlg.GetValue() + self.pyroserver.Stop() + evt.Skip() + + 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() + evt.Skip() + + 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() + evt.Skip() + + 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() + evt.Skip() + + def OnTaskBarLiveShell(self, evt): + if self.pyroserver.plcobj is not None and self.pyroserver.plcobj.python_threads_vars is not None: + from wx import py + #frame = py.shell.ShellFrame(locals=self.pyroserver.plcobj.python_threads_vars) + frame = py.crust.CrustFrame(locals=self.pyroserver.plcobj.python_threads_vars) + frame.Show() + else: + wx.MessageBox(_("No running PLC"), _("Error")) + evt.Skip() + + def OnTaskBarWXInspector(self, evt): + # Activate the widget inspection tool + from wx.lib.inspection import InspectionTool + if not InspectionTool().initialized: + InspectionTool().Init(locals=self.pyroserver.plcobj.python_threads_vars) + + # Find a widget to be selected in the tree. Use either the + # one under the cursor, if any, or this frame. + wnd = wx.FindWindowAtPointer() + if not wnd: + wnd = wx.GetApp() + InspectionTool().Show(wnd, True) + evt.Skip() + + def OnTaskBarQuit(self, evt): + self.pyroserver.Quit() + self.RemoveIcon() + wx.CallAfter(wx.GetApp().Exit) + evt.Skip() + + def UpdateIcon(self, plcstatus): + if plcstatus is "Started" : + currenticon = self.MakeIcon(starticon.GetImage()) + elif plcstatus is "Stopped": + currenticon = self.MakeIcon(stopicon.GetImage()) + else: + currenticon = self.MakeIcon(defaulticon.GetImage()) + self.SetIcon(currenticon, "Beremiz Service") + +from runtime import PLCObject, PLCprint, ServicePublisher +import Pyro.core as pyro + +if not os.path.isdir(WorkingDir): + os.mkdir(WorkingDir) + +def default_evaluator(callable, *args, **kwargs): + return callable(*args,**kwargs) + +class Server(): + def __init__(self, servicename, ip, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator, website=None): + self.continueloop = True + self.daemon = None + self.servicename = servicename + self.ip = ip + self.port = port + self.workdir = workdir + self.argv = argv + self.plcobj = None + self.servicepublisher = None + self.autostart = autostart + self.statuschange = statuschange + self.evaluator = evaluator + self.website = website + + def Loop(self): + while self.continueloop: + self.Start() + + def Restart(self): + self.Stop() + + def Quit(self): + self.continueloop = False + self.Stop() + + def Start(self): + pyro.initServer() + self.daemon=pyro.Daemon(host=self.ip, port=self.port) + self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator, self.website) + 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 != "localhost" and self.ip != "127.0.0.1": + print "Publishing service on local network" + self.servicepublisher = ServicePublisher.ServicePublisher() + self.servicepublisher.RegisterService(self.servicename, self.ip, self.port) + + if self.autostart: + self.plcobj.StartPLC() + + sys.stdout.flush() + + self.daemon.requestLoop() + + def Stop(self): + self.plcobj.StopPLC() + if self.servicepublisher is not None: + self.servicepublisher.UnRegisterService() + self.servicepublisher = None + self.daemon.shutdown(True) + +if enabletwisted: + import warnings + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + from threading import Thread, currentThread + 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 + + havetwisted = True + except: + print "Twisted unavailable !" + havetwisted = False + +if havetwisted: + + xhtml_header = ''' + +''' + + 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" + + 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) + mythread = currentThread() + + def statuschange(status): + wx.CallAfter(taskbar_instance.UpdateIcon,status) + + eval_res = None + def wx_evaluator(callable, *args, **kwargs): + global eval_res + try: + eval_res=callable(*args,**kwargs) + except Exception,e: + PLCprint("#EXCEPTION : "+str(e)) + finally: + wx_eval_lock.release() + + def evaluator(callable, *args, **kwargs): + # call directly the callable function if call from the wx mainloop (avoid dead lock) + if(mythread == currentThread()): + callable(*args,**kwargs) + else: + wx.CallAfter(wx_evaluator,callable,*args,**kwargs) + wx_eval_lock.acquire() + return eval_res + + pyroserver = Server(servicename, ip, port, WorkingDir, argv, autostart, statuschange, evaluator, website) + taskbar_instance = BeremizTaskBarIcon(pyroserver) +else: + pyroserver = Server(servicename, ip, port, WorkingDir, argv, autostart, website=website) + +if havetwisted or havewx: + pyro_thread=Thread(target=pyroserver.Loop) + pyro_thread.start() + + if havetwisted: + reactor.run() + elif havewx: + app.MainLoop() +else: + try : + pyroserver.Loop() + except KeyboardInterrupt,e: + pass +pyroserver.Quit() +sys.exit(0)