diff --git a/SConstruct b/SConstruct index 28749755..12343a85 100644 --- a/SConstruct +++ b/SConstruct @@ -54,6 +54,9 @@ Targets: openwsn-fw directory. --ovdebug Enable debug mode; more detailed logging --usePageZero Use page number 0 in page dispatch of 6lowpan packet (only works within one-hop). + --benchmark Benchmark the network performance using OpenBenchmark cloud service. + --testbed Connect remote motes from a testbed using OpenTestbed software component over MQTT. + --mqttBroker Use specified MQTT broker to interconnect with --testbed motes and --benchmark service. Web UI only --host=
Web server listens on IP address; @@ -157,11 +160,19 @@ AddOption('--trace', action = 'store_true') runnerEnv['TRACEOPT'] = GetOption('traceOpt') -AddOption('--opentestbed', - dest = 'opentestbed', +AddOption('--testbed', + dest = 'testbed', default = False, - action = 'store_true') -runnerEnv['OPENTESTBED'] = GetOption('opentestbed') + choices = ['opentestbed', 'iotlab', 'wilab'], + action = 'store') +runnerEnv['TESTBED'] = GetOption('testbed') + +AddOption('--benchmark', + dest = 'benchmark', + default = False, + choices = ['building-automation', 'home-automation', 'industrial-monitoring', 'demo-scenario'], + action = 'store') +runnerEnv['BENCHMARK'] = GetOption('benchmark') AddOption('--mqtt-broker-address', dest = 'mqtt_broker_address', diff --git a/bin/SConscript b/bin/SConscript index 8f39c060..4027b4e4 100644 --- a/bin/SConscript +++ b/bin/SConscript @@ -55,21 +55,29 @@ def uiRunner(target, source, env): if env['TRACEOPT']: argList.append('--trace') + if env['DEBUGOPT']: argList.append('--debug') - if env['OPENTESTBED']: - argList.append('--opentestbed') + + if env['TESTBED']: + argList.append('--testbed={0}'.format(env['TESTBED'])) + if env['MQTT_BROKER_ADDRESS']: argList.append('--mqtt-broker-address=' + '{0}'.format(env['MQTT_BROKER_ADDRESS'])) + if env['OPENTUN_NULL']: argList.append('--opentun-null') - + + if env['BENCHMARK']: + argList.append('--benchmark={0}'.format(env['BENCHMARK'])) + if env['USEPAGEZERO']: argList.append('--usePageZero') if env['HOSTOPT']: argList.append('-H {0}'.format(env['HOSTOPT'])) + if env['PORTOPT']: argList.append('-p {0}'.format(env['PORTOPT'])) diff --git a/bin/logging.conf b/bin/logging.conf index 8ef248d1..5953b9fb 100644 --- a/bin/logging.conf +++ b/bin/logging.conf @@ -3,12 +3,16 @@ #============================ formatters ====================================== [formatters] -keys=std,console +keys=std,console,benchmark [formatter_std] format=%(asctime)s [%(name)s:%(levelname)s] %(message)s datefmt= +[formatter_benchmark] +format=%(message)s +datefmt= + [formatter_console] format=%(asctime)s %(levelname)s %(message)s datefmt=%H:%M:%S @@ -16,7 +20,7 @@ datefmt=%H:%M:%S #============================ handlers ======================================== [handlers] -keys=std,console +keys=std,console,benchmark [handler_std] class=handlers.RotatingFileHandler @@ -29,10 +33,15 @@ class=StreamHandler args=() formatter=console +[handler_benchmark] +class=handlers.RotatingFileHandler +args=('%(logDir)s/networkEvent.log', 'a', 2000000, 5) +formatter=benchmark + #============================ loggers ========================================= [loggers] -keys=root,eventBusMonitor,openTun,openTunWindows,openTunLinux,eventBusClient,lbrClient,moteConnector,moteProbe,moteProbeUtils,moteState,openLbr,OpenParser,Parser,OpenHdlc,ParserData,ParserPrintf,ParserInfoErrorCritical,ParserStatus,RPL,SourceRoute,JRC,openVisualizerApp,openVisualizerGui,openVisualizerCli,openVisualizerWeb,OVtracer +keys=root,eventBusMonitor,openTun,openTunWindows,openTunLinux,eventBusClient,lbrClient,moteConnector,moteProbe,moteProbeUtils,moteState,openLbr,openBenchmarkAgent,OpenParser,Parser,OpenHdlc,ParserData,ParserPrintf,ParserInfoErrorCritical,ParserStatus,ParserBenchmark,RPL,SourceRoute,JRC,networkEventLogger,openVisualizerApp,openVisualizerGui,openVisualizerCli,openVisualizerWeb,OVtracer,coapServer [logger_root] level=ERROR @@ -104,6 +113,12 @@ handlers=std propagate=0 qualname=moteState +[logger_openBenchmarkAgent] +level=INFO +handlers=std +propagate=0 +qualname=openBenchmarkAgent + [logger_openLbr] level=ERROR handlers=std @@ -146,6 +161,12 @@ handlers=std propagate=0 qualname=ParserStatus +[logger_ParserBenchmark] +level=DEBUG +handlers=std +propagate=0 +qualname=ParserBenchmark + [logger_RPL] level=INFO handlers=std @@ -164,6 +185,12 @@ handlers=std propagate=0 qualname=JRC +[logger_networkEventLogger] +level=INFO +handlers=benchmark +propagate=0 +qualname=networkEventLogger + [logger_OpenHdlc] level=ERROR handlers=std @@ -200,3 +227,9 @@ handlers=std propagate=0 qualname=OVtracer +[logger_coapServer] +level=INFO +handlers=std +propagate=0 +qualname=coapServer + diff --git a/bin/openVisualizerApp.py b/bin/openVisualizerApp.py index 301c9deb..321b8aa3 100644 --- a/bin/openVisualizerApp.py +++ b/bin/openVisualizerApp.py @@ -12,18 +12,25 @@ import os import logging import json +import time +import subprocess from openvisualizer.OVtracer import OVtracer log = logging.getLogger('openVisualizerApp') +from coap import coap, \ + coapDefines + from openvisualizer.eventBus import eventBusMonitor from openvisualizer.eventLogger import eventLogger from openvisualizer.moteProbe import moteProbe from openvisualizer.moteConnector import moteConnector from openvisualizer.moteState import moteState +from openvisualizer.coapServer import coapServer from openvisualizer.RPL import RPL from openvisualizer.JRC import JRC +from openvisualizer.openBenchmarkAgent import openBenchmarkAgent from openvisualizer.openLbr import openLbr from openvisualizer.openTun import openTun from openvisualizer.RPL import topology @@ -38,7 +45,7 @@ class OpenVisualizerApp(object): top-level functionality for several UI clients. ''' - def __init__(self,confdir,datadir,logdir,simulatorMode,numMotes,trace,debug,usePageZero,simTopology,iotlabmotes, testbedmotes, pathTopo, mqtt_broker_address, opentun_null): + def __init__(self,confdir,datadir,logdir,simulatorMode,numMotes,trace,debug,usePageZero,simTopology,iotlabmotes,testbed,benchmark,pathTopo,mqtt_broker_address,opentun_null): # store params self.confdir = confdir @@ -50,15 +57,25 @@ def __init__(self,confdir,datadir,logdir,simulatorMode,numMotes,trace,debug,useP self.debug = debug self.usePageZero = usePageZero self.iotlabmotes = iotlabmotes - self.testbedmotes = testbedmotes + self.testbed = testbed + self.benchmark = benchmark self.pathTopo = pathTopo + self.mqtt_broker_address = mqtt_broker_address # local variables self.eventBusMonitor = eventBusMonitor.eventBusMonitor() self.openLbr = openLbr.OpenLbr(usePageZero) + + # run CoAP server in testing mode + # this mode does not open a real socket, rather uses PyDispatcher for sending/receiving messages + # We interface this mode with OpenVisualizer to run JRC co-located with the DAG root + self.coapServer = coap.coap(udpPort=coapDefines.DEFAULT_UDP_PORT, + testing=True, + socketUdp=coapServer.coapDispatcher) self.rpl = RPL.RPL() - self.jrc = JRC.JRC() + self.jrc = JRC.JRC(self.coapServer) self.topology = topology.topology() + self.openBenchmarkAgent = None self.DAGrootList = [] # create openTun call last since indicates prefix self.openTun = openTun.create(opentun_null) @@ -79,9 +96,9 @@ def __init__(self,confdir,datadir,logdir,simulatorMode,numMotes,trace,debug,useP app.close() os.kill(os.getpid(), signal.SIGTERM) - # create a moteProbe for each mote if self.simulatorMode: + self.testEnvironment = 'opensim' # in "simulator" mode, motes are emulated sys.path.append(os.path.join(self.datadir, 'sim_files')) import oos_openwsn @@ -93,21 +110,23 @@ def __init__(self,confdir,datadir,logdir,simulatorMode,numMotes,trace,debug,useP self.simengine.indicateNewMote(moteHandler) self.moteProbes += [moteProbe.moteProbe(mqtt_broker_address, emulatedMote=moteHandler)] elif self.iotlabmotes: + self.testEnvironment = 'iotlab-tcp' # in "IoT-LAB" mode, motes are connected to TCP ports self.moteProbes = [ moteProbe.moteProbe(mqtt_broker_address, iotlabmote=p) for p in self.iotlabmotes.split(',') ] - elif self.testbedmotes: - motesfinder = moteProbe.OpentestbedMoteFinder(mqtt_broker_address) + elif self.testbed: + self.testEnvironment = self.testbed + motesfinder = moteProbe.OpentestbedMoteFinder(testbed=self.testbed, mqtt_broker_address=self.mqtt_broker_address) self.moteProbes = [ - moteProbe.moteProbe(mqtt_broker_address, testbedmote_eui64=p) + moteProbe.moteProbe(mqtt_broker_address, testbedmote=p) for p in motesfinder.get_opentestbed_motelist() ] else: + self.testEnvironment = 'local' # in "hardware" mode, motes are connected to the serial port - self.moteProbes = [ moteProbe.moteProbe(mqtt_broker_address, serialport=p) for p in moteProbe.findSerialPorts() ] @@ -126,7 +145,7 @@ def __init__(self,confdir,datadir,logdir,simulatorMode,numMotes,trace,debug,useP eventLogger.eventLogger(ms) for ms in self.moteStates ] - if self.testbedmotes: + if self.testbed: # at least, when we use OpenTestbed, we don't need # Rover. Don't instantiate remoteConnectorServer which # consumes a lot of CPU. @@ -185,6 +204,31 @@ def __init__(self,confdir,datadir,logdir,simulatorMode,numMotes,trace,debug,useP prefix += "0" moteid = prefix+hexaDAGroot self.DAGrootList.append(moteid) + + # If cloud-based benchmarking service is requested, start the agent + if self.benchmark: + + # give some time to OV to discover nodes' EUI-64 addresses + motes = {} + for ms in self.moteStates: + attempt = 0 + while ms.getStateElem(ms.ST_IDMANAGER).get_info()['64bAddr'] == '': + if attempt >= 10: + motes['invalid_eui64_' + ms.getStateElem(ms.ST_IDMANAGER).get_info()['serial']] = { + 'serialPort': ms.getStateElem(ms.ST_IDMANAGER).get_info()['serial']} + break + attempt += 1 + time.sleep(1) + motes[ ms.getStateElem(ms.ST_IDMANAGER).get_info()['64bAddr'] ] = { 'serialPort' : ms.getStateElem(ms.ST_IDMANAGER).get_info()['serial'] } + + self.openBenchmarkAgent = openBenchmarkAgent.OpenBenchmarkAgent( + mqttBroker=self.mqtt_broker_address, + coapServer=self.coapServer, + firmware='openwsn-{0}'.format(subprocess.check_output(["git", "describe", "--tags"]).strip()), + testbed=self.testEnvironment, + motes=motes, + scenario=self.benchmark + ) # start tracing threads if self.trace: @@ -205,6 +249,9 @@ def close(self): self.jrc.close() for probe in self.moteProbes: probe.close() + if self.openBenchmarkAgent: + self.openBenchmarkAgent.close() + self.coapServer.close() def getMoteState(self, moteid): ''' @@ -342,14 +389,15 @@ def main(parser=None): argspace.numMotes = DEFAULT_MOTE_COUNT log.info('Initializing OpenVisualizerApp with options:\n\t{0}'.format( - '\n '.join(['appdir = {0}'.format(argspace.appdir), - 'sim = {0}'.format(argspace.simulatorMode), - 'simCount = {0}'.format(argspace.numMotes), - 'trace = {0}'.format(argspace.trace), - 'debug = {0}'.format(argspace.debug), - 'testbedmotes= {0}'.format(argspace.testbedmotes), - - 'usePageZero = {0}'.format(argspace.usePageZero)], + '\n '.join(['appdir = {0}'.format(argspace.appdir), + 'sim = {0}'.format(argspace.simulatorMode), + 'simCount = {0}'.format(argspace.numMotes), + 'trace = {0}'.format(argspace.trace), + 'debug = {0}'.format(argspace.debug), + 'testbed = {0}'.format(argspace.testbed), + 'benchmark = {0}'.format(argspace.benchmark), + 'mqttBroker = {0}'.format(argspace.mqtt_broker_address), + 'usePageZero = {0}'.format(argspace.usePageZero)], ))) log.info('Using external dirs:\n\t{0}'.format( '\n '.join(['conf = {0}'.format(confdir), @@ -369,8 +417,9 @@ def main(parser=None): usePageZero = argspace.usePageZero, simTopology = argspace.simTopology, iotlabmotes = argspace.iotlabmotes, - testbedmotes = argspace.testbedmotes, + testbed = argspace.testbed, pathTopo = argspace.pathTopo, + benchmark = argspace.benchmark, mqtt_broker_address = argspace.mqtt_broker_address, opentun_null = argspace.opentun_null ) @@ -424,11 +473,19 @@ def _addParserArgs(parser): action = 'store', help = 'comma-separated list of IoT-LAB motes (e.g. "wsn430-9,wsn430-34,wsn430-3")' ) - parser.add_argument('-tb', '--opentestbed', - dest = 'testbedmotes', + parser.add_argument('-tb', '--testbed', + dest = 'testbed', default = False, - action = 'store_true', - help = 'connect motes from opentestbed' + choices = ['opentestbed', 'iotlab', 'wilab'], + action = 'store', + help = 'connect remote motes from a --testbed over OpenTestbed serial-MQTT bridge.' + ) + parser.add_argument('-b', '--benchmark', + dest = 'benchmark', + default = False, + choices = ['building-automation', 'home-automation', 'industrial-monitoring', 'demo-scenario'], + action = 'store', + help = 'trigger --benchmark scenario using OpenBenchmark cloud service. see benchmark.6tis.ch' ) parser.add_argument('--mqtt-broker-address', dest = 'mqtt_broker_address', diff --git a/bin/openVisualizerWeb.py b/bin/openVisualizerWeb.py index 8873d554..b3f6129b 100755 --- a/bin/openVisualizerWeb.py +++ b/bin/openVisualizerWeb.py @@ -294,7 +294,6 @@ def _getMoteData(self, moteid): ms.ST_SCHEDULE : ms.getStateElem(ms.ST_SCHEDULE).toJson('data'), ms.ST_QUEUE : ms.getStateElem(ms.ST_QUEUE).toJson('data'), ms.ST_NEIGHBORS : ms.getStateElem(ms.ST_NEIGHBORS).toJson('data'), - ms.ST_JOINED : ms.getStateElem(ms.ST_JOINED).toJson('data'), } else: if log.isEnabledFor(logging.DEBUG): diff --git a/bin/web_files/templates/moteview.tmpl b/bin/web_files/templates/moteview.tmpl index 3a01554a..bd790399 100644 --- a/bin/web_files/templates/moteview.tmpl +++ b/bin/web_files/templates/moteview.tmpl @@ -158,11 +158,6 @@ - - Join ASN - - -
Output Buffer
@@ -291,7 +286,6 @@ queueJson = $.parseJSON(json.Queue); nbrsJson = $.parseJSON(json.Neighbors); kaPeriodJson = $.parseJSON(json.kaPeriod)[0]; - joinedJson = $.parseJSON(json.Joined)[0]; } // Exclude tailing description from server @@ -303,7 +297,6 @@ $("#sync_fld").text( hasJson && syncJson.isSync > 0 ? 'Synchronized!' : 'Not synchronized'); $("#pan_fld").text( hasJson ? idJson.myPANID : ''); $("#asn_fld").text( hasJson ? asnJson.asn : ''); - $("#join_fld").text( hasJson ? joinedJson.joinedAsn : ''); $("#dagrank_fld").text( hasJson ? dagrankJson.myDAGrank : ''); $("#outread_fld").text( hasJson ? outbufJson.index_read : ''); $("#outwrite_fld").text( hasJson ? outbufJson.index_write : ''); diff --git a/nativeSetup.py b/nativeSetup.py index 3f60cb8a..3c7158ed 100644 --- a/nativeSetup.py +++ b/nativeSetup.py @@ -4,20 +4,20 @@ from openvisualizer import appdirs ''' -This implementation of the traditional setup.py uses the application-level -data_files parameter to store data files, rather than the package-level +This implementation of the traditional setup.py uses the application-level +data_files parameter to store data files, rather than the package-level package_data parameter. We store these data files in operating system specific , i.e. "native", locations with the help of the appdirs utility. For example, shared data files on Linux are placed in "/usr/local/share/openvisualizer". We use the site-level data and config directories because we expect the -superuser to run OpenVisualizer, so user-level directories like +superuser to run OpenVisualizer, so user-level directories like "/home//.config" are not available. -For native file storage to work, the installer *must not* modify the location +For native file storage to work, the installer *must not* modify the location of these files at install time. -Use of the legacy distutils package also accommodates existing Linux packaging +Use of the legacy distutils package also accommodates existing Linux packaging tools. ''' @@ -36,26 +36,26 @@ def appdirGlob(globstr, subdir=''): return glob.glob('/'.join([appdir, globstr])) else: return glob.glob('/'.join([appdir, subdir, globstr])) - + setup( name = 'openVisualizer', - packages = ['openvisualizer', - 'openvisualizer.BspEmulator', 'openvisualizer.eventBus', - 'openvisualizer.lbrClient', 'openvisualizer.moteConnector', - 'openvisualizer.moteProbe', 'openvisualizer.moteState', - 'openvisualizer.openLbr', 'openvisualizer.openTun', - 'openvisualizer.openType', 'openvisualizer.openUI', - 'openvisualizer.RPL', 'openvisualizer.SimEngine', - 'openvisualizer.JRC'], + packages = ['openvisualizer', + 'openvisualizer.BspEmulator', 'openvisualizer.eventBus', + 'openvisualizer.lbrClient', 'openvisualizer.moteConnector', + 'openvisualizer.moteProbe', 'openvisualizer.moteState', + 'openvisualizer.openLbr', 'openvisualizer.openTun', + 'openvisualizer.openType', 'openvisualizer.openUI', + 'openvisualizer.RPL', 'openvisualizer.SimEngine', + 'openvisualizer.JRC', 'openvisualizer.openBenchmarkAgent'], package_dir = {'': '.', 'openvisualizer': 'openvisualizer'}, scripts = appdirGlob('openVisualizer*.py'), # Copy simdata files by extension so don't copy .gitignore in that directory. data_files = [(confdir, appdirGlob('*.conf')), - ('/'.join([datadir, webstatic, 'css']), appdirGlob('*', '/'.join([webstatic, 'css']))), - ('/'.join([datadir, webstatic, 'font-awesome', 'css']), appdirGlob('*', '/'.join([webstatic, 'font-awesome', 'css']))), - ('/'.join([datadir, webstatic, 'font-awesome', 'fonts']), appdirGlob('*', '/'.join([webstatic, 'font-awesome', 'fonts']))), - ('/'.join([datadir, webstatic, 'images']), appdirGlob('*', '/'.join([webstatic, 'images']))), + ('/'.join([datadir, webstatic, 'css']), appdirGlob('*', '/'.join([webstatic, 'css']))), + ('/'.join([datadir, webstatic, 'font-awesome', 'css']), appdirGlob('*', '/'.join([webstatic, 'font-awesome', 'css']))), + ('/'.join([datadir, webstatic, 'font-awesome', 'fonts']), appdirGlob('*', '/'.join([webstatic, 'font-awesome', 'fonts']))), + ('/'.join([datadir, webstatic, 'images']), appdirGlob('*', '/'.join([webstatic, 'images']))), ('/'.join([datadir, webstatic, 'js']), appdirGlob('*.js', '/'.join([webstatic, 'js']))), ('/'.join([datadir, webstatic, 'js', 'plugins', 'metisMenu']), appdirGlob('*', '/'.join([webstatic, 'js', 'plugins', 'metisMenu']))), ('/'.join([datadir, webtmpl]), appdirGlob('*', webtmpl)), diff --git a/openvisualizer/JRC/JRC.py b/openvisualizer/JRC/JRC.py index 968990b1..670a4da4 100644 --- a/openvisualizer/JRC/JRC.py +++ b/openvisualizer/JRC/JRC.py @@ -23,13 +23,42 @@ import os # ======================== Top Level JRC Class ============================= -class JRC(): - def __init__(self): - coapResource = joinResource() - self.coapServer = coapServer(coapResource, contextHandler(coapResource).securityContextLookup) +class JRC(eventBusClient.eventBusClient): + def __init__(self, coapServer): + # store params + self.coapServer = coapServer + + self.coapResource = joinResource() + + self.coapServer.addResource(self.coapResource) + self.coapServer.addSecurityContextHandler(contextHandler(self.coapResource).securityContextLookup) + + # initialize parent class + eventBusClient.eventBusClient.__init__( + self, + name='JRC', + registrations=[ + { + 'sender': self.WILDCARD, + 'signal': 'getL2SecurityKey', + 'callback': self._getL2SecurityKey_notif, + }, + + ] + ) + + # ======================== public ========================================== def close(self): - self.coapServer.close() + pass + + # ==== handle EventBus notifications + + def _getL2SecurityKey_notif(self, sender, signal, data): + ''' + Return L2 security key for the network. + ''' + return {'index': [self.coapResource.networkKeyIndex], 'value': self.coapResource.networkKey} # ======================== Security Context Handler ========================= class contextHandler(): @@ -66,184 +95,6 @@ def securityContextLookup(self, kid): return context -# ======================== Interface with OpenVisualizer ====================================== -class coapServer(eventBusClient.eventBusClient): - # link-local prefix - LINK_LOCAL_PREFIX = [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - - def __init__(self, coapResource, contextHandler=None): - # log - log.info("create instance") - - self.coapResource = coapResource - - # run CoAP server in testing mode - # this mode does not open a real socket, rather uses PyDispatcher for sending/receiving messages - # We interface this mode with OpenVisualizer to run JRC co-located with the DAG root - self.coapServer = coap.coap(udpPort=d.DEFAULT_UDP_PORT, testing=True) - self.coapServer.addResource(coapResource) - self.coapServer.addSecurityContextHandler(contextHandler) - self.coapServer.maxRetransmit = 1 - - self.coapClient = None - - self.dagRootEui64 = None - - # store params - - # initialize parent class - eventBusClient.eventBusClient.__init__( - self, - name='JRC', - registrations=[ - { - 'sender': self.WILDCARD, - 'signal': 'getL2SecurityKey', - 'callback': self._getL2SecurityKey_notif, - }, - { - 'sender': self.WILDCARD, - 'signal': 'registerDagRoot', - 'callback': self._registerDagRoot_notif - }, - { - 'sender': self.WILDCARD, - 'signal': 'unregisterDagRoot', - 'callback': self._unregisterDagRoot_notif - }, - ] - ) - - # local variables - self.stateLock = threading.Lock() - - # ======================== public ========================================== - - def close(self): - # nothing to do - pass - - # ======================== private ========================================= - - # ==== handle EventBus notifications - - def _getL2SecurityKey_notif(self, sender, signal, data): - ''' - Return L2 security key for the network. - ''' - return {'index' : [self.coapResource.networkKeyIndex], 'value' : self.coapResource.networkKey} - - def _registerDagRoot_notif(self, sender, signal, data): - # register for the global address of the DAG root - self.register( - sender=self.WILDCARD, - signal=( - tuple(data['prefix'] + data['host']), - self.PROTO_UDP, - d.DEFAULT_UDP_PORT - ), - callback=self._receiveFromMesh, - ) - - # register to receive at link-local DAG root's address - self.register( - sender=self.WILDCARD, - signal=( - tuple(self.LINK_LOCAL_PREFIX + data['host']), - self.PROTO_UDP, - d.DEFAULT_UDP_PORT - ), - callback=self._receiveFromMesh, - ) - - self.dagRootEui64 = data['host'] - - def _unregisterDagRoot_notif(self, sender, signal, data): - # unregister global address - self.unregister( - sender=self.WILDCARD, - signal=( - tuple(data['prefix'] + data['host']), - self.PROTO_UDP, - d.DEFAULT_UDP_PORT - ), - callback=self._receiveFromMesh, - ) - # unregister link-local address - self.unregister( - sender=self.WILDCARD, - signal=( - tuple(self.LINK_LOCAL_PREFIX + data['host']), - self.PROTO_UDP, - d.DEFAULT_UDP_PORT - ), - callback=self._receiveFromMesh, - ) - - self.dagRootEui64 = None - - def _receiveFromMesh(self, sender, signal, data): - ''' - Receive packet from the mesh destined for JRC's CoAP server. - Forwards the packet to the virtual CoAP server running in test mode (PyDispatcher). - ''' - sender = openvisualizer.openvisualizer_utils.formatIPv6Addr(data[0]) - # FIXME pass source port within the signal and open coap client at this port - self.coapClient = coap.coap(ipAddress=sender, udpPort=d.DEFAULT_UDP_PORT, testing=True, receiveCallback=self._receiveFromCoAP) - self.coapClient.socketUdp.sendUdp(destIp='', destPort=d.DEFAULT_UDP_PORT, msg=data[1]) # low level forward of the CoAP message - return True - - def _receiveFromCoAP(self, timestamp, sender, data): - ''' - Receive CoAP response and forward it to the mesh network. - Appends UDP and IPv6 headers to the CoAP message and forwards it on the Eventbus towards the mesh. - ''' - self.coapClient.close() - - # UDP - udplen = len(data) + 8 - - udp = u.int2buf(sender[1], 2) # src port - udp += u.int2buf(self.coapClient.udpPort, 2) # dest port - udp += [udplen >> 8, udplen & 0xff] # length - udp += [0x00, 0x00] # checksum - udp += data - - # destination address of the packet is CoAP client's IPv6 address (address of the mote) - dstIpv6Address = u.ipv6AddrString2Bytes(self.coapClient.ipAddress) - assert len(dstIpv6Address)==16 - # source address of the packet is DAG root's IPV6 address - # use the same prefix (link-local or global) as in the destination address - srcIpv6Address = dstIpv6Address[:8] - srcIpv6Address += self.dagRootEui64 - assert len(srcIpv6Address)==16 - - # CRC See https://tools.ietf.org/html/rfc2460. - - udp[6:8] = openvisualizer.openvisualizer_utils.calculatePseudoHeaderCRC( - src=srcIpv6Address, - dst=dstIpv6Address, - length=[0x00, 0x00] + udp[4:6], - nh=[0x00, 0x00, 0x00, 17], # UDP as next header - payload=udp, - ) - - # IPv6 - ip = [6 << 4] # v6 + traffic class (upper nybble) - ip += [0x00, 0x00, 0x00] # traffic class (lower nibble) + flow label - ip += udp[4:6] # payload length - ip += [17] # next header (protocol); UDP=17 - ip += [64] # hop limit (pick a safe value) - ip += srcIpv6Address # source - ip += dstIpv6Address # destination - ip += udp - - # announce network prefix - self.dispatch( - signal = 'v6ToMesh', - data = ip - ) - # ==================== Implementation of CoAP join resource ===================== class joinResource(coapResource.coapResource): def __init__(self): @@ -260,7 +111,7 @@ def __init__(self): self.addSecurityBinding((None, [d.METHOD_POST])) # security context should be returned by the callback - def POST(self,options=[], payload=[]): + def POST(self,options=[], payload=[], metaData={}): link_layer_keyset = [self.networkKeyIndex, u.buf2str(self.networkKey)] diff --git a/openvisualizer/coapServer/__init__.py b/openvisualizer/coapServer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/openvisualizer/coapServer/coapServer.py b/openvisualizer/coapServer/coapServer.py new file mode 100644 index 00000000..235700f9 --- /dev/null +++ b/openvisualizer/coapServer/coapServer.py @@ -0,0 +1,216 @@ +from coap import coap, \ + coapResource, \ + coapDefines as d, \ + coapOption as o, \ + coapUtils as u, \ + coapObjectSecurity as oscoap, \ + socketUdp +import logging.handlers +try: + from openvisualizer.eventBus import eventBusClient + import openvisualizer.openvisualizer_utils +except ImportError: + pass + +log = logging.getLogger('coapServer') +log.setLevel(logging.ERROR) +log.addHandler(logging.NullHandler()) + +import cbor +import binascii +import os +import time +import threading + +# default IPv6 hop limit +COAP_SERVER_DEFAULT_IPv6_HOP_LIMIT = 65 + +class coapDispatcher(socketUdp.socketUdp, eventBusClient.eventBusClient): + + # link-local prefix + LINK_LOCAL_PREFIX = [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + + def __init__(self, ipAddress, udpPort, callback): + # log + log.debug('creating instance') + + # params + self.udpPort = udpPort + self.dagRootEui64 = None + self.networkPrefix = None + self.callback = callback + + # initialize the parent socketUdp class + socketUdp.socketUdp.__init__(self, ipAddress, self.udpPort, self.callback) + + # initialize the parent class eventBusClient class + eventBusClient.eventBusClient.__init__( + self, + name='coapDispatcher', + registrations=[ + { + 'sender': self.WILDCARD, + 'signal': 'registerDagRoot', + 'callback': self._registerDagRoot_notif + }, + { + 'sender': self.WILDCARD, + 'signal': 'unregisterDagRoot', + 'callback': self._unregisterDagRoot_notif + }, + ] + ) + + # change name + self.name = 'coapDispatcher@DagRootIpv6:{0}'.format(self.udpPort) + self.gotMsgSem = threading.Semaphore() + + # start myself + self.start() + + # ======================== public ========================================== + # TODO rework the class for it to be completely stateless and not depend on self.dagRootEui64 + def sendUdp(self, destIp, destPort, msg): + ''' + Receive CoAP response and forward it to the mesh network. + Appends UDP and IPv6 headers to the CoAP message and forwards it on the Eventbus towards the mesh. + ''' + + assert self.dagRootEui64 + assert self.networkPrefix + + log.debug("sendUdp to {0}:{1} message {2}".format(destIp,destPort,msg)) + + # UDP + udplen = len(msg) + 8 + + # FIXME need to signal the source port from the packet + udp = u.int2buf(self.udpPort, 2) # src port + udp += u.int2buf(destPort, 2) # dest port + udp += [udplen >> 8, udplen & 0xff] # length + udp += [0x00, 0x00] # checksum + udp += msg + + # destination address of the packet is CoAP client's IPv6 address (address of the mote) + dstIpv6Address = u.ipv6AddrString2Bytes(destIp) + assert len(dstIpv6Address) == 16 + # source address of the packet is DAG root's IPV6 address + # use the same prefix (link-local or global) as in the destination address + srcIpv6Address = dstIpv6Address[:8] + srcIpv6Address += self.dagRootEui64 + assert len(srcIpv6Address) == 16 + + # CRC See https://tools.ietf.org/html/rfc2460. + + udp[6:8] = openvisualizer.openvisualizer_utils.calculatePseudoHeaderCRC( + src=srcIpv6Address, + dst=dstIpv6Address, + length=[0x00, 0x00] + udp[4:6], + nh=[0x00, 0x00, 0x00, 17], # UDP as next header + payload=udp, + ) + + # IPv6 + ip = [6 << 4] # v6 + traffic class (upper nybble) + ip += [0x00, 0x00, 0x00] # traffic class (lower nibble) + flow label + ip += udp[4:6] # payload length + ip += [17] # next header (protocol); UDP=17 + ip += [COAP_SERVER_DEFAULT_IPv6_HOP_LIMIT] # hop limit (pick a safe value) + ip += srcIpv6Address # source + ip += dstIpv6Address # destination + ip += udp + + self.dispatch( + signal='v6ToMesh', + data=ip + ) + + # update stats + self._incrementTx() + + def close(self): + # stop + self.goOn = False + self.gotMsgSem.release() + + # ======================== private ========================================= + + def _messageNotification(self, sender, signal, data): + # log + log.debug("messageNotification: got {1} from {0}".format(sender, data)) + + srcIpv6 = openvisualizer.openvisualizer_utils.formatIPv6Addr(data[0]) + rawbytes = data[1] + hopLimit = data[2] # IPv6 metadata + timestamp = str(data[3]) # timestamp of the received packet + + sender = (srcIpv6, self.udpPort, (hopLimit, timestamp)) + + # call the callback + self.callback(timestamp, sender, rawbytes) + + # update stats + self._incrementRx() + + # release the lock + self.gotMsgSem.release() + + # return success in order to acknowledge the reception + return True + + def run(self): + while self.goOn: + self.gotMsgSem.acquire() + + # ==== handle EventBus notifications + + def _registerDagRoot_notif(self, sender, signal, data): + # register for the global address of the DAG root + self.register( + sender=self.WILDCARD, + signal=( + tuple(data['prefix'] + data['host']), + self.PROTO_UDP, + d.DEFAULT_UDP_PORT + ), + callback=self._messageNotification, + ) + + # register to receive at link-local DAG root's address + self.register( + sender=self.WILDCARD, + signal=( + tuple(self.LINK_LOCAL_PREFIX + data['host']), + self.PROTO_UDP, + d.DEFAULT_UDP_PORT + ), + callback=self._messageNotification, + ) + + self.dagRootEui64 = data['host'] + self.networkPrefix = data['prefix'] + + def _unregisterDagRoot_notif(self, sender, signal, data): + # unregister global address + self.unregister( + sender=self.WILDCARD, + signal=( + tuple(data['prefix'] + data['host']), + self.PROTO_UDP, + d.DEFAULT_UDP_PORT + ), + callback=self._messageNotification, + ) + # unregister link-local address + self.unregister( + sender=self.WILDCARD, + signal=( + tuple(self.LINK_LOCAL_PREFIX + data['host']), + self.PROTO_UDP, + d.DEFAULT_UDP_PORT + ), + callback=self._messageNotification, + ) + + self.dagRootEui64 = None + self.networkPrefix = None diff --git a/openvisualizer/eventBus/eventBusMonitor.py b/openvisualizer/eventBus/eventBusMonitor.py index 7e2b8b35..25375ac5 100644 --- a/openvisualizer/eventBus/eventBusMonitor.py +++ b/openvisualizer/eventBus/eventBusMonitor.py @@ -132,7 +132,7 @@ def _eventBusNotification(self,signal,sender,data): if signal=='fromMote.data': # Forwards a copy of the data received from a mode # to the Internet interface for debugging. - (previousHop,lowpan) = data + (previousHop,lowpan,timestamp) = data zep = self._wrapMacAndZep( previousHop = previousHop, diff --git a/openvisualizer/moteConnector/OpenParser.py b/openvisualizer/moteConnector/OpenParser.py index cf85976f..9886e122 100644 --- a/openvisualizer/moteConnector/OpenParser.py +++ b/openvisualizer/moteConnector/OpenParser.py @@ -15,6 +15,7 @@ import ParserData import ParserPacket import ParserPrintf +import ParserBenchmark class OpenParser(Parser.Parser): @@ -27,6 +28,7 @@ class OpenParser(Parser.Parser): SERFRAME_MOTE2PC_CRITICAL = ParserIEC.ParserInfoErrorCritical.SEVERITY_CRITICAL SERFRAME_MOTE2PC_SNIFFED_PACKET = ord('P') SERFRAME_MOTE2PC_PRINTF = ord('F') + SERFRAME_MOTE2PC_BENCHMARK = ord('B') SERFRAME_PC2MOTE_SETDAGROOT = ord('R') SERFRAME_PC2MOTE_DATA = ord('D') @@ -53,6 +55,7 @@ def __init__(self, mqtt_broker_address): self.parserData = ParserData.ParserData(mqtt_broker_address) self.parserPacket = ParserPacket.ParserPacket() self.parserPrintf = ParserPrintf.ParserPrintf() + self.parserBenchmark = ParserBenchmark.ParserBenchmark() # register subparsers self._addSubParser( @@ -90,6 +93,11 @@ def __init__(self, mqtt_broker_address): val = self.SERFRAME_MOTE2PC_PRINTF, parser = self.parserPrintf.parseInput, ) + self._addSubParser( + index = 0, + val = self.SERFRAME_MOTE2PC_BENCHMARK, + parser = self.parserBenchmark.parseInput, + ) #======================== public ========================================== diff --git a/openvisualizer/moteConnector/ParserBenchmark.py b/openvisualizer/moteConnector/ParserBenchmark.py new file mode 100644 index 00000000..b8996fcd --- /dev/null +++ b/openvisualizer/moteConnector/ParserBenchmark.py @@ -0,0 +1,63 @@ +# Copyright (c) 2010-2013, Regents of the University of California. +# All rights reserved. +# +# Released under the BSD 3-Clause license as published at the link below. +# https://openwsn.atlassian.net/wiki/display/OW/License +import logging + +log = logging.getLogger('ParserBenchmark') +log.setLevel(logging.ERROR) +log.addHandler(logging.NullHandler()) + +import struct + +from pydispatch import dispatcher + +from ParserException import ParserException +import Parser + +from openvisualizer.openType import typeAsn +from openvisualizer import openvisualizer_utils as u + +class ParserBenchmark(Parser.Parser): + HEADER_LENGTH = 2 + + def __init__(self): + + # log + log.info("create instance") + + # initialize parent class + Parser.Parser.__init__(self, self.HEADER_LENGTH) + + self._asn = ['asn_4', # B + 'asn_2_3', # H + 'asn_0_1', # H + ] + + # ======================== public ========================================== + + def parseInput(self, input): + # log + log.debug("received data {0}".format(input)) + + # ensure input not short longer than header + self._checkLength(input) + + source = input[:8] + source = u.formatAddr(source) + + event = input[8] + + asnParsed = struct.unpack(' 6pList tx, 5, 3" print "comma. e.g. set 6pClear all" return [outcome,dataToSend] - elif data[0] == 'joinKey': + elif commandName == 'joinKey': try: - if len(data[1]) != commandLen*2: # two hex chars is one byte + if len(parameter) != commandLen*2: # two hex chars is one byte raise ValueError - payload = binascii.unhexlify(data[1]) + payload = binascii.unhexlify(parameter) dataToSend = [OpenParser.OpenParser.SERFRAME_PC2MOTE_COMMAND, commandId, commandLen, @@ -294,8 +297,29 @@ def _commandToBytes(self,data): except: print "=============================================" print "Wrong joinKey format. Input 16-byte long hex string. e.g. cafebeefcafebeefcafebeefcafebeef" + elif commandName == 'sendPacket': + try: + + if len(parameter) != commandLen: + raise ValueError("Invalid sendPacket payload, expecting {0} bytes".format(commandLen)) + + dataToSend = [OpenParser.OpenParser.SERFRAME_PC2MOTE_COMMAND, + commandId, + commandLen, + ] + dataToSend += parameter + except: + debug = "=============================================\n" + debug += "Wrong sendPacket command format.\n" + debug += "Supported: ( destination, confirmable, packetsInBurst, packetToken, packetPayloadLen )\n" + debug += "destination: dash-separated EUI-64 string, e.g. AA-BB-CC-DD-EE-FF-00-11\n" + debug += "confirmable: boolean\n" + debug += "packetsInBurst: integer\n" + debug += "packetToken: integer array\n" + debug += "packetPayloadLen: integer\n" + log.warning(debug) else: - parameter = int(data[1]) + parameter = int(parameter) if parameter <= 0xffff: parameter = [(parameter & 0xff),((parameter >> 8) & 0xff)] dataToSend = [OpenParser.OpenParser.SERFRAME_PC2MOTE_COMMAND, @@ -315,7 +339,6 @@ def _commandToBytes(self,data): outcome = True return [outcome,dataToSend] - def _bytesToMesh_handler(self,sender,signal,data): assert type(data)==tuple assert len(data)==2 diff --git a/openvisualizer/moteProbe/OpenHdlc.py b/openvisualizer/moteProbe/OpenHdlc.py index f581aebd..b325830b 100644 --- a/openvisualizer/moteProbe/OpenHdlc.py +++ b/openvisualizer/moteProbe/OpenHdlc.py @@ -95,8 +95,8 @@ def dehdlcify(self,inBuf): :returns: the extracted frame, or -1 if wrong checksum ''' - assert inBuf[ 0]==self.HDLC_FLAG - assert inBuf[-1]==self.HDLC_FLAG + if inBuf[0]!=self.HDLC_FLAG or inBuf[-1]!=self.HDLC_FLAG: + raise HdlcException('unexpected frame') # make copy of input outBuf = inBuf[:] diff --git a/openvisualizer/moteProbe/moteProbe.py b/openvisualizer/moteProbe/moteProbe.py index da7231cf..1b9b2766 100644 --- a/openvisualizer/moteProbe/moteProbe.py +++ b/openvisualizer/moteProbe/moteProbe.py @@ -103,9 +103,10 @@ class OpentestbedMoteFinder (object): OPENTESTBED_RESP_STATUS_TIMEOUT = 10 - def __init__(self, mqtt_broker_address): - self.opentestbed_motelist = set() + def __init__(self, testbed, mqtt_broker_address): + self.testbed = testbed self.mqtt_broker_address = mqtt_broker_address + self.opentestbed_motelist = set() def get_opentestbed_motelist(self): @@ -130,14 +131,14 @@ def _on_mqtt_connect(self, client, userdata, flags, rc): print "connected to : {0}".format(self.mqtt_broker_address) - client.subscribe('opentestbed/deviceType/box/deviceId/+/resp/status') + client.subscribe('{0}/deviceType/box/deviceId/+/resp/status'.format(self.testbed)) payload_status = { 'token': 123, } # publish the cmd message client.publish( - topic = 'opentestbed/deviceType/box/deviceId/all/cmd/status', + topic = '{0}/deviceType/box/deviceId/all/cmd/status'.format(self.testbed), payload = json.dumps(payload_status), ) @@ -146,10 +147,19 @@ def _on_mqtt_message(self, client, userdata, message): # get the motes list from payload payload_status = json.loads(message.payload) + + try: + host = payload_status['returnVal']['host_name'] + except KeyError: + host = payload_status['returnVal']['IP_address'] + except: + host = '' for mote in payload_status['returnVal']['motes']: if 'EUI64' in mote: - self.opentestbed_motelist.add(mote['EUI64']) + self.opentestbed_motelist.add( + (host, mote['EUI64'], self.testbed, self.mqtt_broker_address) + ) class moteProbe(threading.Thread): @@ -163,7 +173,7 @@ class moteProbe(threading.Thread): MODE_IOTLAB, MODE_TESTBED, ] - + XOFF = 0x13 XON = 0x11 XONXOFF_ESCAPE = 0x12 @@ -172,25 +182,25 @@ class moteProbe(threading.Thread): # XON is transmitted as [XONXOFF_ESCAPE, XON^XONXOFF_MASK]==[0x12,0x11^0x10]==[0x12,0x01] # XONXOFF_ESCAPE is transmitted as [XONXOFF_ESCAPE, XONXOFF_ESCAPE^XONXOFF_MASK]==[0x12,0x12^0x10]==[0x12,0x02] - def __init__(self,mqtt_broker_address,serialport=None,emulatedMote=None,iotlabmote=None,testbedmote_eui64=None): + def __init__(self,mqtt_broker_address,serialport=None,emulatedMote=None,iotlabmote=None,testbedmote=None): # verify params if serialport: assert not emulatedMote assert not iotlabmote - assert not testbedmote_eui64 + assert not testbedmote self.mode = self.MODE_SERIAL elif emulatedMote: assert not serialport assert not iotlabmote - assert not testbedmote_eui64 + assert not testbedmote self.mode = self.MODE_EMULATED elif iotlabmote: assert not serialport assert not emulatedMote - assert not testbedmote_eui64 + assert not testbedmote self.mode = self.MODE_IOTLAB - elif testbedmote_eui64: + elif testbedmote: assert not serialport assert not emulatedMote assert not iotlabmote @@ -210,8 +220,8 @@ def __init__(self,mqtt_broker_address,serialport=None,emulatedMote=None,iotlabmo self.iotlabmote = iotlabmote self.portname = 'IoT-LAB{0}'.format(iotlabmote) elif self.mode==self.MODE_TESTBED: - self.testbedmote_eui64 = testbedmote_eui64 - self.portname = 'opentestbed_{0}'.format(testbedmote_eui64) + (self.testbed_host, self.testbedmote_eui64, self.testbed, self.mqtt_broker_address) = testbedmote + self.portname = 'testbed_{0}_{1}_{2}'.format(self.testbed, self.testbed_host, self.testbedmote_eui64) else: raise SystemError() # at this moment, MQTT broker is used even if the mode is not @@ -409,7 +419,7 @@ def _sendData(self,data): payload_buffer['serialbytes'] = [ord(i) for i in hdlcData] # publish the cmd message self.mqttclient.publish( - topic = 'opentestbed/deviceType/mote/deviceId/{0}/cmd/tomoteserialbytes'.format(self.testbedmote_eui64), + topic = '{0}/deviceType/mote/deviceId/{1}/cmd/tomoteserialbytes'.format(self.testbed, self.testbedmote_eui64), payload = json.dumps(payload_buffer), ) else: @@ -420,7 +430,7 @@ def _sendData(self,data): def _on_mqtt_connect(self, client, userdata, flags, rc): - client.subscribe('opentestbed/deviceType/mote/deviceId/{0}/notif/frommoteserialbytes'.format(self.testbedmote_eui64)) + client.subscribe('{0}/deviceType/mote/deviceId/{1}/notif/frommoteserialbytes'.format(self.testbed, self.testbedmote_eui64)) def _on_mqtt_message(self, client, userdata, message): diff --git a/openvisualizer/moteState/moteState.py b/openvisualizer/moteState/moteState.py index c1d675bc..2cc13b32 100644 --- a/openvisualizer/moteState/moteState.py +++ b/openvisualizer/moteState/moteState.py @@ -13,6 +13,7 @@ log.setLevel(logging.ERROR) log.addHandler(logging.NullHandler()) + import copy import time import threading @@ -27,6 +28,8 @@ typeComponent, \ typeRssi +from openvisualizer import openvisualizer_utils as u + class OpenEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (StateElem,openType.openType)): @@ -121,7 +124,8 @@ def _elemToDict(self,elem): class StateOutputBuffer(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -130,7 +134,8 @@ def update(self,notif): class StateAsn(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -139,21 +144,16 @@ def update(self,notif): self.data[0]['asn'].update(notif.asn_0_1, notif.asn_2_3, notif.asn_4) -class StateJoined(StateElem): - - def update(self,notif): - StateElem.update(self) - if len(self.data)==0: - self.data.append({}) - if 'joinedAsn' not in self.data[0]: - self.data[0]['joinedAsn'] = typeAsn.typeAsn() - self.data[0]['joinedAsn'].update(notif.joinedAsn_0_1, - notif.joinedAsn_2_3, - notif.joinedAsn_4) + + def getAsn(self): + if len(self.data) != 0: + return self.data[0]['asn'] + return None class StateMacStats(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -168,9 +168,15 @@ def update(self,notif): else: self.data[0]['dutyCycle'] = '?' + def getDutyCycle(self): + if len(self.data)!=0: + return self.data[0]['dutyCycle'] + return None + class StateScheduleRow(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -194,9 +200,13 @@ def update(self,notif): notif.lastUsedAsn_2_3, notif.lastUsedAsn_4) + def getType(self): + return self.data[0]['type'] + class StateBackoff(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -225,7 +235,8 @@ def __init__(self): for i in range(20): self.data.append(StateQueueRow()) - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) self.data[0].update(notif.creator_0,notif.owner_0) self.data[1].update(notif.creator_1,notif.owner_1) @@ -250,7 +261,8 @@ def update(self,notif): class StateNeighborsRow(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -285,7 +297,8 @@ def update(self,notif): class StateIsSync(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -303,9 +316,28 @@ def get16bAddr(self): try: return self.data[0]['my16bID'].addr[:] except IndexError: - return None - - def update(self,notif): + return [] + + def get64bAddr(self): + try: + return self.data[0]['my64bID'].addr[:] + except IndexError: + return [] + + def get_serial(self): + return self.moteConnector.serialport + + def get_info(self): + return { + '64bAddr' : u.formatAddr(self.get64bAddr()), + '16bAddr' : u.formatAddr(self.get16bAddr()), + 'isDAGroot' : self.isDAGroot, + 'serial' : self.get_serial(), + } + + def update(self,data): + + (moteInfo, notif) = data # update state StateElem.update(self) @@ -376,7 +408,8 @@ def update(self,notif): class StateMyDagRank(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) @@ -384,12 +417,14 @@ def update(self,notif): class StatekaPeriod(StateElem): - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) if len(self.data)==0: self.data.append({}) self.data[0]['kaPeriod'] = notif.kaPeriod +# abstract class class StateTable(StateElem): def __init__(self,rowClass,columnOrder=None): @@ -399,29 +434,58 @@ def __init__(self,rowClass,columnOrder=None): self.meta[0]['columnOrder'] = columnOrder self.data = [] - def update(self,notif): + def update(self,data): + (moteInfo, notif) = data StateElem.update(self) while len(self.data) 10: + self.outstandingPacketsFromDagRoot.pop(0) + self.outstandingPacketsFromDagRoot.append(packet) + + # ======================== private ========================================= + + # packetSent + def _handler_event_packetSent(self, buf): + returnVal = {} + + packetToken = buf[:5] + destBuf = buf[5:13] + dest = openvisualizer.openvisualizer_utils.formatAddr(destBuf) + hopLimit = buf[13] + + returnVal['packetToken'] = packetToken + returnVal['destination'] = dest + returnVal['hopLimit'] = hopLimit + + return (True, returnVal) + + def _handler_event_packetSentDagRoot(self, buf): + receivedToken = buf + newBuf = [] + + for packet in self.outstandingPacketsFromDagRoot: + (token, destination, hopLimit) = packet + if receivedToken == token: + # we have a hit, remove it from outstanding packets + log.debug("packetSentDagRoot event: hit for packet {0}".format(packet)) + self.outstandingPacketsFromDagRoot.remove(packet) + + # construct the missing fields from the saved values + newBuf += token + newBuf += destination + newBuf += [hopLimit] + return self._handler_event_packetSent(newBuf) + # not found, not all packets sent by dag root are originated by openbenchmark: ignore it + log.debug("packetSentDagRoot event: miss for token {0}".format(receivedToken)) + return (False, {}) + + # packetReceived, same syntax as packetSent + def _handler_event_packetReceived(self, buf): + return self._handler_event_packetSent(buf) + + # synchronizationCompleted + def _handler_event_synchronizationCompleted(self, buf): + return (True, {}) + + # desynchronized + def _handler_event_desynchronized(self, buf): + return (True, {}) + + # secureJoinCompleted + def _handler_event_secureJoinCompleted(self, buf): + return (True, {}) + + # bandwidthAssigned + def _handler_event_bandwidthAssigned(self, buf): + return (True, {}) + + # networkFormationCompleted + def _handler_event_networkFormationCompleted(self, buf): + # TODO + return (True, {}) + +# ================= helper class that polls for periodic measurements ===================== + +class PerformanceUpdatePoller(eventBusClient.eventBusClient, threading.Thread): + + # periodic event names + EV_RADIO_DUTY_CYCLE_MEASUREMENT = ['radioDutyCycleMeasurement', 258 ] + EV_CLOCK_DRIFT_MEASUREMENT = ['clockDriftMeasurement', 259 ] + + EV_SYNC_ALL = [ + EV_RADIO_DUTY_CYCLE_MEASUREMENT, + EV_CLOCK_DRIFT_MEASUREMENT, + ] + + def __init__(self, experimentId, mqttClient, period): + # log + log.info("creating a thread for periodic polling of performance metrics") + + # params + self.experimentId = experimentId + self.mqttClient = mqttClient + self.period = period + + # local vars + self.dutyCycleMeasurements = set() + self.dataLock = threading.Lock() + + # flag to permit exit from infinite loop + self.goOn = True + + # initialize the parent class + threading.Thread.__init__(self) + + # give this thread a name + self.name = 'performanceUpdatePollerThread' + + # subscribe to eventBus performance-related events + eventBusClient.eventBusClient.__init__( + self, + name='performanceUpdatePoller', + registrations=[ + { + 'sender': self.WILDCARD, + 'signal': 'dutyCycleMeasurement', + 'callback': self.handle_dutyCycleMeasurement, + }, + # TODO clock drift measurements + ] + ) + # start myself + self.start() + + # ======================== public ========================================= + + def run(self): + try: + # log + log.info("start running") + + while self.goOn: + + # poll moteState for latest measurements + self.dispatch('getDutyCycleMeasurement', []) + + # wait for a while to gather the response from motes + time.sleep(1) + + for measurement in self.dutyCycleMeasurements: + + # dispatch each as an individual message + (source, timestamp, dutyCycle) = measurement + + topic = 'openbenchmark/experimentId/{0}/nodeId/{1}/performanceData'.format(self.experimentId, source) + payload = { + 'event' : self.EV_RADIO_DUTY_CYCLE_MEASUREMENT[0], + 'timestamp' : int(timestamp,16), + 'source' : source, + 'dutyCycle' : dutyCycle, + } + + log.debug("Publishing on topic: {0} Payload: {1}".format(topic, payload)) + + self.mqttClient.publish( + topic='openbenchmark/experimentId/{0}/nodeId/{0}/performanceData'.format(self.experimentId, source), + payload=json.dumps(payload), + ) + + with self.dataLock: + # reset for the next measurement + self.dutyCycleMeasurements = set() + + time.sleep(self.period) + + except Exception as err: + errMsg = openvisualizer.openvisualizer_utils.formatCrashMessage(self.name, err) + print errMsg + log.critical(errMsg) + self.close() + finally: + pass + + def close(self): + self.goOn = False + + def handle_dutyCycleMeasurement(self, sender, signal, data): + with self.dataLock: + self.dutyCycleMeasurements.add( + ( data['source'], data['timestamp'], data['dutyCycle'] ) + ) + +# ==================== Implementation of CoAP openbenchmark resource ===================== + +class OpenbenchmarkResource(coapResource.coapResource): + + def __init__(self, performanceEvent, dagRootEui64): + + # params + self.performanceEvent = performanceEvent + self.dagRootEui64Buf = dagRootEui64 + + # initialize parent class + coapResource.coapResource.__init__( + self, + path='b', + ) + + def POST(self, options=[], payload=[], metaData={}): + + assert self.dagRootEui64Buf + + respPayload = [] + respOptions = [] + + # token is the payload + token = payload + timestamp = metaData['generic_1'] + source = openvisualizer.openvisualizer_utils.formatAddr(self.dagRootEui64Buf) + + destinationBuf = u.ipv6AddrString2Bytes(metaData['srcIP'])[8:] + destinationEui64String = openvisualizer.openvisualizer_utils.formatAddr(destinationBuf) + + log.debug("OpenbenchmarkResource: POST handler received metadata: {0}".format(metaData)) + + dict = { + 'packetToken' : token, + 'destination' : destinationEui64String, + 'hopLimit' : metaData['generic_0'], + } + + self.performanceEvent.publish_event(PerformanceEvent.EV_PACKET_RECEIVED[2], timestamp, source, dict) + + noResponse = False + for option in options: + if isinstance(option, o.NoResponse): + noResponse = True + + # prepare the response + if not noResponse: + respPayload = token + respPayload[4] = (respPayload[4] + 1) % 255 + self.performanceEvent.add_outstanding_packet( + (respPayload, destinationBuf, coapServer.COAP_SERVER_DEFAULT_IPv6_HOP_LIMIT) + ) + + return d.COAP_RC_2_04_CHANGED, respOptions, respPayload diff --git a/openvisualizer/openLbr/openLbr.py b/openvisualizer/openLbr/openLbr.py index a17439a4..64c7e44a 100644 --- a/openvisualizer/openLbr/openLbr.py +++ b/openvisualizer/openLbr/openLbr.py @@ -283,7 +283,11 @@ def _meshToV6_notif(self,sender,signal,data): try: ipv6dic={} #build lowpan dictionary from the data - ipv6dic = self.lowpan_to_ipv6(data) + + source = data[0] + buf = data[1] + timestamp = data[2] + ipv6dic = self.lowpan_to_ipv6(source, buf) success = True dispatchSignal = None @@ -302,7 +306,7 @@ def _meshToV6_notif(self,sender,signal,data): #ipv6 header (inner) ipv6dic_inner = {} # parsing the iphc inner header and get the next_header - ipv6dic_inner = self.lowpan_to_ipv6([ipv6dic['pre_hop'],ipv6dic['payload']]) + ipv6dic_inner = self.lowpan_to_ipv6(ipv6dic['pre_hop'],ipv6dic['payload']) ipv6dic['next_header'] = ipv6dic_inner['next_header'] ipv6dic['payload'] = ipv6dic_inner['payload'] ipv6dic['payload_length'] = ipv6dic_inner['payload_length'] @@ -416,7 +420,7 @@ def _meshToV6_notif(self,sender,signal,data): #as source address is being retrieved from the IPHC header, the signal includes it in case #receiver such as RPL DAO processing needs to know the source. - success = self._dispatchProtocol(dispatchSignal,(ipv6dic['src_addr'],ipv6dic['app_payload'])) + success = self._dispatchProtocol(dispatchSignal,(ipv6dic['src_addr'],ipv6dic['app_payload'],ipv6dic['hop_limit'],timestamp)) if success: return @@ -754,11 +758,9 @@ def reassemble_lowpan(self,lowpan): #===== 6LoWPAN -> IPv6 - def lowpan_to_ipv6(self,data): + def lowpan_to_ipv6(self, mac_prev_hop, pkt_lowpan): pkt_ipv6 = {} - mac_prev_hop=data[0] - pkt_lowpan=data[1] if pkt_lowpan[0]==self.PAGE_ONE_DISPATCH: ptr = 1 diff --git a/openvisualizer/openType/typeAsn.py b/openvisualizer/openType/typeAsn.py index 5a268a47..1117433b 100644 --- a/openvisualizer/openType/typeAsn.py +++ b/openvisualizer/openType/typeAsn.py @@ -18,10 +18,24 @@ def __init__(self): # initialize parent class openType.openType.__init__(self) + + self.asn = None def __str__(self): + if self.asn is None: + return '?' return '0x{0}'.format(''.join(["%.2x"%b for b in self.asn])) - + + def __eq__(self, other): + if isinstance(other, typeAsn): + return self.asn == other.asn + return False + + def __ne__(self, other): + if isinstance(other, typeAsn): + return self.asn != other.asn + return True + #======================== public ========================================== def update(self,byte0_1,byte2_3,byte4): diff --git a/openvisualizer/openType/typeCellType.py b/openvisualizer/openType/typeCellType.py index e3ac5d01..87b641b1 100644 --- a/openvisualizer/openType/typeCellType.py +++ b/openvisualizer/openType/typeCellType.py @@ -48,6 +48,9 @@ def update(self,type): else: self.desc = 'unknown' self.addr = None + + def getCellType(self): + return self.desc #======================== private ========================================= \ No newline at end of file diff --git a/openvisualizer/openvisualizer_utils.py b/openvisualizer/openvisualizer_utils.py index 998d1d68..d13844f0 100644 --- a/openvisualizer/openvisualizer_utils.py +++ b/openvisualizer/openvisualizer_utils.py @@ -53,24 +53,32 @@ def formatThreadList(): #===== parsing -def hex2buf(s): +def hex2buf(s,separator=None): ''' Convert a string of hex caracters into a byte list. For example: ``'abcdef00' -> [0xab,0xcd,0xef,0x00]`` :param s: [in] The string to convert + :param separator: [in] Optional separator char used in the string, e.g ab-cd-ef-00' :returns: A list of integers, each element in [0x00..0xff]. ''' assert type(s)==str - assert len(s)%2 == 0 + if separator: + assert type(separator)==str + assert len(separator)==1 + newVal = s.replace(separator,'') # remove the separator before proceding + else: + newVal = s + + assert len(newVal)%2 == 0 returnVal = [] - for i in range(len(s)/2): + for i in range(len(newVal)/2): realIdx = i*2 - returnVal.append(int(s[realIdx:realIdx+2],16)) + returnVal.append(int(newVal[realIdx:realIdx+2],16)) return returnVal diff --git a/setup.py b/setup.py index 619ad78c..4cb5323c 100644 --- a/setup.py +++ b/setup.py @@ -7,9 +7,9 @@ from openvisualizer import ovVersion ''' -This implementation of the traditional setup.py uses the root package's -package_data parameter to store data files, rather than the application-level -data_files parameter. This arrangement organizes OpenVisualizer within a +This implementation of the traditional setup.py uses the root package's +package_data parameter to store data files, rather than the application-level +data_files parameter. This arrangement organizes OpenVisualizer within a single tree of directories, and so is more portable. In contrast to the native setup, the installer is free to relocate the tree @@ -25,7 +25,7 @@ simdata = 'data/sim_files' with open('README.md') as f: LONG_DESCRIPTION = f.read() - + # Create list of required modules for 'install_requires' parameter. Cannot create # this list with pip.req.parse_requirements() because it requires the pwd module, # which is Unix only. @@ -42,11 +42,11 @@ def appdirGlob(globstr, subdir=''): return glob.glob('/'.join([appdir, globstr])) else: return glob.glob('/'.join([appdir, subdir, globstr])) - + class build_py(_build_py): ''' Extends setuptools build of openvisualizer package data at installation time. - Selects and copies the architecture-specific simulation module from an OS-based + Selects and copies the architecture-specific simulation module from an OS-based subdirectory up to the parent 'sim_files' directory. Excludes the OS subdirectories from installation. ''' @@ -67,37 +67,37 @@ def build_package_data(self): simPath = os.path.join(build_dir, 'data', 'sim_files') target = os.path.join(simPath, 'oos_openwsn.{0}'.format(fileExt)) self.copy_file(srcfile, target) - + if simPath: shutil.rmtree(os.path.join(simPath, 'linux')) shutil.rmtree(os.path.join(simPath, 'windows')) setup( name = 'openVisualizer', - packages = ['openvisualizer', - 'openvisualizer.BspEmulator', 'openvisualizer.eventBus', - 'openvisualizer.lbrClient', 'openvisualizer.moteConnector', - 'openvisualizer.moteProbe', 'openvisualizer.moteState', - 'openvisualizer.openLbr', 'openvisualizer.openTun', + packages = ['openvisualizer', + 'openvisualizer.BspEmulator', 'openvisualizer.eventBus', + 'openvisualizer.lbrClient', 'openvisualizer.moteConnector', + 'openvisualizer.moteProbe', 'openvisualizer.moteState', + 'openvisualizer.openLbr', 'openvisualizer.openTun', 'openvisualizer.openType', 'openvisualizer.openUI', 'openvisualizer.RPL', 'openvisualizer.SimEngine', 'openvisualizer.remoteConnectorServer', - 'openvisualizer.JRC'], + 'openvisualizer.JRC', 'openvisualizer.openBenchmarkAgent'], scripts = appdirGlob('openVisualizer*.py'), package_dir = {'': '.', 'openvisualizer': 'openvisualizer'}, # Copy simdata files by extension so don't copy .gitignore in that directory. package_data = {'openvisualizer': [ 'data/*.conf', 'data/requirements.txt', - '/'.join([webstatic, 'css', '*']), - '/'.join([webstatic, 'font-awesome', 'css', '*']), - '/'.join([webstatic, 'font-awesome', 'fonts', '*']), - '/'.join([webstatic, 'images', '*']), - '/'.join([webstatic, 'js', '*.js']), - '/'.join([webstatic, 'js', 'plugins', 'metisMenu', '*']), - '/'.join([webtmpl, '*']), - '/'.join([simdata, 'windows', '*.pyd']), - '/'.join([simdata, 'linux', '*.so']), - '/'.join([simdata, '*.h']) + '/'.join([webstatic, 'css', '*']), + '/'.join([webstatic, 'font-awesome', 'css', '*']), + '/'.join([webstatic, 'font-awesome', 'fonts', '*']), + '/'.join([webstatic, 'images', '*']), + '/'.join([webstatic, 'js', '*.js']), + '/'.join([webstatic, 'js', 'plugins', 'metisMenu', '*']), + '/'.join([webtmpl, '*']), + '/'.join([simdata, 'windows', '*.pyd']), + '/'.join([simdata, 'linux', '*.so']), + '/'.join([simdata, '*.h']) ]}, install_requires = deplist, # Must extract zip to edit conf files.