Edouard@1439: #!/usr/bin/env python Edouard@1439: # -*- coding: utf-8 -*- Edouard@1439: andrej@1667: # This file is part of Beremiz runtime. andrej@1511: # andrej@1511: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1511: # andrej@1667: # See COPYING.Runtime file for copyrights details. andrej@1511: # andrej@1667: # This library is free software; you can redistribute it and/or andrej@1667: # modify it under the terms of the GNU Lesser General Public andrej@1667: # License as published by the Free Software Foundation; either andrej@1667: # version 2.1 of the License, or (at your option) any later version. andrej@1667: andrej@1667: # This library is distributed in the hope that it will be useful, andrej@1511: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1667: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU andrej@1667: # Lesser General Public License for more details. andrej@1667: andrej@1667: # You should have received a copy of the GNU Lesser General Public andrej@1667: # License along with this library; if not, write to the Free Software andrej@1667: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA andrej@1511: andrej@1826: andrej@1881: from __future__ import absolute_import andrej@1826: from __future__ import print_function Edouard@1899: import time Edouard@1893: import json Edouard@1439: from autobahn.twisted import wamp Edouard@1439: from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS Edouard@1893: from autobahn.wamp import types, auth Edouard@1440: from autobahn.wamp.serializer import MsgPackSerializer andrej@1834: from twisted.internet.defer import inlineCallbacks Edouard@1441: from twisted.internet.protocol import ReconnectingClientFactory andrej@1832: Edouard@1439: Edouard@1439: _WampSession = None Edouard@1439: _PySrv = None Edouard@1439: andrej@1878: ExposedCalls = [ andrej@1878: "StartPLC", andrej@1878: "StopPLC", andrej@1878: "ForceReload", andrej@1878: "GetPLCstatus", andrej@1878: "NewPLC", andrej@1878: "MatchMD5", andrej@1878: "SetTraceVariablesList", andrej@1878: "GetTraceVariables", andrej@1878: "RemoteExec", andrej@1878: "GetLogMessage", andrej@1878: "ResetLogCount", andrej@1878: ] Edouard@1440: Edouard@1898: # Those two lists are meant to be filled by customized runtime Edouard@1901: # or User python code. Edouard@1898: Edouard@1901: """ crossbar Events to register to """ Edouard@1446: SubscribedEvents = [] Edouard@1446: Edouard@1901: """ things to do on join (callables) """ Edouard@1446: DoOnJoin = [] Edouard@1446: andrej@1736: Edouard@1445: def GetCallee(name): Edouard@1446: """ Get Callee or Subscriber corresponding to '.' spearated object path """ Edouard@1445: names = name.split('.') Edouard@1445: obj = _PySrv.plcobj andrej@1756: while names: andrej@1756: obj = getattr(obj, names.pop(0)) Edouard@1445: return obj Edouard@1440: andrej@1736: Edouard@1439: class WampSession(wamp.ApplicationSession): Edouard@1893: def onConnect(self): Edouard@1901: if "secret" in self.config.extra: Edouard@1893: user = self.config.extra["ID"].encode('utf8') Edouard@1893: self.join(u"Automation", [u"wampcra"], user) Edouard@1893: else: Edouard@1893: self.join(u"Automation") Edouard@1893: Edouard@1893: def onChallenge(self, challenge): Edouard@1893: if challenge.method == u"wampcra": Edouard@1893: secret = self.config.extra["secret"].encode('utf8') Edouard@1893: signature = auth.compute_wcs(secret, challenge.extra['challenge'].encode('utf8')) Edouard@1893: return signature.decode("ascii") Edouard@1893: else: Edouard@1893: raise Exception("don't know how to handle authmethod {}".format(challenge.method)) Edouard@1440: Edouard@1440: @inlineCallbacks Edouard@1439: def onJoin(self, details): Edouard@1439: global _WampSession Edouard@1439: _WampSession = self Edouard@1901: ID = self.config.extra["ID"] andrej@1826: print('WAMP session joined by :', ID) Edouard@1440: for name in ExposedCalls: Edouard@1955: regoption = types.RegisterOptions(u'exact', u'last') Edouard@1898: yield self.register(GetCallee(name), u'.'.join((ID, name)), regoption) Edouard@1439: Edouard@1446: for name in SubscribedEvents: Edouard@1898: yield self.subscribe(GetCallee(name), unicode(name)) Edouard@1446: Edouard@1446: for func in DoOnJoin: Edouard@1446: yield func(self) Edouard@1446: Edouard@1439: def onLeave(self, details): Edouard@1439: global _WampSession Edouard@1439: _WampSession = None Edouard@1890: print(_('WAMP session left')) Edouard@1439: andrej@1736: Edouard@1441: class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory): Edouard@1441: def clientConnectionFailed(self, connector, reason): Edouard@1901: print(_("WAMP Client connection failed (%s) .. retrying .." % time.ctime())) Edouard@1891: ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) andrej@1751: Edouard@1441: def clientConnectionLost(self, connector, reason): Edouard@1901: print(_("WAMP Client connection lost (%s) .. retrying .." % time.ctime())) Edouard@1891: ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) Edouard@1441: andrej@1736: Edouard@1446: def LoadWampClientConf(wampconf): Edouard@1893: try: Edouard@1893: WSClientConf = json.load(open(wampconf)) Edouard@1893: return WSClientConf Edouard@1893: except ValueError, ve: Edouard@1893: print(_("WAMP load error: "), ve) Edouard@1893: return None Edouard@1893: except Exception: Edouard@1893: return None Edouard@1446: Edouard@1901: Edouard@1893: def LoadWampSecret(secretfname): Edouard@1893: try: Edouard@1893: WSClientWampSecret = open(secretfname, 'rb').read() Edouard@1893: return WSClientWampSecret Edouard@1893: except ValueError, ve: Edouard@1893: print(_("Wamp secret load error:"), ve) Edouard@1893: return None Edouard@1893: except Exception: Edouard@1893: return None Edouard@1446: andrej@1736: Edouard@1893: def RegisterWampClient(wampconf, secretfname): Edouard@1439: Edouard@1446: WSClientConf = LoadWampClientConf(wampconf) Edouard@1439: Edouard@1893: if not WSClientConf: Edouard@1894: print(_("WAMP client connection not established!")) Edouard@1893: return Edouard@1893: Edouard@1893: WampSecret = LoadWampSecret(secretfname) Edouard@1439: Edouard@1900: if WampSecret is not None: Edouard@1900: WSClientConf["secret"] = WampSecret Edouard@1900: Edouard@1440: # create a WAMP application session factory Edouard@1439: component_config = types.ComponentConfig( andrej@1744: realm=WSClientConf["realm"], Edouard@1901: extra=WSClientConf) Edouard@1439: session_factory = wamp.ApplicationSessionFactory( andrej@1744: config=component_config) Edouard@1439: session_factory.session = WampSession Edouard@1439: Edouard@1440: # create a WAMP-over-WebSocket transport client factory Edouard@1441: transport_factory = ReconnectingWampWebSocketClientFactory( Edouard@1439: session_factory, andrej@1744: url=WSClientConf["url"], Edouard@1893: serializers=[MsgPackSerializer()]) Edouard@1439: Edouard@1440: # start the client from a Twisted endpoint Edouard@1439: conn = connectWS(transport_factory) Edouard@1894: print(_("WAMP client connecting to :"), WSClientConf["url"]) Edouard@1439: return conn Edouard@1439: andrej@1736: Edouard@1439: def GetSession(): Edouard@1439: return _WampSession Edouard@1439: andrej@1736: Edouard@1439: def SetServer(pysrv): Edouard@1439: global _PySrv Edouard@1439: _PySrv = pysrv