diff --git a/dist/tools/pyterm/pyterm b/dist/tools/pyterm/pyterm
index d159d12c986c0154f1e66e83cb691f3f07d43894..1c6a66dc0e46ab53056c93faa6e9459c955dbc33 100755
--- a/dist/tools/pyterm/pyterm
+++ b/dist/tools/pyterm/pyterm
@@ -75,7 +75,8 @@ class SerCmd(cmd.Cmd):
     """
 
     def __init__(self, port=None, baudrate=None, tcp_serial=None,
-                 confdir=None, conffile=None, host=None, run_name=None):
+                 confdir=None, conffile=None, host=None, run_name=None, 
+                 log_dir_name=None):
         """Constructor.
 
         Args:
@@ -98,6 +99,7 @@ class SerCmd(cmd.Cmd):
         self.configfile = conffile
         self.host = host
         self.run_name = run_name
+        self.log_dir_name = log_dir_name
 
         if not self.host:
             self.host = defaulthostname
@@ -105,6 +107,9 @@ class SerCmd(cmd.Cmd):
         if not self.run_name:
             self.run_name = defaultrunname
 
+        if not self.log_dir_name:
+            self.log_dir_name = self.hostname
+
         if not os.path.exists(self.configdir):
             os.makedirs(self.configdir)
 
@@ -132,7 +137,7 @@ class SerCmd(cmd.Cmd):
         # create formatter
         formatter = logging.Formatter(self.fmt_str)
 
-        directory = self.configdir + os.path.sep + self.host
+        directory = self.configdir + os.path.sep + self.log_dir_name
         if not os.path.exists(directory):
             os.makedirs(directory)
         logging.basicConfig(filename = directory + os.path.sep +
@@ -579,21 +584,33 @@ class SerCmd(cmd.Cmd):
             #sys.stdout.flush()
 
 class PytermProt(Protocol):
+    def __init__(self, factory):
+        self.factory = factory
+        
+    def connectionMade(self):
+        print("writing to transport")
+        self.transport.write("hostname: %s\n" % (self.factory.shell.host))
+    
     def dataReceived(self, data):
         sys.stdout.write(data)
+        if(data.strip() == "/exit"):
+            reactor.callLater(2, self.factory.shell.do_PYTERM_exit, data)
+        else:
+            self.factory.shell.ser.write(data + "\n")               
 
     def sendMessage(self, msg):
         self.transport.writeSomeData("%d#%s\n" % (len(msg), msg))
 
 class PytermClientFactory(ReconnectingClientFactory):
 
-    def __init__(self):
+    def __init__(self, shell = None):
         self.myproto = None
+        self.shell = shell
 
     def buildProtocol(self, addr):
         print('Connected.')
         self.resetDelay()
-        self.myproto = PytermProt()
+        self.myproto = PytermProt(self)
         return self.myproto
 
     def clientConnectionLost(self, connector, reason):
@@ -655,14 +672,19 @@ if __name__ == "__main__":
             help="Hostname of this maschine")
     parser.add_argument("-rn", "--run-name",
             help="Run name, used for logfile")
+    parser.add_argument("-ln", "--log-dir-name",
+            help="Log directory name (default is hostname e.g. %s/<hostname>)"
+                 %defaultdir,
+                 default=defaultdir)
     args = parser.parse_args()
 
     myshell = SerCmd(args.port, args.baudrate, args.tcp_serial,
-                     args.directory, args.config, args.host, args.run_name)
+                     args.directory, args.config, args.host, args.run_name,
+                     args.log_dir_name)
     myshell.prompt = ''
 
     if args.server and args.tcp_port:
