diff --git a/Scripts/TestMaster/TestMaster.py b/Scripts/TestMaster/TestMaster.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a280fbc32e1c4432e3e19359b7c0677ee63b0a6
--- /dev/null
+++ b/Scripts/TestMaster/TestMaster.py
@@ -0,0 +1,122 @@
+from pexpect import pxssh
+
+
+class Node:
+	def __init__(self, username, hostname, password, testCommand):
+		self.username = username
+		self.hostname = hostname
+		self.password = password
+		self.testCommand = testCommand
+
+
+class NodeConnection(pxssh.pxssh):
+	def __init__(self, node):
+		pxssh.pxssh.__init__(self)
+		self.node = node
+		self.connected = False
+
+	def __enter__(self):
+		self.connect()
+		return self
+
+	def __exit__(self, exc_type, exc_val, exc_tb):
+		self.disconnect()
+
+	def connect(self):
+		"""
+		Connects to a node via ssh. This may take a few seconds (due to the underlying module).
+		:return:
+		"""
+		try:
+			self.login(self.node.hostname, self.node.username, self.node.password,
+					   original_prompt="/home/" + self.node.username + ">")
+		except pxssh.ExceptionPxssh:
+			print("ERROR: failed to connect to node: " + self.node.ssh_id)
+			raise
+		self.connected = True
+		print("Connected to: " + str(self.node.username) + "@" + str(self.node.hostname))
+
+	def disconnect(self):
+		if self.connected:
+			self.logout()
+			self.connected = False
+			print("Disconnected from: " + str(self.node.username) + "@" + str(self.node.hostname))
+
+	def prepare_dump_settings(self, sim_id):
+		self.start_clt()
+		# set sim id
+		self.sendline("tx id " + str(sim_id))
+		# set number of coefficients to dump
+		self.sendline("rx ncoff 30")
+		self.stop_clt()
+
+	def start_transmissions(self, num_transmissions, transmission_delay):
+		"""
+		Non-Blocking, initiates a broadcast transmission on the node.
+		:param num_transmissions: number of packets to broadcast
+		:param transmission_delay: time delay between each consecutive transmission in seconds
+				which must be in the range from 0.05 to 60
+		"""
+		self.start_clt()
+		# the delay parameter is given in milliseconds
+		self.sendline("tx delay " + str(int(transmission_delay * 1000)))
+		self.clt_prompt()
+		# this is non blocking
+		self.sendline("tx " + str(num_transmissions))
+		self.clt_prompt()
+		self.stop_clt()
+
+	def get_dump(self, sender, sim_id):
+		rx_content = self.exec_cmd("cd /data/rx && ls -1")
+		dump_files = list(
+			filter(lambda filename: self.check_filename(filename, sender, sim_id), rx_content.split("\r\n")))
+		if len(dump_files) == 0:
+			raise FileNotFoundError("ERROR: Dump not found! (receiver: " + str(self.node.node_id) +
+									", sender: " + str(sender.node_id) + ", sim_id: " + str(sim_id))
+		if len(dump_files) > 1:
+			raise Exception("ERROR: There are multiple dump files for receiver: " + str(self.node.node_id) +
+							", sender: " + str(sender.node_id) + ", sim_id: " + str(sim_id) + "!")
+		content = self.exec_cmd("cat " + dump_files[0])
+		return Dump(self.node, sender, content)
+
+	def clean_dump_dir(self):
+		self.sendline("cd /data/rx && rm *")
+		self.prompt()
+
+	def start_clt(self):
+		self.sendline("clt")
+		self.clt_prompt()
+
+	def stop_clt(self):
+		self.sendline("quit")
+		self.prompt()
+
+	def clt_prompt(self):
+		self.expect("CMD>")
+
+	def check_filename(self, filename, sender, sim_id):
+		# Dumped receive data is written to a file with name "dump-RR-CCC-TT-DDD.txt" where RR
+		# identifies the receiver node, CCC is a run counter which increments when the daemon starts,
+		# TT identifies the transmitting node, and DDD is a test identifier (sim_id)
+		filename = filename.split(".txt")[0]
+		split = filename.split("-")
+		if len(split) < 5:
+			return False
+		fn_dump, fn_receiver, fn_run, fn_sender, fn_id = split
+		return fn_dump == "dump" \
+			   and int(fn_receiver) == self.node.node_id \
+			   and int(fn_sender) == sender.node_id \
+			   and int(fn_id) == sim_id
+
+	def exec_cmd(self, cmd):
+		"""
+		:param cmd: command to execute on the shell
+		:return: the output produced by the command as a string
+		"""
+		self.sendline(cmd)
+		self.prompt()
+		return self.before.decode("utf-8").split(cmd + "\r\r\n")[1]
+
+
+if __name__ == "__main__":
+	print("hello")
\ No newline at end of file
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/CMakeLists.txt b/modules/catkin_ws/src/PlatoonProtocolLib/CMakeLists.txt
index 7bc0a6137ef39742b88c4e9039f70cba84573486..d557cbd250b2055947670340480fca310848a56b 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/CMakeLists.txt
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/CMakeLists.txt
@@ -157,5 +157,9 @@ add_executable(FollowerTest ${SOURCE_FILES} test/FollowerTest.cpp)
 target_link_libraries(FollowerTest NetworkingLib)
 target_include_directories(FollowerTest PUBLIC ${NetworkingLib_INCLUDE_DIRS})
 
