--- a/Beremiz_service.py Fri Jun 15 09:48:05 2018 +0200
+++ b/Beremiz_service.py Fri Jul 20 11:05:17 2018 +0200
@@ -45,17 +45,17 @@
print("""
Usage of Beremiz PLC execution service :\n
%s {[-n servicename] [-i IP] [-p port] [-x enabletaskbar] [-a autostart]|-h|--help} working_dir
- -n - zeroconf service name (default:disabled)
- -i - IP address of interface to bind to (default:localhost)
- -p - port number default:3000
- -h - print this help text and quit
- -a - autostart PLC (0:disable 1:enable) (default:0)
- -x - enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1)
- -t - enable/disable Twisted web interface (0:disable 1:enable) (default:1)
- -w - web server port or "off" to disable web server (default:8009)
- -c - WAMP client default config file (default:wampconf.json)
- -s - WAMP client secret, given as a file
- -e - python extension (absolute path .py)
+ -n zeroconf service name (default:disabled)
+ -i IP address of interface to bind to (default:localhost)
+ -p port number default:3000
+ -h print this help text and quit
+ -a autostart PLC (0:disable 1:enable) (default:0)
+ -x enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1)
+ -t enable/disable Twisted web interface (0:disable 1:enable) (default:1)
+ -w web server port or "off" to disable web server (default:8009)
+ -c WAMP client config file (can be overriden by wampconf.json in project)
+ -s WAMP client secret, given as a file (can be overriden by wamp.secret in project)
+ -e python extension (absolute path .py)
working_dir - directory where are stored PLC files
""" % sys.argv[0])
@@ -582,29 +582,23 @@
installThreadExcepthook()
+havewamp = False
if havetwisted:
if webport is not None:
try:
import runtime.NevowServer as NS # pylint: disable=ungrouped-imports
- except Exception, e:
- print(_("Nevow/Athena import failed :"), e)
+ except Exception:
+ LogMessageAndException(_("Nevow/Athena import failed :"))
webport = None
NS.WorkingDir = WorkingDir
- # Find pre-existing project WAMP config file
- _wampconf = os.path.join(WorkingDir, "wampconf.json")
-
- # If project's WAMP config file exits, override default (-c)
- if os.path.exists(_wampconf):
- wampconf = _wampconf
-
- if wampconf is not None:
- try:
- import runtime.WampClient as WC # pylint: disable=ungrouped-imports
- except Exception, e:
- print(_("WAMP import failed :"), e)
- wampconf = None
+ try:
+ import runtime.WampClient as WC # pylint: disable=ungrouped-imports
+ WC.WorkingDir = WorkingDir
+ havewamp = True
+ except Exception:
+ LogMessageAndException(_("WAMP import failed :"))
# Load extensions
for extention_file, extension_folder in extensions:
@@ -616,22 +610,16 @@
try:
website = NS.RegisterWebsite(webport)
pyruntimevars["website"] = website
+ NS.SetServer(pyroserver)
statuschange.append(NS.website_statuslistener_factory(website))
except Exception:
LogMessageAndException(_("Nevow Web service failed. "))
- if wampconf is not None:
+ if havewamp:
try:
- _wampconf = WC.LoadWampClientConf(wampconf)
- if _wampconf:
- if _wampconf["url"]: # TODO : test more ?
- WC.RegisterWampClient(wampconf, wampsecret)
- pyruntimevars["wampsession"] = WC.GetSession
- WC.SetServer(pyroserver)
- else:
- raise Exception(_("WAMP config is incomplete."))
- else:
- raise Exception(_("WAMP config is missing."))
+ WC.SetServer(pyroserver)
+ WC.RegisterWampClient(wampconf, wampsecret)
+ WC.RegisterWebSettings(NS)
except Exception:
LogMessageAndException(_("WAMP client startup failed. "))
--- a/ProjectController.py Fri Jun 15 09:48:05 2018 +0200
+++ b/ProjectController.py Fri Jul 20 11:05:17 2018 +0200
@@ -1352,6 +1352,31 @@
if self.AppFrame is not None:
self.AppFrame.LogViewer.SetLogCounters(log_count)
+ DefaultMethods = {
+ "_Run": False,
+ "_Stop": False,
+ "_Transfer": False,
+ "_Connect": True,
+ "_Disconnect": False
+ }
+
+ MethodsFromStatus = {
+ "Started": {"_Stop": True,
+ "_Transfer": True,
+ "_Connect": False,
+ "_Disconnect": True},
+ "Stopped": {"_Run": True,
+ "_Transfer": True,
+ "_Connect": False,
+ "_Disconnect": True},
+ "Empty": {"_Transfer": True,
+ "_Connect": False,
+ "_Disconnect": True},
+ "Broken": {"_Connect": False,
+ "_Disconnect": True},
+ "Disconnected": {},
+ }
+
def UpdateMethodsFromPLCStatus(self):
updated = False
status = None
@@ -1364,21 +1389,11 @@
self._SetConnector(None, False)
status = "Disconnected"
if self.previous_plcstate != status:
- for args in {
- "Started": [("_Run", False),
- ("_Stop", True)],
- "Stopped": [("_Run", True),
- ("_Stop", False)],
- "Empty": [("_Run", False),
- ("_Stop", False)],
- "Broken": [],
- "Disconnected": [("_Run", False),
- ("_Stop", False),
- ("_Transfer", False),
- ("_Connect", True),
- ("_Disconnect", False)],
- }.get(status, []):
- self.ShowMethod(*args)
+ allmethods = self.DefaultMethods.copy()
+ allmethods.update(
+ self.MethodsFromStatus.get(status, {}))
+ for method, active in allmethods.items():
+ self.ShowMethod(method,active)
self.previous_plcstate = status
if self.AppFrame is not None:
updated = True
@@ -1713,10 +1728,6 @@
# Oups.
self.logger.write_error(_("Connection failed to %s!\n") % uri)
else:
- self.ShowMethod("_Connect", False)
- self.ShowMethod("_Disconnect", True)
- self.ShowMethod("_Transfer", True)
-
self.CompareLocalAndRemotePLC()
# Init with actual PLC status and print it
--- a/runtime/NevowServer.py Fri Jun 15 09:48:05 2018 +0200
+++ b/runtime/NevowServer.py Fri Jul 20 11:05:17 2018 +0200
@@ -26,10 +26,19 @@
from __future__ import absolute_import
from __future__ import print_function
import os
-from nevow import appserver, inevow, tags, loaders, athena
+import platform
+from zope.interface import implements
+from nevow import appserver, inevow, tags, loaders, athena, url, rend
from nevow.page import renderer
+from formless import annotate
+from formless import webform
+from formless import configurable
from twisted.internet import reactor
+
import util.paths as paths
+from runtime.loglevels import LogLevels, LogLevelsDict
+
+PAGE_TITLE = 'Beremiz Runtime Web Interface'
xhtml_header = '''<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
@@ -37,6 +46,7 @@
'''
WorkingDir = None
+_PySrv = None
class PLCHMI(athena.LiveElement):
@@ -49,7 +59,6 @@
def HMIinitialisation(self):
self.HMIinitialised(None)
-
class DefaultPLCStartedHMI(PLCHMI):
docFactory = loaders.stan(
tags.div(render=tags.directive('liveElement'))[
@@ -119,19 +128,113 @@
for child in self.liveFragmentChildren[:]:
child.detach()
+class ConfigurableBindings(configurable.Configurable):
+
+ def __init__(self):
+ configurable.Configurable.__init__(self, None)
+ self.bindingsNames = []
+
+ def getBindingNames(self, ctx):
+ return self.bindingsNames
+
+ def addExtension(self, name, desc, fields, btnlabel, callback):
+ def _bind(ctx):
+ return annotate.MethodBinding(
+ 'action_'+name,
+ annotate.Method(arguments=[
+ annotate.Argument(*field)
+ for field in fields],
+ label = desc),
+ action = btnlabel)
+ setattr(self, 'bind_'+name, _bind)
+
+ setattr(self, 'action_'+name, callback)
+
+ self.bindingsNames.append(name)
+
+ConfigurableSettings = ConfigurableBindings()
+
+class ISettings(annotate.TypedInterface):
+ platform = annotate.String(label = _("Platform"),
+ default = platform.system() + " " + platform.release(),
+ immutable = True)
+ # TODO version ?
+
+ def sendLogMessage(
+ ctx = annotate.Context(),
+ level = annotate.Choice(LogLevels,
+ required=True,
+ label=_("Log message level")),
+ message = annotate.String(label=_("Message text"))):
+ pass
+ sendLogMessage = annotate.autocallable(sendLogMessage,
+ label=_("Send a message to the log"),
+ action=_("Send"))
+
+customSettingsURLs = {
+}
+
+class SettingsPage(rend.Page):
+ # We deserve a slash
+ addSlash = True
+
+ # This makes webform_css url answer some default CSS
+ child_webform_css = webform.defaultCSS
+
+ implements(ISettings)
+
+
+ docFactory = loaders.stan([tags.html[
+ tags.head[
+ tags.title[_("Beremiz Runtime Settings")],
+ tags.link(rel='stylesheet',
+ type='text/css',
+ href=url.here.child("webform_css"))
+ ],
+ tags.body[
+ tags.h1["Runtime settings:"],
+ webform.renderForms('staticSettings'),
+ tags.h2["Extensions settings:"],
+ webform.renderForms('dynamicSettings'),
+ ]]])
+
+ def configurable_staticSettings(self, ctx):
+ return configurable.TypedInterfaceConfigurable(self)
+
+ def configurable_dynamicSettings(self, ctx):
+ return ConfigurableSettings
+
+ def sendLogMessage(self, level, message, **kwargs):
+ level = LogLevelsDict[level]
+ if _PySrv.plcobj is not None:
+ _PySrv.plcobj.LogMessage(level, "Web form log message: " + message )
+
+ def locateChild(self, ctx, segments):
+ if segments[0] in customSettingsURLs :
+ return customSettingsURLs[segments[0]](ctx, segments)
+ return super(SettingsPage, self).locateChild(ctx, segments)
+
class WebInterface(athena.LivePage):
docFactory = loaders.stan([tags.raw(xhtml_header),
tags.html(xmlns="http://www.w3.org/1999/xhtml")[
- tags.head(render=tags.directive('liveglue')),
+ tags.head(render=tags.directive('liveglue'))[
+ tags.title[PAGE_TITLE],
+ tags.link(rel='stylesheet',
+ type='text/css',
+ href=url.here.child("webform_css"))
+ ],
tags.body[
tags.div[
- tags.div(render=tags.directive("MainPage"))
+ tags.div(render=tags.directive("MainPage")),
]]]])
MainPage = MainPage()
PLCHMI = PLCHMI
+ def child_settings(self, context):
+ return SettingsPage()
+
def __init__(self, plcState=False, *a, **kw):
super(WebInterface, self).__init__(*a, **kw)
self.jsModules.mapping[u'WebInterface'] = paths.AbsNeighbourFile(__file__, 'webinterface.js')
@@ -184,6 +287,7 @@
# print "We will be called back when the client disconnects"
+
def RegisterWebsite(port):
website = WebInterface()
site = appserver.NevowSite(website)
@@ -209,3 +313,9 @@
def website_statuslistener_factory(site):
return statuslistener(site).listen
+
+
+def SetServer(pysrv):
+ global _PySrv
+ _PySrv = pysrv
+
--- a/runtime/WampClient.py Fri Jun 15 09:48:05 2018 +0200
+++ b/runtime/WampClient.py Fri Jul 20 11:05:17 2018 +0200
@@ -26,31 +26,53 @@
from __future__ import print_function
import time
import json
+import os
+import re
from autobahn.twisted import wamp
from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
from autobahn.wamp import types, auth
from autobahn.wamp.serializer import MsgPackSerializer
from twisted.internet.defer import inlineCallbacks
from twisted.internet.protocol import ReconnectingClientFactory
-
-
+from twisted.python.components import registerAdapter
+
+from formless import annotate, webform
+import formless
+from nevow import tags, url, static
+
+mandatoryConfigItems = ["ID", "active", "realm", "url"]
+
+_transportFactory = None
_WampSession = None
_PySrv = None
+WorkingDir = None
+
+# Find pre-existing project WAMP config file
+_WampConf = None
+_WampSecret = None
ExposedCalls = [
- "StartPLC",
- "StopPLC",
- "ForceReload",
- "GetPLCstatus",
- "NewPLC",
- "MatchMD5",
- "SetTraceVariablesList",
- "GetTraceVariables",
- "RemoteExec",
- "GetLogMessage",
- "ResetLogCount",
+ ("StartPLC", {}),
+ ("StopPLC", {}),
+ ("ForceReload", {}),
+ ("GetPLCstatus", {}),
+ ("NewPLC", {}),
+ ("MatchMD5", {}),
+ ("SetTraceVariablesList", {}),
+ ("GetTraceVariables", {}),
+ ("RemoteExec", {}),
+ ("GetLogMessage", {}),
+ ("ResetLogCount", {})
]
+# de-activated dumb wamp config
+defaultWampConfig = {
+ "ID": "wamptest",
+ "active": False,
+ "realm": "Automation",
+ "url": "ws://127.0.0.1:8888"
+}
+
# Those two lists are meant to be filled by customized runtime
# or User python code.
@@ -60,6 +82,7 @@
""" things to do on join (callables) """
DoOnJoin = []
+lastKnownConfig = None
def GetCallee(name):
""" Get Callee or Subscriber corresponding to '.' spearated object path """
@@ -73,16 +96,19 @@
class WampSession(wamp.ApplicationSession):
def onConnect(self):
if "secret" in self.config.extra:
- user = self.config.extra["ID"].encode('utf8')
+ user = self.config.extra["ID"]
self.join(u"Automation", [u"wampcra"], user)
else:
self.join(u"Automation")
def onChallenge(self, challenge):
if challenge.method == u"wampcra":
- secret = self.config.extra["secret"].encode('utf8')
- signature = auth.compute_wcs(secret, challenge.extra['challenge'].encode('utf8'))
- return signature.decode("ascii")
+ if "secret" in self.config.extra:
+ secret = self.config.extra["secret"].encode('utf8')
+ signature = auth.compute_wcs(secret, challenge.extra['challenge'].encode('utf8'))
+ return signature.decode("ascii")
+ else:
+ raise Exception("no secret given for authentication")
else:
raise Exception("don't know how to handle authmethod {}".format(challenge.method))
@@ -91,10 +117,15 @@
global _WampSession
_WampSession = self
ID = self.config.extra["ID"]
- print('WAMP session joined by :', ID)
- for name in ExposedCalls:
- regoption = types.RegisterOptions(u'exact', u'last')
- yield self.register(GetCallee(name), u'.'.join((ID, name)), regoption)
+
+ for name, kwargs in ExposedCalls:
+ try:
+ registerOptions = types.RegisterOptions(**kwargs)
+ except TypeError as e:
+ registerOptions = None
+ print(_("TypeError register option: {}".format(e)))
+
+ yield self.register(GetCallee(name), u'.'.join((ID, name)), registerOptions)
for name in SubscribedEvents:
yield self.subscribe(GetCallee(name), unicode(name))
@@ -102,81 +133,283 @@
for func in DoOnJoin:
yield func(self)
+ print(_('WAMP session joined (%s) by:' % time.ctime()), ID)
+
def onLeave(self, details):
- global _WampSession
+ global _WampSession, _transportFactory
+ super(WampSession, self).onLeave(details)
_WampSession = None
+ _transportFactory = None
print(_('WAMP session left'))
class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
+ def __init__(self, config, *args, **kwargs):
+ global _transportFactory
+ WampWebSocketClientFactory.__init__(self, *args, **kwargs)
+
+ try:
+ protocolOptions = config.extra.get('protocolOptions', None)
+ if protocolOptions:
+ self.setProtocolOptions(**protocolOptions)
+ _transportFactory = self
+ except Exception, e:
+ print(_("Custom protocol options failed :"), e)
+ _transportFactory = None
+
+ def buildProtocol(self, addr):
+ self.resetDelay()
+ return ReconnectingClientFactory.buildProtocol(self, addr)
+
def clientConnectionFailed(self, connector, reason):
- print(_("WAMP Client connection failed (%s) .. retrying .." % time.ctime()))
- ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
+ if self.continueTrying:
+ print(_("WAMP Client connection failed (%s) .. retrying ..") % time.ctime())
+ super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason)
+ else:
+ del connector
def clientConnectionLost(self, connector, reason):
- print(_("WAMP Client connection lost (%s) .. retrying .." % time.ctime()))
- ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
-
-
-def LoadWampClientConf(wampconf):
- try:
- WSClientConf = json.load(open(wampconf))
- return WSClientConf
- except ValueError, ve:
- print(_("WAMP load error: "), ve)
- return None
- except Exception:
- return None
+ if self.continueTrying:
+ print(_("WAMP Client connection lost (%s) .. retrying ..") % time.ctime())
+ super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason)
+ else:
+ del connector
+
+
+def CheckConfiguration(WampClientConf):
+ url = WampClientConf["url"]
+ if not IsCorrectUri(url):
+ raise annotate.ValidateError(
+ {"url":"Invalid URL: {}".format(url)},
+ _("WAMP configuration error:"))
+
+def GetConfiguration():
+ global lastKnownConfig
+
+ if os.path.exists(_WampConf):
+ WampClientConf = json.load(open(_WampConf))
+ else:
+ WampClientConf = defaultWampConfig.copy()
+
+ for itemName in mandatoryConfigItems:
+ if WampClientConf.get(itemName, None) is None :
+ raise Exception(_("WAMP configuration error : missing '{}' parameter.").format(itemName))
+
+ CheckConfiguration(WampClientConf)
+
+ lastKnownConfig = WampClientConf.copy()
+ return WampClientConf
+
+
+def SetWampSecret(wampSecret):
+ with open(os.path.realpath(_WampSecret), 'w') as f:
+ f.write(wampSecret)
+
+def SetConfiguration(WampClientConf):
+ global lastKnownConfig
+
+ CheckConfiguration(WampClientConf)
+
+ lastKnownConfig = WampClientConf.copy()
+
+ with open(os.path.realpath(_WampConf), 'w') as f:
+ json.dump(WampClientConf, f, sort_keys=True, indent=4)
+ if 'active' in WampClientConf and WampClientConf['active']:
+ if _transportFactory and _WampSession:
+ StopReconnectWampClient()
+ StartReconnectWampClient()
+ else:
+ StopReconnectWampClient()
+
+ return WampClientConf
def LoadWampSecret(secretfname):
- try:
- WSClientWampSecret = open(secretfname, 'rb').read()
- return WSClientWampSecret
- except ValueError, ve:
- print(_("Wamp secret load error:"), ve)
- return None
- except Exception:
- return None
-
-
-def RegisterWampClient(wampconf, secretfname):
-
- WSClientConf = LoadWampClientConf(wampconf)
-
- if not WSClientConf:
- print(_("WAMP client connection not established!"))
+ WSClientWampSecret = open(secretfname, 'rb').read()
+ if len(WSClientWampSecret) == 0 :
+ raise Exception(_("WAMP secret empty"))
+ return WSClientWampSecret
+
+
+def IsCorrectUri(uri):
+ return re.match(r'wss?://[^\s?:#-]+(:[0-9]+)?(/[^\s]*)?$', uri) is not None
+
+
+def RegisterWampClient(wampconf=None, wampsecret=None):
+ global _WampConf, _WampSecret
+ _WampConfDefault = os.path.join(WorkingDir, "wampconf.json")
+ _WampSecretDefault = os.path.join(WorkingDir, "wamp.secret")
+
+ # set config file path only if not already set
+ if _WampConf is None:
+ # default project's wampconf has precedance over commandline given
+ if os.path.exists(_WampConfDefault) or wampconf is None:
+ _WampConf = _WampConfDefault
+ else:
+ _WampConf = wampconf
+
+ WampClientConf = GetConfiguration()
+
+ # set secret file path only if not already set
+ if _WampSecret is None:
+ # default project's wamp secret also has precedance over commandline given
+ if os.path.exists(_WampSecretDefault):
+ _WampSecret = _WampSecretDefault
+ else:
+ _WampSecret = wampsecret
+
+ if _WampSecret is not None:
+ WampClientConf["secret"] = LoadWampSecret(_WampSecret)
+ else :
+ print(_("WAMP authentication has no secret configured"))
+ _WampSecret = _WampSecretDefault
+
+ if not WampClientConf["active"]:
+ print(_("WAMP deactivated in configuration"))
return
- WampSecret = LoadWampSecret(secretfname)
-
- if WampSecret is not None:
- WSClientConf["secret"] = WampSecret
-
# create a WAMP application session factory
component_config = types.ComponentConfig(
- realm=WSClientConf["realm"],
- extra=WSClientConf)
+ realm=WampClientConf["realm"],
+ extra=WampClientConf)
session_factory = wamp.ApplicationSessionFactory(
config=component_config)
session_factory.session = WampSession
# create a WAMP-over-WebSocket transport client factory
- transport_factory = ReconnectingWampWebSocketClientFactory(
+ ReconnectingWampWebSocketClientFactory(
+ component_config,
session_factory,
- url=WSClientConf["url"],
+ url=WampClientConf["url"],
serializers=[MsgPackSerializer()])
# start the client from a Twisted endpoint
- conn = connectWS(transport_factory)
- print(_("WAMP client connecting to :"), WSClientConf["url"])
- return conn
+ if _transportFactory:
+ conn = connectWS(_transportFactory)
+ print(_("WAMP client connecting to :"), WampClientConf["url"])
+ return True
+ else:
+ print(_("WAMP client can not connect to :"), WampClientConf["url"])
+ return False
+
+
+def StopReconnectWampClient():
+ if _transportFactory is not None :
+ _transportFactory.stopTrying()
+ if _WampSession is not None :
+ _WampSession.leave()
+
+
+def StartReconnectWampClient():
+ if _WampSession:
+ # do reconnect
+ _WampSession.disconnect()
+ return True
+ else:
+ # do connect
+ RegisterWampClient()
+ return True
def GetSession():
return _WampSession
+def getWampStatus():
+ if _transportFactory is not None :
+ if _WampSession is not None :
+ if _WampSession.is_attached() :
+ return "Attached"
+ return "Established"
+ return "Connecting"
+ return "Disconnected"
+
def SetServer(pysrv):
- global _PySrv
_PySrv = pysrv
+
+
+#### WEB CONFIGURATION INTERFACE ####
+WAMP_SECRET_URL = "secret"
+webExposedConfigItems = ['active', 'url', 'ID']
+
+def wampConfigDefault(ctx,argument):
+ if lastKnownConfig is not None :
+ return lastKnownConfig.get(argument.name, None)
+
+def wampConfig(**kwargs):
+ secretfile_field = kwargs["secretfile"]
+ if secretfile_field is not None:
+ secretfile = getattr(secretfile_field, "file", None)
+ if secretfile is not None:
+ secret = secretfile_field.file.read()
+ SetWampSecret(secret)
+
+ newConfig = lastKnownConfig.copy()
+ for argname in webExposedConfigItems:
+ arg = kwargs.get(argname, None)
+ if arg is not None :
+ newConfig[argname] = arg
+
+ SetConfiguration(newConfig)
+
+class FileUploadDownload(annotate.FileUpload):
+ pass
+
+
+class FileUploadDownloadRenderer(webform.FileUploadRenderer):
+ def input(self, context, slot, data, name, value):
+ slot[_("Upload:")]
+ slot = webform.FileUploadRenderer.input(self, context, slot, data, name, value)
+ download_url = data.typedValue.getAttribute('download_url')
+ return slot[tags.a(href=download_url)[_("Download")]]
+
+registerAdapter(FileUploadDownloadRenderer, FileUploadDownload, formless.iformless.ITypedRenderer)
+
+def getDownloadUrl(ctx, argument):
+ if lastKnownConfig is not None :
+ return url.URL.fromContext(ctx).\
+ child(WAMP_SECRET_URL).\
+ child(lastKnownConfig["ID"]+".secret")
+
+webFormInterface = [
+ ("status",
+ annotate.String(label=_("Current status"),
+ immutable = True,
+ default = lambda *k:getWampStatus())),
+ ("ID",
+ annotate.String(label=_("ID"),
+ default = wampConfigDefault)),
+ ("secretfile",
+ FileUploadDownload(
+ label = _("File containing secret for that ID"),
+ download_url = getDownloadUrl,
+ )),
+ ("active",
+ annotate.Boolean(label=_("Enable WAMP connection"),
+ default=wampConfigDefault)),
+ ("url",
+ annotate.String(label=_("WAMP Server URL"),
+ default=wampConfigDefault))]
+
+
+def deliverWampSecret(ctx, segments):
+ filename = segments[1].decode('utf-8')
+ # FIXME: compare filename to ID+".secret"
+ # for now all url under /secret returns the secret
+
+ # TODO: make beutifull message in case of exception
+ # while loading secret (if empty or dont exist)
+ secret = LoadWampSecret(_WampSecret)
+ return static.Data(secret, 'application/octet-stream'),()
+
+def RegisterWebSettings(NS):
+ NS.ConfigurableSettings.addExtension(
+ "wamp",
+ _("Wamp Settings"),
+ webFormInterface,
+ _("Set"),
+ wampConfig)
+
+
+ NS.customSettingsURLs[WAMP_SECRET_URL] = deliverWampSecret
+
--- a/tests/wamp/.crossbar/config.json Fri Jun 15 09:48:05 2018 +0200
+++ b/tests/wamp/.crossbar/config.json Fri Jul 20 11:05:17 2018 +0200
@@ -39,13 +39,15 @@
"transports": [
{
"type": "websocket",
+ "debug": true,
"endpoint": {
"type": "tcp",
"port": 8888
},
"url": "ws://127.0.0.1:8888/",
"serializers": [
- "msgpack"
+ "msgpack",
+ "json"
]
}
]
--- a/tests/wamp/README Fri Jun 15 09:48:05 2018 +0200
+++ b/tests/wamp/README Fri Jul 20 11:05:17 2018 +0200
@@ -1,25 +1,26 @@
-Crossbar test router configuration is available in .crossbar directory.
-
-Starting command:
-crossbar start
-
-This project contains wamp client config to be loaded at runtime startup.
-
-project_files/wampconf.json
+/* This project contains wamp client config to be loaded at runtime startup. */
+./project_files/wampconf.json
wampconf.json is in "Project Files", so it is copied to runtime's working directory, and then loaded after program transfer + runtime restart.
Otherwise, wamp config file path can be forced :
./Beremiz_service.py -c /path/to/my/wampconf.json /working/dir
-Otherwise, path for CRA secret can be forced :
-./Beremiz_service.py -s /path/to/my/secret /working/dir
+/* Crossbar install */
+#sudo apt-get update
+#sudo apt-get -y dist-upgrade
+sudo apt-get -y install build-essential libssl-dev libffi-dev libreadline-dev libbz2-dev libsqlite3-dev libncurses5-dev
+sudo python -m pip install -U pip
+sudo pip install crossbar
+crossbar version
+/* Start Crossbar command: */
+crossbar start
+
+/* Crossbar test router configuration is available in .crossbar directory. */
Tested on version:
- Crossbar.io : 17.12.1 (Crossbar.io COMMUNITY)
- Autobahn : 17.10.1 (with JSON, MessagePack, CBOR, UBJSON)
+ Crossbar.io : 18.3.1 (Crossbar.io COMMUNITY)
+ Autobahn : 18.3.1 (with JSON, MessagePack, CBOR, UBJSON)
Twisted : 17.9.0-EPollReactor
LMDB : 0.93/lmdb-0.9.18
- Python : 2.7.12/CPython
-
-
+ Python : 2.7.12/CPython
\ No newline at end of file
--- a/tests/wamp/beremiz.xml Fri Jun 15 09:48:05 2018 +0200
+++ b/tests/wamp/beremiz.xml Fri Jul 20 11:05:17 2018 +0200
@@ -1,4 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
-<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="WAMP://127.0.0.1:8888#Automation#wamptest">
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="WAMP://127.0.0.1:8888#Automation#WampID">
<TargetType/>
</BeremizRoot>
--- a/tests/wamp/project_files/wampconf.json Fri Jun 15 09:48:05 2018 +0200
+++ b/tests/wamp/project_files/wampconf.json Fri Jul 20 11:05:17 2018 +0200
@@ -1,7 +1,10 @@
{
- "url":"ws://127.0.0.1:8888",
- "realm":"Automation",
- "ID":"wamptest",
- "password":"1234567890",
- "key":"ABCDEFGHIJ"
+ "ID": "wamptest",
+ "active": true,
+ "protocolOptions": {
+ "autoPingInterval": 60,
+ "autoPingTimeout": 20
+ },
+ "realm": "Automation",
+ "url": "ws://127.0.0.1:8888"
}