-        myfactory = PytermClientFactory()
+        myfactory = PytermClientFactory(myshell)
         reactor.connectTCP(args.server, args.tcp_port, myfactory)
         myshell.factory = myfactory
         reactor.callInThread(myshell.cmdloop, "Welcome to pyterm!\n"
diff --git a/dist/tools/pyterm/pytermcontroller/__init__.py b/dist/tools/pyterm/pytermcontroller/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c55508af04afc55ae081437aa77eaa06e7255fb
--- /dev/null
+++ b/dist/tools/pyterm/pytermcontroller/__init__.py
@@ -0,0 +1,2 @@
+__all__ = ["pytermcontroller"]
+
diff --git a/dist/tools/pyterm/pytermcontroller/pytermcontroller.py b/dist/tools/pyterm/pytermcontroller/pytermcontroller.py
new file mode 100755
index 0000000000000000000000000000000000000000..2ea2df2885237877716a4dad07fbc56d4dabcb87
--- /dev/null
+++ b/dist/tools/pyterm/pytermcontroller/pytermcontroller.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2014  Philipp Rosenkranz <philipp.rosenkranz@fu-berlin.de>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+
+import sys, signal, threading
+
+from twisted.internet import reactor
+from twisted.internet.protocol import Protocol, Factory
+
+
+class PubProtocol(Protocol):
+    def __init__(self, factory):
+        self.factory = factory
+
+    def connectionMade(self):
+        print("new connection made")   
+
+    def connectionLost(self, reason):
+        self.factory.numProtocols = self.factory.numProtocols - 1
+
+    def connectionLost(self, reason):
+        self.factory.clients = {key: value for key, value in self.factory.clients.items() 
+             if value is not self.transport}
+
+    def dataReceived(self, data):
+        if data.startswith("hostname: "):
+            remoteHostname = data.split()[1].strip()
+            print("dataReceived added: " + remoteHostname)
+            self.factory.clients[remoteHostname] = self.transport
+        else:
+            print("received some useless data...")
+
+class PubFactory(Factory):
+    def __init__(self):
+        self.clients = dict()
+
+    def buildProtocol(self, addr):
+        return PubProtocol(self)
+  
+    
+class ExperimentRunner():
+    def __init__(self, experiment, testbed):
+        self.publisher = PubFactory()
+        self.port = int(testbed.serverPort)
+        self.experiment = experiment(self.publisher, self)
+        self.testbed = testbed
+    
+    def run(self):
+        signal.signal(signal.SIGINT, self.handle_sigint)
+        self.experiment.run()
+        reactor.listenTCP(self.port, self.publisher)
+        # clean logfiles and start nodes but don't flash nodes
+        self.testbed.initClean() 
+        reactor.run() 
+        
+    def stop(self):
+        self.testbed.stop()
+        reactor.callFromThread(self.safeReactorStop)
+            
+    def safeReactorStop(self):
+        if reactor.running:
+            try:
+                reactor.stop()
+            except:
+                print("tried to shutdown reactor twice!")
+                
+        
+    def handle_sigint(self, signal, frame):        
+        self.experiment.stop()
+        self.testbed.stop()
+        self.stop() # shutdown if experiment didn't already
+         
+
+class Experiment():
+    def __init__(self, factory, runner):
+        self.factory = factory
+        self.runner = runner
+        self.hostid = dict()
+        self.sumDelay = 0
+        
+    def run(self):
+        print("Running preHook")
+        self.preHook()
+        print("Running experiment")
+        self.start()        
+
+    def start(self):
+        raise NotImplementedError("Inherit from Experiment and implement start")  
+    
+    def stop(self): 
+        print("Running postHook")       
+        self.postHook()  
+        self.runner.stop()       
+      
+    def preHook(self):
+        pass   
+    
+    def postHook(self):
+        pass 
+ 
+    def readHostFile(self, path):
+        id = 1
+        with open(path) as f:
+            for line in f:
+                self.hostid[line.strip()] = id
+                id += 1
+    
+    def sendToHost(self, host=None, cmd=""):
+        if host in self.factory.clients:
+            self.factory.clients[host].write(cmd + "\n")
+        else:
+            print("sendToHost: no such host known: " + host + " !")
+    
+    def sendToAll(self, cmd=""):
+       for host, transport in self.factory.clients.items():
+            self.sendToHost(host, cmd)        
+         
+    def pauseInSeconds(self, seconds=0):
+        from time import time, sleep
+        start = time()
+        while (time() - start < seconds):
+            sleep(seconds - (time() - start))
+            
+    def callLater(self, absoluteDelay = 0.0, function = None):
+        reactor.callLater(absoluteDelay, function)
+        
+    def waitAndCall(self, relativeDelay = 0.0, function = None):
+        self.sumDelay += relativeDelay
+        self.callLater(self.sumDelay, function)
+        
+    def clientIterator(self):
+        return self.factory.clients.items()
+    
+    def connectionByHostname(self, host=None):
+        if host in self.factory.clients:
+            return self.factory.clients[host] 
+    
+    @staticmethod 
+    def sendToConnection(connection, line):
+        connection.write(line + "\n")   
+  
diff --git a/dist/tools/pyterm/testbeds/__init__.py b/dist/tools/pyterm/testbeds/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d98a81798dcf4730dde2e1eb64cbc76a7c0e9d64
--- /dev/null
+++ b/dist/tools/pyterm/testbeds/__init__.py
@@ -0,0 +1,2 @@
+__all__ = ["testbeds"]
+
diff --git a/dist/tools/pyterm/testbeds/testbeds.py b/dist/tools/pyterm/testbeds/testbeds.py
new file mode 100644
index 0000000000000000000000000000000000000000..e51e2d2fb9eba335f39b26be8556a6f44ac613d2
--- /dev/null
+++ b/dist/tools/pyterm/testbeds/testbeds.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python2
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2014  Philipp Rosenkranz <philipp.rosenkranz@fu-berlin.de>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+
+import os, re, datetime
+from subprocess import call, Popen, PIPE
+
+
+class Testbed():
+    log_dir_name = 'log'
+    
+    def __init__(self):
+        pass
+    
+    def initCleanWithFlash(self):
+        self.stop()
+        self.cleanLogs()
+        self.flashNodes()
+        self.start()
+        
+    def initClean(self):
+        self.cleanLogs()
+        self.start()
+    
+    def flashNodes(self):
+        raise NotImplementedError("Inherit from Testbed and implement flashNodes") 
+    
+    def cleanLogs(self):
+        raise NotImplementedError("Inherit from Testbed and implement flashNodes") 
+    
+    def archiveLogs(self, experiment = None):
+        raise NotImplementedError("Inherit from Testbed and implement flashNodes") 
+    
+    def start(self):
+        raise NotImplementedError("Inherit from Testbed and implement flashNodes")  
+    
+    def stop(self):
+        raise NotImplementedError("Inherit from Testbed and implement flashNodes") 
+    
+    def defaultArchivePostfix(self, experimentName = None):
+        if not experimentName:
+            experimentName = "unknown"
+        time = datetime.datetime.now().strftime("%Y-%m-%d_%H_%M_%S")
+        postfix = "-" + experimentName +"_" + time  
+        return postfix     
+    
+    def printAndCall(self, cmdString):
+        print(cmdString) 
+        call(cmdString, shell=True)         
+    
+    
+class DESTestbed(Testbed):    
+    def __init__(self, serverHost = None, serverPort=None, userName = None, flasher = None, 
+                 hexfilePath = None, pyterm = None, logFilePath = None, hostFile = None):
+        self.serverHost = serverHost
+        self.serverPort = str(serverPort)
+        self.userName = userName
+        self.flasher = flasher
+        self.hexFilePath = hexfilePath
+        self.pyterm = pyterm
+        self.logFilePath = logFilePath
+        self.hostFile = hostFile
+        
+    def flashNodes(self):       
+        self.printAndCall("parallel-ssh -h %s -l %s 'python %s'" % (self.hostFile, self.userName, self.flasher))
+        
+    def cleanLogs(self):        
+        self.printAndCall("rm -rf %s/*.log" % (self.logFilePath))
+        
+    def archiveLogs(self, postfix = None): 
+        postfix = self.defaultArchivePostfix(postfix)
+        logDir = self.logFilePath.split("/")[-1]
+        self.printAndCall("cd %s/..; tar -cjf archived_logs%s.tar.bz2 %s/*.log" % (self.logFilePath, postfix, logDir))
+        
+    def start(self):        
+        self.printAndCall("parallel-ssh -h %s -l %s 'screen -S pyterm -d -m python %s -ln %s'" % (self.hostFile, self.userName, self.pyterm, self.log_dir_name))
+        
+    def stop(self):        
+        self.printAndCall("parallel-ssh -h %s -l %s 'screen -X -S pyterm quit'" % (self.hostFile, self.userName))
+        
+class LocalTestbed(Testbed):    
+    def __init__(self, serverHost = None, serverPort=None, flasher = None, hexfilePath = None, pyterm = None, logFilePath = None):
+        self.serverHost = serverHost
+        self.serverPort = str(serverPort)
+        self.flasher = flasher
+        self.hexFilePath = hexfilePath
+        self.pyterm = pyterm
+        self.logFilePath = logFilePath
+        
+    def findPorts(self):
+        devlist = os.listdir("/dev/")        
+        regex = re.compile('^ttyUSB')        
+        return sorted([port for port in devlist if regex.match(port)])
+
+    def flashNodes(self):       
+        self.printAndCall("python %s %s" % (self.flasher, self.hexFilePath))
+        
+    def cleanLogs(self):      
+        self.printAndCall("rm -rf %s/*.log" % (self.logFilePath))
+        
+    def archiveLogs(self, postfix = None): 
+        postfix = self.defaultArchivePostfix(postfix)
+        logDir = self.logFilePath.split("/")[-1]
+        self.printAndCall("cd %s/..; tar -cjf archived_logs%s.tar.bz2 %s/*.log" % (self.logFilePath, postfix, logDir))
+        
+    def start(self):
+        portList = self.findPorts()
+        for port in portList:           
+            self.printAndCall("screen -S pyterm-%s -d -m python %s -H %s -rn %s -p /dev/%s -ln %s" % (port, self.pyterm, port, port, port, self.log_dir_name))
+            
+    def stop(self):
+        portList = self.findPorts()
+        for port in portList:         
+            self.printAndCall("screen -X -S pyterm-%s quit" % (port))
+            
+class DesVirtTestbed(Testbed):       
+    def __init__(self, serverHost = None, serverPort=None, desvirtPath = None, topologyName = None, pyterm = None, logFilePath = None):
+        self.serverHost = serverHost
+        self.serverPort = str(serverPort)
+        self.desvirtPath = desvirtPath
+        self.topologyName = topologyName
+        self.pyterm = pyterm
+        self.logFilePath = logFilePath
+        self.namePortList = []
+        
+    def findPorts(self):        
+        return self.namePortList
+    
+    def startDesVirtNetwork(self):
+        print "executing: " + "./vnet --start --name " + self.topologyName + " in: " + self.desvirtPath
+        call("sh -c \"./vnet --define --name " + self.topologyName + "\"", cwd=self.desvirtPath, shell=True)
+        stream = Popen("sh -c \"./vnet --start --name " + self.topologyName + "\"", cwd=self.desvirtPath, shell=True, stderr=PIPE).stderr
+        pats = r'.*riotnative.*\.elf (\S+) -t (\S+)'
+        pattern = re.compile(pats)
+        for line in stream:
+            match = pattern.match(line)
+            if(match):
+                tuple = match.groups()
+                self.namePortList.append((tuple[0], int(tuple[1])))
+        self.namePortList = sorted(self.namePortList)
+        for tuple in self.namePortList:
+            print "name: " + tuple[0] + " port: " + str(tuple[1])
+            
+    def stopDesVirtNetwork(self):
+        call("sh -c \"./vnet --stop --name " + self.topologyName + "\"", cwd=self.desvirtPath, shell=True)                
+
+    def flashNodes(self):       
+        pass
+        
+    def cleanLogs(self): 
+        self.printAndCall("rm -rf %s/*.log" % (self.logFilePath))
+        
+    def archiveLogs(self, postfix = None): 
+        postfix = self.defaultArchivePostfix(postfix)
+        logDir = self.logFilePath.split("/")[-1]
+        self.printAndCall("cd %s/..; tar -cjf archived_logs%s.tar.bz2 %s/*.log" % (self.logFilePath, postfix, logDir))
+        
+    def start(self):
+        for node in self.namePortList:           
+            self.printAndCall("screen -S pyterm-%s -d -m python %s -H %s -rn %s -ts %s -ln %s" % (node[0], self.pyterm, node[0], node[0], node[1], self.log_dir_name))
+            
+    def stop(self):
+        print "stop called"
+        for node in self.namePortList:         
+            self.printAndCall("screen -X -S pyterm-%s quit" % (node[0]))
+        self.stopDesVirtNetwork()
+
+
+