+add_executable(TestScenarios ${SOURCE_FILES} test/TestScenarios.cpp)
+target_link_libraries(TestScenarios NetworkingLib)
+target_include_directories(TestScenarios PUBLIC ${NetworkingLib_INCLUDE_DIRS})
+
 # For debugging
 #target_compile_options(LeaderTest PUBLIC -fopenmp -fPIC -O0 -g3 -ggdb)
\ No newline at end of file
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/FollowerVehicle.h b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/FollowerVehicle.h
index c1067561ec1cef192e4f91c7f71a6e24f2f220ca..6008663a41564198133a8d7b8e46a286c497ded2 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/FollowerVehicle.h
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/FollowerVehicle.h
@@ -24,6 +24,10 @@ private:
     };
 
 public:
+    static constexpr networking::time::Duration RESPONSE_TIMEOUT = std::chrono::milliseconds(3000);
+    static constexpr networking::time::Duration HEARTBEAT_INTERVAL = std::chrono::milliseconds(200);
+    static constexpr networking::time::Duration BROADCAST_TIMEOUT = std::chrono::milliseconds(500);
+
     using Ptr = std::shared_ptr<FollowerVehicle>;
 
     static Ptr create(networking::Networking & net, const NetworkInfo & info);
@@ -50,15 +54,11 @@ protected:
     Ptr shared_from_this();
 
 private:
-    static constexpr networking::time::Duration RESPONSE_TIMEOUT = std::chrono::milliseconds(3000);
-    static constexpr networking::time::Duration HEARTBEAT_INTERVAL = std::chrono::milliseconds(200);
-    static constexpr networking::time::Duration BROADCAST_TIMEOUT = std::chrono::milliseconds(500);
-
     VehicleId leader;
     networking::time::Timer::Ptr responseTimer;
     networking::time::Timer::Ptr broadcastTimer;
     networking::time::Timer::Ptr heartbeatTimer;
-    Callback onPlatoonConfigUpdatedCallback;
+    Callback onPlatoonConfigUpdatedCallback{DEFAULT_CALLBACK};
 
     void sendPlatoonCreateRequest();
 
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/LeaderVehicle.h b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/LeaderVehicle.h
index 5e8a491674a703dd3c3470818113e18d5ad2a808..58ea5b10ac89354801c3c46189873e4fef918d89 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/LeaderVehicle.h
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/LeaderVehicle.h
@@ -26,6 +26,9 @@ private:
     };
 
 public:
