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 denis@2203: import os denis@2201: import re 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@2212: mandatoryConfigItems = ["ID", "active", "realm", "url"] Edouard@2212: denis@2201: _transportFactory = None Edouard@1439: _WampSession = None Edouard@1439: _PySrv = None denis@2202: _WampConf = None denis@2202: _WampSecret = None Edouard@1439: andrej@1878: ExposedCalls = [ denis@2206: ("StartPLC", {}), denis@2206: ("StopPLC", {}), denis@2206: ("ForceReload", {}), denis@2206: ("GetPLCstatus", {}), denis@2206: ("NewPLC", {}), denis@2206: ("MatchMD5", {}), denis@2206: ("SetTraceVariablesList", {}), denis@2206: ("GetTraceVariables", {}), denis@2206: ("RemoteExec", {}), denis@2206: ("GetLogMessage", {}), denis@2206: ("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: denis@2194: user = self.config.extra["ID"] 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"] denis@2206: denis@2206: for name, kwargs in ExposedCalls: denis@2206: try: denis@2206: registerOptions = types.RegisterOptions(**kwargs) denis@2206: except TypeError as e: denis@2206: registerOptions = None denis@2206: print(_("TypeError register option: {}".format(e))) denis@2206: denis@2197: yield self.register(GetCallee(name), u'.'.join((ID, name)), registerOptions) 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: denis@2194: print(_('WAMP session joined (%s) by:' % time.ctime()), ID) denis@2194: Edouard@1439: def onLeave(self, details): denis@2201: global _WampSession, _transportFactory denis@2201: super(WampSession, self).onLeave(details) Edouard@1439: _WampSession = None denis@2201: _transportFactory = None Edouard@1890: print(_('WAMP session left')) Edouard@1439: andrej@1736: Edouard@1441: class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory): denis@2195: def __init__(self, config, *args, **kwargs): denis@2201: global _transportFactory denis@2195: WampWebSocketClientFactory.__init__(self, *args, **kwargs) denis@2195: denis@2207: try: denis@2207: protocolOptions = config.extra.get('protocolOptions', None) denis@2207: if protocolOptions: denis@2207: self.setProtocolOptions(**protocolOptions) denis@2207: _transportFactory = self denis@2207: except Exception, e: denis@2207: print(_("Custom protocol options failed :"), e) denis@2207: _transportFactory = None denis@2195: denis@2193: def buildProtocol(self, addr): denis@2193: self.resetDelay() denis@2193: return ReconnectingClientFactory.buildProtocol(self, addr) denis@2193: Edouard@1441: def clientConnectionFailed(self, connector, reason): denis@2201: if self.continueTrying: denis@2201: print(_("WAMP Client connection failed (%s) .. retrying .." % time.ctime())) denis@2201: super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason) denis@2201: else: denis@2201: del connector andrej@1751: Edouard@1441: def clientConnectionLost(self, connector, reason): denis@2201: if self.continueTrying: denis@2201: print(_("WAMP Client connection lost (%s) .. retrying .." % time.ctime())) denis@2201: super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason) denis@2201: else: denis@2201: del connector Edouard@1441: andrej@1736: Edouard@2212: def GetConfiguration(): Edouard@2212: WSClientConf = json.load(open(_WampConf)) Edouard@2212: for itemName in mandatoryConfigItems: Edouard@2212: if WSClientConf.get(itemName, None) is None : Edouard@2212: raise Exception(_("WAMP configuration error : missing '{}' parameter.").format(itemName)) Edouard@2212: Edouard@2212: return WSClientConf Edouard@2212: Edouard@2212: Edouard@2212: def SetConfiguration(WSClientConf): Edouard@1893: try: Edouard@2212: with open(os.path.realpath(_WampConf), 'w') as f: Edouard@2212: json.dump(WSClientConf, f, sort_keys=True, indent=4) Edouard@2212: if 'active' in WSClientConf and WSClientConf['active']: Edouard@2212: if _transportFactory and _WampSession: denis@2204: StopReconnectWampClient() Edouard@2212: StartReconnectWampClient() Edouard@2212: else: Edouard@2212: StopReconnectWampClient() denis@2202: denis@2202: return WSClientConf denis@2202: except ValueError, ve: denis@2203: print(_("WAMP save error: "), ve) denis@2203: return None denis@2203: except Exception, e: denis@2203: print(_("WAMP save error: "), e) denis@2202: return None Edouard@1901: Edouard@2212: 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: denis@2201: def IsCorrectUri(uri): denis@2201: if re.match(r'w{1}s{1,2}:{1}/{2}.+:{1}[0-9]+/{1}.+', uri): denis@2201: return True denis@2201: else: denis@2201: return False denis@2201: denis@2201: Edouard@2212: def RegisterWampClient(wampconf=None, wampsecret=None): Edouard@2212: global _WampConf, _WampSecret Edouard@2212: if wampsecret: Edouard@2212: _WampSecret = wampsecret denis@2202: if wampconf: denis@2204: _WampConf = wampconf Edouard@2212: Edouard@2212: WSClientConf = GetConfiguration() denis@2201: denis@2201: if not IsCorrectUri(WSClientConf["url"]): Edouard@2212: raise Exception(_("WAMP url {} is not correct!").format(WSClientConf["url"])) Edouard@2212: Edouard@2212: if not WSClientConf["active"]: Edouard@2212: print(_("WAMP deactivated in configuration")) Edouard@2212: return Edouard@2212: Edouard@2212: WampSecret = LoadWampSecret(_WampSecret) 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 denis@2207: ReconnectingWampWebSocketClientFactory( denis@2195: component_config, Edouard@1439: session_factory, andrej@1744: url=WSClientConf["url"], Edouard@1893: serializers=[MsgPackSerializer()]) Edouard@1439: Edouard@1440: # start the client from a Twisted endpoint denis@2207: if _transportFactory: denis@2207: conn = connectWS(_transportFactory) denis@2207: print(_("WAMP client connecting to :"), WSClientConf["url"]) denis@2207: return True denis@2207: else: denis@2207: print(_("WAMP client can not connect to :"), WSClientConf["url"]) denis@2207: return False denis@2204: denis@2201: denis@2203: def StopReconnectWampClient(): denis@2203: _transportFactory.stopTrying() denis@2203: return _WampSession.leave() denis@2203: denis@2204: denis@2203: def StartReconnectWampClient(): denis@2203: if _WampSession: denis@2203: # do reconnect denis@2202: _WampSession.disconnect() denis@2202: return True denis@2204: else: denis@2203: # do connect denis@2202: RegisterWampClient() denis@2202: return True denis@2203: andrej@1736: Edouard@1439: def GetSession(): Edouard@1439: return _WampSession Edouard@1439: denis@2204: denis@2202: def StatusWampClient(): denis@2202: return _WampSession and _WampSession.is_attached() denis@2202: denis@2204: Edouard@2212: def SetServer(pysrv): Edouard@1439: _PySrv = pysrv