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: 
andrej@1881: from __future__ import absolute_import
andrej@1826: from __future__ import print_function
denis@2006: import sys
andrej@1732: import traceback
Edouard@2472: from functools import partial
andrej@1832: from threading import Thread, Event
Edouard@2470: from six import text_type as text
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: 
andrej@2416: from runtime import PlcStatus
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: 
andrej@1747: PLCObjDefaults = {
andrej@1747:     "StartPLC":          False,
andrej@1747:     "GetTraceVariables": ("Broken", None),
andrej@2416:     "GetPLCstatus":      (PlcStatus.Broken, None),
andrej@1747:     "RemoteExec":        (-1, "RemoteExec script failed!")
andrej@1747: }
Edouard@1440: 
andrej@1736: 
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(
andrej@2434:             realm=text(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:     def WampSessionProcMapper(funcname):
andrej@2434:         wampfuncname = text('.'.join((ID, funcname)))
andrej@1750: 
andrej@1740:         def catcher_func(*args, **kwargs):
andrej@1739:             if _WampSession is not None:
Edouard@1440:                 try:
Edouard@1440:                     return threads.blockingCallFromThread(
Edouard@1443:                         reactor, _WampSession.call, wampfuncname,
andrej@1740:                         *args, **kwargs)
andrej@1846:                 except TransportLost:
andrej@1595:                     confnodesroot.logger.write_error(_("Connection lost!\n"))
Edouard@1440:                     confnodesroot._SetConnector(None)
andrej@1846:                 except Exception:
Edouard@1440:                     errmess = traceback.format_exc()
Edouard@1440:                     confnodesroot.logger.write_error(errmess+"\n")
andrej@1826:                     print(errmess)
andrej@1782:                     # confnodesroot._SetConnector(None)
Edouard@1440:             return PLCObjDefaults.get(funcname)
Edouard@1440:         return catcher_func
Edouard@1440: 
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@1440:         def __getattr__(self, attrName):
Edouard@1440:             member = self.__dict__.get(attrName, None)
Edouard@1440:             if member is None:
Edouard@1440:                 member = 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@2463:     return WampPLCObjectProxy
Edouard@2472: 
edouard@2492: 
Edouard@2472: WAMP_connector_factory = partial(_WAMP_connector_factory, WampSession)