Runtime : Added PLCobject methods registring. IDE : Added WAMP connector. Still need some fixes
authorEdouard Tisserant
Sun, 08 Feb 2015 16:50:54 +0100
changeset 1440 e8daabf2c438
parent 1439 a68cd4253259
child 1441 826730e60407
Runtime : Added PLCobject methods registring. IDE : Added WAMP connector. Still need some fixes
connectors/PYRO/__init__.py
connectors/WAMP/__init__.py
connectors/__init__.py
runtime/PLCObject.py
runtime/WampClient.py
tests/wamp/.crossbar/config.json
tests/wamp/beremiz.xml
tests/wamp/plc.xml
--- a/connectors/PYRO/__init__.py	Thu Feb 05 23:32:31 2015 +0100
+++ b/connectors/PYRO/__init__.py	Sun Feb 08 16:50:54 2015 +0100
@@ -37,7 +37,7 @@
     """
     This returns the connector to Pyro style PLCobject
     """
-    confnodesroot.logger.write(_("Connecting to URI : %s\n")%uri)
+    confnodesroot.logger.write(_("PYRO connecting to URI : %s\n")%uri)
 
     servicetype, location = uri.split("://")
     if location.find(service_type) != -1:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/WAMP/__init__.py	Sun Feb 08 16:50:54 2015 +0100
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2015: Edouard TISSERANT
+#
+#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 sys, traceback
+#from twisted.python import log
+from twisted.internet import reactor, threads
+from autobahn.twisted import wamp
+from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
+from autobahn.wamp import types
+from autobahn.wamp.exception import TransportLost
+from autobahn.wamp.serializer import MsgPackSerializer
+from threading import Thread, Event
+
+_WampSession = None
+_ReactorThread = None
+_WampSessionEvent = Event()
+
+class WampSession(wamp.ApplicationSession):
+    def onJoin(self, details):
+        global _WampSession
+        _WampSession = self
+        _WampSessionEvent.set()
+        print 'WAMP session joined for :', self.config.extra["ID"]
+
+    def onLeave(self, details):
+        global _WampSession
+        _WampSessionEvent.clear()
+        _WampSession = None
+        print 'WAMP session left'
+
+PLCObjDefaults = { "StartPLC": False,
+                   "GetTraceVariables" : ("Broken",None),
+                   "GetPLCstatus" : ("Broken",None),
+                   "RemoteExec" : (-1, "RemoteExec script failed!")}
+
+def WAMP_connector_factory(uri, confnodesroot):
+    """
+    WAMP://127.0.0.1:12345/path#realm#ID
+    WAMPS://127.0.0.1:12345/path#realm#ID
+    """
+    global _WampSession, _ReactorThread, _WampSessionEvent
+
+    servicetype, location = uri.split("://")
+    urlpath, realm, ID = location.split('#')
+    urlprefix = {"WAMP":"ws",
+                 "WAMPS":"wss"}[servicetype]
+    url = urlprefix+"://"+urlpath
+
+    def RegisterWampClient():
+
+        ## start logging to console
+        # log.startLogging(sys.stdout)
+
+        # create a WAMP application session factory
+        component_config = types.ComponentConfig(
+            realm = realm,
+            extra = {"ID":ID})
+        session_factory = wamp.ApplicationSessionFactory(
+            config = component_config)
+        session_factory.session = WampSession
+
+        # create a WAMP-over-WebSocket transport client factory
+        transport_factory = WampWebSocketClientFactory(
+            session_factory,
+            url = url,
+            serializers = [MsgPackSerializer()],
+            debug = False,
+            debug_wamp = False)
+
+        # start the client from a Twisted endpoint
+        conn = connectWS(transport_factory)
+        confnodesroot.logger.write(_("WAMP connecting to URL : %s\n")%url)
+        return conn
+
+    def WampSessionProcMapper(funcname):
+        def catcher_func(*args,**kwargs):
+            if _WampSession is not None :
+                try:
+                    return threads.blockingCallFromThread(
+                        reactor, _WampSession.call, funcname,
+                        *args,**kwargs)
+                except TransportLost, e:
+                    confnodesroot.logger.write_error("Connection lost!\n")
+                    confnodesroot._SetConnector(None)
+                except Exception,e:
+                    errmess = traceback.format_exc()
+                    confnodesroot.logger.write_error(errmess+"\n")
+                    print errmess
+                    #confnodesroot._SetConnector(None)
+            return PLCObjDefaults.get(funcname)
+        return catcher_func
+
+    class WampPLCObjectProxy(object):
+        def __init__(self):
+            if not reactor.running:
+                def ThreadProc():
+                    self.connection = RegisterWampClient()
+                    reactor.run(installSignalHandlers=False)
+                Thread(target=ThreadProc).start()
+            else:
+                self.connection = threads.blockingCallFromThread(
+                    reactor, RegisterWampClient)
+            if not _WampSessionEvent.wait(5):
+                self.connection.stopConnecting()
+                raise Exception, _("WAMP connection timeout")
+
+        def __del__(self):
+            self.connection.disconnect()
+            #reactor.Stop()
+
+        def __getattr__(self, attrName):
+            member = self.__dict__.get(attrName, None)
+            if member is None:
+                member = WampSessionProcMapper(attrName)
+                self.__dict__[attrName] = member
+            return member
+
+    # Try to get the proxy object
+    try :
+        return WampPLCObjectProxy()
+    except Exception, msg:
+        confnodesroot.logger.write_error(_("WAMP connection to '%s' failed.\n")%location)
+        confnodesroot.logger.write_error(traceback.format_exc())
+        return None
+
+
+
+
--- a/connectors/__init__.py	Thu Feb 05 23:32:31 2015 +0100
+++ b/connectors/__init__.py	Sun Feb 08 16:50:54 2015 +0100
@@ -30,9 +30,9 @@
 def _GetLocalConnectorClassFactory(name):
     return lambda:getattr(__import__(name,globals(),locals()), name + "_connector_factory")
 