+    static constexpr networking::time::Duration HEARTBEAT_TIMEOUT = std::chrono::milliseconds(1000);
+    static constexpr networking::time::Duration BROADCAST_INTERVAL = std::chrono::milliseconds(50);
+
     using Ptr = std::shared_ptr<LeaderVehicle>;
 
     static Ptr create(networking::Networking & net, const NetworkInfo & info);
@@ -55,9 +58,6 @@ protected:
     Ptr shared_from_this();
 
 private:
-    static constexpr networking::time::Duration HEARTBEAT_TIMEOUT = std::chrono::milliseconds(1000);
-    static constexpr networking::time::Duration BROADCAST_INTERVAL = std::chrono::milliseconds(50);
-
     struct Follower
     {
         VehicleId vehicleId;
@@ -88,6 +88,8 @@ private:
 
     void stopBroadcasting();
 
+    void sendBroadcastMessage();
+
     void removeAllFollowers();
 
     bool checkFollowerMessage(const PlatoonMessage & message) const;
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/PlatoonMessage.h b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/PlatoonMessage.h
index cbcd60a9b6a84e8f96885debed0d68eff3b263c4..dff590acd3be3fd817ea6afc10ad64e9a6300320 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/PlatoonMessage.h
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/PlatoonMessage.h
@@ -33,6 +33,13 @@ inline bool isStandardMessageType(MessageType messageType)
     return messageTypes::standardMessageTypes.find(messageType) != messageTypes::standardMessageTypes.end();
 }
 
+template<typename ... MessageTypes>
+bool contains(MessageType type, MessageTypes ... types)
+{
+    std::unordered_set<MessageType> set{types...};
+    return set.find(type) != set.end();
+}
+
 }
 
 namespace jsonNames
@@ -145,11 +152,16 @@ struct Encoder<platoonProtocol::PlatoonMessage>
 
         using json = nlohmann::json;
         using namespace jsonNames;
-        auto j = json{{SRC_VEHICLE, message.srcVehicle},
-                      {DST_VEHICLE, message.dstVehicle},
-                      {PLATOON_ID, message.platoonId}};
+        auto j = json{{SRC_VEHICLE, message.srcVehicle}};
+
+        using namespace messageTypes;
+        if (messageTypes::contains(messageType, FV_HEARTBEAT, LV_ACCEPT, REJECT, FV_LEAVE))
+            j[DST_VEHICLE] = message.dstVehicle;
 
