--- 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 = '''<?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__, '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 = '''<?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__, '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)