-connectors = {name:_GetLocalConnectorClassFactory(name) 
-                  for name in listdir(_base_path) 
-                      if path.isdir(path.join(_base_path, name)) 
+connectors = {name:_GetLocalConnectorClassFactory(name)
+                  for name in listdir(_base_path)
+                      if path.isdir(path.join(_base_path, name))
                           and not name.startswith("__")}
 
 def ConnectorFactory(uri, confnodesroot):
@@ -40,15 +40,23 @@
     Return a connector corresponding to the URI
     or None if cannot connect to URI
     """
-    servicetype = uri.split("://")[0]
-    if servicetype in connectors:
-        # import module according to uri type
-        connectorclass = connectors[servicetype]()
-    elif servicetype == "LOCAL":
-        from PYRO import PYRO_connector_factory as connectorclass
-        runtime_port = confnodesroot.AppFrame.StartLocalRuntime(taskbaricon=True)
+    servicetype = uri.split("://")[0].upper()
+    if servicetype == "LOCAL":
+        # Local is special case
+        # pyro connection to local runtime
+        # started on demand, listening on random port
+        servicetype = "PYRO"
+        runtime_port = confnodesroot.AppFrame.StartLocalRuntime(
+            taskbaricon=True)
         uri="PYRO://127.0.0.1:"+str(runtime_port)
+    elif servicetype in connectors:
+        pass
+    elif servicetype[-1]=='S' and servicetype[:-1] in connectors:
+        servicetype = servicetype[:-1]
     else :
-        return None    
+        return None
+
+    # import module according to uri type
+    connectorclass = connectors[servicetype]()
     return connectorclass(uri, confnodesroot)
 
--- a/runtime/PLCObject.py	Thu Feb 05 23:32:31 2015 +0100
+++ b/runtime/PLCObject.py	Sun Feb 08 16:50:54 2015 +0100
@@ -447,7 +447,8 @@
             last_md5 = open(self._GetMD5FileName(), "r").read()
             return last_md5 == MD5
         except:
-            return False
+            pass
+        return False
 
     def SetTraceVariablesList(self, idxs):
         """
@@ -534,7 +535,7 @@
         self._TracesFlush()
 
 
-    def RemoteExec(self, script, **kwargs):
+    def RemoteExec(self, script, *kwargs):
         try:
             exec script in kwargs
         except:
--- a/runtime/WampClient.py	Thu Feb 05 23:32:31 2015 +0100
+++ b/runtime/WampClient.py	Sun Feb 08 16:50:54 2015 +0100
@@ -2,38 +2,59 @@
 # -*- coding: utf-8 -*-
 
 import sys
-from twisted.python import log
-
-from twisted.internet import reactor, ssl
+#from twisted.python import log
 from autobahn.twisted import wamp
 from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
+from twisted.internet.defer import inlineCallbacks
 from autobahn.wamp import types
+from autobahn.wamp.serializer import MsgPackSerializer
 import json
 
 _WampSession = None
 _PySrv = None
 
+ExposedCalls = ["StartPLC",
+                "StopPLC",
+                "ForceReload",
+                "GetPLCstatus",
+                "NewPLC",
+                "MatchMD5",
+                "SetTraceVariablesList",
+                "GetTraceVariables",
+                "RemoteExec",
+                "GetLogMessage",
+                ]
+
+def MakeCallee(name):
+    global _PySrv
+    def Callee(*args,**kwargs):
+        return getattr(_PySrv.plcobj, name)(*args,**kwargs)
+    return Callee
+
+
 class WampSession(wamp.ApplicationSession):
+
+    @inlineCallbacks
     def onJoin(self, details):
         global _WampSession
         _WampSession = self
         print 'WAMP session joined by :', self.config.extra["ID"]
+        for name in ExposedCalls:
+            reg = yield self.register(MakeCallee(name), name)
 
     def onLeave(self, details):
         global _WampSession
         _WampSession = None
         print 'WAMP session left'
 
-
 def RegisterWampClient(wampconf):
 
     WSClientConf = json.load(open(wampconf))
 
-    ## TODO log to PLC console instead
-    ## 0) start logging to console
-    log.startLogging(sys.stdout)
+    ## start logging to console
+    # log.startLogging(sys.stdout)
 
-    ## 1) create a WAMP application session factory
+    # create a WAMP application session factory
     component_config = types.ComponentConfig(
         realm = WSClientConf["realm"],
         extra = {"ID":WSClientConf["ID"]})
@@ -41,27 +62,17 @@
         config = component_config)
     session_factory.session = WampSession
 