-        if (messageType == messageTypes::LV_BROADCAST)
+        if (messageTypes::contains(messageType, LV_BROADCAST, FV_HEARTBEAT, LV_ACCEPT, FV_LEAVE))
+            j[PLATOON_ID] = message.platoonId;
+
+        if (messageTypes::contains(messageType, LV_BROADCAST))
         {
             j[PLATOON_SPEED] = message.platoonSpeed;
             j[INNER_PLATOON_DISTANCE] = message.innerPlatoonDistance;
@@ -184,10 +196,15 @@ struct Decoder<platoonProtocol::PlatoonMessage>
 
         using namespace jsonNames;
         message.srcVehicle = j.at(SRC_VEHICLE).get<VehicleId>();
-        message.dstVehicle = j.at(DST_VEHICLE).get<VehicleId>();
-        message.platoonId = j.at(PLATOON_ID).get<PlatoonId>();
 
-        if (messageType == messageTypes::LV_BROADCAST)
+        using namespace messageTypes;
+        if (messageTypes::contains(messageType, FV_HEARTBEAT, LV_ACCEPT, REJECT, FV_LEAVE))
+            message.dstVehicle = j.at(DST_VEHICLE).get<VehicleId>();
+
+        if (messageTypes::contains(messageType, LV_BROADCAST, FV_HEARTBEAT, LV_ACCEPT, FV_LEAVE))
+            message.platoonId = j.at(PLATOON_ID).get<VehicleId>();
+
+        if (messageTypes::contains(messageType, LV_BROADCAST))
         {
             message.platoonSpeed = j.at(PLATOON_SPEED).get<PlatoonSpeed>();
             message.innerPlatoonDistance = j.at(INNER_PLATOON_DISTANCE).get<InnerPlatoonDistance>();
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Protocol.h b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Protocol.h
index 898ce0080055a78dad067fa62197610569189a42..875dc8ae05116252b3ab4e4d0c510ddb71a7972c 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Protocol.h
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Protocol.h
@@ -22,6 +22,8 @@ using InnerPlatoonDistance = float;
 
 struct PlatoonConfig
 {
+    static constexpr float EPSILON{0.00001f};
+
     PlatoonConfig() : platoonSpeed(0.0f), innerPlatoonDistance(0.0f)
     {}
 
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Vehicle.h b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Vehicle.h
index f29de7d184415bd3ae476ad8283c875f7c33b93c..71a2358fa67735f800ca82d3f78f873ad8c8ed43 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Vehicle.h
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/include/PlatoonProtocolLib/Vehicle.h
@@ -70,6 +70,7 @@ protected:
     };
 
     static const PlatoonConfig DEFAULT_PLATOON_CONFIG;
+    static const Callback DEFAULT_CALLBACK;
 
     const NetworkInfo myInfo;
     PlatoonId platoonId;
@@ -79,8 +80,8 @@ protected:
     networking::Networking & net;
     networking::message::DatagramReceiver<PlatoonMessage>::Ptr receiver;
     networking::message::DatagramSender<PlatoonMessage>::Ptr sender;
-    Callback onRunningPlatoonCallback;
-    Callback onLeavingPlatoonCallback;
+    Callback onRunningPlatoonCallback{DEFAULT_CALLBACK};
+    Callback onLeavingPlatoonCallback{DEFAULT_CALLBACK};
 
     virtual void doCreatePlatoon();
 
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/src/FollowerVehicle.cpp b/modules/catkin_ws/src/PlatoonProtocolLib/src/FollowerVehicle.cpp
index 78f1027e1573d36cd745a92a56dc8b56c0851e28..130ae81a4394e22f5753f2d9ccbca71d87b0df36 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/src/FollowerVehicle.cpp
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/src/FollowerVehicle.cpp
@@ -67,9 +67,9 @@ void FollowerVehicle::doLeavePlatoon()
     if (state == State::IDLE)
         return;
 
-    Vehicle::doLeavePlatoon();
-
     sendLeavePlatoonMessage();
+
+    Vehicle::doLeavePlatoon();
 }
 
 void FollowerVehicle::receiveMessage(const PlatoonMessage & message)
@@ -174,6 +174,8 @@ void FollowerVehicle::receiveBroadcast(const PlatoonMessage & broadcast)
 
 void FollowerVehicle::receiveResponse(const PlatoonMessage & response)
 {
+    log("response\n");
+
     if (state != State::CREATING_PLATOON ||
         response.dstVehicle != myInfo.vehicleId)
         return;
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/src/LeaderVehicle.cpp b/modules/catkin_ws/src/PlatoonProtocolLib/src/LeaderVehicle.cpp
index 6b6796bdcbf0e366eb302c9de307a1487ebfa214..a908df36624545a834fa07b938efbd1cf556e45a 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/src/LeaderVehicle.cpp
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/src/LeaderVehicle.cpp
@@ -194,6 +194,8 @@ void LeaderVehicle::receiveLeavePlatoonMessage(const PlatoonMessage & message)
 
 void LeaderVehicle::startBroadcasting()
 {
+    sendBroadcastMessage(); // Send broadcast immediately.
+
     auto self = shared_from_this();
     broadcastTimer->startPeriodicTimeout(
         BROADCAST_INTERVAL,
@@ -202,15 +204,7 @@ void LeaderVehicle::startBroadcasting()
             if (self->state != State::RUNNING_PLATOON)
                 return;
 
-            PlatoonConfig configCopy = self->platoonConfig.load();
-
-            // Send the broadcast message and forget about it.
-            self->sendMessage(PlatoonMessage::broadcastMessage(
-                self->myInfo.vehicleId,
-                self->platoonId,
-                configCopy.platoonSpeed,
-                configCopy.innerPlatoonDistance,
-                utils::keys(self->followers)), BROADCAST_INTERVAL);
+            self->sendBroadcastMessage();
         });
 }
 
@@ -219,6 +213,18 @@ void LeaderVehicle::stopBroadcasting()
     broadcastTimer->stop();
 }
 
