Edouard@1440: #!/usr/bin/env python
Edouard@1440: # -*- coding: utf-8 -*-
andrej@1571: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
Edouard@1440: #
andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
Edouard@1440: #
andrej@1571: # See COPYING file for copyrights details.
Edouard@1440: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
Edouard@1440: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
Edouard@1440: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Edouard@1440: 
andrej@1826: 
denis@2006: import sys
andrej@1732: import traceback
Edouard@2472: from functools import partial
andrej@1832: from threading import Thread, Event
andrej@1832: 
Edouard@1440: from twisted.internet import reactor, threads
Edouard@1440: from autobahn.twisted import wamp
Edouard@1440: from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
Edouard@1440: from autobahn.wamp import types
Edouard@1440: from autobahn.wamp.exception import TransportLost
Edouard@1440: from autobahn.wamp.serializer import MsgPackSerializer
andrej@1832: 
Edouard@1440: 
Edouard@1440: _WampSession = None
Edouard@1441: _WampConnection = None
Edouard@1440: _WampSessionEvent = Event()
Edouard@1440: 
andrej@1736: 
Edouard@1440: class WampSession(wamp.ApplicationSession):
Edouard@1440:     def onJoin(self, details):
andrej@1841:         global _WampSession
Edouard@1440:         _WampSession = self
Edouard@1440:         _WampSessionEvent.set()
andrej@1826:         print('WAMP session joined for :', self.config.extra["ID"])
Edouard@1440: 
Edouard@1440:     def onLeave(self, details):
andrej@1841:         global _WampSession
Edouard@1440:         _WampSessionEvent.clear()
Edouard@1440:         _WampSession = None
andrej@1826:         print('WAMP session left')
Edouard@1440: 
andrej@1749: 
Edouard@2472: def _WAMP_connector_factory(cls, uri, confnodesroot):
Edouard@1440:     """
Edouard@1440:     WAMP://127.0.0.1:12345/path#realm#ID
Edouard@1440:     WAMPS://127.0.0.1:12345/path#realm#ID
Edouard@1440:     """
Edouard@2338:     scheme, location = uri.split("://")
Edouard@1440:     urlpath, realm, ID = location.split('#')
andrej@1740:     urlprefix = {"WAMP":  "ws",
Edouard@2338:                  "WAMPS": "wss"}[scheme]
Edouard@1440:     url = urlprefix+"://"+urlpath
Edouard@1440: 
Edouard@1440:     def RegisterWampClient():
Edouard@1440: 
andrej@1753:         # start logging to console
Edouard@1440:         # log.startLogging(sys.stdout)
Edouard@1440: 
Edouard@1440:         # create a WAMP application session factory
Edouard@1440:         component_config = types.ComponentConfig(
kinsamanka@3752:             realm=str(realm),
andrej@1744:             extra={"ID": ID})
Edouard@1440:         session_factory = wamp.ApplicationSessionFactory(
andrej@1744:             config=component_config)
Edouard@2472:         session_factory.session = cls
Edouard@1440: 
Edouard@1440:         # create a WAMP-over-WebSocket transport client factory
Edouard@1440:         transport_factory = WampWebSocketClientFactory(
Edouard@1440:             session_factory,
andrej@1744:             url=url,
Edouard@1894:             serializers=[MsgPackSerializer()])
Edouard@1440: 
Edouard@1440:         # start the client from a Twisted endpoint
Edouard@1440:         conn = connectWS(transport_factory)
andrej@1734:         confnodesroot.logger.write(_("WAMP connecting to URL : %s\n") % url)
Edouard@1440:         return conn
Edouard@1440: 
Edouard@1441:     AddToDoBeforeQuit = confnodesroot.AppFrame.AddToDoBeforeQuit
andrej@1750: 
Edouard@1441:     def ThreadProc():
Edouard@1441:         global _WampConnection
Edouard@1441:         _WampConnection = RegisterWampClient()
Edouard@1441:         AddToDoBeforeQuit(reactor.stop)
Edouard@1441:         reactor.run(installSignalHandlers=False)
Edouard@1441: 
Edouard@1440:     class WampPLCObjectProxy(object):
Edouard@1440:         def __init__(self):
andrej@1841:             global _WampConnection
Edouard@1440:             if not reactor.running:
Edouard@1440:                 Thread(target=ThreadProc).start()
Edouard@1440:             else:
Edouard@1441:                 _WampConnection = threads.blockingCallFromThread(
Edouard@1440:                     reactor, RegisterWampClient)
Edouard@1440:             if not _WampSessionEvent.wait(5):
andrej@1872:                 _WampConnection.stopConnecting()
andrej@1765:                 raise Exception(_("WAMP connection timeout"))
Edouard@1440: 
Edouard@1440:         def __del__(self):
Edouard@1441:             _WampConnection.disconnect()
Edouard@1441:             #
Edouard@1441:             # reactor.stop()
Edouard@1440: 
Edouard@3861:         def WampSessionProcMapper(self, funcname):
edouard@3881:             wampfuncname = str('.'.join((ID, funcname)))
Edouard@3861: 
Edouard@3861:             def catcher_func(*args, **kwargs):
Edouard@3861:                 if _WampSession is not None:
Edouard@3861:                     try:
Edouard@3861:                         return threads.blockingCallFromThread(
Edouard@3861:                             reactor, _WampSession.call, wampfuncname,
Edouard@3861:                             *args, **kwargs)
Edouard@3861:                     except TransportLost:
Edouard@3861:                         confnodesroot.logger.write_error(_("Connection lost!\n"))
Edouard@3861:                         confnodesroot._SetConnector(None)
Edouard@3861:                     except Exception:
Edouard@3861:                         errmess = traceback.format_exc()
Edouard@3861:                         confnodesroot.logger.write_error(errmess+"\n")
Edouard@3861:                         print(errmess)
Edouard@3861:                         # confnodesroot._SetConnector(None)
Edouard@3861:                 return self.PLCObjDefaults.get(funcname)
Edouard@3861:             return catcher_func
Edouard@3861: 
Edouard@1440:         def __getattr__(self, attrName):
Edouard@1440:             member = self.__dict__.get(attrName, None)
Edouard@1440:             if member is None:
Edouard@3861:                 member = self.WampSessionProcMapper(attrName)
Edouard@1440:                 self.__dict__[attrName] = member
Edouard@1440:             return member
Edouard@1440: 
Edouard@2339:     # TODO : GetPLCID()
Edouard@2339:     # TODO : PSK.UpdateID()
Edouard@2339: 
edouard@3884:     return WampPLCObjectProxy()
Edouard@2472: 
edouard@2492: 
Edouard@2472: WAMP_connector_factory = partial(_WAMP_connector_factory, WampSession)