-    ## TODO select optimum serializer for passing session lists
-    ## optional: use specific set of serializers
-    #from autobahn.wamp.serializer import *
-    #serializers = []
-    ##serializers.append(JsonSerializer(batched = True))
-    ##serializers.append(MsgPackSerializer(batched = True))
-    #serializers.append(JsonSerializer())
-    ##serializers.append(MsgPackSerializer())
-    serializers = None
-
-    ## 2) create a WAMP-over-WebSocket transport client factory
+    # create a WAMP-over-WebSocket transport client factory
     transport_factory = WampWebSocketClientFactory(
         session_factory,
         url = WSClientConf["url"],
-        serializers = serializers,
+        serializers = [MsgPackSerializer()],
         debug = False,
         debug_wamp = False)
 
-    ## 3) start the client from a Twisted endpoint
+    # start the client from a Twisted endpoint
     conn = connectWS(transport_factory)
-    print "WAMP clien connecting to :",WSClientConf["url"]
+    print "WAMP client connecting to :",WSClientConf["url"]
     return conn
 
 def GetSession():
--- a/tests/wamp/.crossbar/config.json	Thu Feb 05 23:32:31 2015 +0100
+++ b/tests/wamp/.crossbar/config.json	Sun Feb 08 16:50:54 2015 +0100
@@ -34,7 +34,8 @@
                   "type": "tcp",
                   "port": 8888
                },
-               "url": "ws://127.0.0.1:8888/"
+               "url": "ws://127.0.0.1:8888/",
+               "serializers" : ["msgpack"]
             }
          ]
       }
--- a/tests/wamp/beremiz.xml	Thu Feb 05 23:32:31 2015 +0100
+++ b/tests/wamp/beremiz.xml	Sun Feb 08 16:50:54 2015 +0100
@@ -1,4 +1,4 @@
 <?xml version='1.0' encoding='utf-8'?>
-<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="PYRO://127.0.0.1:3000">
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="WAMP://127.0.0.1:8888#Automation#wamptest">
   <TargetType/>
 </BeremizRoot>
--- a/tests/wamp/plc.xml	Thu Feb 05 23:32:31 2015 +0100
+++ b/tests/wamp/plc.xml	Sun Feb 08 16:50:54 2015 +0100
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="Beremiz" productName="Beremiz" productVersion="1" creationDateTime="2015-02-05T11:44:55" contentDescription=" &#10;&#10;"/>
-  <contentHeader name="WAMPTest" modificationDateTime="2015-02-05T13:45:38">
+  <contentHeader name="WAMPTest" modificationDateTime="2015-02-07T22:25:01">
     <coordinateInfo>
       <fbd>
         <scaling x="0" y="0"/>