# HG changeset patch # User laurent # Date 1249908174 -7200 # Node ID 86ecd8374dae3dceb1ef9839b0d6bd403cb5fc05 # Parent a76ee5307bb75a189c309aaebc1e1c2da639f602 Adding support for twisted website HMI diff -r a76ee5307bb7 -r 86ecd8374dae Beremiz_service.py --- 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 """%sys.argv[0] try: - 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 @@ autostart = False enablewx = True havewx = False +enabletwisted = True +havetwisted = False for o, a in opts: if o == "-h": @@ -71,6 +74,8 @@ name = a elif o == "-x": enablewx = int(a) + elif o == "-t": + enabletwisted = int(a) elif o == "-a": autostart = int(a) else: @@ -426,7 +431,7 @@ return callable(*args,**kwargs) class Server(): - 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): self.continueloop = True self.daemon = None self.name = name @@ -439,6 +444,7 @@ self.autostart = autostart self.statuschange = statuschange self.evaluator = evaluator + self.website = website def Loop(self): while self.continueloop: @@ -454,7 +460,7 @@ 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.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) - + +if enabletwisted: + try: + 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: + havetwisted = False + +if havetwisted: + + xhtml_header = ''' + +''' + + + 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.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(executeOnHMI) + + 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() + self.HMI = f + f.setFragmentParent(self) + 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() + + 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, plc, jsmodules): + for name, path in jsmodules.iteritems(): + self.jsModules.mapping[name] = os.path.join(WorkingDir, path) + self.MainPage.setPLCStarted(plc) + + 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) + res = WebInterface() + site = appserver.NevowSite(res) + reactor.listenTCP(8009, site) +else: + res = None if havewx: from threading import Semaphore @@ -510,12 +670,17 @@ wx_eval_lock.acquire() return eval_res - 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) pyro_thread.start() +else: + pyroserver = Server(name, ip, port, WorkingDir, argv, autostart, website=res) + +if havetwisted: + reactor.run() +elif havewx: app.MainLoop() else: - pyroserver = Server(name, ip, port, WorkingDir, argv, autostart) pyroserver.Loop() diff -r a76ee5307bb7 -r 86ecd8374dae runtime/PLCObject.py --- 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): _Idxs = [] - 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.daemon = daemon self.statuschange = statuschange self.hmi_frame = None + self.website = website # Get the last transfered PLC if connector must be restart try: @@ -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): -# try: -# execfile(hmifile, self.python_threads_vars) -# if os.path.exists(pyfile): -# try: -# # TODO handle exceptions in runtime.py -# # pyfile may redefine _runtime_cleanup -# # or even call _PythonThreadProc itself. -# execfile(pyfile, self.python_threads_vars) -# except: -# 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(): -# # obj is a class -# if type(obj)==type(type) and issubclass(obj,wx.Frame): -# def create_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) -# self.hmi_frame.Show() -# -# def OnCloseFrame(evt): -# wx.MessageBox(_("Please stop PLC to close")) -# create_frame() -# break -# except: -# PLCprint(traceback.format_exc()) -# elif os.path.exists(pyfile): -# try: -# # TODO handle exceptions in runtime.py -# # pyfile may redefine _runtime_cleanup -# # or even call _PythonThreadProc itself. -# execfile(pyfile, self.python_threads_vars) -# except: -# 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", []): 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", []): 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: -# runtime_cleanup() -# 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):