--- a/Beremiz_service.py Fri Aug 07 18:27:50 2009 +0200
+++ b/Beremiz_service.py Mon Aug 10 14:42:54 2009 +0200
@@ -34,12 +34,13 @@
-h - print this help text and quit
-a - autostart PLC (0:disable 1:enable)
-x - enable/disable wxTaskbarIcon (0:disable 1:enable)
+ -t - enable/disable Twisted web interface (0:disable 1:enable) working_dir - directory where are stored PLC files
- opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:a:h")
+ 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"
@@ -56,6 +57,8 @@
@@ -71,6 +74,8 @@
@@ -426,7 +431,7 @@
return callable(*args,**kwargs)
- def __init__(self, name, ip, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator):
+ def __init__(self, name, ip, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator, website=None): @@ -439,6 +444,7 @@
self.autostart = autostart
self.statuschange = statuschange
self.evaluator = evaluator
@@ -454,7 +460,7 @@
self.daemon=pyro.Daemon(host=self.ip, port=self.port)
- self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator)
+ self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator, self.website) uri = self.daemon.connect(self.plcobj,"PLCObject")
print "The daemon runs on port :",self.port
@@ -481,7 +487,161 @@
self.servicepublisher.UnRegisterService()
del self.servicepublisher
self.daemon.shutdown(True)
+ from twisted.internet import wxreactor + 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 + 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 DefaultPLCStartedHMI(athena.LiveElement): + docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[ + tags.h1["PLC IS NOW STARTED"], + class PLCStoppedHMI(athena.LiveElement): + 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.resetPLCStartedHMI() + def setPLCState(self, state): + if self.HMI is not None: + self.callRemote('updateHMI') + def setPLCStartedHMI(self, hmi): + self.PLCStartedHMIClass = hmi + def resetPLCStartedHMI(self): + self.PLCStartedHMIClass = DefaultPLCStartedHMI + def HMIexec(self, function, *args, **kwargs): + if self.HMI is not None: + getattr(self.HMI, function, lambda:None)(*args, **kwargs) + athena.expose(executeOnHMI) + def PLCElement(self, ctx, data): + return self.getPLCElement() + def getPLCElement(self): + self.detachFragmentChildren() + f = self.PLCStartedHMIClass() + f.setFragmentParent(self) + athena.expose(getPLCElement) + def detachFragmentChildren(self): + for child in self.liveFragmentChildren[:]: + 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.div( render = tags.directive( "MainPage" )) + 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) + return self.MainPage.getHMI() + def LoadHMI(self, plc, jsmodules): + for name, path in jsmodules.iteritems(): + self.jsModules.mapping[name] = os.path.join(WorkingDir, path) + self.MainPage.setPLCStarted(plc) + self.MainPage.resetPLCStartedHMI() + self.MainPage.setPLCState(True) + 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.setFragmentParent(self) + 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 "We will be called back when the client disconnects" + reactor.registerWxApp(app) + site = appserver.NevowSite(res) + reactor.listenTCP(8009, site) from threading import Semaphore
@@ -510,12 +670,17 @@
- pyroserver = Server(name, ip, port, WorkingDir, argv, autostart, statuschange, evaluator)
+ pyroserver = Server(name, ip, port, WorkingDir, argv, autostart, statuschange, evaluator, res) taskbar_instance = BeremizTaskBarIcon(pyroserver)
pyro_thread=Thread(target=pyroserver.Loop)
+ pyroserver = Server(name, ip, port, WorkingDir, argv, autostart, website=res) - pyroserver = Server(name, ip, port, WorkingDir, argv, autostart)
--- a/runtime/PLCObject.py Fri Aug 07 18:27:50 2009 +0200
+++ b/runtime/PLCObject.py Mon Aug 10 14:42:54 2009 +0200
@@ -45,7 +45,7 @@
class PLCObject(pyro.ObjBase):
- def __init__(self, workingdir, daemon, argv, statuschange, evaluator):
+ def __init__(self, workingdir, daemon, argv, statuschange, evaluator, website): pyro.ObjBase.__init__(self)
self.evaluator = evaluator
self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
@@ -59,6 +59,7 @@
self.statuschange = statuschange
# Get the last transfered PLC if connector must be restart
@@ -205,49 +206,10 @@
def PrepareRuntimePy(self):
self.python_threads_vars = globals().copy()
self.python_threads_vars["WorkingDir"] = self.workingdir
+ self.python_threads_vars["website"] = self.website self.python_threads_vars["_runtime_begin"] = []
self.python_threads_vars["_runtime_cleanup"] = []
-# pyfile = os.path.join(self.workingdir, "runtime.py")
-# hmifile = os.path.join(self.workingdir, "hmi.py")
-# if os.path.exists(hmifile):
-# execfile(hmifile, self.python_threads_vars)
-# if os.path.exists(pyfile):
-# # TODO handle exceptions in runtime.py
-# # pyfile may redefine _runtime_cleanup
-# # or even call _PythonThreadProc itself.
-# execfile(pyfile, self.python_threads_vars)
-# PLCprint(traceback.format_exc())
-# if self.python_threads_vars.has_key('wx'):
-# wx = self.python_threads_vars['wx']
-# # try to instanciate the first frame found.
-# for name, obj in self.python_threads_vars.iteritems():
-# if type(obj)==type(type) and issubclass(obj,wx.Frame):
-# self.hmi_frame = obj(None)
-# self.python_threads_vars[name] = self.hmi_frame
-# # keep track of class... never know
-# self.python_threads_vars['Class_'+name] = obj
-# self.hmi_frame.Bind(wx.EVT_CLOSE, OnCloseFrame)
-# def OnCloseFrame(evt):
-# wx.MessageBox(_("Please stop PLC to close"))
-# PLCprint(traceback.format_exc())
-# elif os.path.exists(pyfile):
-# # TODO handle exceptions in runtime.py
-# # pyfile may redefine _runtime_cleanup
-# # or even call _PythonThreadProc itself.
-# execfile(pyfile, self.python_threads_vars)
-# PLCprint(traceback.format_exc())
for filename in os.listdir(self.workingdir):
name, ext = os.path.splitext(filename)
if name.startswith("runtime") and ext == ".py":
@@ -267,16 +229,15 @@
for runtime_begin in self.python_threads_vars.get("_runtime_begin", []):
+ if self.website is not None: + self.website.PLCStarted() def FinishRuntimePy(self):
for runtime_cleanup in self.python_threads_vars.get("_runtime_cleanup", []):
-# if self.python_threads_vars is not None:
-# runtime_cleanup = self.python_threads_vars.get("_runtime_cleanup",None)
-# if runtime_cleanup is not None:
-# if self.hmi_frame is not None:
-# self.hmi_frame.Destroy()
+ if self.website is not None: + self.website.PLCStopped() self.python_threads_vars = None
def PythonThreadProc(self, debug):