Adding support for twisted website HMI
authorlaurent
Mon, 10 Aug 2009 14:42:54 +0200
changeset 368 86ecd8374dae
parent 367 a76ee5307bb7
child 369 bd54d41a7573
Adding support for twisted website HMI
Beremiz_service.py
runtime/PLCObject.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 = '''<?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.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()
--- 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):