+void LeaderVehicle::sendBroadcastMessage()
+{
+    PlatoonConfig configCopy = platoonConfig.load();
+    // Send the broadcast message and forget about it.
+    sendMessage(PlatoonMessage::broadcastMessage(
+        myInfo.vehicleId,
+        platoonId,
+        configCopy.platoonSpeed,
+        configCopy.innerPlatoonDistance,
+        utils::keys(followers)), BROADCAST_INTERVAL);
+}
+
 void LeaderVehicle::removeAllFollowers()
 {
     utils::foreachInMap(followers,
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/src/Protocol.cpp b/modules/catkin_ws/src/PlatoonProtocolLib/src/Protocol.cpp
index 3974d9b7d16a853bd24ef9f4d928d003834808a7..b53047c8b46ab3371981de8c435e80da124d592a 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/src/Protocol.cpp
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/src/Protocol.cpp
@@ -3,13 +3,15 @@
 //
 
 #include "../include/PlatoonProtocolLib/Protocol.h"
+#include <cmath>
 
 namespace platoonProtocol
 {
 
 bool operator==(const PlatoonConfig & lhs, const PlatoonConfig & rhs) noexcept
 {
-    return lhs.platoonSpeed == rhs.platoonSpeed && lhs.innerPlatoonDistance == rhs.innerPlatoonDistance;
+    return std::fabs(lhs.platoonSpeed - rhs.platoonSpeed) < PlatoonConfig::EPSILON &&
+           std::fabs(lhs.innerPlatoonDistance - rhs.innerPlatoonDistance) < PlatoonConfig::EPSILON;
 }
 
 bool operator!=(const PlatoonConfig & lhs, const PlatoonConfig & rhs) noexcept
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/src/Vehicle.cpp b/modules/catkin_ws/src/PlatoonProtocolLib/src/Vehicle.cpp
index 5d80ec68146edfa3ef54b6fcf867c9df64444280..4c1c1eb5805375ec6f8e3ea6018a6fdad4f9a352 100644
--- a/modules/catkin_ws/src/PlatoonProtocolLib/src/Vehicle.cpp
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/src/Vehicle.cpp
@@ -12,6 +12,8 @@ namespace platoonProtocol
 {
 
 constexpr std::uint16_t Vehicle::UDP_PORT;
+const Vehicle::Callback Vehicle::DEFAULT_CALLBACK = []
+{};
 const PlatoonConfig Vehicle::DEFAULT_PLATOON_CONFIG{0.0f, 0.0f};
 
 Vehicle::Vehicle(networking::Networking & net,
diff --git a/modules/catkin_ws/src/PlatoonProtocolLib/test/TestScenarios.cpp b/modules/catkin_ws/src/PlatoonProtocolLib/test/TestScenarios.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9fe2c35c785161d2cf85ed60ed09578d31e5cfc9
--- /dev/null
+++ b/modules/catkin_ws/src/PlatoonProtocolLib/test/TestScenarios.cpp
@@ -0,0 +1,415 @@
+//
+// Created by philipp on 12.04.18.
+//
+
+#include "../include/PlatoonProtocolLib/Protocol.h"
+#include "../include/PlatoonProtocolLib/FollowerVehicle.h"
+#include "../include/PlatoonProtocolLib/LeaderVehicle.h"
+#include "NetworkingLib/Networking.h"
+#include <chrono>
+
+namespace platoonProtocol
+{
+
+networking::Networking net;
+const std::string broadcastAddress = "10.255.255.255";
+auto leader = LeaderVehicle::create(net, NetworkInfo{1, broadcastAddress});
+auto follower1 = FollowerVehicle::create(net, NetworkInfo{2, broadcastAddress});
+auto follower2 = FollowerVehicle::create(net, NetworkInfo{3, broadcastAddress});
+
+using namespace std::chrono_literals;
+const auto DEFAULT_WAIT_TOLERANCE = 30s;
+const auto TIMEOUT_TOLERANCE = 10ms;
+
+const PlatoonConfig TEST_CONFIG{1.0f, 10.0f};
+
+networking::time::Duration abs(networking::time::Duration d)
+{
+    return d >= d.zero() ? d : -d;
+}
+
+bool waitUntil(networking::time::Duration maxWaitDuration, const std::function<bool()> & condition)
+{
+    auto startTime = networking::time::now();
+    while (!condition() && networking::time::now() - startTime < maxWaitDuration);
+    return condition() && networking::time::now() - startTime < maxWaitDuration;
+}
+
+// -------------------
+// Test 1
+// -------------------
+
+bool test1Leader()
+{
+    leader->setPlatoonConfig(TEST_CONFIG);
+    leader->createPlatoon();
+    return waitUntil(DEFAULT_WAIT_TOLERANCE, [&]
+    { return leader->isPlatoonRunning(); });
+}
+
+bool test1Follower1()
+{
+    follower1->createPlatoon();
+    return waitUntil(DEFAULT_WAIT_TOLERANCE, [&]
+    { return follower1->isPlatoonRunning(); });
+}
+
+bool test1Follower2()
+{
+    return true;
+}
+
+// -------------------
+// Test 2
+// -------------------
+
+bool test2Leader()
+{
+    if (!test1Leader())
+        return false;
+    // Give some time to send the first broadcast message.
+    std::this_thread::sleep_for(1s);
+    return true;
+}
+
+bool test2Follower1()
+{
+    follower1->createPlatoon();
+    return waitUntil(DEFAULT_WAIT_TOLERANCE, [&]
+    {
+        return follower1->isPlatoonRunning() &&
+               follower1->getPlatoonConfig() == TEST_CONFIG;
+    });
+}
+
+bool test2Follower2()
+{
+    return true;
+}
+
+// -------------------
+// Test 3
+// -------------------
+
+bool test3Leader()
+{
+    if (!test1Leader())
+        return false;
+
+    // Wait a little bit so the first config can be send.
+    std::this_thread::sleep_for(30ms);
+
+    for (std::size_t i = 1; i <= 9; i++)
+    {
+        leader->setPlatoonConfig(PlatoonConfig{TEST_CONFIG.platoonSpeed + i * 0.1f,
+                                               TEST_CONFIG.innerPlatoonDistance + i});
+        std::this_thread::sleep_for(LeaderVehicle::BROADCAST_INTERVAL);
+    }
+
+    // Give some time to send the last broadcast message.
+    std::this_thread::sleep_for(50ms);
+    return true;
+}
+
+bool test3Follower1()
+{
+    std::atomic<bool> running{true};
+    std::atomic<bool> success{false};
+    auto expectedConfig = TEST_CONFIG;
+    auto lastConfigTime = networking::time::TimePoint::min();
+    std::size_t numConfigsReceived = 0;
+
+    follower1->setOnPlatoonConfigUpdatedCallback(
+        [&]
+        {
+            auto now = networking::time::now();
+            auto deltaTime = now - lastConfigTime;
+            lastConfigTime = now;
+
+            if (follower1->getPlatoonConfig() != expectedConfig)
+            {
+                success = false;
+                running = false;
+                return;
+            }
+
+            if (numConfigsReceived > 0 &&
+                abs(deltaTime - LeaderVehicle::BROADCAST_INTERVAL) > TIMEOUT_TOLERANCE)
+            {
+                success = false;
+                running = false;
+                return;
+            }
+
+            expectedConfig.platoonSpeed += 0.1f;
+            expectedConfig.innerPlatoonDistance += 1.0f;
+            numConfigsReceived++;
+            if (numConfigsReceived >= 10)
+            {
+                success = true;
+                running = false;
+            }
+        });
+
+    follower1->createPlatoon();
+
+    return waitUntil(DEFAULT_WAIT_TOLERANCE, [&]
+    { return !running; }) && success;
+}
+
+bool test3Follower2()
+{
+    return true;
+}
+
+// -------------------
+// Test 4
+// -------------------
+
+bool test4Leader()
+{
+    if (!test1Leader())
+        return false;
+    std::this_thread::sleep_for(1s);
+    leader->stop();
+    return true;
+}
+
+bool test4Follower1()
+{
+    if (!test1Follower1())
+        return false;
+    return waitUntil(1s + FollowerVehicle::BROADCAST_TIMEOUT + TIMEOUT_TOLERANCE, [&]
+    { return !follower1->isPlatoonRunning(); });
+}
+
+bool test4Follower2()
+{
+    return true;
+}
+
+// -------------------
+// Test 5
+// -------------------
+
+bool test5Leader()
+{
+    if (!test1Leader())
+        return false;
+    return waitUntil(1s + LeaderVehicle::HEARTBEAT_TIMEOUT + TIMEOUT_TOLERANCE, [&]
+    { return !leader->isPlatoonRunning(); });
+}
+
+bool test5Follower1()
+{
+    if (!test1Follower1())
+        return false;
+    std::this_thread::sleep_for(1s);
+    follower1->stop();
+    return true;
+}
+
+bool test5Follower2()
+{
+    return true;
+}
+
+// -------------------
+// Test 6
+// -------------------
+
+bool test6Leader()
+{
+    if (!test1Leader())
+        return false;
+    return waitUntil(1100ms, [&]
+    { return !leader->isPlatoonRunning(); });
+}
+
+bool test6Follower1()
+{
+    if (!test1Follower1())
+        return false;
+    std::this_thread::sleep_for(1s);
+    follower1->leavePlatoon();
+    // Give some time to send leave platoon message.
+    std::this_thread::sleep_for(50ms);
+    return true;
+}
+
+bool test6Follower2()
+{
+    return true;
+}
+
+// -------------------
+// Test 7
+// -------------------
+
+bool test7Leader()
+{
+    return true;
+}
+
+bool test7Follower1()
+{
+    follower1->createPlatoon();
+    std::this_thread::sleep_for(5s);
+    return !follower1->isPlatoonRunning();
+}
+
+bool test7Follower2()
+{
+    follower2->createPlatoon();
+    std::this_thread::sleep_for(5s);
+    return !follower2->isPlatoonRunning();
+}
+
+// -------------------
+// Test 8
+// -------------------
+
+bool test8Leader()
+{
+    if (!test1Leader())
+        return false;
+    std::this_thread::sleep_for(100ms);
+    leader->setPlatoonConfig(PlatoonConfig{TEST_CONFIG.platoonSpeed + 0.1f, TEST_CONFIG.innerPlatoonDistance + 1.0f});
+    std::this_thread::sleep_for(100ms);
+    leader->setPlatoonConfig(PlatoonConfig{TEST_CONFIG.platoonSpeed + 0.2f, TEST_CONFIG.innerPlatoonDistance + 2.0f});
+    std::this_thread::sleep_for(100ms);
+    return true;
+}
+
+bool test8Followers(FollowerVehicle::Ptr follower)
+{
+    std::atomic<bool> success{false};
+    PlatoonConfig expectedConfig = TEST_CONFIG;
+    std::size_t numConfigsReceived = 0;
+    auto last = networking::time::now();
+
+    follower->setOnPlatoonConfigUpdatedCallback(
+        [&]
+        {
+            auto delta = networking::time::now() - last;
+            std::cout << "time passed [ms]: " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() << "\n";
+            std::cout << "ps: " << follower->getPlatoonConfig().platoonSpeed << "\n";
+            if (follower->getPlatoonConfig() != expectedConfig)
+            {
+                success = false;
+                return;
+            }
+
+            expectedConfig.platoonSpeed += 0.1f;
+            expectedConfig.innerPlatoonDistance += 1.0f;
+            numConfigsReceived++;
+            if (numConfigsReceived >= 3)
+                success = true;
+        });
+
+    follower->createPlatoon();
+
+    return waitUntil(DEFAULT_WAIT_TOLERANCE, [&]
+    { return success.load(); });
+}
+
+bool test8Follower1()
+{
+    return test8Followers(follower1);
+}
+
+bool test8Follower2()
+{
+    return test8Followers(follower2);
+}
+
+// -------------------
+// Test 9
+// -------------------
+
+bool test9Leader()
+{
+    if (!test1Leader())
+        return false;
+    std::this_thread::sleep_for(1100ms);
+    bool result = leader->isPlatoonRunning();
+    // Wait some time to signal that the platoon is still running.
+    std::this_thread::sleep_for(900ms);
+    return result;
+}
+
+bool test9Follower1()
+{
+    if (!test1Follower1())
+        return false;
+    std::this_thread::sleep_for(1s);
+    follower1->leavePlatoon();
+    // Give some time to send the leave platoon message.
+    std::this_thread::sleep_for(50ms);
+    return true;
+}
+
+bool test9Follower2()
+{
+    follower2->createPlatoon();
+    auto result = waitUntil(DEFAULT_WAIT_TOLERANCE, [&]
+    { return follower2->isPlatoonRunning(); });
+    if (!result)
+        return false;
+    std::this_thread::sleep_for(1100ms);
+    result = follower2->isPlatoonRunning();
+    // Wait some time to signal that the platoon is still running.
+    std::this_thread::sleep_for(900ms);
+    return result;
+}
+
+bool runTestScenario(int testNum, int testRole)
+{
+    using Test = bool (*)();
+    Test tests[9][3] = {
+        {test1Leader, test1Follower1, test1Follower2},
+        {test2Leader, test2Follower1, test2Follower2},
+        {test3Leader, test3Follower1, test3Follower2},
+        {test4Leader, test4Follower1, test4Follower2},
+        {test5Leader, test5Follower1, test5Follower2},
+        {test6Leader, test6Follower1, test6Follower2},
+        {test7Leader, test7Follower1, test7Follower2},
+        {test8Leader, test8Follower1, test8Follower2},
+        {test9Leader, test9Follower1, test9Follower2}
+    };
+    return tests[testNum - 1][testRole - 1]();
+}
+
+}
+
+int main(int argc, char ** argv)
+{
+    if (argc < 3)
+    {
+        std::cerr << "Too few arguments!\n";
+        return -1;
+    }
+
+    int testNum = atoi(argv[1]);
+    int testRole = atoi(argv[2]);
+
+    if (testNum < 1 || testNum > 9)
+    {
+        std::cerr << "First argument must be between 1 and 9!\n";
+        return -1;
+    }
+
+    if (testRole < 1 || testRole > 3)
+    {
+        std::cerr << "Second argument must be between 1 and 3";
+        return -1;
+    }
+
+    auto result = platoonProtocol::runTestScenario(testNum, testRole);
+    if (result)
+        std::cout << "1";
+    else
+        std::cout << "0";
+
+    return 0;
+}
\ No newline at end of file
diff --git a/modules/clean.sh b/modules/clean.sh
new file mode 100644
index 0000000000000000000000000000000000000000..508f1af5e6f8fd6066ccd140d4ce440f06e71d45
--- /dev/null
+++ b/modules/clean.sh
@@ -0,0 +1,8 @@
+rm -r catkin_ws/install
+rm -r catkin_ws/build
+rm -r catkin_ws/devel
+rm -r catkin_ws/src/car/cmake-build-debug
+rm -r catkin_ws/src/NetworkingLib/cmake-build-debug
+rm -r catkin_ws/src/PlatoonProtocolLib/cmake-build-debug
+rm -r catkin_ws/src/PC2CarLib/cmake-build-debug
+rm -r catkin_ws/src/PC/cmake-build-debug