diff --git a/.gitattributes b/.gitattributes
index 2d407a27d836e314a93305bcefd826e41d051904..ea9df6aed0c1507dc691b9d30174895c1bba1bab 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1769,6 +1769,7 @@ LCS/WinCCWrapper/include/CMakeLists.txt -text
 LCS/WinCCWrapper/include/WinCCManager.h -text
 LCS/WinCCWrapper/include/WinCCResources.h -text
 LCS/WinCCWrapper/include/WinCCWrapper.h -text
+LCS/WinCCWrapper/include/exceptions.h -text
 LCS/WinCCWrapper/src/CMakeLists.txt -text
 LCS/WinCCWrapper/src/ConnectWaitForAnswer.cc -text
 LCS/WinCCWrapper/src/ConnectWaitForAnswer.h -text
@@ -1777,8 +1778,10 @@ LCS/WinCCWrapper/src/WinCCResources.cc -text
 LCS/WinCCWrapper/src/WinCCWrapper.cc -text
 LCS/WinCCWrapper/src/WinCCWrapper_boost_python.cc -text
 LCS/WinCCWrapper/src/__init__.py -text
+LCS/WinCCWrapper/src/exceptions.h -text
 LCS/WinCCWrapper/test/CMakeLists.txt -text
 LCS/WinCCWrapper/test/WinCCGet.cc -text
+LCS/WinCCWrapper/test/WinCCQuery.cc -text
 LCS/WinCCWrapper/test/WinCCSet.cc -text
 LCS/WinCCWrapper/test/mock.py -text
 LCS/doc/package.dox -text
@@ -1835,8 +1838,6 @@ LCU/Maintenance/DBInterface/monitoringdb/__init__.py -text
 LCU/Maintenance/DBInterface/monitoringdb/admin.py -text
 LCU/Maintenance/DBInterface/monitoringdb/apps.py -text
 LCU/Maintenance/DBInterface/monitoringdb/exceptions.py -text
-LCU/Maintenance/DBInterface/monitoringdb/migrations/0001_initial.py -text
-LCU/Maintenance/DBInterface/monitoringdb/migrations/0002_winccantennastatuses.py -text
 LCU/Maintenance/DBInterface/monitoringdb/migrations/__init__.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/__init__.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/component.py -text
@@ -1846,8 +1847,10 @@ LCU/Maintenance/DBInterface/monitoringdb/models/element_error.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/fixed_types.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/log.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py -text
+LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/station.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/station_test.py -text
+LCU/Maintenance/DBInterface/monitoringdb/models/test.py -text
 LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py -text
 LCU/Maintenance/DBInterface/monitoringdb/pagination.py -text
 LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py -text
@@ -4234,6 +4237,28 @@ MAC/Tools/jtagctl/rsp_infraCnn.Bin -text svneol=unset#unset
 MAC/Tools/jtagctl/rsp_interApl.Bin -text svneol=unset#unset
 MAC/Tools/jtagctl/rsp_interCnn.Bin -text svneol=unset#unset
 MAC/Tools/jtagctl/testdata.bin -text svneol=unset#unset
+MAC/WinCCDBBridge/CMakeLists.txt -text
+MAC/WinCCDBBridge/DB.ini -text
+MAC/WinCCDBBridge/Docker/Dockerfile -text
+MAC/WinCCDBBridge/Docker/LOFAR.tar.gz -text svneol=unset#application/x-gzip
+MAC/WinCCDBBridge/Docker/run.sh -text
+MAC/WinCCDBBridge/etc/CMakeLists.txt -text
+MAC/WinCCDBBridge/etc/DB.ini -text
+MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xml -text
+MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xsd -text
+MAC/WinCCDBBridge/src/CMakeLists.txt -text
+MAC/WinCCDBBridge/src/DatapointAttribute.cpp -text
+MAC/WinCCDBBridge/src/DatapointAttribute.hpp -text
+MAC/WinCCDBBridge/src/DatapointIndex.cpp -text
+MAC/WinCCDBBridge/src/DatapointIndex.hpp -text
+MAC/WinCCDBBridge/src/DatapointRule.cpp -text
+MAC/WinCCDBBridge/src/DatapointRule.hpp -text
+MAC/WinCCDBBridge/src/Program.cpp -text
+MAC/WinCCDBBridge/src/Program.hpp -text
+MAC/WinCCDBBridge/src/main.cpp -text
+MAC/WinCCDBBridge/test/CMakeLists.txt -text
+MAC/WinCCDBBridge/test/t_DatapointAttribute.cpp -text
+MAC/WinCCDBBridge/test/t_DatapointIndex.cpp -text
 MAC/WinCCPublisher/CMakeLists.txt -text
 MAC/WinCCPublisher/config/WinCCPublisher.conf -text
 MAC/WinCCPublisher/doc/package.dox -text
diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake
index 242feb2178656fb7cb7544af75ed3286fafa0e0f..59dbec37fc4c3dd1c74b516bc9c8f6ca3e19d17a 100644
--- a/CMake/LofarPackageList.cmake
+++ b/CMake/LofarPackageList.cmake
@@ -1,7 +1,7 @@
 # - Create for each LOFAR package a variable containing the absolute path to
 # its source directory. 
 #
-# Generated by gen_LofarPackageList_cmake.sh at Tue Feb 26 11:44:22 CET 2019
+# Generated by gen_LofarPackageList_cmake.sh at Wed May 29 15:45:16 CEST 2019
 #
 #                      ---- DO NOT EDIT ----
 #
@@ -148,6 +148,8 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED)
   set(OTDB_Comps_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Deployment/data/OTDB)
   set(StaticMetaData_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Deployment/data/StaticMetaData)
   set(WinCCPublisher_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/WinCCPublisher)
+  set(WinCCREST_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/WinCCREST)
+  set(WinCCDBBridge_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/WinCCDBBridge)
   set(TaskManagement_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/Services/TaskManagement)
   set(TBB_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/TBB)
   set(GCFTM_SOURCE_DIR ${CMAKE_SOURCE_DIR}/MAC/GCF/TM)
diff --git a/LCS/WinCCWrapper/include/CMakeLists.txt b/LCS/WinCCWrapper/include/CMakeLists.txt
index 9341a3420be5b55ea0c821bc02652445231d2430..48366da002dc0da6ca064036ffaf96e0f46532a4 100644
--- a/LCS/WinCCWrapper/include/CMakeLists.txt
+++ b/LCS/WinCCWrapper/include/CMakeLists.txt
@@ -1,5 +1,5 @@
 # Create symbolic link to include directory.
-execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink 
+execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink
   ${CMAKE_CURRENT_SOURCE_DIR}
   ${CMAKE_BINARY_DIR}/include/${PACKAGE_NAME})
 
@@ -8,4 +8,5 @@ install(FILES
   WinCCManager.h
   WinCCResources.h
   WinCCWrapper.h
+  exceptions.h
   DESTINATION include/${PACKAGE_NAME})
diff --git a/LCS/WinCCWrapper/include/WinCCManager.h b/LCS/WinCCWrapper/include/WinCCManager.h
index 69e70cbb947cf731c2a3bbf24224aa4ed7c16b5c..c29d37cefaad6c4d20936064db3ae4c074cf68b4 100644
--- a/LCS/WinCCWrapper/include/WinCCManager.h
+++ b/LCS/WinCCWrapper/include/WinCCManager.h
@@ -31,6 +31,7 @@
 #include <Variable.hxx>
 
 #include <boost/python.hpp>
+#include <boost/any.hpp>
 
 namespace LOFAR {
     namespace WINCCWRAPPER {
@@ -41,7 +42,6 @@ class WinCCManager : public Manager
 public:
     //! default constructor
     WinCCManager();
-
     //! run the manager, connect to the running wincc instance.
     void run();
     //! exit the manager, disconnect from the running wincc instance.
@@ -49,8 +49,15 @@ public:
 
     //! subscribe for changes on this list of datapoints. Whenever any of these datapoints changes value, the callback function from set_connect_datapoints_callback is called with the changed datapoint name and value.
     void connect_datapoints(const std::vector<std::string> &data_points);
+    //! subscribe for changes on this list of datapoints. Whenever any of these datapoints change all the values from these datapoints gets returned in one call.
+    void connect_datapoints_multiple(const std::vector<std::string> &data_points);
+    //! unsubscribe for changes on this list of datapoints.
+    void disconnect_datapoints(const std::vector<std::string> &data_points);
+    //! provide your callback function which is called whenever any of the connected datapoints is changed.
+    void set_connect_datapoints_callback(std::function<void(std::string name, std::string value)> callback_functor) {callback = callback_functor;};
     //! provide your callback function which is called whenever any of the connected datapoints is changed.
-    void set_connect_datapoints_callback(std::function<void(std::string name, std::string value)> callback) {callback = callback;};
+    void set_connect_datapoints_callback(std::function<void(std::map<std::string, std::string>)> callback_functor) {callback_multi = callback_functor;};
+
 
     //! set the datapoint with given name to the given value, returns true upon success.
     /*! set the datapoint with given name to the given value
@@ -75,7 +82,10 @@ public:
     bool get_datapoint(const std::string &name, boost::python::list &value);
     //! get the datapoint with the given name and return it's std::vector<int> value in parameter value. returns true upon success.
     bool get_datapoint(const std::string &name, std::vector<int> &value);
-
+    //! get the datapoint with a any type
+    bool get_datapoint_any(const std::string &datapoint_name, boost::any &value);
+    //! set the datapoint with a any type
+    bool set_datapoint_any(const std::string &datapoint_name, const boost::any &value);
     //! mark the datapoint with given name valid. returns true upon success.
     bool set_datapoint_valid(const std::string &name) { return set_datapoint_validity(name, true, nullptr); }
     //! mark the datapoint with given name invalid. returns true upon success.
@@ -83,24 +93,32 @@ public:
     //! set the datapoint with given name valid/invalid. returns true upon success.
     bool set_datapoint_validity(const std::string &name, bool validity, const Variable *value=nullptr);
 
+    bool get_query(const std::string &query, std::vector<std::vector<std::string>> & result);
     //! handle signals to exit nicely
     virtual void signalHandler(int sig);
+
+    void wait_for_event(long sec, long microSec);
+
 private:
     void init();
 
     friend class ConnectWaitForAnswer;
 
     void handle_hotlink(const std::string &name, const std::string &value);
+    void handle_hotlink(const std::map<std::string, std::string> &);
+
     void handle_get(const std::string &name, Variable *&value);
 
     bool request_datapoint(const std::string &name);
 
+    bool request_query_result(const std::string &query, PVSSulong & identifier);
     template <typename Tval>
     bool _get_datapoint(const std::string &name, Tval &value);
 
     template <typename Tval>
     bool _get_datapoint(const std::string &name, Tval *value);
 
+
     bool get_datapoint_variable(const std::string &name, Variable *&value);
 
     bool has_received_variable(const std::string &name);
@@ -109,6 +127,7 @@ private:
 
     volatile static bool doExit;
     std::function<void(std::string name, std::string value)> callback;
+    std::function<void(std::map<std::string, std::string>)> callback_multi;
     std::mutex mtx;
     std::condition_variable cv;
     std::map<std::string, Variable *> values;
diff --git a/LCS/WinCCWrapper/include/WinCCResources.h b/LCS/WinCCWrapper/include/WinCCResources.h
index 552e9fcf771938a27fdad189e915440db5f91500..b387368eb42f7c379894621960b07de3db0ea905 100644
--- a/LCS/WinCCWrapper/include/WinCCResources.h
+++ b/LCS/WinCCWrapper/include/WinCCResources.h
@@ -27,9 +27,12 @@ namespace LOFAR {
 class WinCCResources
 {
 public:
-    WinCCResources(const std::string &projec_name);
+    WinCCResources(const std::string &project_name);
+
+    WinCCResources(const std::string &program_name, const std::string &project_name, const int num);
 private:
-    void init(const std::string &projec_name);
+    void init(const std::string &project_name);
+    void init(const std::string &program_name, const std::string &project_name, const int num);
 };
 
     } // namespace WINCCWRAPPER
diff --git a/LCS/WinCCWrapper/include/WinCCWrapper.h b/LCS/WinCCWrapper/include/WinCCWrapper.h
index 26fa060d6064ee4fcff3f620e44b86a90c52327a..4e57aa17166f24a9fc34c283d9dce20047f61ee1 100644
--- a/LCS/WinCCWrapper/include/WinCCWrapper.h
+++ b/LCS/WinCCWrapper/include/WinCCWrapper.h
@@ -21,6 +21,7 @@
 #define WINCCSERVICES_WINCC_WRAPPER_H
 
 #include <string>
+#include <boost/variant.hpp>
 #include <vector>
 #include <ctime>
 #include <functional>
@@ -29,6 +30,7 @@
 #include "WinCCManager.h"
 
 #include <boost/python.hpp>
+#include <boost/any.hpp>
 
 namespace LOFAR {
     namespace WINCCWRAPPER {
@@ -40,6 +42,9 @@ class WinCCWrapper
 public:
     //! default constructor
     WinCCWrapper(const std::string &project_name);
+
+    //! default constructor
+    WinCCWrapper(const std::string &program_name, const std::string &project_name, const int num);
     //! disconnect from the running wincc instance.
     void exit();
     //! connect to the running wincc instance.
@@ -47,14 +52,21 @@ public:
 
     //! subscribe for changes on this list of datapoints. Whenever any of these datapoints changes value, the callback function from set_connect_datapoints_callback is called with the changed datapoint name and value.
     void connect_datapoints(const std::vector<std::string> &data_points);
+    //! subscribe for changes on this list of datapoints. Whenever any of these datapoints changes value, the callback function from set_connect_datapoints_callback is called with all the specified values.
+    void connect_datapoints_multi(const std::vector<std::string> &data_points);
+
+    //! unsubscribe for changes on the given list of datapoints.
+    void disconnect_datapoints(const std::vector<std::string> &data_points);
     //! provide your callback function which is called whenever any of the connected datapoints is changed.
     void set_connect_datapoints_callback(std::function<void(std::string name, std::string value)> callback);
+    //! provide your callback function which is called whenever any of the connected datapoints is changed. It returns all the specified datapoints even if only one changes.
+    void set_connect_datapoints_callback(std::function<void(std::map<std::string, std::string>)> callback);
 
     //! set the datapoint with given name to the given int value, mark it valid/invalid, returns true upon success.
     bool set_datapoint(const std::string &name, int value, bool valid=true);
     //! set the datapoint with given name to the given boost::python::list value, mark it valid/invalid, returns true upon success.
     bool set_datapoint(const std::string &name, boost::python::list &value, bool valid=true);
-    //! set the datapoint with given name to the given boost::python::tuple value, mark it valid/invalid, returns true upon success.  
+    //! set the datapoint with given name to the given boost::python::tuple value, mark it valid/invalid, returns true upon success.
     bool set_datapoint(const std::string &name, boost::python::tuple &value, bool valid=true);
     //! set the datapoint with given name to the given std::vector<int> value, mark it valid/invalid, returns true upon success.
     bool set_datapoint(const std::string &name, std::vector<int> &value, bool valid=true);
@@ -70,11 +82,14 @@ public:
     //! set the datapoint with given name to the given time_t value, mark it valid/invalid, returns true upon success.
     bool set_datapoint_time(const std::string &name, time_t value, bool valid=true);
 
+    bool set_datapoint_any(const std::string &name, boost::any value);
     //! mark the datapoint with given name valid.
     bool set_datapoint_valid(const std::string &name);
     //! mark the datapoint with given name invalid.
     bool set_datapoint_invalid(const std::string &name);
 
+    //! provide the boost::any for the given datapoint name
+    boost::any get_datapoint_any (const std::string &name);
     //! get the datapoint with the given name and return it as an int value if possible, otherwise an exception is raised.
     int get_datapoint_int(const std::string &name);
     //! get the datapoint with the given name and return it as a long value if possible, otherwise an exception is raised.
@@ -88,11 +103,17 @@ public:
     //! get the datapoint with the given name and return it as a time_t value if possible, otherwise an exception is raised.
     time_t get_datapoint_time(const std::string &name);
     //! get the datapoint with the given name and return it as a boost::python::list value if possible, otherwise an exception is raised.
-    boost::python::list get_datapoint_list(const std::string &name); 
+    boost::python::list get_datapoint_list(const std::string &name);
+    //! get the datapoint last set time
+    time_t get_datapoint_set_time(const std::string &name);
     //! get the datapoint with the given name and return it as a std::vector<int> value if possible, otherwise an exception is raised.
     //! this method is used in the WinCCGet test
     std::vector<int> get_datapoint_vector(const std::string &name);
+    //! get the datapoint with the given name and return it as a std::vector<std::string> value.
+    std::vector<std::string> get_datapoint_vector_string(const std::string &name);
 
+    std::string get_formatted_datapoint(const std::string &name);
+    void wait_for_event(long sec, long microSec);
 private:
     // get_datapoint
     template <typename T>
diff --git a/LCS/WinCCWrapper/include/exceptions.h b/LCS/WinCCWrapper/include/exceptions.h
new file mode 100644
index 0000000000000000000000000000000000000000..998ab30809e845243ed043bdc8159bbb7cd7a5bd
--- /dev/null
+++ b/LCS/WinCCWrapper/include/exceptions.h
@@ -0,0 +1,23 @@
+#ifndef WINCC_EXCEPTIONS
+#define WINCC_EXCEPTIONS
+
+namespace LOFAR{
+namespace WINCCWRAPPER{
+
+class DatapointNameNotFound : public std::exception
+{
+private:
+    std::string message;
+public:
+    DatapointNameNotFound(const std::string & datapointName):
+        message{"Datapoint " + datapointName + " not found"}{}
+	const char * what () const throw ()
+    {
+    	return message.c_str();
+    }
+};
+
+}
+}
+
+#endif
diff --git a/LCS/WinCCWrapper/src/ConnectWaitForAnswer.cc b/LCS/WinCCWrapper/src/ConnectWaitForAnswer.cc
index 767ea576d761ca1ee4e99202ba2f07af82c3ba11..b35c3cfe0396b49532f141ca1161d76e17e9a9aa 100644
--- a/LCS/WinCCWrapper/src/ConnectWaitForAnswer.cc
+++ b/LCS/WinCCWrapper/src/ConnectWaitForAnswer.cc
@@ -25,13 +25,22 @@ namespace LOFAR {
 
 using namespace std;
 
+ConnectWaitForAnswer::ConnectWaitForAnswer(const bool handle_multi): HotLinkWaitForAnswer{}, multi{handle_multi}{
+
+}
+
+ConnectWaitForAnswer::ConnectWaitForAnswer(): HotLinkWaitForAnswer{}{
+
+}
+
 void ConnectWaitForAnswer::hotLinkCallBack(DpMsgAnswer &answer)
 {
+    const std::string answer_id{std::to_string(answer.getAnswerId())};
     for (AnswerGroup *grpPtr = answer.getFirstGroup(); grpPtr; grpPtr = answer.getNextGroup())
     {
         if (grpPtr->getError())
         {
-            cout << "Error!" << endl;
+            cout <<grpPtr->getError()->toString()<< endl;
         }
         else
         {
@@ -42,6 +51,8 @@ void ConnectWaitForAnswer::hotLinkCallBack(DpMsgAnswer &answer)
                 if(varPtr)
                 {
                     string name = get_datapoint_name(item);
+                    if(name.compare("") == 0) name += answer_id;
+
                     Variable *value = varPtr->clone(); //WinCCManager should delete cloned pointer
 
     	            ((WinCCManager *) Manager::getManPtr())->handle_get(name, value);
@@ -52,6 +63,14 @@ void ConnectWaitForAnswer::hotLinkCallBack(DpMsgAnswer &answer)
 }
 
 void ConnectWaitForAnswer::hotLinkCallBack(DpHLGroup &group)
+{
+    if(multi){
+        handle_multi(group);
+    }else{
+        handle_one_by_one(group);
+    }
+}
+void ConnectWaitForAnswer::handle_one_by_one(DpHLGroup &group)
 {
     for (DpVCItem *item = group.getFirstItem(); item; item = group.getNextItem())
     {
@@ -59,6 +78,25 @@ void ConnectWaitForAnswer::hotLinkCallBack(DpHLGroup &group)
     }
 }
 
+void ConnectWaitForAnswer::handle_multi(DpHLGroup &group)
+{
+
+    std::map<std::string, std::string> values;
+    for (DpVCItem *item = group.getFirstItem(); item; item = group.getNextItem())
+    {
+        Variable *varPtr = item->getValuePtr();
+
+        if (varPtr){
+            const string name = get_datapoint_name(item);
+            const string value = varPtr->formatValue().c_str();
+
+            values[name] = value;
+        }
+    }
+    ((WinCCManager *) Manager::getManPtr())->handle_hotlink(values);
+}
+
+
 void ConnectWaitForAnswer::handle_group_item(const DpVCItem* const item)
 {
     Variable *varPtr = item->getValuePtr();
@@ -66,6 +104,7 @@ void ConnectWaitForAnswer::handle_group_item(const DpVCItem* const item)
     if (varPtr)
     {
         string name = get_datapoint_name(item);
+
         string value = varPtr->formatValue().c_str();
 
     	((WinCCManager *) Manager::getManPtr())->handle_hotlink(name, value);
diff --git a/LCS/WinCCWrapper/src/ConnectWaitForAnswer.h b/LCS/WinCCWrapper/src/ConnectWaitForAnswer.h
index 28dd3f0138a5c84e25374ade4ee2bea51ad9745a..dba485153cad7e3848ec381da58588b903639bb6 100644
--- a/LCS/WinCCWrapper/src/ConnectWaitForAnswer.h
+++ b/LCS/WinCCWrapper/src/ConnectWaitForAnswer.h
@@ -32,12 +32,18 @@ namespace LOFAR {
 class ConnectWaitForAnswer : public HotLinkWaitForAnswer
 {
 public:
+    ConnectWaitForAnswer();
+    ConnectWaitForAnswer(const bool handle_multi);
     using HotLinkWaitForAnswer::hotLinkCallBack;
     virtual void hotLinkCallBack(DpMsgAnswer &answer);
     virtual void hotLinkCallBack(DpHLGroup &group);
 private:
+    bool multi;
+    void handle_one_by_one(DpHLGroup &group);
+    void handle_multi(DpHLGroup &group);
     void handle_group_item(const DpVCItem* const item);
     std::string get_datapoint_name(const DpVCItem* const item);
+
 };
 
     } // namespace WINCCWRAPPER
diff --git a/LCS/WinCCWrapper/src/WinCCManager.cc b/LCS/WinCCWrapper/src/WinCCManager.cc
index 6aa6c34b42ef58025ee2c2e37199c6820783e7f0..68559dc23a634ba85da0bd413e07c482128afaf5 100644
--- a/LCS/WinCCWrapper/src/WinCCManager.cc
+++ b/LCS/WinCCWrapper/src/WinCCManager.cc
@@ -34,9 +34,12 @@
 #include <TextVar.hxx>
 #include <DynVar.hxx>
 #include <DynPtrArray.hxx>
+#include <Resources.hxx>
 #include <cassert>
 #include <vector>
 #include <boost/python.hpp>
+#include <boost/any.hpp>
+#include <exceptions.h>
 
 namespace LOFAR {
     namespace WINCCWRAPPER {
@@ -100,12 +103,77 @@ void WinCCManager::connect_datapoints(const std::vector<std::string> &dataPoints
     }
 }
 
+void WinCCManager::connect_datapoints_multiple(const std::vector<std::string> &dataPoints)
+{
+
+    DpIdentList  dpList;
+    for(vector<string>::const_iterator it = dataPoints.begin(); it != dataPoints.end(); it++)
+    {
+        DpIdentifier dpIdConnect;
+
+        if (Manager::getId(it->c_str(), dpIdConnect) == PVSS_FALSE)
+        {
+            // This name was unknown
+            ErrHdl::error(ErrClass::PRIO_SEVERE,
+                          ErrClass::ERR_PARAM,
+                          ErrClass::UNEXPECTEDSTATE,
+                          "PublishManager",
+                          "connect_datapoints",
+                          CharString("Datapoint ") + CharString(it->c_str()) +
+                          CharString(" missing"));
+        }
+        else
+        {
+            dpList.append(dpIdConnect);
+
+        }
+    }
+    // We give the dpConnect a nice naked pointer because it will delete it when the manager stops.
+    HotLinkWaitForAnswer* wait = new ConnectWaitForAnswer(true);
+    Manager::dpConnect(dpList, wait);
+}
+
+
+void WinCCManager::disconnect_datapoints(const std::vector<std::string> &dataPoints)
+{
+    for(vector<string>::const_iterator it = dataPoints.begin(); it != dataPoints.end(); it++)
+    {
+        DpIdentifier dpIdConnect;
+
+        if (Manager::getId(it->c_str(), dpIdConnect) == PVSS_FALSE)
+        {
+            // This name was unknown
+            ErrHdl::error(ErrClass::PRIO_SEVERE,
+                          ErrClass::ERR_PARAM,
+                          ErrClass::UNEXPECTEDSTATE,
+                          "PublishManager",
+                          "connect_datapoints",
+                          CharString("Datapoint ") + CharString(it->c_str()) +
+                          CharString(" missing"));
+        }
+        else
+        {
+            // We give the dpConnect a nice naked pointer because it will delete it when the manager stops.
+            HotLinkWaitForAnswer* wait = new ConnectWaitForAnswer();
+            Manager::dpDisconnect(dpIdConnect, wait);
+        }
+    }
+}
+
 bool WinCCManager::set_datapoint(const std::string &name, const Variable &value, bool valid)
 {
     //reuse the set_datapoint_validity, and explicitly set the value to the given value
     return set_datapoint_validity(name, valid, &value);
 }
 
+
+// request the query (async). is called by _get_query which makes it blocking (synchronous) by waiting for the answer.
+bool WinCCManager::request_query_result(const std::string &query, PVSSulong & identifier)
+{
+    HotLinkWaitForAnswer* wait = new ConnectWaitForAnswer();
+    return (PVSS_TRUE == Manager::dpQuery(query.c_str(), identifier, wait));
+}
+
 // request the datapoint (async). is called by _get_datapoint which makes it blocking (synchronous) by waiting for the answer.
 bool WinCCManager::request_datapoint(const std::string &name)
 {
@@ -333,6 +401,72 @@ Variable::ConvertResult convert(Variable *var, struct tm &value, Variable *&conv
     return cr;
 }
 
+boost::any convert_any(Variable * variable, boost::any & value){
+    Variable * converted_variable{nullptr};
+    switch(variable->isA()){
+        case VariableType::INTEGER_VAR:{
+            int casted_variable;
+            convert(variable, casted_variable, converted_variable);
+            value = casted_variable;
+
+            break;
+        }
+        case VariableType::FLOAT_VAR: {
+            float casted_variable;
+            convert(variable, casted_variable, converted_variable);
+            value = casted_variable;
+
+            break;
+        }
+        case VariableType::TEXT_VAR: {
+            std::string casted_variable;
+            convert(variable, casted_variable, converted_variable);
+            value = casted_variable;
+
+            break;
+        }
+        case VariableType::LONG_VAR:{
+            // Find the wincc guy who though this out
+            long casted_variable;
+            convert(variable, casted_variable, converted_variable);
+            value = casted_variable;
+
+            break;
+        }
+        case VariableType::BIT_VAR: {
+            bool casted_variable;
+            convert(variable, casted_variable, converted_variable);
+            value = casted_variable;
+
+            break;
+        }
+        case VariableType::TIME_VAR: {
+            time_t casted_variable;
+            convert(variable, casted_variable, converted_variable);
+            value = casted_variable;
+
+            break;
+        }
+        case VariableType::ANYTYPE_VAR: {
+            const AnyTypeVar * converted_var{static_cast<AnyTypeVar*>(variable)};
+            convert_any(converted_var->getVar(), value);
+            break;
+        }
+        case VariableType::DPIDENTIFIER_VAR: {
+            value = std::string{variable->formatValue().c_str()};
+            break;
+        }
+        default:
+            CharString value_str = variable->formatValue();
+            std::cerr<<value_str;
+            std::cerr<<"Datapoint type still not supported:  "<<std::hex<<variable->isA()<<std::endl;
+            value = std::string{value_str.c_str()};
+    }
+
+    if(converted_variable != nullptr) delete converted_variable;
+    return true;
+}
+
 //internal generic method to get the typed (Tval) value of a datapoint
 //used by the public strictly typed methods
 template <typename Tval>
@@ -355,6 +489,42 @@ bool WinCCManager::_get_datapoint(const std::string &name, Tval &value)
     return false;
 }
 
+bool convert_to_vector2(Variable * variable, std::vector<std::vector<std::string>> & result){
+    if(variable->isA(VariableType::DYN_VAR)){
+        const DynVar * rows{static_cast<DynVar *>(variable)};
+        for(DynPtrArrayIndex i=0; i < rows->getNumberOfItems(); i++){
+            const DynVar * row{static_cast<DynVar *>(rows->getAt(i))};
+            std::vector<std::string> * current_row{new std::vector<std::string>{}};
+            Variable * column{static_cast<DynVar *>(row->getNext())};
+            while(column){
+                current_row->push_back(column->formatValue().c_str());
+                column = static_cast<DynVar *>(row->getNext());
+            }
+            result.push_back(*current_row);
+            row = static_cast<DynVar *>(rows->getNext());
+        }
+        return true;
+    }
+    return false;
+}
+
+//internal generic method to query a set of WinCC data points. Since the type is not previously know
+bool WinCCManager::get_query(const std::string &query, std::vector<std::vector<std::string>> & result){
+    Variable * variable_value = nullptr;
+    PVSSulong identifier;
+    if(request_query_result(query, identifier))
+    {
+        std::string identifier_str{std::to_string(identifier)};
+        if(wait_for_received_variable(identifier_str, 1000)) {
+            if(get_received_variable(identifier_str, variable_value)) {
+                convert_to_vector2(variable_value, result);
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
 
 //below, a few strictly type methods for get_datapoint are defined
 //they just call the templated _get_datapoint, so why not just use the one and only templated method?
@@ -401,10 +571,11 @@ bool WinCCManager::get_datapoint(const std::string &name, std::vector<int> &valu
     return _get_datapoint(name, value);
 }
 
+
 bool WinCCManager::set_datapoint_validity(const std::string &name, bool validity, const Variable *value)
 {
     DpIdentifier dpId;
-    
+
     if (Manager::getId(name.c_str(), dpId) == PVSS_FALSE)
     {
         // This name was unknown.
@@ -472,6 +643,10 @@ void WinCCManager::run()
     }
 }
 
+void WinCCManager::wait_for_event(long sec, long microSec){
+    dispatch(sec, microSec);
+}
+
 void WinCCManager::exit()
 {
     Manager::exit(0);
@@ -485,6 +660,14 @@ void WinCCManager::handle_hotlink(const std::string &name, const std::string &va
     }
 }
 
+void WinCCManager::handle_hotlink(const std::map<std::string, std::string> & values)
+{
+    if(callback_multi)
+    {
+        callback_multi(values);
+    }
+}
+
 void WinCCManager::signalHandler(int sig)
 {
     if ((sig == SIGINT) || (sig == SIGTERM))
@@ -497,6 +680,81 @@ void WinCCManager::signalHandler(int sig)
     }
 }
 
+bool WinCCManager::set_datapoint_any(const std::string &datapoint_name, const boost::any &value){
+    Variable * variable{nullptr};
+
+    if(get_datapoint_variable(datapoint_name, variable)){
+        switch(variable->isA()){
+            case VariableType::INTEGER_VAR: {
+                    IntegerVar converted_value{boost::any_cast<int>(value)};
+                    set_datapoint(datapoint_name, converted_value);
+                    break;
+                }
+            case VariableType::FLOAT_VAR: {
+                    FloatVar converted_value{boost::any_cast<float>(value)};
+                    set_datapoint(datapoint_name, converted_value);
+                    break;
+                }
+            case VariableType::TEXT_VAR: {
+                    TextVar converted_value{CharString(boost::any_cast<std::string>(value).c_str())};
+                    set_datapoint(datapoint_name, converted_value);
+                    break;
+                }
+            case VariableType::LONG_VAR:{
+                    LongVar converted_value{boost::any_cast<long>(value)};
+                    set_datapoint(datapoint_name, converted_value);
+
+                    break;
+                }
+            case VariableType::BIT_VAR: {
+                    BitVar converted_value{boost::any_cast<bool>(value)};
+                    set_datapoint(datapoint_name, converted_value);
+
+                    break;
+                }
+            case VariableType::TIME_VAR: {
+                    TimeVar converted_value(0,0);
+                    converted_value.setSeconds(boost::any_cast<time_t>(value));
+                    set_datapoint(datapoint_name, converted_value);
+
+                    break;
+                }
+            default:
+                std::cerr<<"Datapoint type still not supported:  "<<std::hex<<variable->isA()<<std::endl;
+                return false;
+        }
+    }else{
+        return false;
+    }
+
+    return true;
+}
+
+bool WinCCManager::get_datapoint_any(const std::string &datapoint_name, boost::any & value){
+    // Yeah I know... WinCC API is getting a &* to allocate the memory space...
+    Variable * variable{nullptr};
+    bool returnValue{true};
+
+    if(!get_datapoint_variable(datapoint_name, variable)){
+        // This name was unknown.
+        ErrHdl::error(ErrClass::PRIO_SEVERE,
+                      ErrClass::ERR_PARAM,
+                      ErrClass::UNEXPECTEDSTATE,
+                      Resources::getProgName(),
+                      "get_datapoint",
+                      CharString("Datapoint ") + CharString(datapoint_name.c_str()) +
+                      CharString(" missing"));
+        throw DatapointNameNotFound{datapoint_name};
+    }
+
+    convert_any(variable, value);
+
+    if(variable != nullptr) delete variable;
+
+    return returnValue;
+
+}
+
     } // namespace WINCCWRAPPER
 } // namespace LOFAR
 
diff --git a/LCS/WinCCWrapper/src/WinCCResources.cc b/LCS/WinCCWrapper/src/WinCCResources.cc
index 5eb7dbf8d1e3935abb068f6aa843514ba7b1c84a..a35accb448a859744f62a74adeeb8755ad2bc5f7 100644
--- a/LCS/WinCCWrapper/src/WinCCResources.cc
+++ b/LCS/WinCCWrapper/src/WinCCResources.cc
@@ -31,11 +31,62 @@ WinCCResources::WinCCResources(const std::string &project_name)
     init(project_name);
 }
 
-void WinCCResources::init(const std::string & /*project_name*/)
+WinCCResources::WinCCResources(const::std::string &program_name, const std::string &project_name, const int num)
 {
-    // TODO set specific project instead of current project.
-    char* ownArgv[] = {(char *)"WinCCWrapper", (char *)"-currentproj", (char *)"-log", (char *)"+stderr"};
-    int ownArgc = sizeof ownArgv / sizeof ownArgv[0];
+    init(program_name, project_name, num);
+}
+
+
+void WinCCResources::init(const::std::string &program_name, const std::string & project_name, const int num)
+{
+    std::vector<std::string> args{program_name};
+
+    if(project_name.compare("-currentproj") == 0){
+        args.push_back("-currentproj");
+    }else{
+        args.push_back("-proj");
+        args.push_back(project_name);
+    }
+    args.push_back("-log");
+    args.push_back("+stderr");
+
+    args.push_back("-num");
+    args.push_back(std::to_string(num));
+
+
+    int ownArgc = args.size();
+
+    char * ownArgv[ownArgc];
+
+    for(int i=0; i<ownArgc; i++){
+        ownArgv[i] = const_cast<char *>(args.at(i).c_str());
+    }
+
+    Resources::init(ownArgc, ownArgv);
+}
+
+
+
+void WinCCResources::init(const std::string & project_name)
+{
+    std::vector<std::string> args{"WinCCWrapper"};
+
+    if(project_name.compare("-currentproj") == 0){
+        args.push_back("-currentproj");
+    }else{
+        args.push_back("-proj");
+        args.push_back(project_name);
+    }
+    args.push_back("-log");
+    args.push_back("+stderr");
+
+    int ownArgc = args.size();
+
+    char * ownArgv[ownArgc];
+
+    for(int i=0; i<ownArgc; i++){
+        ownArgv[i] = const_cast<char *>(args.at(i).c_str());
+    }
 
     Resources::init(ownArgc, ownArgv);
 }
diff --git a/LCS/WinCCWrapper/src/WinCCWrapper.cc b/LCS/WinCCWrapper/src/WinCCWrapper.cc
index ab6191dc8d62d0f793b0c91541d59e3e2ecd4986..d8e129f59b8b81ac596a8ae80b6e7c423bd58c0f 100644
--- a/LCS/WinCCWrapper/src/WinCCWrapper.cc
+++ b/LCS/WinCCWrapper/src/WinCCWrapper.cc
@@ -43,6 +43,9 @@ using namespace std;
 
 //! Each datapoint has a human readable name in the wincc database, but the actual value is stored in a sub-item. Append that to each set/get datapoint name.
 static const string DP_SUFFIX = ":_original.._value";
+//! This property refers to the last set time
+static const string DP_SUFFIX_STIME = ":_original.._stime";
+
 
 WinCCWrapper::WinCCWrapper(const std::string &project_name) :
   resources(project_name)
@@ -50,6 +53,12 @@ WinCCWrapper::WinCCWrapper(const std::string &project_name) :
     manager = new WinCCManager();
 }
 
+WinCCWrapper::WinCCWrapper(const std::string &program_name, const std::string &project_name, const int num) :
+  resources(program_name, project_name, num)
+{
+    manager = new WinCCManager();
+}
+
 void WinCCWrapper::run()
 {
     manager->run();
@@ -65,11 +74,31 @@ void WinCCWrapper::connect_datapoints(const std::vector<std::string> &data_point
     manager->connect_datapoints(data_points);
 }
 
+void WinCCWrapper::connect_datapoints_multi(const std::vector<std::string> &data_points)
+{
+    manager->connect_datapoints_multiple(data_points);
+}
+
+void WinCCWrapper::disconnect_datapoints(const std::vector<std::string> &data_points)
+{
+    manager->connect_datapoints(data_points);
+}
+
+
+void WinCCWrapper::wait_for_event(long sec, long microSec){
+    manager->wait_for_event(sec, microSec);
+}
+
 void WinCCWrapper::set_connect_datapoints_callback(std::function<void(std::string name, std::string value)> callback)
 {
     manager->set_connect_datapoints_callback(callback);
 }
 
+void WinCCWrapper::set_connect_datapoints_callback(std::function<void(std::map<std::string, std::string>)> callback)
+{
+    manager->set_connect_datapoints_callback(callback);
+}
+
 // set_datapoint
 bool WinCCWrapper::set_datapoint(const std::string &name, int value, bool valid)
 {
@@ -99,7 +128,7 @@ bool WinCCWrapper::set_datapoint(const std::string &name, boost::python::tuple &
 bool WinCCWrapper::set_datapoint(const std::string &name, std::vector<int> &value, bool valid)
 {
     DynVar variable(VariableType::INTEGER_VAR);
-    
+
     for(auto iter = value.cbegin(); iter != value.cend(); iter++) {
         IntegerVar elem{*iter};
         variable.append(elem);}
@@ -142,6 +171,12 @@ bool WinCCWrapper::set_datapoint_time(const std::string &name, time_t value, boo
     return manager->set_datapoint(name + DP_SUFFIX, variable, valid);
 }
 
+boost::any WinCCWrapper::get_datapoint_any (const std::string &name){
+    boost::any datapoint_value;
+    manager->get_datapoint_any(name + DP_SUFFIX, datapoint_value);
+    return datapoint_value;
+}
+
 // get_datapoint
 template <typename T>
 bool WinCCWrapper::get_datapoint(const std::string &name, T &value)
@@ -213,6 +248,21 @@ time_t WinCCWrapper::get_datapoint_time(const std::string &name)
     throw std::runtime_error("Could not get datapoint");
 }
 
+std::string WinCCWrapper::get_formatted_datapoint(const std::string &name){
+    std::string value;
+    if(manager->get_datapoint(name, value)){
+        return value;
+    }
+    throw std::runtime_error("Could not get datapoint");
+}
+
+time_t WinCCWrapper::get_datapoint_set_time(const std::string &name){
+    struct tm value;
+    if(manager->get_datapoint(name + DP_SUFFIX_STIME, value))
+        return mktime(&value);
+    throw std::runtime_error("Could not get datapoint");
+}
+
 bool WinCCWrapper::set_datapoint_valid(const std::string &name)
 {
     return manager->set_datapoint_valid(name + DP_SUFFIX);
@@ -223,5 +273,8 @@ bool WinCCWrapper::set_datapoint_invalid(const std::string &name)
     return manager->set_datapoint_invalid(name + DP_SUFFIX);
 }
 
+bool WinCCWrapper::set_datapoint_any(const std::string &name, boost::any value){
+    return manager->set_datapoint_any(name + DP_SUFFIX, value);
+}
     } // namespace WINCCWRAPPER
 } // namespace LOFAR
diff --git a/LCS/WinCCWrapper/src/exceptions.h b/LCS/WinCCWrapper/src/exceptions.h
new file mode 100644
index 0000000000000000000000000000000000000000..cf0da769433fabc453caea25a37d76f885ff211f
--- /dev/null
+++ b/LCS/WinCCWrapper/src/exceptions.h
@@ -0,0 +1,12 @@
+class DatapointNameNotFound : public std::exception
+{
+private:
+    std::string message;
+public:
+    DatapointNameNotFound(const std::string & datapointName):
+        message{"Datapoint " + datapointName + " not found"}{}
+	const char * what () const throw ()
+    {
+    	return message.c_str();
+    }
+}
diff --git a/LCS/WinCCWrapper/test/CMakeLists.txt b/LCS/WinCCWrapper/test/CMakeLists.txt
index 93e3e67b7f1dd434c11c41fd399d18972c55937f..08c5036d1acb50498adbf4f295eefeb1bc8b999c 100644
--- a/LCS/WinCCWrapper/test/CMakeLists.txt
+++ b/LCS/WinCCWrapper/test/CMakeLists.txt
@@ -8,6 +8,15 @@ IF(BUILD_TESTING)
 
         lofar_add_bin_program(WinCCSet WinCCSet.cc)
         lofar_add_bin_program(WinCCGet WinCCGet.cc)
+        lofar_add_bin_program(WinCCQuery WinCCQuery.cc)
+        find_package(Boost COMPONENTS program_options system REQUIRED)
+
+        target_link_libraries(WinCCQuery
+            wincc_wrapper
+            ${Boost_LIBRARIES}
+
+        )
+
     ENDIF(WINCC_FOUND)
 ENDIF(BUILD_TESTING)
 
diff --git a/LCS/WinCCWrapper/test/WinCCGet.cc b/LCS/WinCCWrapper/test/WinCCGet.cc
index 5958d82e915ae681f7e11d206b062c0eebeb1983..aa10b76c422d4866cbec3a7e3bfa14e29dc745ae 100644
--- a/LCS/WinCCWrapper/test/WinCCGet.cc
+++ b/LCS/WinCCWrapper/test/WinCCGet.cc
@@ -3,28 +3,28 @@
 #include <WinCCWrapper.h>
 #include <vector>
 #include <iostream>
+#include <Resources.hxx>
 
 using namespace LOFAR::WINCCWRAPPER;
 using namespace std;
 
 void get_help(){
     cout << "Usage:" << endl;
-    cout << "WinCCGet \"datapoint_name\" datapoint_type" << endl;
+    cout << "WinCCGet \"datapoint_name\" datapoint_type project_name" << endl;
     cout << "Accepted datapoint types:" << endl;
     cout << "  int, float, string, list (for int lists)" << endl;
 }
 
 int main(int argc, char * argv[])
 {
-  bool asking_for_help = ((argc == 2) && (string(argv[1]) == "--help" || string(argv[1]) == "--h"));
-  bool invalid_args = (argc != 3);
+  bool asking_for_help = ((argc < 4) && (string(argv[1]) == "--help" || string(argv[1]) == "--h"));
+  bool invalid_args = (argc != 4);
 
   if (asking_for_help || invalid_args){
     get_help();
     return 0;
   }
-
-  WinCCWrapper wrapper{""};
+  WinCCWrapper wrapper{std::string(argv[3])};
   string dpname{argv[1]};
 
   if (string(argv[2]) == "int") {
diff --git a/LCS/WinCCWrapper/test/WinCCQuery.cc b/LCS/WinCCWrapper/test/WinCCQuery.cc
new file mode 100644
index 0000000000000000000000000000000000000000..108c626d304c56e1cb22ecbd3075328732488cee
--- /dev/null
+++ b/LCS/WinCCWrapper/test/WinCCQuery.cc
@@ -0,0 +1,68 @@
+
+
+
+#include <cstdlib>
+#include <string>
+#include <WinCCManager.h>
+#include <WinCCResources.h>
+
+#include <vector>
+#include <iostream>
+#include <Resources.hxx>
+
+#include <boost/program_options.hpp>
+#include <boost/any.hpp>
+
+
+
+using namespace LOFAR::WINCCWRAPPER;
+using namespace std;
+namespace po = boost::program_options;
+
+void set_options(po::options_description & description){
+    description.add_options()
+    ("help", "produce help message")
+    ("sql", po::value<std::string>(), "SQL_query")
+    ("project", po::value<std::string>(), "WinCC project");
+
+}
+
+int main(int argc, char * argv[])
+{
+    // Declare the supported options.
+    po::options_description desc("Query the WinCCDatabase");
+    set_options(desc);
+
+    po::variables_map vm;
+    po::store(po::parse_command_line(argc, argv, desc), vm);
+    po::notify(vm);
+
+
+    if (vm.count("help")) {
+        cout << desc << "\n";
+        return 1;
+    }
+
+    if (vm.count("sql") == 1 && vm.count("project") == 1) {
+
+        const string sql{vm["sql"].as<string>()};
+
+        WinCCResources resource{"WinCCQuery", vm["project"].as<string>(), 0};
+        WinCCManager manager;
+        cout << "The SQL is: [" << sql << "].\n";
+
+        std::vector<std::vector<std::string>> queryResult;
+        manager.get_query(sql, queryResult);
+        std::cout<< "RESULTS ----"<<"\n\n";
+        std::cout<< "datapoint" << "\t";
+        for(auto & row : queryResult){
+            for(std::string & column : row){
+                std::cout<<column<<"\t";
+            }
+            std::cout<<"\n";
+        }
+
+    } else {
+        cout << desc;
+    }
+}
diff --git a/LCU/Maintenance/DBInterface/django_postgresql/settings.py b/LCU/Maintenance/DBInterface/django_postgresql/settings.py
index 12b5486b5c1efb62ee61a3f2b6536cfec295f33b..b3e57efb1e9c550ca604173a8cb2ccace57eba74 100644
--- a/LCU/Maintenance/DBInterface/django_postgresql/settings.py
+++ b/LCU/Maintenance/DBInterface/django_postgresql/settings.py
@@ -30,7 +30,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 SECRET_KEY = 'x)5=9*2a&)32h-loh@rlt_9eyw%t$-fqao*#1j2gh^7=^bnjyy'
 
 # SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = False
+DEBUG = True
 
 ALLOWED_HOSTS = ['lofarmonitortest.control.lofar', 'localhost', '127.0.0.1']
 
@@ -47,9 +47,11 @@ INSTALLED_APPS = [
     'rest_framework',
     'django_filters',
     'lofar.maintenance.monitoringdb.apps.MonitoringDbConfig',
+    'silk'
 ]
 
 MIDDLEWARE = [
+    'silk.middleware.SilkyMiddleware',
     'django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
@@ -60,7 +62,8 @@ MIDDLEWARE = [
 ]
 
 ROOT_URLCONF = 'lofar.maintenance.django_postgresql.urls'
-
+SILKY_PYTHON_PROFILER = True
+SILKY_PYTHON_PROFILER_BINARY = True
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
@@ -114,9 +117,9 @@ LOGGING = {
             'handlers': ['console'],
             'level': 'DEBUG'
         },
-        'django.db.backends':{
+        'django.db.backends': {
             'handlers': ['console'],
-            'level': 'DEBUG'
+            'level': 'INFO'
         }
     },
 }
@@ -188,8 +191,9 @@ USE_TZ = True
 
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/2.0/howto/static-files/
-
-STATIC_URL = 'static/'
+PATH = '/home/mmancini/svn-tree/MonitoringMaintenance-OSB-45/build/gnucxx11_debug'
+STATIC_ROOT = os.path.join(PATH, "static")
+STATIC_URL = '/static/'
 
 REST_FRAMEWORK = {
     'DEFAULT_PAGINATION_CLASS': 'lofar.maintenance.monitoringdb.pagination.DefaultPaginationSettings',
@@ -199,4 +203,4 @@ REST_FRAMEWORK = {
 
 CELERY_RESULT_BACKEND = 'amqp://guest@localhost//'
 # LOFAR SPECIFIC PARAMETERS
-URL_TO_STORE_RTSM_PLOTS = '/data/deploy/production/rtsm_plots'
\ No newline at end of file
+URL_TO_STORE_RTSM_PLOTS = PATH
\ No newline at end of file
diff --git a/LCU/Maintenance/DBInterface/django_postgresql/urls.py b/LCU/Maintenance/DBInterface/django_postgresql/urls.py
index 1d14e92ffba1fe5f039e0446493aaf16c8dd035d..d024aff41f84182fdd7c179d98f9cb22f1de520f 100644
--- a/LCU/Maintenance/DBInterface/django_postgresql/urls.py
+++ b/LCU/Maintenance/DBInterface/django_postgresql/urls.py
@@ -14,9 +14,15 @@ Including another URLconf
     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 """
 
-from django.conf.urls import include
-from django.urls import path
+from django.conf.urls import include, url
+
+from django.conf import settings
+from django.conf.urls.static import static
+
 
 urlpatterns = [
-    path('', include('lofar.maintenance.monitoringdb.urls'))
-]
+#    path('', include('lofar.maintenance.monitoringdb.urls'))
+] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+from lofar.maintenance.monitoringdb.urls import urlpatterns as urls
+urlpatterns += urls
+urlpatterns += [url(r'^silk/', include('silk.urls', namespace='silk'))]
\ No newline at end of file
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/migrations/0001_initial.py b/LCU/Maintenance/DBInterface/monitoringdb/migrations/0001_initial.py
deleted file mode 100644
index 187852808bdf87e771a4f1be02dc26d7314ce2a7..0000000000000000000000000000000000000000
--- a/LCU/Maintenance/DBInterface/monitoringdb/migrations/0001_initial.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# Generated by Django 2.0.7 on 2019-01-09 14:44
-
-import django.contrib.postgres.fields
-import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='ActionLog',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('entry_id', models.IntegerField(help_text='database id of the concerned object')),
-                ('model_name', models.CharField(max_length=100)),
-                ('who', models.CharField(max_length=100)),
-                ('when', models.DateTimeField(auto_now_add=True)),
-                ('what', models.CharField(max_length=1000)),
-            ],
-        ),
-        migrations.CreateModel(
-            name='Component',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('type', models.CharField(max_length=50)),
-                ('component_id', models.IntegerField()),
-            ],
-        ),
-        migrations.CreateModel(
-            name='ComponentError',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('type', models.CharField(max_length=50)),
-                ('details', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
-                ('component', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='monitoringdb.Component')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='Element',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('element_id', models.PositiveIntegerField()),
-                ('component', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='monitoringdb.Component')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='ElementError',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('type', models.CharField(max_length=50)),
-                ('details', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
-                ('component_error', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='failing_elements', to='monitoringdb.ComponentError')),
-                ('element', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='monitoringdb.Element')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='RTSMError',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('rcu', models.SmallIntegerField(default=None, null=True)),
-                ('mode', models.SmallIntegerField(default=None, null=True)),
-                ('error_type', models.CharField(max_length=50)),
-                ('start_frequency', models.FloatField(default=None, null=True)),
-                ('stop_frequency', models.FloatField(default=None, null=True)),
-                ('time', models.DateTimeField()),
-            ],
-        ),
-        migrations.CreateModel(
-            name='RTSMErrorSummary',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('percentage', models.FloatField(default=None, null=True)),
-                ('rcu', models.SmallIntegerField(default=None, null=True)),
-                ('mode', models.SmallIntegerField(default=None, null=True)),
-                ('count', models.IntegerField(default=None, null=True)),
-                ('error_type', models.CharField(max_length=50)),
-                ('start_frequency', models.FloatField(default=None, null=True)),
-                ('stop_frequency', models.FloatField(default=None, null=True)),
-            ],
-        ),
-        migrations.CreateModel(
-            name='RTSMObservation',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('observation_id', models.PositiveIntegerField(default=0)),
-                ('start_datetime', models.DateTimeField()),
-                ('end_datetime', models.DateTimeField()),
-                ('samples', models.IntegerField(default=None, null=True)),
-            ],
-        ),
-        migrations.CreateModel(
-            name='RTSMSpectrum',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('bad_spectrum', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(), size=None)),
-                ('average_spectrum', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(), size=None)),
-                ('rtsm_error', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='spectra', to='monitoringdb.RTSMError')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='RTSMSummaryPlot',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('uri', models.CharField(default=None, max_length=10000, null=True)),
-                ('error_summary', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='summary_plot', to='monitoringdb.RTSMErrorSummary')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='Station',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('location', models.CharField(blank=True, help_text='where the station is located', max_length=50, null=True)),
-                ('name', models.CharField(help_text='name of the station', max_length=10)),
-                ('type', models.CharField(choices=[('c', 'core'), ('i', 'international'), ('r', 'remote')], help_text='station type one of [Core[C], Remote[R], International[I]]', max_length=1)),
-            ],
-        ),
-        migrations.CreateModel(
-            name='StationTest',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('start_datetime', models.DateTimeField()),
-                ('end_datetime', models.DateTimeField()),
-                ('checks', models.CharField(max_length=2000)),
-                ('station', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='monitoringdb.Station')),
-            ],
-        ),
-        migrations.AddField(
-            model_name='rtsmobservation',
-            name='station',
-            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='monitoringdb.Station'),
-        ),
-        migrations.AddField(
-            model_name='rtsmerrorsummary',
-            name='observation',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='errors_summary', to='monitoringdb.RTSMObservation'),
-        ),
-        migrations.AddField(
-            model_name='rtsmerror',
-            name='observation',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='errors', to='monitoringdb.RTSMObservation'),
-        ),
-        migrations.AddField(
-            model_name='componenterror',
-            name='station_test',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='component_errors', to='monitoringdb.StationTest'),
-        ),
-        migrations.AddField(
-            model_name='component',
-            name='station',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='monitoringdb.Station'),
-        ),
-    ]
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/migrations/0002_winccantennastatuses.py b/LCU/Maintenance/DBInterface/monitoringdb/migrations/0002_winccantennastatuses.py
deleted file mode 100644
index d75d4485685f9fa63b564a244d09b6c2d4086b35..0000000000000000000000000000000000000000
--- a/LCU/Maintenance/DBInterface/monitoringdb/migrations/0002_winccantennastatuses.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Generated by Django 2.0.7 on 2019-03-07 16:11
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('monitoringdb', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='WinCCAntennaStatus',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('timestamp', models.DateTimeField(auto_now_add=True)),
-                ('station', models.CharField(max_length=10)),
-                ('antenna_type', models.CharField(max_length=4)),
-                ('antenna_id', models.IntegerField()),
-                ('status_code', models.IntegerField()),
-                ('status_code_reason', models.CharField(max_length=1000)),
-                ('status_code_modified_at', models.CharField(max_length=100)),
-                ('element_00', models.IntegerField(null=True)),
-                ('element_00_reason', models.CharField(max_length=1000, null=True)),
-                ('element_00_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_01', models.IntegerField(null=True)),
-                ('element_01_reason', models.CharField(max_length=1000, null=True)),
-                ('element_01_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_02', models.IntegerField(null=True)),
-                ('element_02_reason', models.CharField(max_length=1000, null=True)),
-                ('element_02_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_03', models.IntegerField(null=True)),
-                ('element_03_reason', models.CharField(max_length=1000, null=True)),
-                ('element_03_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_04', models.IntegerField(null=True)),
-                ('element_04_reason', models.CharField(max_length=1000, null=True)),
-                ('element_04_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_05', models.IntegerField(null=True)),
-                ('element_05_reason', models.CharField(max_length=1000, null=True)),
-                ('element_05_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_06', models.IntegerField(null=True)),
-                ('element_06_reason', models.CharField(max_length=1000, null=True)),
-                ('element_06_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_07', models.IntegerField(null=True)),
-                ('element_07_reason', models.CharField(max_length=1000, null=True)),
-                ('element_07_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_08', models.IntegerField(null=True)),
-                ('element_08_reason', models.CharField(max_length=1000, null=True)),
-                ('element_08_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_09', models.IntegerField(null=True)),
-                ('element_09_reason', models.CharField(max_length=1000, null=True)),
-                ('element_09_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_10', models.IntegerField(null=True)),
-                ('element_10_reason', models.CharField(max_length=1000, null=True)),
-                ('element_10_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_11', models.IntegerField(null=True)),
-                ('element_11_reason', models.CharField(max_length=1000, null=True)),
-                ('element_11_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_12', models.IntegerField(null=True)),
-                ('element_12_reason', models.CharField(max_length=1000, null=True)),
-                ('element_12_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_13', models.IntegerField(null=True)),
-                ('element_13_reason', models.CharField(max_length=1000, null=True)),
-                ('element_13_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_14', models.IntegerField(null=True)),
-                ('element_14_reason', models.CharField(max_length=1000, null=True)),
-                ('element_14_modified_at', models.CharField(max_length=100, null=True)),
-                ('element_15', models.IntegerField(null=True)),
-                ('element_15_reason', models.CharField(max_length=1000, null=True)),
-                ('element_15_modified_at', models.CharField(max_length=100, null=True)),
-            ],
-            options={
-                'db_table': 'antenna_statuses',
-                'ordering': ['-timestamp', 'station', 'antenna_type', 'antenna_id'],
-            },
-        ),
-        migrations.AddIndex(
-            model_name='winccantennastatus',
-            index=models.Index(fields=['station', 'antenna_type', 'antenna_id', 'timestamp'], name='antenna_sta_station_2632f8_idx'),
-        ),
-        migrations.RunSQL(
-            sql="ALTER TABLE antenna_statuses ALTER COLUMN timestamp SET DEFAULT now()",
-            reverse_sql=""
-        )
-    ]
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/component.py b/LCU/Maintenance/DBInterface/monitoringdb/models/component.py
index a8bf7a0a9a6cb509e37cb09341b92f996b8a2359..7995893b53995ea3b76898608b6e042bc4c9c4aa 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/component.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/component.py
@@ -1,11 +1,33 @@
 from django.db import models
 from .station import Station
+from typing import Tuple
+
+
+HBA_ELEMENT_ID_RANGE = range(0, 16, 1)
+
+def antenna_type_antenna_id_to_component_type_component_id(station_name,
+                                                           antenna_type,
+                                                           antenna_id) -> Tuple[str, int]:
+    if antenna_type == 'HBA':
+        return 'HBA', antenna_id
+
+    if station_name.startswith('CS') or station_name.startswith('RS'):
+        if antenna_id >= 48:
+            component_type = 'LBL'
+            component_id = antenna_id - 48
+        else:
+            component_type = 'LBH'
+            component_id = antenna_id
+
+        return component_type, component_id
+    else:
+        return 'LBH', antenna_id
 
 
 class Component(models.Model):
     type = models.CharField(max_length=50)
     component_id = models.IntegerField()
-    station = models.ForeignKey(Station, on_delete=models.DO_NOTHING)
+    station = models.ForeignKey(Station, on_delete=models.DO_NOTHING, related_name='components')
 
     class Meta:
         unique_together = ('station', 'type', 'component_id')
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/element.py b/LCU/Maintenance/DBInterface/monitoringdb/models/element.py
index 708adfc1fff162392a7af8c1806e2aaa946f43bc..3694fb3b0c5822acea0cee6799654cdd09cb4790 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/element.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/element.py
@@ -3,7 +3,7 @@ from .component import Component
 
 
 class Element(models.Model):
-    component = models.ForeignKey(Component, on_delete=models.DO_NOTHING)
+    component = models.ForeignKey(Component, on_delete=models.DO_NOTHING, related_name='elements')
     element_id = models.PositiveIntegerField()
 
     class Meta:
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py b/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py
index c50dda3dad6a724554253e24df3f7a70264edc12..af3101d631b893ab752620ee42390a8b1cb843a6 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py
@@ -3,8 +3,9 @@ In this file all the model regarding the real time station monitor output are co
 """
 from django.db import models
 from django.contrib.postgres.fields import ArrayField
-from .station import Station
+
 from .component import Component
+from .test import GenericTest
 
 MODE_TO_COMPONENT = {
     1: 'LBL',
@@ -16,38 +17,80 @@ MODE_TO_COMPONENT = {
     7: 'HBA'
 }
 
-COMPONENT_TO_MODE = {
-    'LBL': [1, 2],
-    'LBH': [3, 4],
-    'HBA': [5, 6, 7]
-}
-
 
-class RTSMObservation(models.Model):
-    observation_id = models.PositiveIntegerField(default=0)
+def antenna_id_polarization_from_rcu_type(rcu, type):
+    """
+    Compute the antenna id for a given rcu, type and polarization
+    :param rcu: id of the rcu
+    :param type: type of the antenna
+    :return: the antenna id and polarization
+    :rtype: (int, str)
+    """
+    polarization_index = rcu % 2
 
-    station = models.ForeignKey(Station, on_delete=models.SET_NULL, null=True)
+    if type in ['LBH', 'HBA']:
+        antenna_id = rcu - polarization_index
+        antenna_id /= 2.
+        polarization = 'Y' if polarization_index > 0 else 'X'
+    elif type == 'LBL':
+        antenna_id = (rcu - polarization_index) / 2. + 48
+        polarization = 'X' if polarization_index > 0 else 'Y'
+    else:
+        antenna_id = -1
+        polarization = ''
+    return antenna_id, polarization
 
-    start_datetime = models.DateTimeField()
-    end_datetime = models.DateTimeField()
 
-    samples = models.IntegerField(default=None, null=True)
+class RTSMObservation(GenericTest):
+    sas_id = models.PositiveIntegerField(default=0)
+    samples = models.PositiveIntegerField(default=0)
 
 
 class RTSMError(models.Model):
     observation = models.ForeignKey(RTSMObservation, related_name='errors', on_delete=models.CASCADE)
-    component = models.ForeignKey(Component, on_delete=models.SET_NULL)
+    component = models.ForeignKey(Component, on_delete=models.SET_NULL, null=True)
+    count = models.PositiveIntegerField(default=0, null=True)
     rcu = models.SmallIntegerField(default=None, null=True)
     mode = models.SmallIntegerField(default=None, null=True)
     percentage = models.FloatField(default=None, null=True)
-
+    polarization = models.CharField(max_length=1)
     error_type = models.CharField(max_length=50)
 
+    @staticmethod
+    def get_or_create_error_from_rtsm_result(observation: RTSMObservation,
+                                      rcu,
+                                      mode,
+                                      percentage,
+                                      error_type,
+                                      count):
+        station = observation.station
+        component_type = MODE_TO_COMPONENT[mode]
+        component_id, polarization = antenna_id_polarization_from_rcu_type(rcu, component_type)
+        component_entity, _ = Component.objects.get_or_create(station=station,
+                                        type=component_type,
+                                        component_id=component_id)
+        rtsm_error, created = RTSMError.objects.get_or_create(observation=observation,
+                                        component=component_entity,
+                                        rcu=rcu,
+                                        mode=mode,
+                                        polarization=polarization,
+                                        error_type=error_type,
+                                        percentage=percentage,
+                                        count=count
+                                        )
+
+        return rtsm_error, created
+
 
 class RTSMErrorSample(models.Model):
     time = models.DateTimeField()
     error = models.ForeignKey(RTSMError, related_name='samples', on_delete=models.CASCADE)
-    observation_id = models.ForeignKey(RTSMObservation, on_delete=models.CASCADE)
+
+    start_frequency = models.FloatField(default=None, null=True)
+    stop_frequency = models.FloatField(default=None, null=True)
+
+    bad_spectrum = ArrayField(models.FloatField())
+    average_spectrum = ArrayField(models.FloatField())
 
 
 class RTSMSummaryPlot(models.Model):
@@ -58,11 +101,3 @@ class RTSMSummaryPlot(models.Model):
 
     uri = models.CharField(max_length=10000, default=None, null=True)
 
-
-class RTSMSpectrum(models.Model):
-    start_frequency = models.FloatField(default=None, null=True)
-    stop_frequency = models.FloatField(default=None, null=True)
-
-    bad_spectrum = ArrayField(models.FloatField())
-    average_spectrum = ArrayField(models.FloatField())
-    rtsm_error = models.ForeignKey(RTSMErrorSample, related_name='spectra', on_delete=models.CASCADE)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py b/LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a4ae82847bb4178251aab50210a6b4f0743192a
--- /dev/null
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/signals/generic_test_insert.py
@@ -0,0 +1,80 @@
+from typing import List
+
+from ..component import HBA_ELEMENT_ID_RANGE, Component, \
+    antenna_type_antenna_id_to_component_type_component_id
+from ..element import Element
+from ..test import GenericTest
+from ..wincc import ElementStatus, \
+    ComponentStatus, \
+    StationStatus, WinCCAntennaStatus, STATUS_CODE_TO_STATUS, latest_status_per_station_raw
+
+
+def insert_hba_element_status(component_status: ComponentStatus,
+                              hba_wincc_status: WinCCAntennaStatus):
+    element_statuses = []
+    for element_id in HBA_ELEMENT_ID_RANGE:
+        element_status_field = 'element_{:02d}'.format(element_id)
+        element_status_code = hba_wincc_status.serializable_value(element_status_field)
+        element_status_description = STATUS_CODE_TO_STATUS[element_status_code]
+        element_status_message = hba_wincc_status.serializable_value(element_status_field +
+                                                                     '_reason')
+        element_status_modified_at = hba_wincc_status.serializable_value(element_status_field +
+                                                                         '_modified_at')
+
+        element, created = Element.objects.get_or_create(element_id=element_id,
+                                                         component=component_status.component)
+        element_status = ElementStatus(
+            component_status=component_status,
+            status_code=element_status_code,
+            status_description=element_status_description,
+            status_message=element_status_message,
+            modified_at=element_status_modified_at,
+            element=element)
+        element_statuses.append(element_status)
+
+    return ElementStatus.objects.bulk_create(element_statuses)
+
+
+def insert_component_status(station_status: StationStatus, component_status: WinCCAntennaStatus):
+    station = station_status.station
+    component_type, component_id = antenna_type_antenna_id_to_component_type_component_id(
+        station.name,
+        component_status.antenna_type,
+        component_status.antenna_id)
+
+    component, created = Component.objects.get_or_create(station=station,
+                                                         type=component_type,
+                                                         component_id=component_id)
+    component_status_entity = ComponentStatus(
+        component=component,
+        status_code=component_status.status_code,
+        status_description=STATUS_CODE_TO_STATUS[
+            component_status.status_code],
+        status_message=component_status.status_code_reason,
+        modified_at=component_status.status_code_modified_at,
+        station_status=station_status)
+    component_status_entity.save()
+
+    if component_status.antenna_type == 'HBA':
+        insert_hba_element_status(component_status_entity, component_status)
+    return component_status
+
+
+def insert_station_status(test_instance: GenericTest, statuses: List[WinCCAntennaStatus]):
+    station_status_instance = StationStatus(station=test_instance.station)
+    station_status_instance.save()
+
+    test_instance.station_status = station_status_instance
+    for status in statuses:
+        insert_component_status(station_status_instance, status)
+
+
+def on_inserting_test(sender, instance: GenericTest, **kwargs):
+    logger.info('received post_insert signal from %s for instance %s', sender, instance)
+    queryset = WinCCAntennaStatus.objects.all().order_by()
+    query_results = [wincc_status for wincc_status in
+                     latest_status_per_station_raw(queryset=queryset,
+                                                   station=instance.station.name,
+                                                   to_date=instance.start_datetime)]
+
+    insert_station_status(instance, query_results)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/station_test.py b/LCU/Maintenance/DBInterface/monitoringdb/models/station_test.py
index 9d7ff07d90ca1d76fb2f49044c85a353f5e8f97b..29d982eabb77edf01db8090125e3371ee471a58e 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/station_test.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/station_test.py
@@ -1,10 +1,8 @@
 from django.db import models
-from .station import Station
 
+from .test import GenericTest
 
-class StationTest(models.Model):
-    start_datetime = models.DateTimeField()
-    end_datetime = models.DateTimeField()
+
+class StationTest(GenericTest):
     checks = models.CharField(max_length=2000)
-    station = models.ForeignKey(Station, on_delete=models.DO_NOTHING)
 
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/test.py b/LCU/Maintenance/DBInterface/monitoringdb/models/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7565bde2e2c2d96c244861448e78d1cead3b48c
--- /dev/null
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/test.py
@@ -0,0 +1,19 @@
+import logging
+
+from django.db import models
+
+from .station import Station
+from .wincc import StationStatus
+
+logger = logging.getLogger(__name__)
+
+
+class GenericTest(models.Model):
+    start_datetime = models.DateTimeField()
+    end_datetime = models.DateTimeField()
+
+    station = models.ForeignKey(Station, on_delete=models.DO_NOTHING, related_name='tests')
+
+    station_status = models.OneToOneField(StationStatus,
+                                          on_delete=models.CASCADE,
+                                          null=True)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py b/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py
index 12d5b6aee7778d321223e36b7a9ba8be8f07ade0..0e9d2bef37bb535f50a92d47e3a580f08affef8f 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/models/wincc.py
@@ -1,16 +1,20 @@
-import django.db.models as models
 from collections import OrderedDict
 
+import django.db.models as models
+
+from .component import Component
+from .element import Element
+from .station import Station
 
 COMPONENT_TYPE_TO_ANTENNA_TYPE = dict(HBA='HBA', LBH='LBA', LBL='LBA')
-STATUS_CODE_TO_STATUS = {0:'OFF',
-                         10:'OPERATIONAL',
-                         20:'MAINTENANCE',
-                         30:'TEST',
-                         40:'SUSPICIOUS',
-                         50:'BROKEN',
-                         60:'BEYOND REPAIR',
-                         70:'MISSING DATAPOINT'}
+STATUS_CODE_TO_STATUS = {0: 'OFF',
+                         10: 'OPERATIONAL',
+                         20: 'MAINTENANCE',
+                         30: 'TEST',
+                         40: 'SUSPICIOUS',
+                         50: 'BROKEN',
+                         60: 'BEYOND REPAIR',
+                         70: 'MISSING DATAPOINT'}
 
 
 def to_antenna_type(component_type):
@@ -49,6 +53,33 @@ def _format_result(raw_query_result, antenna_type):
     return results
 
 
+def latest_status_per_station_raw(queryset, station, to_date):
+    """
+    Retrieve the latest antenna statuses for a given station and antenna type
+    :param queryset: source queryset
+    :param station: station name
+    :type station: str
+    :param component_type: component type name
+    :type component_type: str
+    :param to_date: select the latest test before the to date
+    :type to_date: datetime.dattime
+    :return: the status of an antenna per antenna id
+    """
+    raw_results = queryset.raw('''
+    SELECT *
+    FROM antenna_statuses master
+    INNER JOIN (
+    SELECT antenna_type, antenna_id, MAX("timestamp") last_entry_timestamp 
+    FROM antenna_statuses
+    WHERE timestamp <= %(to_date)s AND station=%(station_name)s
+    GROUP BY antenna_type, antenna_id 
+    ) slave ON master.antenna_type=slave.antenna_type AND master.antenna_id = slave.antenna_id AND last_entry_timestamp=master.timestamp
+    WHERE master.station=%(station_name)s
+    ''', dict(to_date=to_date, station_name=station.rstrip('C')))
+
+    return raw_results
+
+
 def latest_status_per_station_and_antenna_type(queryset, station, antenna_type, to_date):
     """
     Retrieve the latest antenna statuses for a given station and antenna type
@@ -65,10 +96,10 @@ def latest_status_per_station_and_antenna_type(queryset, station, antenna_type,
     SELECT *
     FROM antenna_statuses master
     INNER JOIN (
-    SELECT station, antenna_type, antenna_id, MAX("timestamp") last_entry_timestamp 
+    SELECT antenna_type, antenna_id, MAX("timestamp") last_entry_timestamp 
     FROM antenna_statuses
-    WHERE timestamp <= %s
-    GROUP BY station, antenna_type, antenna_id 
+    WHERE timestamp <= %s AND station=%s
+    GROUP BY antenna_type, antenna_id 
     ) slave ON master.station=slave.station AND master.antenna_type=slave.antenna_type AND master.antenna_id = slave.antenna_id AND last_entry_timestamp=master.timestamp
     WHERE master.station=%s AND master.antenna_type=%s
     ''', [to_date, station.rstrip('C'), antenna_type])
@@ -91,7 +122,7 @@ def latest_status_per_station_and_component_type(queryset, station, component_ty
     antenna_type = to_antenna_type(component_type)
     if antenna_type:
         return latest_status_per_station_and_antenna_type(queryset, station, antenna_type,
-                                                                    to_date)
+                                                          to_date)
     else:
         return OrderedDict()
 
@@ -113,9 +144,10 @@ def latest_status_per_station_and_component_type_antenna_id(queryset, station,
     """
     antenna_type = to_antenna_type(component_type)
     if antenna_type:
-        return latest_status_per_station_and_antenna_type_antenna_id(queryset, station, antenna_type,
-                                                                               component_id,
-                                                                               to_date)
+        return latest_status_per_station_and_antenna_type_antenna_id(queryset, station,
+                                                                     antenna_type,
+                                                                     component_id,
+                                                                     to_date)
     else:
         return OrderedDict()
 
@@ -129,8 +161,8 @@ def latest_status_per_station_and_antenna_type_antenna_id(queryset, station,
     :param queryset: source queryset
     :param station: station name
     :type station: str
-    :param component_type: component type name
-    :type component_type: str
+    :param antenna_type: antenna type name
+    :type antenna_type: str
     :param to_date: select the latest test before the to date
     :type to_date: datetime.datetime
     :return: the status of an antenna per antenna id
@@ -164,69 +196,92 @@ class WinCCAntennaStatus(models.Model):
 
     status_code = models.IntegerField()
     status_code_reason = models.CharField(max_length=1000)
-    status_code_modified_at = models.CharField(max_length=100)
+    status_code_modified_at = models.DateTimeField(max_length=100)
 
     element_00 = models.IntegerField(null=True)
     element_00_reason = models.CharField(max_length=1000, null=True)
-    element_00_modified_at = models.CharField(max_length=100, null=True)
+    element_00_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_01 = models.IntegerField(null=True)
     element_01_reason = models.CharField(max_length=1000, null=True)
-    element_01_modified_at = models.CharField(max_length=100, null=True)
+    element_01_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_02 = models.IntegerField(null=True)
     element_02_reason = models.CharField(max_length=1000, null=True)
-    element_02_modified_at = models.CharField(max_length=100, null=True)
+    element_02_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_03 = models.IntegerField(null=True)
     element_03_reason = models.CharField(max_length=1000, null=True)
-    element_03_modified_at = models.CharField(max_length=100, null=True)
+    element_03_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_04 = models.IntegerField(null=True)
     element_04_reason = models.CharField(max_length=1000, null=True)
-    element_04_modified_at = models.CharField(max_length=100, null=True)
+    element_04_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_05 = models.IntegerField(null=True)
     element_05_reason = models.CharField(max_length=1000, null=True)
-    element_05_modified_at = models.CharField(max_length=100, null=True)
+    element_05_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_06 = models.IntegerField(null=True)
     element_06_reason = models.CharField(max_length=1000, null=True)
-    element_06_modified_at = models.CharField(max_length=100, null=True)
+    element_06_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_07 = models.IntegerField(null=True)
     element_07_reason = models.CharField(max_length=1000, null=True)
-    element_07_modified_at = models.CharField(max_length=100, null=True)
+    element_07_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_08 = models.IntegerField(null=True)
     element_08_reason = models.CharField(max_length=1000, null=True)
-    element_08_modified_at = models.CharField(max_length=100, null=True)
+    element_08_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_09 = models.IntegerField(null=True)
     element_09_reason = models.CharField(max_length=1000, null=True)
-    element_09_modified_at = models.CharField(max_length=100, null=True)
+    element_09_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_10 = models.IntegerField(null=True)
     element_10_reason = models.CharField(max_length=1000, null=True)
-    element_10_modified_at = models.CharField(max_length=100, null=True)
+    element_10_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_11 = models.IntegerField(null=True)
     element_11_reason = models.CharField(max_length=1000, null=True)
-    element_11_modified_at = models.CharField(max_length=100, null=True)
+    element_11_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_12 = models.IntegerField(null=True)
     element_12_reason = models.CharField(max_length=1000, null=True)
-    element_12_modified_at = models.CharField(max_length=100, null=True)
+    element_12_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_13 = models.IntegerField(null=True)
     element_13_reason = models.CharField(max_length=1000, null=True)
-    element_13_modified_at = models.CharField(max_length=100, null=True)
+    element_13_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_14 = models.IntegerField(null=True)
     element_14_reason = models.CharField(max_length=1000, null=True)
-    element_14_modified_at = models.CharField(max_length=100, null=True)
+    element_14_modified_at = models.DateTimeField(max_length=100, null=True)
 
     element_15 = models.IntegerField(null=True)
     element_15_reason = models.CharField(max_length=1000, null=True)
-    element_15_modified_at = models.CharField(max_length=100, null=True)
+    element_15_modified_at = models.DateTimeField(max_length=100, null=True)
+
+
+class EntityStatus(models.Model):
+    status_code = models.IntegerField(default=0)
+    status_description = models.CharField(max_length=20)
+    status_message = models.CharField(max_length=100, default='')
+    modified_at = models.DateTimeField(null=True, default=None)
+
+    class Meta:
+        abstract = True
+
+
+class StationStatus(models.Model):
+    station = models.ForeignKey(Station, on_delete=models.DO_NOTHING, null=True)
+
+
+class ComponentStatus(EntityStatus):
+    station_status = models.ForeignKey(StationStatus, on_delete=models.CASCADE)
+    component = models.ForeignKey(Component, on_delete=models.DO_NOTHING)
+
 
+class ElementStatus(EntityStatus):
+    component_status = models.ForeignKey(ComponentStatus, on_delete=models.CASCADE)
+    element = models.ForeignKey(Element, on_delete=models.DO_NOTHING)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py b/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py
index 04f5f8b589c6acb81888bacd8c2ed91fe0f24307..f1ff3df1e2950c13a42ca847009b0416260e4113 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py
@@ -1,6 +1,7 @@
 from datetime import datetime
 import pytz
 
+
 rtsm_error_types_to_error_types = dict(HN='HIGH_NOISE',
                                        SN='SUMMATOR_NOISE',
                                        CR='CABLE_REFLECTION',
@@ -86,17 +87,18 @@ def group_samples_by_error_type(content):
                       average_spectrum=average_spectrum,
                       bad_spectrum=bad_spectrum)
         if key in grouped_errors:
-            grouped_errors[key]['n_samples'] += 1
+            grouped_errors[key]['count'] += 1
             grouped_errors[key]['samples'] += [sample]
         else:
             grouped_errors[key] = dict(
-                n_samples = 1,
+                count = 1,
                 samples = [sample],
                 **error
             )
     content['errors'] = list(grouped_errors.values())
     return content
 
+
 def parse_rtsm_test(content):
     """
     Expects a string content with the rtsm test output
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py
index 85f26935647bfa70453c457e49d45f947a95f18f..288f75f13ad07c2449c55588a523ecff15ee9a9a 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py
@@ -11,21 +11,7 @@ import os
 logger = logging.getLogger('serializers')
 
 
-class RTSMSpectrumSerializer(serializers.ModelSerializer):
-    bad_spectrum = serializers.ListField(child=serializers.FloatField())
-    average_spectrum = serializers.ListField(child=serializers.FloatField())
-
-    class Meta:
-        model = RTSMSpectrum
-        fields = '__all__'
-
-
 class RTSMErrorSampleSerializer(serializers.ModelSerializer):
-    observation = serializers.PrimaryKeyRelatedField(read_only=True)
-
-    def __init__(self, *args, **kwargs):
-        self.Meta.depth = kwargs.pop('depth', 1)
-        super(RTSMErrorSampleSerializer, self).__init__(*args, **kwargs)
 
     class Meta:
         model = RTSMErrorSample
@@ -34,6 +20,7 @@ class RTSMErrorSampleSerializer(serializers.ModelSerializer):
 
 class RTSMErrorSerializer(serializers.ModelSerializer):
     observation = serializers.PrimaryKeyRelatedField(read_only=True)
+    samples = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
 
     class Meta:
         model = RTSMError
@@ -47,62 +34,6 @@ class RTSMObservationSerializer(serializers.ModelSerializer):
         model = RTSMObservation
         fields = '__all__'
 
-    @staticmethod
-    def compute_summary(RTSMObservation_entry):
-        summary_entries = RTSMObservation_entry.errors.defer('spectra'). \
-            filter(observation=RTSMObservation_entry).values('error_type', 'mode', 'rcu', 'start_frequency',
-                                                             'stop_frequency'). \
-            annotate(count=models.Count('error_type', unique=True, output_field=models.FloatField())). \
-            annotate(percentage=models.ExpressionWrapper(models.F('count') * 100. / models.F('observation__samples'),
-                                                         output_field=models.FloatField()))
-        for summary_entry in summary_entries:
-            RTSMError(observation=RTSMObservation_entry, **summary_entry).save()
-
-    @atomic
-    def create(self, validated_data):
-        rtsm_errors = validated_data.pop('errors')
-
-        # derive the station type from the station name
-        station_name = validated_data.pop('station_name')
-        station_type = station_name[0].capitalize()
-        station_type = station_type if station_type in ['C', 'R'] else 'I'
-
-        station_entry = Station.objects.filter(name=station_name).first()
-        if station_entry is None:
-            station_entry = Station(name=station_name, type=station_type)
-            station_entry.save()
-        # searches if the RTSM is already present
-        observation_id = validated_data['observation_id']
-        RTSMObservation_instance = RTSMObservation.objects.filter(observation_id=observation_id,
-                                                                  station=station_entry).first()
-
-        if RTSMObservation_instance is None:
-            logger.info('rtsm not found inserting %s', validated_data)
-            RTSMObservation_instance = RTSMObservation.objects.create(station=station_entry,
-                                                                      **validated_data)
-            RTSMObservation_instance.save()
-            ActionLogSerializer.log_model_insert(RTSMObservation_instance)
-        else:
-            raise ItemAlreadyExists(RTSMObservation_instance)
-        for rtsm_error in rtsm_errors:
-            rtsm_error.update(observation=RTSMObservation_instance)
-            print(rtsm_error)
-            average_spectrum = rtsm_error.pop('average_spectrum')
-            bad_spectrum = rtsm_error.pop('bad_spectrum')
-
-            rtsm_error_instance = RTSMErrorSample.objects.create(**rtsm_error)
-            rtsm_error_instance.save()
-            ActionLogSerializer.log_model_insert(rtsm_error_instance)
-
-            rtsm_spectrum_instance = RTSMSpectrum.objects.create(average_spectrum=average_spectrum,
-                                                                 bad_spectrum=bad_spectrum,
-                                                                 rtsm_error=rtsm_error_instance)
-            rtsm_spectrum_instance.save()
-            ActionLogSerializer.log_model_insert(rtsm_spectrum_instance)
-        self.compute_summary(RTSMObservation_instance)
-
-        return RTSMObservation_instance
-
 
 class FilePathSerializer(serializers.Field):
     def __init__(self, path, *args, **kwargs):
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py b/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py
index 43cb0c9e0d6068b38a46df60802d6428c8224692..9d69bbad43de598d55d20086cac1afed45c490ee 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py
@@ -11,6 +11,12 @@ from collections import defaultdict
 logger = logging.getLogger('station_test.parser')
 
 
+def station_type_from_station_name(station_name):
+    station_type = station_name[0].capitalize()
+    station_type = station_type if station_type in ['C', 'R'] else 'I'
+    return station_type
+
+
 def parse_key_value_pairs(content):
     """
     Parse a key value pair returning a dict.
@@ -81,6 +87,8 @@ def split_history_into_tests(content):
     all_tests = []
     current_test = []
     for i, line in enumerate(content[:-1]):
+        if line.startswith('#'):
+            continue
         next_line_columns = content[i + 1].split(',')
         line_type = next_line_columns[3]
         current_test.append(line)
@@ -206,7 +214,6 @@ def preparse_content(raw_content):
     return list(content.values())
 
 
-
 def dict_from_raw_station_test(content):
     """
     Expects a string content with the station test output
@@ -222,8 +229,7 @@ def dict_from_raw_station_test(content):
         row_type = row[3]
         if row_type == "STATION":
             station_name = row[4]['name']
-            station_type = station_name[0].capitalize()
-            station_type = station_type if station_type in ['C', 'R'] else 'I'
+            station_type = station_type_from_station_name(station_name)
             result.update(station=dict(name=station_name, type=station_type))
         elif row_type == 'VERSIONS':
             pass
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py b/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py
index 31abe3ae27f60def3723ad7651cdd7f79e552193..f3b77f45522a247176da100bab8b5aaff7674198 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/tasks/generate_plots.py
@@ -1,10 +1,13 @@
 import logging
 import os
+try:
 
-import matplotlib
+    import matplotlib
 
-matplotlib.use('agg')
-import matplotlib.pyplot as plt
+    matplotlib.use('agg')
+    import matplotlib.pyplot as plt
+except ImportError:
+    pass
 import numpy
 from celery import shared_task
 from django.conf import settings
@@ -110,7 +113,9 @@ def render_summary_text(error_metadata, observation_metadata):
 
 def produce_plot(observation_metadata,
                  error_metadata,
-                 list_bad_spectra, list_good_specta, path):
+                 start_frequency,
+                 stop_frequency,
+                 list_bad_spectra, list_good_spectra, path):
     """
     Produces a plot given the observation metadata the error summary metadata and
     the list of the bad spectra for the given (error, rcu) and the average spectrum
@@ -118,7 +123,7 @@ def produce_plot(observation_metadata,
     :param observation_metadata: the metadata information regarding the observation
     :param error_metadata: the metadata information regarding the error
     :param list_bad_spectra: a list containing the bad spectra for the given error, rcu couple
-    :param list_good_specta: the average spectrum of the rest of the array
+    :param list_good_spectra: the average spectrum of the rest of the array
     :param path: the path where to store the file
     """
     plt.figure(figsize=(12, 9))
@@ -127,18 +132,18 @@ def produce_plot(observation_metadata,
     plt.xlabel('MHz')
     plt.ylabel('dB')
 
-    frequency_sampling = len(list_bad_spectra[0][0])
+    frequency_sampling = len(list_bad_spectra[0])
     n_spectra = len(list_bad_spectra)
-    frequency = numpy.linspace(error_metadata.start_frequency,
-                               error_metadata.stop_frequency,
+    frequency = numpy.linspace(start_frequency,
+                               stop_frequency,
                                frequency_sampling)
     bad_spectra_cube = numpy.zeros((n_spectra, frequency_sampling))
     average_spectra_cube = numpy.zeros((n_spectra, frequency_sampling))
 
     for i, spectrum in enumerate(list_bad_spectra):
-        bad_spectra_cube[i, :] = spectrum[0]
-    for i, spectrum in enumerate(list_good_specta):
-        average_spectra_cube[i, :] = spectrum[0]
+        bad_spectra_cube[i, :] = spectrum
+    for i, spectrum in enumerate(list_good_spectra):
+        average_spectra_cube[i, :] = spectrum
 
     min_bad_spectrum = numpy.min(bad_spectra_cube, axis=0)
     max_bad_spectrum = numpy.max(bad_spectra_cube, axis=0)
@@ -191,7 +196,7 @@ def generate_summary_plot_for_error(error_summary_id):
     try:
         observation_metadata = ObservationMetadata()
         observation = error_summary.observation
-        observation_metadata.observation_id = observation.observation_id
+        observation_metadata.observation_id = observation.sas_id
         observation_metadata.station_name = observation.station.name
         observation_metadata.set_start_time(observation.start_datetime)
         observation_metadata.set_end_time(observation.end_datetime)
@@ -201,8 +206,13 @@ def generate_summary_plot_for_error(error_summary_id):
                                            rcu=error_summary.rcu,
                                            error_type=error_summary.error_type)
 
-        list_bad_spectra = errors.values_list('spectra__bad_spectrum')
-        list_good_spectra = errors.values_list('spectra__average_spectrum')
+        list_bad_spectra, list_average_spectra = zip(*errors.values_list('samples__bad_spectrum',
+                                                                         'samples__average_spectrum'))
+
+        (start_frequency, *_), (stop_frequency, *_) = \
+            zip(*errors.values_list('samples__start_frequency',
+                                    'samples__stop_frequency').distinct())
+
         file_name = '%(observation_id)d_%(station_name)s_rcu%(rcu)d_%(error_type)s.png' % dict(
             observation_id=observation_metadata.observation_id,
             station_name=observation_metadata.station_name,
@@ -211,10 +221,13 @@ def generate_summary_plot_for_error(error_summary_id):
         )
 
         full_path = os.path.join(basePath, file_name)
-        produce_plot(observation_metadata,
-                     error_summary,
-                     list_bad_spectra, list_good_spectra,
-                     full_path)
+        produce_plot(observation_metadata=observation_metadata,
+                     error_metadata=error_summary,
+                     start_frequency=start_frequency,
+                     stop_frequency=stop_frequency,
+                     list_bad_spectra=list_bad_spectra,
+                     list_good_spectra=list_average_spectra,
+                     path=full_path)
         summary_plot.uri = file_name
         summary_plot.save()
         return summary_plot.pk
@@ -234,7 +247,7 @@ def check_error_summary_plot(error_summary_id):
     :return:
     """
     logger.debug('looking for rtsm error summary %s', error_summary_id)
-    error_summary = RTSMErrorSummary.objects.get(pk=error_summary_id)
+    error_summary = RTSMError.objects.get(pk=error_summary_id)
     summary_plot = error_summary.summary_plot.first()
     logger.debug('summary error found %s', summary_plot)
 
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py b/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py
index 92f1f48a0519f0299f766334846ade31e79c0751..7cdf93672d6c0c8984623d755bbb4490b162a8b3 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/tasks/insert_raw_tests.py
@@ -1,18 +1,38 @@
 import logging
+from typing import Dict
 
 from celery import shared_task
 from django.db import transaction
+
+# Station Models
 from lofar.maintenance.monitoringdb.models.component import Component
-from lofar.maintenance.monitoringdb.models.component_error import ComponentError
-from lofar.maintenance.monitoringdb.models.element import Element
-from lofar.maintenance.monitoringdb.models.element_error import ElementError
 from lofar.maintenance.monitoringdb.models.station import Station
-from lofar.maintenance.monitoringdb.models.station_test import StationTest
-from lofar.maintenance.monitoringdb.station_test_raw_parser import parse_raw_station_test
+from lofar.maintenance.monitoringdb.models.element import Element
 from lofar.maintenance.monitoringdb.models.log import ActionLog
+from lofar.maintenance.monitoringdb.models.signals.generic_test_insert import on_inserting_test
+
+# RTSM Models
+from lofar.maintenance.monitoringdb.models.rtsm import RTSMObservation, RTSMError, RTSMErrorSample
+
+# Station Test Models
+from lofar.maintenance.monitoringdb.models.station_test import StationTest
+from lofar.maintenance.monitoringdb.models.component_error import ComponentError
+from lofar.maintenance.monitoringdb.models.element_error import ElementError
+
+from lofar.maintenance.monitoringdb.rtsm_test_raw_parser import parse_rtsm_test
+from lofar.maintenance.monitoringdb.station_test_raw_parser import parse_raw_station_test, station_type_from_station_name
+
 logger = logging.getLogger(__name__)
 
 
+
+
+from django.db.models.signals import post_save
+
+post_save.connect(on_inserting_test, sender=RTSMObservation)
+post_save.connect(on_inserting_test, sender=StationTest)
+
+
 def create_component_errors(station: Station, station_test: StationTest, component_errors_data):
     component_list = []
     component_error_list = []
@@ -22,8 +42,8 @@ def create_component_errors(station: Station, station_test: StationTest, compone
 
         component_list.append(component_entity)
         component_error_entity, _ = ComponentError.objects.get_or_create(component=component_entity,
-                                                                             station_test=station_test,
-                                                                             **component_error)
+                                                                         station_test=station_test,
+                                                                         **component_error)
 
         component_error_list.append(component_error_entity)
 
@@ -51,7 +71,11 @@ def create_element_errors(station, station_test, elements_error_data):
 
 
 def create_station(station_data):
-    station, created = Station.objects.get_or_create(**station_data)
+
+    station, created = Station.objects.update_or_create(defaults=station_data,
+                                                        name=station_data['name'])
+
+
     return station
 
 
@@ -70,7 +94,6 @@ def create_station_tests(station_tests_data):
         element_errors = station_test_data.pop('element_errors')
 
         station = create_station(station_data)
-        station, created = Station.objects.get_or_create(station)
 
         station_test = create_station_test(station, station_test_data)
 
@@ -80,6 +103,52 @@ def create_station_tests(station_tests_data):
         create_element_errors(station, station_test, element_errors)
 
 
+def create_rtsm_error(observation:RTSMObservation, rtsm_error):
+    start_frequency = rtsm_error.pop('start_frequency')
+    stop_frequency = rtsm_error.pop('stop_frequency')
+    n_samples = observation.samples
+    samples = rtsm_error.pop('samples')
+
+    count = rtsm_error.pop('count')
+    percentage = count / float(n_samples)
+    rtsm_error, created = RTSMError.get_or_create_error_from_rtsm_result(observation=observation,
+                                                                count=count,
+                                                                percentage=percentage,
+                                                                **rtsm_error)
+
+    if created:
+        rtsm_error_samples = [RTSMErrorSample(error=rtsm_error,
+                                              start_frequency=start_frequency,
+                                              stop_frequency=stop_frequency,
+                                              **error_sample) for error_sample in samples]
+        RTSMErrorSample.objects.bulk_create(rtsm_error_samples)
+    else:
+        for error_sample in samples:
+            RTSMErrorSample.objects.get_or_create(error=rtsm_error,
+                                                  start_frequency=start_frequency,
+                                                  stop_frequency=stop_frequency,
+                                                  **error_sample)
+
+    return rtsm_error
+
+
+@transaction.atomic
+def create_rtsm_test(rtsm_observation) -> RTSMObservation:
+    errors = rtsm_observation.pop('errors')
+
+    station_name = rtsm_observation.pop('station_name')
+    sas_id = rtsm_observation.pop('observation_id')
+    station, _ = create_station(dict(name=station_name,
+                                     type=station_type_from_station_name(station_name)))
+
+    rtsm_observation, _ = RTSMObservation.objects.get_or_create(station=station,
+                                                                sas_id=sas_id,
+                                                                **rtsm_observation)
+    for error in errors:
+        create_rtsm_error(rtsm_observation, error)
+    return rtsm_observation
+
+
 @shared_task()
 def insert_station_test(action_log_id: ActionLog, raw_tests):
     action_log = ActionLog.objects.get(pk=action_log_id)
@@ -95,4 +164,32 @@ def insert_station_test(action_log_id: ActionLog, raw_tests):
     except Exception as e:
         action_log.what = 'INSERT_FAILED'
         action_log.save()
-        raise e
\ No newline at end of file
+        raise e
+
+
+@shared_task()
+def insert_rtsm_test(action_log_id: ActionLog, request_data: Dict):
+    action_log = ActionLog.objects.get(pk=action_log_id)
+    try:
+        #logger.debug('handling raw request_data for %s', request_data)
+        content = request_data['content']
+
+        station_name = request_data['station_name']
+        #logger.debug('handling raw request_data for data %s', content)
+
+        entry = parse_rtsm_test(content)
+
+        entry.update(station_name=station_name)
+
+        rtsm_test = create_rtsm_test(entry)
+
+        action_log.what = 'INSERTED'
+        action_log.save()
+        logger.info('RTSM parsed successfully for obsid %d and station %s',
+                    rtsm_test.sas_id,
+                    rtsm_test.station.name)
+
+    except Exception as e:
+        action_log.what = 'INSERT_FAILED'
+        action_log.save()
+        raise e
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/urls.py b/LCU/Maintenance/DBInterface/monitoringdb/urls.py
index d2a37ed245fd00ae3d96307b5a84efb0b7d3085d..16d6c3520eef0a29d8aa1c79c5b524211b2aff4f 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/urls.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/urls.py
@@ -31,7 +31,8 @@ station_test_router.register(r'', StationTestViewSet)
 rtsm_router = routers.DefaultRouter()
 
 rtsm_router.register(r'errors', RTSMErrorsViewSet)
-rtsm_router.register(r'spectra', RTSMSpectrumViewSet)
+rtsm_router.register(r'error_sample', RTSMErrorSampleViewSet)
+
 rtsm_router.register(r'error_summary_plot', RTSMSummaryPlot, base_name='rtsm-summary-plot')
 
 rtsm_router.register(r'', RTSMObservationViewSet)
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
index 8d91dc0d5b1572f87c2054682974d3903f6e9de3..e182e42303852678dec3394d2ede8470e877c892 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py
@@ -1,28 +1,31 @@
 import datetime
 import logging
 from collections import OrderedDict
+from collections import defaultdict
 from math import ceil
 
+from django.utils import timezone
 import coreapi
 import coreschema
 import pytz
 from django.db.models import Count
-from lofar.maintenance.monitoringdb.models.component_error import ComponentError
-from lofar.maintenance.monitoringdb.models.rtsm import RTSMError, MODE_TO_COMPONENT, \
-    COMPONENT_TO_MODE
-from lofar.maintenance.monitoringdb.models.rtsm import RTSMObservation
-from lofar.maintenance.monitoringdb.models.station import Station
-from lofar.maintenance.monitoringdb.models.station_test import StationTest
-from lofar.maintenance.monitoringdb.models.wincc import WinCCAntennaStatus,\
-    latest_status_per_station_and_component_type,\
-    latest_status_per_station_and_component_type_antenna_id
 from rest_framework import status
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
 from rest_framework.schemas import ManualSchema
 from rest_framework.views import APIView
 
+from lofar.maintenance.monitoringdb.models.component_error import ComponentError
+from lofar.maintenance.monitoringdb.models.rtsm import RTSMError
+from lofar.maintenance.monitoringdb.models.rtsm import RTSMObservation
+from lofar.maintenance.monitoringdb.models.station import Station
+from lofar.maintenance.monitoringdb.models.station_test import StationTest
+from lofar.maintenance.monitoringdb.models.wincc import WinCCAntennaStatus, \
+    latest_status_per_station_and_component_type, \
+    latest_status_per_station_and_component_type_antenna_id
+
 logger = logging.getLogger(__name__)
+from lofar.maintenance.monitoringdb.models.rtsm import MODE_TO_COMPONENT
 
 
 def parse_date(date):
@@ -97,7 +100,6 @@ def antenna_id_polarization_from_rcu_type_polarization(rcu, type):
     :param type: type of the antenna
     :return: the antenna id and polarization
     :rtype: (int, str)
-    :rtype: int
     """
     polarization_index = rcu % 2
 
@@ -188,6 +190,31 @@ class ValidableReadOnlyView(APIView):
         return response
 
 
+def compute_error_summary(station_test:StationTest, selected_error_types=None):
+    component_error_summary = dict()
+
+    for component_error in station_test.component_errors.all():
+        component_type = component_error.component.type
+        component_error_type = component_error.type
+
+        if selected_error_types and component_error_type not in selected_error_types:
+            continue
+
+        if component_type not in component_error_summary:
+            component_error_summary[component_type] = {component_error_type: 1}
+        elif component_error_type not in component_error_summary[component_type]:
+            component_error_summary[component_type][component_error_type] = 1
+        else:
+            component_error_summary[component_type][component_error_type] += 1
+
+    return component_error_summary
+
+from django.db.models import Window, F
+from django.db.models.functions import Rank
+
+from silk.profiling.profiler import silk_profile
+
+
 class ControllerStationOverview(ValidableReadOnlyView):
     description = "Overview of the latest tests performed on the stations"
 
@@ -241,91 +268,101 @@ class ControllerStationOverview(ValidableReadOnlyView):
         )
     ]
 
-    def compute_response(self):
-        station_entities = Station.objects.all()
-        for group in self.station_group:
-            if group is not 'A':
-                station_entities = station_entities.filter(type=group)
+    def get_last_station_test_per_station(self, selected_stations):
+        expected_tests = len(selected_stations) * self.n_station_tests
 
-        # Since django preferes a ordered dict over a dict we make it happy... for now
-        response_payload = list()
-        for station_entity in station_entities:
-            station_payload = OrderedDict()
+        station_test_instances = StationTest.objects.filter(station__name__in=selected_stations). \
+                                     annotate(order=Window(
+            expression=Rank(),
+            partition_by=[F('station')],
+            order_by=F('start_datetime').desc())
+        ).order_by('order')[:expected_tests].\
+            select_related('station').prefetch_related('component_errors',
+                                                       'component_errors__component')
 
-            station_payload['station_name'] = station_entity.name
+        st_per_station = defaultdict(list)
+        for station_test in station_test_instances:
+            station_name = station_test.station.name
 
-            station_test_list = StationTest.objects.filter(
-                station__name=station_entity.name).order_by('-end_datetime')[:self.n_station_tests]
-            rtsm_list = RTSMObservation.objects.filter(
-                station__name=station_entity.name).order_by('-end_datetime')[:self.n_rtsm]
+            test_summary = dict()
+            test_summary.update(start_datetime=station_test.start_datetime)
+            test_summary.update(end_datetime=station_test.end_datetime)
+            test_summary.update(checks=station_test.checks)
 
-            station_payload['station_tests'] = list()
-            for station_test in station_test_list:
-                station_test_payload = OrderedDict()
-                component_errors = station_test.component_errors
-                selected_component_errors = component_errors
-                if self.error_types:
-                    selected_component_errors = component_errors.filter(type__in=self.error_types)
-                station_test_payload[
-                    'total_component_errors'] = selected_component_errors.count()
-                station_test_payload['start_datetime'] = station_test.start_datetime
-                station_test_payload['end_datetime'] = station_test.end_datetime
-                station_test_payload['checks'] = station_test.checks
-
-                component_errors_summary = selected_component_errors. \
-                    values('component__type', 'type').annotate(
-                    total=Count('type')).order_by('-total')
-                component_errors_summary_dict = OrderedDict()
-                for item in component_errors_summary:
-                    item_component_type = item['component__type']
-                    item_error_type = item['type']
-                    item_error_total = item['total']
-
-                    if item_component_type not in component_errors_summary_dict:
-                        component_errors_summary_dict[item_component_type] = OrderedDict()
-
-                    component_errors_summary_dict[item_component_type][item_error_type] = \
-                        item_error_total
-                station_test_payload['component_error_summary'] = component_errors_summary_dict
-
-                station_payload['station_tests'].append(station_test_payload)
-
-            station_payload['rtsm'] = list()
-            for rtsm in rtsm_list:
-                rtsm_payload = OrderedDict()
-                rtsm_payload['observation_id'] = rtsm.observation_id
-                rtsm_payload['start_datetime'] = rtsm.start_datetime
-                rtsm_payload['end_datetime'] = rtsm.end_datetime
-
-                unique_modes = [item['mode'] for item in
-                                rtsm.errors_summary.values('mode').distinct()]
-                rtsm_payload['mode'] = unique_modes
-
-                selected_rtsm_errors = rtsm.errors_summary
-                if self.error_types:
-                    selected_rtsm_errors = rtsm.errors_summary.filter(
-                        error_type__in=self.error_types)
+            component_error_summary = compute_error_summary(station_test, self.error_types)
 
-                rtsm_payload['total_component_errors'] = selected_rtsm_errors.count()
+            test_summary.update(component_error_summary=component_error_summary)
 
-                errors_summary = OrderedDict()
+            st_per_station[station_name] = test_summary
+        return st_per_station
 
-                errors_summary_query = selected_rtsm_errors.values('error_type').annotate(
-                    total=Count('error_type'))
+    def get_last_rtsm(self, selected_stations):
 
-                for error_summary in errors_summary_query:
-                    errors_summary[error_summary['error_type']] = error_summary['total']
+        expected_tests = len(selected_stations) * self.n_rtsm
 
-                rtsm_payload['error_summary'] = errors_summary
-                station_payload['rtsm'].append(rtsm_payload)
+        rtsm_instances = RTSMObservation.objects.filter(station__name__in=selected_stations). \
+                             annotate(order=Window(
+            expression=Rank(),
+            partition_by=[F('station')],
+            order_by=F('start_datetime').desc())
+        ).order_by('order')[:expected_tests].prefetch_related('errors', 'station')
+
+        rtsm_per_station = defaultdict(list)
+        for rtsm in rtsm_instances:
+
+            station_name = rtsm.station.name
+            observation_id = rtsm.sas_id
+            start_datetime = rtsm.start_datetime
+            end_datetime = rtsm.end_datetime
+
+            rtsm_summary = dict()
+            rtsm_summary.update(observation_id=observation_id,
+                                start_datetime=start_datetime,
+                                end_datetime=end_datetime)
+            error_summary = dict()
+
+            count = 0
+            for error in rtsm.errors.all():
+                if self.error_types and error not in self.error_types:
+                    continue
+
+                if error.error_type not in error_summary:
+                    error_summary[error.error_type] = 1
+                    count = 1
+
+                error_summary[error.error_type] += 1
+                count += 1
+
+            rtsm_summary.update(total_component_errors=count,
+                                error_summary=error_summary)
+
+            rtsm_per_station[station_name].append(rtsm_summary)
+
+        return rtsm_per_station
+
+    @silk_profile(name='Computing response')
+    def compute_response(self):
+        station_entities = Station.objects.all()
+
+        if 'A' not in self.station_group:
+            station_entities = station_entities.filter(type__in=self.station_group)\
+
+        station_entities = list(zip(*station_entities.values_list('name')))[0]
+
+
+        # Since django preferes a ordered dict over a dict we make it happy... for now
+        rtsm_per_station = self.get_last_rtsm(station_entities)
+        st_per_station = self.get_last_station_test_per_station(station_entities)
+        response_payload = [
+            dict(
+                station_name=station,
+                rtsm=rtsm_per_station[station],
+                station_tests=st_per_station[station]
+            )
+
+            for station in station_entities
+        ]
 
-            response_payload.append(station_payload)
-        if self.errors_only:
-            response_payload = filter(
-                lambda station_entry:
-                len(station_entry['station_tests']) + len(station_entry['rtsm']) > 0,
-                response_payload)
-        response_payload = sorted(response_payload, key=lambda item: item['station_name'])
         return Response(status=status.HTTP_200_OK, data=response_payload)
 
 
@@ -372,51 +409,36 @@ class ControllerStationTestsSummary(ValidableReadOnlyView):
         )
     ]
 
+    @silk_profile(name='Computing response')
     def compute_response(self):
-        self.lookback_time = datetime.timedelta(days=self.lookback_time)
 
-        station_test_list = StationTest.objects \
-            .filter(start_datetime__gte=datetime.date.today() - self.lookback_time) \
-            .order_by('-start_datetime', 'station__name')
-        for group in self.station_group:
-            if group is not 'A':
-                station_test_list = station_test_list.filter(station__type=group)
+        from_date = timezone.now() - timezone.timedelta(days=self.lookback_time)
+
+        station_test_entities = StationTest.objects.filter(start_datetime__gt=from_date).\
+            select_related('station')
+
+        if 'A' not in self.station_group:
+            station_test_entities = station_test_entities.filter(station__type__in=self.station_group)
+
+
+        station_test_entities = \
+            station_test_entities.prefetch_related('component_errors',
+                                                   'component_errors__component').order_by('-start_datetime')
 
         # Since django preferes a ordered dict over a dict we make it happy... for now
         response_payload = list()
 
-        for station_test in station_test_list:
-
-            station_test_payload = OrderedDict()
-            station_test_payload['station_name'] = station_test.station.name
-            selected_component_errors = station_test.component_errors
-            if (self.error_types):
-                selected_component_errors = selected_component_errors.filter(
-                    type__in=self.error_types)
-
-            station_test_payload[
-                'total_component_errors'] = selected_component_errors.count()
-            station_test_payload['date'] = station_test.start_datetime.strftime('%Y-%m-%d')
-            station_test_payload['start_datetime'] = station_test.start_datetime
-            station_test_payload['end_datetime'] = station_test.end_datetime
-            station_test_payload['checks'] = station_test.checks
-            component_errors_summary = selected_component_errors. \
-                values('component__type', 'type').annotate(
-                total=Count('type')).order_by('-total')
-            component_errors_summary_dict = OrderedDict()
-            for item in component_errors_summary:
-                item_component_type = item['component__type']
-                item_error_type = item['type']
-                item_error_total = item['total']
-
-                if item_component_type not in component_errors_summary_dict:
-                    component_errors_summary_dict[item_component_type] = OrderedDict()
-
-                component_errors_summary_dict[item_component_type][item_error_type] = \
-                    item_error_total
-            station_test_payload['component_error_summary'] = component_errors_summary_dict
-
-            response_payload.append(station_test_payload)
+        for station_test in station_test_entities:
+            station_test_summary = dict()
+            station_test_summary.update(station_name=station_test.station.name)
+            station_test_summary.update(total_component_errors=station_test.component_errors.count())
+            station_test_summary.update(start_datetime=station_test.start_datetime)
+            station_test_summary.update(end_datetime=station_test.end_datetime)
+            station_test_summary.update(date=station_test.start_datetime.strftime("%Y-%m-%d"))
+
+            component_error_summary = compute_error_summary(station_test, self.error_types)
+            station_test_summary.update(component_error_summary=component_error_summary)
+            response_payload.append(station_test_summary)
 
         if self.errors_only:
             response_payload = filter(
@@ -795,65 +817,59 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
     ]
 
     def collect_station_test_errors(self):
-        station_entry = Station.objects.filter(name=self.station_name).first()
+
+        component_errors = ComponentError.objects.filter(station_test__station__name=self.station_name).\
+                filter(station_test__start_datetime__range=(self.from_date, self.to_date)).\
+                select_related('station_test', 'component').\
+                prefetch_related('station_test__station','station_test__station_status').order_by('-station_test__start_datetime', 'component__station__name')
 
         response_payload = OrderedDict()
 
-        station_tests = station_entry.stationtest_set \
-            .filter(start_datetime__gte=self.from_date,
-                    end_datetime__lte=self.to_date)
+        for component_error in component_errors:
+            component_id = component_error.component.component_id
+            component_type = component_error.component.type
+            test_id = component_error.station_test.pk
 
-        failing_component_types = station_tests.distinct('component_errors__component__type'). \
-            exclude(component_errors__component__type__isnull=True). \
-            values_list('component_errors__component__type')
+            component_error_summary = dict(error_type=component_error.type,
+                                           details=component_error.details)
 
-        for failing_component_type in failing_component_types:
-            failing_component_type = failing_component_type[0]
-            component_type_errors_list = list()
+            if component_type not in response_payload:
+                response_payload[component_type] = dict()
 
-            response_payload[failing_component_type] = component_type_errors_list
+            per_component_type = response_payload[component_type]
 
-            for station_test in station_tests.order_by('-start_datetime'):
-                test_summary = OrderedDict()
-                test_summary['test_type'] = 'S'
-                test_summary['start_date'] = station_test.start_datetime
-                test_summary['end_date'] = station_test.end_datetime
-                component_errors_dict = OrderedDict()
-                antenna_statuses = latest_status_per_station_and_component_type(
-                    WinCCAntennaStatus.objects,
-                    self.station_name,
-                    failing_component_type,
-                    to_date=station_test.end_datetime)
+            if test_id not in per_component_type:
 
-                test_summary['status'] = antenna_statuses
-                test_summary['component_errors'] = component_errors_dict
-                component_errors = station_test.component_errors \
-                    .filter(component__type=failing_component_type)
-                if self.error_types:
-                    component_errors = component_errors.filter(type__in=self.error_types)
+                per_component_type[test_id] = dict(
+                    start_datetime = component_error.station_test.start_datetime,
+                    end_datetime = component_error.station_test.end_datetime,
+                    test_type = 'S',
+                    status=component_error.station_test.station_status,
+                    component_errors=dict()
+                )
 
-                for component_error in component_errors:
-                    component_id = component_error.component.component_id
-                    error_type = component_error.type
-                    details = component_error.details
-                    if component_id not in component_errors:
-                        component_errors_dict[str(component_id)] = list()
-                    component_errors_dict[str(component_id)] += [dict(error_type=error_type,
-                                                                      details=details)]
+            per_test = per_component_type[test_id]['component_errors']
+
+            if component_id not in per_test:
+                per_test[component_id] = [component_error_summary]
+            else:
+                per_test[component_id] += [component_error_summary]
 
-                component_type_errors_list.append(test_summary)
+
+        for type in response_payload:
+            response_payload[type] = list(response_payload[type].values())
 
         return response_payload
 
     def collect_rtsm_errors(self):
         station_entry = Station.objects.filter(name=self.station_name).first()
         response_payload = OrderedDict()
-        rtsm_observations = station_entry.rtsmobservation_set \
+        rtsm_observations = RTSMObservation.objects.filter(station=station_entry) \
             .filter(start_datetime__gte=self.from_date,
                     end_datetime__lte=self.to_date)
 
-        failing_component_modes = rtsm_observations.exclude(errors_summary__isnull=True).distinct(
-            'errors_summary__mode').values_list('errors_summary__mode')
+        failing_component_modes = rtsm_observations.exclude(errors__isnull=True).distinct(
+            'errors__mode').values_list('errors__mode')
         for observing_mode in failing_component_modes:
 
             observing_mode = observing_mode[0]
@@ -867,7 +883,7 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
                 rtsm_summary['test_type'] = 'R'
                 rtsm_summary['start_date'] = rtsm_observation.start_datetime
                 rtsm_summary['end_date'] = rtsm_observation.end_datetime
-                rtsm_summary['observation_id'] = rtsm_observation.observation_id
+                rtsm_summary['observation_id'] = rtsm_observation.sas_id
                 antenna_statuses = latest_status_per_station_and_component_type(
                     WinCCAntennaStatus.objects,
                     self.station_name,
@@ -877,7 +893,7 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
                 component_errors_dict = OrderedDict()
                 rtsm_summary['component_errors'] = component_errors_dict
 
-                component_errors = rtsm_observation.errors_summary \
+                component_errors = rtsm_observation.errors \
                     .filter(mode=observing_mode) \
                     .values('error_type', 'start_frequency',
                             'stop_frequency', 'percentage',
@@ -938,22 +954,23 @@ class ControllerStationComponentErrors(ValidableReadOnlyView):
             station_test_errors = self.collect_station_test_errors()
 
         if self.test_type in ['R', 'B']:
-            rtsm_errors = self.collect_rtsm_errors()
+            rtsm_errors = {} # self.collect_rtsm_errors()
 
         payload = OrderedDict()
         for component_type in set(rtsm_errors.keys() | station_test_errors.keys()):
             station_test_errors_per_type = station_test_errors.get(component_type, [])
             rtsm_errors_per_type = rtsm_errors.get(component_type, [])
             payload[component_type] = OrderedDict()
-
-            payload[component_type]['errors'] = sorted(station_test_errors_per_type + rtsm_errors_per_type,
-                                             key=lambda item: item['start_date'], reverse=True)
-
-            payload[component_type]['current_status'] =\
-                latest_status_per_station_and_component_type(WinCCAntennaStatus.objects,
-                                                             self.station_name,
-                                                             component_type,
-                                                             to_date=datetime.datetime.now())
+            payload[component_type]['errors'] = station_test_errors_per_type
+            #payload[component_type]['errors'] = sorted(
+            #    station_test_errors_per_type + rtsm_errors_per_type,
+            #    key=lambda item: item['start_date'], reverse=True)
+
+            #payload[component_type]['current_status'] = \
+            #    latest_status_per_station_and_component_type(WinCCAntennaStatus.objects,
+            #                                                 self.station_name,
+            #                                                 component_type,
+            #                                                 to_date=datetime.datetime.now())
         return Response(status=status.HTTP_200_OK, data=payload)
 
 
@@ -1079,17 +1096,17 @@ class ControllerStationComponentElementErrors(ValidableReadOnlyView):
                                                            self.component_type)
 
         rtsm_errors = RTSMError.objects.values('observation__pk',
-                                                      'observation__start_datetime',
-                                                      'observation__end_datetime',
-                                                      'observation__observation_id',
-                                                      'observation__station__name',
-                                                      'pk',
-                                                      'rcu',
-                                                      'mode',
-                                                      'error_type',
-                                                      'percentage',
-                                                      'count',
-                                                      'observation__samples').filter(
+                                               'observation__start_datetime',
+                                               'observation__end_datetime',
+                                               'observation__observation_id',
+                                               'observation__station__name',
+                                               'pk',
+                                               'rcu',
+                                               'mode',
+                                               'error_type',
+                                               'percentage',
+                                               'count',
+                                               'observation__samples').filter(
             observation__start_datetime__gt=self.from_date,
             observation__end_datetime__lt=self.to_date,
             observation__station__name=self.station_name,
@@ -1204,4 +1221,3 @@ class ControllerStationComponentElementErrors(ValidableReadOnlyView):
             self.antenna_id,
             to_date=datetime.datetime.now())
         return Response(status=status.HTTP_200_OK, data=payload)
-
diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py b/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py
index d41760307a5bc5c9ae1f2b64272017e16b4dcff9..1a7a78e3220c959c27e440ab06f6cae20f325803 100644
--- a/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py
+++ b/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py
@@ -1,22 +1,18 @@
 from .common import *
-from ..models.rtsm import RTSMObservation, RTSMError, RTSMSpectrum
-from ..serializers.rtsm import RTSMObservationSerializer, RTSMErrorSerializer, \
-    RTSMSpectrumSerializer, RTSMSummaryPlotSerializer, RTSMError
-from ..rtsm_test_raw_parser import parse_rtsm_test
+from ..models.rtsm import RTSMObservation
+from ..models.log import ActionLog
+from ..serializers.rtsm import RTSMObservationSerializer, RTSMErrorSerializer,\
+    RTSMSummaryPlotSerializer, RTSMError, RTSMErrorSample, RTSMErrorSampleSerializer
+from datetime import datetime
+from lofar.maintenance.monitoringdb.tasks.insert_raw_tests import insert_rtsm_test
 from django.shortcuts import get_object_or_404
-from django.http import Http404, HttpResponseServerError
+from django.http import Http404
 import os
 from lofar.maintenance.monitoringdb.tasks.generate_plots import check_error_summary_plot
 
 logger = logging.getLogger('views')
 
 
-class RTSMSpectrumViewSet(viewsets.ReadOnlyModelViewSet):
-    queryset = RTSMSpectrum.objects.all()
-    serializer_class = RTSMSpectrumSerializer
-    filter_fields = ['rtsm_error']
-
-
 class RTSMErrorsViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = RTSMError.objects.all()
     serializer_class = RTSMErrorSerializer
@@ -29,6 +25,12 @@ class RTSMObservationViewSet(viewsets.ReadOnlyModelViewSet):
     filter_fields = '__all__'
 
 
+class RTSMErrorSampleViewSet(viewsets.ReadOnlyModelViewSet):
+    queryset = RTSMErrorSample.objects.all()
+    serializer_class = RTSMErrorSampleSerializer
+    exclude_fields = 'bad_spectrum', 'average_spectrum'
+
+
 class RTSMSummaryPlot(viewsets.ViewSet):
     """
     Get the summary plot associated to the error summary given
@@ -74,37 +76,28 @@ def insert_raw_rtsm_test(request):
 
         if 'content' in request.data:
             try:
-                logger.debug('handling raw request for %s', request)
-                content = request.data['content']
-                station_name = request.data['station_name']
-                logger.debug('handling raw request for data %s', content)
-                entry = parse_rtsm_test(content)
-                entry.update(station_name=station_name)
-
-                logger.info('RTSM parsed successfully for obsid %d and station %s',
-                            entry['observation_id'],
-                            entry['station_name'])
-
-                try:
-                    rtsm_item = RTSMObservationSerializer().create(dict(entry))
-                    rtsm_id = rtsm_item.id
-                except ItemAlreadyExists as e:
-                    return Response(status=status.HTTP_200_OK,
-                                    data='RTSM test was already present with id {}\n'.format(e.instance_id))
-                logger.info('RTSM inserted successfully for obsid %d and station %s',
-                            entry['observation_id'],
-                            entry['station_name'])
-            except KeyError as e:
-                logger.exception("raw station info malformed %s: %s",
-                                 request.data['content'], e)
-                logger.info('station test malformed skipping insertion.')
-                return Response(status=status.HTTP_406_NOT_ACCEPTABLE, data='skipping insertion RTSM malformed')
+                logger.info('handling raw request for %s', request)
+
+                logger.info('handling raw request for data %s', request.data)
+
+                action_log = ActionLog(who=request.META['REMOTE_ADDR'],
+                                       what='INSERT_REQUESTED',
+                                       when=datetime.now(),
+                                       model_name='StationTest',
+                                       entry_id=-1)
+
+                action_log.save()
+
+                insert_rtsm_test.delay(action_log.pk, request.data)
+
+                return Response(status=status.HTTP_200_OK,
+                                data='RTSM tests insert acknowledged.'
+                                     ' Check log for id %s' % action_log.pk)
             except Exception as e:
-                logger.exception("exception occurred while parsing raw station info %s: %s",
-                                 request.data['content'], e)
+                logger.exception("exception occurred while parsing raw station info: %s", e)
                 return Response(exception=True,
                                 data="the post message is not correct." +
-                                     " It has to be of the form {content:[RAWSTRING], station_name:[STATION_NAME]}: %s. Request provided %s" % (
+                                     " It has to be of the form \{'content':[RAWSTRING]\}: %s. Request provided %s" % (
                                          e, request),
                                 status=status.HTTP_400_BAD_REQUEST)
         else:
@@ -112,7 +105,3 @@ def insert_raw_rtsm_test(request):
                             data="the post message is not correct." +
                                  " It has to be of the form \{'content':[RAWSTRING]\}",
                             status=status.HTTP_400_BAD_REQUEST)
-        logger.info('request processed correctly inserted')
-
-        return Response(status=status.HTTP_200_OK,
-                        data='RTSM test has been inserted with id {}\n'.format(rtsm_id))
diff --git a/LCU/Maintenance/MDB_tools/lib/client.py b/LCU/Maintenance/MDB_tools/lib/client.py
index 64036c4cfb3d9fd8ef4ba23401adbe2cba97ff7b..b84538036130a1e1bab354cf0c031efdc1958d78 100644
--- a/LCU/Maintenance/MDB_tools/lib/client.py
+++ b/LCU/Maintenance/MDB_tools/lib/client.py
@@ -64,10 +64,14 @@ def is_station_test(content):
     :return: True if the content refers to a RTSM and it is not empty
     """
     if content:
-        pattern = r'(^\d*,[A-z]{3},[\d-]{3})'
+        pattern = r'(^#.*)|(^\d*,[A-z]{3},[\d-]{3})'
         number_of_lines = len(content.splitlines())
         pattern_matching_lines = re.findall(pattern, content, re.MULTILINE)
         number_pattern_matching_lines = len(pattern_matching_lines)
+        logger.debug('Station Test: number of matched lines %d/%d number of lines in files',
+                     number_pattern_matching_lines,
+                     number_of_lines)
+
         return number_of_lines == number_pattern_matching_lines
     else:
         return False
@@ -97,6 +101,9 @@ OBS-ID-INFO=641999,1520208240.000,1520211840.000,59
         number_of_lines = len(content.splitlines())
         pattern_matching_lines = re.findall(pattern, content, re.MULTILINE)
         number_pattern_matching_lines = len(pattern_matching_lines)
+        logger.debug('RTSM: number of matched lines %d/%d number of lines in files',
+                     number_pattern_matching_lines,
+                     number_of_lines)
         return number_of_lines == number_pattern_matching_lines
     else:
         return False
diff --git a/MAC/CMakeLists.txt b/MAC/CMakeLists.txt
index 6ad267da65bc4ae5f951af579c5a00d4b3fbbd0d..c1c9d2dcfb965a6dbdd6572cc3300a12fdf420e8 100644
--- a/MAC/CMakeLists.txt
+++ b/MAC/CMakeLists.txt
@@ -11,6 +11,10 @@ lofar_add_package(WinCC_Datapoints Deployment/data/PVSS)
 lofar_add_package(OTDB_Comps Deployment/data/OTDB)
 lofar_add_package(StaticMetaData Deployment/data/StaticMetaData)
 lofar_add_package(WinCCPublisher WinCCPublisher)
+lofar_add_package(WinCCREST)
+lofar_add_package(WinCCDBBridge)
+
+
 lofar_add_package(TaskManagement Services/TaskManagement)
 lofar_add_package(TBB)
 
diff --git a/MAC/WinCCDBBridge/CMakeLists.txt b/MAC/WinCCDBBridge/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..15920ec401f759d14377860b23d0b266c5050178
--- /dev/null
+++ b/MAC/WinCCDBBridge/CMakeLists.txt
@@ -0,0 +1,20 @@
+lofar_package(WinCCDBBridge 1.0 DEPENDS WinCCWrapper)
+
+include(LofarFindPackage)
+lofar_find_package(WINCC REQUIRED)
+
+find_package(Qt5 COMPONENTS Core Network Sql Xml REQUIRED)
+
+lofar_find_package(UnitTest++)
+
+# Auto generate moc files
+set(CMAKE_AUTOMOC ON)
+# As moc files are generated in the binary dir, tell CMake
+# to always look for includes there:
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+include_directories(${WinCCWrapper_SOURCE_DIR}/include/)
+
+add_subdirectory(etc)
+add_subdirectory(src)
+add_subdirectory(test)
diff --git a/MAC/WinCCDBBridge/DB.ini b/MAC/WinCCDBBridge/DB.ini
new file mode 100644
index 0000000000000000000000000000000000000000..6a9648414d6ec8a949120c7288454338a0c3af5a
--- /dev/null
+++ b/MAC/WinCCDBBridge/DB.ini
@@ -0,0 +1,7 @@
+[Database]
+type=QSQLITE
+name=datapoints.db
+address=localhost
+port=
+user=
+pass=
diff --git a/MAC/WinCCDBBridge/Docker/Dockerfile b/MAC/WinCCDBBridge/Docker/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..b0fefa428c181f6b113308438ff5d4bab0e7d926
--- /dev/null
+++ b/MAC/WinCCDBBridge/Docker/Dockerfile
@@ -0,0 +1,54 @@
+#DOCKER FILE TO BUILD THE WinCCDBBridge service that connects to WinCC to copy the datapoint value
+#into any external sql database (using the QT libraries)
+# THE WINCC RPM HAVE TO BE AVAILABLE IN THE DIRECTORY IN WHICH THE DOCKER IMAGE IS BUILT
+# ./build/
+ARG SOURCES_ROOT=./
+
+# THE BASE IMAGE STAGE
+# it contains all the dependecies to run the application with WinCC
+FROM centos:latest as base
+WORKDIR /root/wincc_rpm
+COPY ./build/*3.14*.rpm ./
+RUN yum -y install libicu
+RUN yum -y install *3.14*.rpm
+RUN yum -y install python boost qt5-qtbase
+ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/WinCC_OA/3.14/bin/:/opt/WinCC_OA/3.14/api/lib.linux/
+RUN ldconfig
+ENV PATH=$PATH:/opt/WinCC_OA/3.14/bin/
+
+# THE BUILDER IMAGE STAGE
+# In this stage all the development tools are installed
+# the source code pulled from svn and the software built 
+# as it is necessary
+FROM base as builder
+RUN yum -y groupinstall "Development Tools"
+RUN yum -y install cmake python-devel boost-devel qt5-qtbase-devel
+
+WORKDIR /root
+COPY ${SOURCES_ROOT} ./
+RUN mkdir -p build/gnucxx11_debug/
+WORKDIR /root/build/gnucxx11_debug/
+ARG INSTALLDIR=/opt/lofar/
+RUN cmake ../../ -DBUILD_PACKAGES=WinCCDBBridge -DUSE_LOG4CPLUS=OFF -DWINCC_ROOT_DIR=/opt/WinCC_OA/3.14 -DCMAKE_INSTALL_PREFIX=$INSTALLDIR
+ARG BUILD_PROCESSES=4
+RUN make -j $BUILD_PROCESSES && make install
+
+# DEPLOY IMAGE STAGE
+# The image to deploy is built at this step
+# all the develping dependecies are not useful anymore
+# hence a image pruned with unnecessary dependecies is built
+FROM base as deploy
+COPY --from=builder /opt/lofar /opt/lofar
+WORKDIR /opt/lofar
+ARG WINCCPROJECT_DIR=$INSTALLDIR/var/wincc/LOFAR/
+ENV WINCCPROJ=LOFAR
+ADD MAC/WinCCDBBridge/Docker/LOFAR.tar.gz $WINCCPROJECT_DIR
+WORKDIR $WINCCPROJECT_DIR
+RUN PVSSutil.sh RegisterProject 1.0 $WINCCPROJECT_DIR /opt/WinCC_OA/3.14/
+ADD MAC/WinCCDBBridge/Docker/run.sh /opt/lofar/bin/run.sh
+ENV PATH=$PATH:/opt/lofar/bin
+ENV QT_LOGGING_RULES="*.debug=true"
+ENV QT_MESSAGE_PATTERN="%{time} - %{appname} %{pid}/%{threadid}:  %{message}"
+ENTRYPOINT ["/opt/lofar/bin/run.sh"]
+CMD ["-h"]
+
diff --git a/MAC/WinCCDBBridge/Docker/LOFAR.tar.gz b/MAC/WinCCDBBridge/Docker/LOFAR.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..95d3deab96e174eeb9abbd88e72603722bf14fa5
Binary files /dev/null and b/MAC/WinCCDBBridge/Docker/LOFAR.tar.gz differ
diff --git a/MAC/WinCCDBBridge/Docker/run.sh b/MAC/WinCCDBBridge/Docker/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9d2c32fccc5be63403c0f30c21cbc938c477a60c
--- /dev/null
+++ b/MAC/WinCCDBBridge/Docker/run.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+
+exec /opt/lofar/bin/winccbridge "$@" ${WINCCPROJ}
diff --git a/MAC/WinCCDBBridge/etc/CMakeLists.txt b/MAC/WinCCDBBridge/etc/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/MAC/WinCCDBBridge/etc/DB.ini b/MAC/WinCCDBBridge/etc/DB.ini
new file mode 100644
index 0000000000000000000000000000000000000000..6a9648414d6ec8a949120c7288454338a0c3af5a
--- /dev/null
+++ b/MAC/WinCCDBBridge/etc/DB.ini
@@ -0,0 +1,7 @@
+[Database]
+type=QSQLITE
+name=datapoints.db
+address=localhost
+port=
+user=
+pass=
diff --git a/MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xml b/MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xml
new file mode 100644
index 0000000000000000000000000000000000000000..37cecbbff1b05953ad94faf4ef4f39f394a51c68
--- /dev/null
+++ b/MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xml
@@ -0,0 +1,34 @@
+<Table name="antenna_statuses">
+	<Datapoints>
+		<Datapoint name="System1:T1.level" alias="WaterLevel1">
+			<description>Test datapoint in the GettingStarted example of WinCCOA</description>
+			<CheckpointInterval value="-3" />
+			<index>
+				<regex>
+					<![CDATA[(?<system>\w*)\.((?<attribute>\w*))]]>
+				</regex>
+				<attribute name="system" alias="system" type="STRING"/>
+				<attribute name="attribute" alias="attribute" type="STRING"/>
+			</index>
+			<attributes>
+				<attribute name=":_original.._value" alias="value" type="STRING" unit="m" min="-0.3" max="0.3"/>
+				<attribute name=":_original.._stime" alias="lastChangeDate" type="STRING" unit=""/>
+			</attributes>
+		</Datapoint>
+		<Datapoint name="System1:T2.level" alias="WaterLevel2">
+			<description>Test datapoint in the GettingStarted example of WinCCOA</description>
+			<CheckpointInterval value="-3" />
+			<index>
+				<regex>
+					<![CDATA[(?<system>\w*)\.((?<attribute>\w*))]]>
+				</regex>
+				<attribute name="system" alias="system" type="STRING"/>
+				<attribute name="attribute" alias="attribute" type="STRING"/>
+			</index>
+			<attributes>
+				<attribute name=":_original.._value" alias="value" type="STRING" unit=""/>
+				<attribute name=":_original.._stime" alias="lastChangeDate" type="STRING" unit=""/>
+			</attributes>
+		</Datapoint>
+	</Datapoints>
+</Table>
diff --git a/MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xsd b/MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xsd
new file mode 100644
index 0000000000000000000000000000000000000000..963fec103337ba583d938cdafb5be93fa338ef3c
--- /dev/null
+++ b/MAC/WinCCDBBridge/etc/Datapoints/antennaStatuses.xsd
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+  <xs:element name="Table">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element ref="Datapoints"/>
+      </xs:sequence>
+      <xs:attribute name="name" use="required" type="xs:NCName"/>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="Datapoints">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element maxOccurs="unbounded" ref="Datapoint"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="Datapoint">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element ref="description"/>
+        <xs:element ref="CheckpointInterval"/>
+        <xs:element ref="index"/>
+        <xs:element ref="attributes"/>
+      </xs:sequence>
+      <xs:attribute name="name" use="required" type="xs:NMTOKEN"/>
+      <xs:attribute name="alias" use="required" type="xs:NCName"/>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="description" type="xs:string"/>
+  <xs:element name="CheckpointInterval">
+    <xs:complexType>
+      <xs:attribute name="value" use="required" type="xs:integer"/>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="index">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element ref="regex"/>
+        <xs:element maxOccurs="unbounded" ref="attribute"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="regex" type="xs:string"/>
+  <xs:element name="attributes">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element maxOccurs="unbounded" ref="attribute"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="attribute">
+    <xs:complexType>
+      <xs:attribute name="name" use="required" type="xs:NMTOKEN"/>
+      <xs:attribute name="alias" use="required" type="xs:NCName"/>
+      <xs:attribute name="type" use="required" type="xs:NCName"/>
+      <xs:attribute name="max" type="xs:decimal"/>
+      <xs:attribute name="min" type="xs:decimal"/>
+      <xs:attribute name="unit" type="xs:string"/>
+      <xs:attribute name="value" type="xs:string"/>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>
diff --git a/MAC/WinCCDBBridge/src/CMakeLists.txt b/MAC/WinCCDBBridge/src/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e97fdf40375e2ee97c6f61ba35db0e396104783c
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/CMakeLists.txt
@@ -0,0 +1,25 @@
+
+include(LofarFindPackage)
+cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
+file(GLOB_RECURSE WinCCDispatcher_SRCS *.cpp *.hpp)
+
+
+include_directories(${WinCCWrapper_SOURCE_DIR}/include/)
+include_directories(
+    ${CMAKE_SOURCE_DIR}
+    ${CMAKE_BINARY_DIR}
+    )
+include_directories(${Qt5_INCLUDE_DIRS})
+
+# Create the application
+lofar_add_bin_program(winccbridge ${WinCCDispatcher_SRCS})
+
+target_link_libraries(winccbridge
+    Qt5::Core
+    Qt5::Network
+    Qt5::Sql
+    Qt5::Xml
+    wincc_wrapper
+)
+
+target_compile_options(winccbridge PRIVATE -std=c++11)
diff --git a/MAC/WinCCDBBridge/src/DatapointAttribute.cpp b/MAC/WinCCDBBridge/src/DatapointAttribute.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..44bae647d71914febf1d3c29c5097513e4712617
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/DatapointAttribute.cpp
@@ -0,0 +1,180 @@
+#include "DatapointAttribute.hpp"
+#include "DatapointRule.hpp"
+
+#include <limits>
+
+#include <QSqlField>
+#include <QVariant>
+#include <QSqlDatabase>
+#include <QSqlDriver>
+#include <QStringList>
+#include <QDomElement>
+
+#include <QDebug>
+
+DatapointAttribute::DatapointAttribute():
+    m_max{std::numeric_limits<double>::max()},
+    m_min{std::numeric_limits<double>::min()},
+    checkBoundaries{false}
+{
+
+}
+
+DatapointAttribute::DatapointAttribute(QString name, QString alias, QString type, QString unit, QString value, double minValue, double maxValue, bool check):
+    m_name{name}, m_alias{alias}, m_type{type}, m_unit{unit}, m_value{value}, m_max{maxValue}, m_min{minValue}, checkBoundaries{check}
+{
+
+}
+
+QString const DatapointAttribute::formatValue() const{
+    if(m_type == "STRING"){
+
+    	QSqlField field{m_alias, QVariant::String};
+    	field.setValue(m_value);
+
+    	QSqlDatabase db = QSqlDatabase::database();
+
+    	if(db.isValid() == true) {
+    		return db.driver()->formatValue(field);
+    	}else{
+    		qWarning() << "Default database it is not configured";
+    		return "\'" + m_value + "\'";
+    	}
+    }else{
+        return m_value;
+    }
+}
+
+
+QString DatapointAttribute::composeDatapointURI(DatapointRule &rule){
+    return rule.name() + m_name;
+}
+
+void DatapointAttribute::setParentRule(DatapointRule& rule)
+{
+    m_uri = composeDatapointURI(rule);
+}
+
+const QString& DatapointAttribute::URI() const
+{
+    return m_uri;
+}
+
+void DatapointAttribute::setURI(const QString& uri)
+{
+    m_uri = uri;
+}
+
+const QString& DatapointAttribute::name() const
+{
+    return m_name;
+}
+
+void DatapointAttribute::setName(const QString& name)
+{
+    m_name = name;
+}
+
+const QString& DatapointAttribute::alias() const
+{
+    return m_alias;
+}
+
+void DatapointAttribute::setAlias(const QString& alias)
+{
+    m_alias = alias;
+}
+
+const QString& DatapointAttribute::type() const
+{
+    return m_type;
+}
+
+void DatapointAttribute::setType(const QString& type)
+{
+    m_type = type;
+}
+
+const QString& DatapointAttribute::unit() const
+{
+    return m_unit;
+}
+
+void DatapointAttribute::setUnit(const QString& unit)
+{
+    m_unit = unit;
+}
+
+const QString& DatapointAttribute::value() const
+{
+    return m_value;
+}
+
+void DatapointAttribute::setValue(const QString& value)
+{
+    m_value = value;
+}
+
+const double& DatapointAttribute::upperLimit() const
+{
+    return m_max;
+}
+
+void DatapointAttribute::setUpperLimit(const double upperLimit)
+{
+    m_max = upperLimit;
+}
+
+const double& DatapointAttribute::lowerLimit() const
+{
+    return m_min;
+}
+
+void DatapointAttribute::setLowerLimit(const double lowerLimit)
+{
+    m_min = lowerLimit;
+}
+
+
+const QString DatapointAttribute::toString() const {
+    return "DatapointAttribute(" + QStringList({m_name, m_alias, m_type, m_unit, m_value}).join(", ") + ")";
+}
+
+bool DatapointAttribute::isBoundariesCheckEnabled(){
+    return checkBoundaries;
+}
+
+void DatapointAttribute::enableBoundariesCheck(){
+    checkBoundaries = true;
+}
+
+bool DatapointAttribute::isInRange(QString value){
+    if(checkBoundaries){
+        const double d_value = value.toDouble();
+        return d_value > m_min && d_value < m_max;
+    }else{
+        return false;
+    }
+}
+
+DatapointAttribute readAttribute(const QDomElement &attributeEntry){
+    DatapointAttribute attribute;
+
+    attribute.m_alias = attributeEntry.attribute("alias", "");
+    attribute.m_name = attributeEntry.attribute("name", "");
+    attribute.m_type = attributeEntry.attribute("type", "");
+    attribute.m_unit = attributeEntry.attribute("unit", "");
+    attribute.m_value = attributeEntry.attribute("value", "");
+
+
+    if(attributeEntry.hasAttribute("max")){
+        attribute.enableBoundariesCheck();
+        attribute.m_max = attributeEntry.attribute("max").toDouble();
+    }
+
+    if(attributeEntry.hasAttribute("min")){
+        attribute.enableBoundariesCheck();
+        attribute.m_min = attributeEntry.attribute("min").toDouble();
+    }
+    return attribute;
+}
diff --git a/MAC/WinCCDBBridge/src/DatapointAttribute.hpp b/MAC/WinCCDBBridge/src/DatapointAttribute.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ef1b223e0982d51799b82a549cc1725a52c597a3
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/DatapointAttribute.hpp
@@ -0,0 +1,76 @@
+#ifndef DATAPOINT_ATTRIBUTE_H
+#define DATAPOINT_ATTRIBUTE_H
+
+#include <QString>
+class DatapointRule;
+class QDomElement;
+
+/*! \class DatapointAttribute
+    \brief Define an attribute for a given datapoint.
+    The DatapointAttribute describes an attribute of a datapoint that could be for example a last modification date or a value.
+    If it is a value it can be subject of jittering. For this reason, it can be possible to specify a range within which the value
+    can be without triggering an update of the database.
+    It has to be specified as in the following XML fragment (ex.
+        <attribute name=":_original.._value" alias="value" type="STRING" unit=""/>
+    )
+*/
+
+
+class DatapointAttribute
+{
+public:
+    DatapointAttribute();
+    DatapointAttribute(QString name, QString alias, QString type, QString unit, QString value, double minValue, double maxValue, bool check);
+
+    const QString formatValue() const;
+    const QString toString() const;
+
+    const QString & URI() const;
+    void setURI(const QString &uri);
+
+    bool isBoundariesCheckEnabled();
+    void enableBoundariesCheck();
+
+    bool isInRange(QString value);
+
+    const QString & name() const;
+    void setName(const QString & name);
+
+    const QString & alias() const;
+    void setAlias(const QString & alias);
+
+    const QString & type() const;
+    void setType(const QString & type);
+
+    const QString & unit() const;
+    void setUnit(const QString & unit);
+
+    const QString & value() const;
+    void setValue(const QString & value);
+
+    const double & upperLimit() const;
+    void setUpperLimit(const double upperLimit);
+
+    const double & lowerLimit() const;
+    void setLowerLimit(const double lowerLimit);
+
+    void setParentRule(DatapointRule & rule);
+
+private:
+    QString m_name;
+    QString m_alias;
+    QString m_type;
+    QString m_unit;
+    QString m_value;
+    double m_max;
+    double m_min;
+    QString m_uri;
+    QString composeDatapointURI(DatapointRule & rule);
+    bool checkBoundaries;
+friend DatapointAttribute readAttribute(const QDomElement &);;
+};
+
+
+DatapointAttribute readAttribute(const QDomElement &attributeEntry);
+
+#endif // DATAPOINT_ATTRIBUTE_H
diff --git a/MAC/WinCCDBBridge/src/DatapointIndex.cpp b/MAC/WinCCDBBridge/src/DatapointIndex.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe952072768514ae72d3ae559611d2a357f9f158
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/DatapointIndex.cpp
@@ -0,0 +1,52 @@
+#include "DatapointIndex.hpp"
+#include "DatapointAttribute.hpp"
+
+#include <QRegularExpression>
+#include <QDomElement>
+#include <QDebug>
+
+DatapointIndex::DatapointIndex()
+{
+
+}
+
+const QString DatapointIndex::toString() const {
+     return "DatapointIndex(\"" + QStringList({pattern}).join(", ") + "\")";
+}
+
+void DatapointIndex::parseDPIndex(QString index)
+{
+    QRegularExpression regexParser{pattern};
+    QRegularExpressionMatch matched = regexParser.match(index);
+
+
+    if(matched.hasMatch()){
+        for(DatapointAttribute & key: m_keys){
+            const QString index_id{key.name()};
+            key.setValue(matched.captured(index_id));
+        }
+    }
+
+}
+
+const std::vector<DatapointAttribute>& DatapointIndex::keys()
+{
+    return m_keys;
+}
+
+
+
+const DatapointIndex readIndexRule(const QDomElement & element, const QString & dpName){
+    DatapointIndex dpIndex;
+
+    dpIndex.pattern = element.firstChildElement("regex").text();
+    QDomNodeList attributes = element.elementsByTagName("attribute");
+    for(int i=0; i < attributes.size(); i++){
+        DatapointAttribute attribute{readAttribute(attributes.item(i).toElement())};
+        dpIndex.m_keys.push_back(attribute);
+    }
+
+    dpIndex.parseDPIndex(dpName);
+
+    return dpIndex;
+}
diff --git a/MAC/WinCCDBBridge/src/DatapointIndex.hpp b/MAC/WinCCDBBridge/src/DatapointIndex.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..21cf9724234fdd006b23e73d12586669515bb36c
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/DatapointIndex.hpp
@@ -0,0 +1,40 @@
+#ifndef DATAPOINT_INDEX_H
+#define DATAPOINT_INDEX_H
+#include <QString>
+
+class DatapointAttribute;
+class QDomElement;
+
+/*! \class DatapointIndex
+    \brief Define a unique index for a given datapoint.
+    The DatapointIndex is meant to reflect a DB table index.
+    Such index is derived from the datapoint name through a regex to give the widest range of possibilities in the configuration file.
+    In particular, the XML fragment parsed by the function readIndexRule and used to instantiate the DatapointIndex is for example:
+        <index>
+            <regex>
+                <![CDATA[(?<system>\w*)\.((?<attribute>\w*))]]>
+            </regex>
+            <attribute name="system" alias="system" type="STRING"/>
+            <attribute name="attribute" alias="attribute" type="STRING"/>
+        </index>
+    The index keys are picked up from the named regex capture group. Therefore, each capture group that has to be used as an index field
+    has to be named.
+*/
+
+class DatapointIndex
+{
+public:
+    DatapointIndex();
+
+    void parseDPIndex(QString index);
+    const QString toString() const;
+    const std::vector<DatapointAttribute> & keys();
+private:
+    std::vector<DatapointAttribute> m_keys;
+    QString pattern;
+friend const DatapointIndex readIndexRule(const QDomElement &, const QString &);
+};
+
+const DatapointIndex readIndexRule(const QDomElement & element, const QString & dpName);
+
+#endif // DATAPOINT_INDEX_H
diff --git a/MAC/WinCCDBBridge/src/DatapointRule.cpp b/MAC/WinCCDBBridge/src/DatapointRule.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0c4663909d0217f97164c8f4c5b7fbc456341e24
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/DatapointRule.cpp
@@ -0,0 +1,199 @@
+#include "DatapointRule.hpp"
+
+#include "DatapointIndex.hpp"
+#include "DatapointAttribute.hpp"
+
+#include <QStringList>
+
+#include <QSqlQuery>
+#include <QTimer>
+
+#include <QDebug>
+#include <QDomElement>
+#include <QDomDocument>
+
+DatapointRule::DatapointRule(): statement{""}
+{
+
+}
+
+std::vector<std::string> DatapointRule::getDatapointsURI()
+{
+    using std::vector;
+    using std::string;
+
+    vector<string> datapointURIs;
+    for(auto & attr: m_attributes){
+        datapointURIs.push_back(attr.second.URI().toStdString());
+    }
+    return datapointURIs;
+}
+
+DatapointRule::DatapointRule(QString name, QString alias, QString description):
+    m_name{name}, m_alias{alias}, m_description{description}, statement{""}, checkBoundaries{false}
+{
+
+}
+
+void DatapointRule::addAttribute(DatapointAttribute attribute){
+    attribute.setParentRule(*this);
+
+    checkBoundaries |= attribute.isBoundariesCheckEnabled();
+
+    m_attributes[attribute.URI().toStdString()] = attribute;
+}
+
+const QString DatapointRule::toString() const {
+     return "DatapointRule(" + QStringList({m_name, m_alias, m_description}).join(", ") + ")";
+}
+
+const QString& DatapointRule::name() const
+{
+    return m_name;
+}
+
+const QString& DatapointRule::alias() const
+{
+    return m_alias;
+}
+
+const QString& DatapointRule::description() const
+{
+    return m_description;
+}
+
+const QString& DatapointRule::table() const
+{
+    return m_table;
+}
+
+void DatapointRule::setName(const QString& name)
+{
+    m_name = name;
+}
+
+void DatapointRule::setAlias(const QString& alias)
+{
+    m_alias = alias;
+}
+
+void DatapointRule::setDescription(const QString& description)
+{
+    m_description = description;
+}
+void DatapointRule::setTable(const QString& table)
+{
+    m_table = table;
+}
+
+
+void DatapointRule::prepareStatement(){
+    QStringList columnNames;
+    QStringList valueNames;
+    QStringList indexNames;
+
+    for(const DatapointAttribute & att : index.keys()){
+        columnNames << att.alias();
+        valueNames << att.formatValue();
+    }
+
+    for(const auto & att : m_attributes){
+        columnNames << att.second.alias();
+        valueNames << ":" + att.second.alias();
+
+    }
+
+    statement = "INSERT INTO " + m_table + " ";
+    statement += "(" + columnNames.join(", ") + ")";
+    statement += " VALUES ";
+    statement += "(" + valueNames.join(", ") + ")";
+}
+
+const QString & DatapointRule::sqlStatement(){
+    if(statement.isEmpty() == true) prepareStatement();
+    return statement;
+}
+
+void DatapointRule::bindValues(QSqlQuery & query, std::map<std::string, std::string> & values){
+    qDebug() << "binding values";
+
+    for(auto & uriValue: values){
+        const QString attribute_alias = ":" + m_attributes[uriValue.first].alias();
+        const QVariant attribute_value = QString::fromStdString(uriValue.second);
+        qDebug() << "alias=" << attribute_alias << ", value=" << attribute_value;
+        query.bindValue(attribute_alias, attribute_value);
+    }
+}
+
+DatapointRule * readDatapointRule(const QDomElement & element){
+
+    DatapointRule * dpRule{new DatapointRule};
+    dpRule->m_description = element.firstChildElement("description").text();
+
+    dpRule->m_checkpointInterval = std::chrono::seconds(element.firstChildElement("CheckpointInterval").attribute("value").toInt());
+    dpRule->m_name = element.attribute("name");
+    dpRule->m_alias = element.attribute("alias");
+
+    dpRule->index = readIndexRule(element.firstChildElement("index"), dpRule->m_name);
+
+    qDebug() << dpRule->index.toString();
+
+    QDomNodeList attributes = element.firstChildElement("attributes").elementsByTagName("attribute");
+
+    for(int i=0; i < attributes.size(); i++){
+        DatapointAttribute attribute{readAttribute(attributes.item(i).toElement())};
+        qDebug()<<attribute.toString();
+        dpRule->addAttribute(attribute);
+    }
+    return dpRule;
+}
+
+bool DatapointRule::hasToTriggerUpdate(std::map<std::string, std::string> & values){
+    if(checkBoundaries){
+        qDebug() << "check values in range";
+        bool inRange = true;
+        for(const auto uriValue: values){
+            DatapointAttribute & attribute = m_attributes[uriValue.first];
+            inRange = inRange && attribute.isInRange(uriValue.second.c_str());
+        }
+        return !inRange;
+    }else{
+        return true;
+    }
+}
+
+void DatapointRule::triggerRequestCheckpoint(){
+    emit requestCheckpoint(this);
+}
+
+void DatapointRule::startCheckpointTimer(){
+	triggerRequestCheckpoint();
+    timer.reset(new QTimer(this));
+    connect(timer.get(), &QTimer::timeout, this, &DatapointRule::triggerRequestCheckpoint);
+    if(m_checkpointInterval.count() > 0){
+        timer->setInterval(std::chrono::milliseconds(m_checkpointInterval).count());
+        timer->start();
+    }
+}
+
+std::vector<std::shared_ptr<DatapointRule>> readDatapointRules(QDomDocument & document){
+    QDomElement table = document.firstChildElement("Table");
+    QString tableName = table.attribute("name");
+
+    qDebug() << "Rule file for table " << tableName;
+
+    QDomElement element = table.firstChildElement("Datapoints")
+                               .firstChildElement("Datapoint");
+    std::vector<std::shared_ptr<DatapointRule>> rules;
+
+    while(element.isNull() == false){
+
+        DatapointRule * rule{readDatapointRule(element)};
+        rule->m_table = tableName;
+        rule->prepareStatement();
+        rules.push_back(std::shared_ptr<DatapointRule>{rule});
+
+        element = element.nextSiblingElement();
+    }
+    return rules;
+}
diff --git a/MAC/WinCCDBBridge/src/DatapointRule.hpp b/MAC/WinCCDBBridge/src/DatapointRule.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2e51506befe56e716739d8da24c4db9cb98156ff
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/DatapointRule.hpp
@@ -0,0 +1,77 @@
+#ifndef DATAPOINT_RULE_H
+#define DATAPOINT_RULE_H
+
+#include <vector>
+#include <map>
+#include <memory>
+
+#include <QString>
+#include <QObject>
+#include <chrono>
+class QTimer;
+class QSqlQuery;
+class QDomElement;
+class QDomDocument;
+
+#include "DatapointAttribute.hpp"
+#include "DatapointIndex.hpp"
+
+class DatapointRule: public QObject
+{
+    Q_OBJECT
+public:
+    DatapointRule();
+    DatapointRule(QString name, QString alias, QString description);
+    std::vector<std::string> getDatapointsURI();
+
+    std::chrono::seconds m_checkpointInterval;
+
+    DatapointIndex index;
+
+    const QString toString() const;
+    void addAttribute(DatapointAttribute attribute);
+
+    const QString & sqlStatement();
+    void bindValues(QSqlQuery & query, std::map<std::string, std::string> & values);
+
+    bool hasToTriggerUpdate(std::map<std::string, std::string> & values);
+
+    void prepareStatement();
+
+    const QString & name() const;
+    void setName(const QString & name);
+
+    const QString & alias() const;
+    void setAlias(const QString & alias);
+
+    const QString & description() const;
+    void setDescription(const QString & description);
+
+    const QString & table() const;
+    void setTable(const QString & table);
+private:
+    QString m_name;
+    QString m_alias;
+    QString m_description;
+    QString m_table;
+
+    QString statement;
+    std::map<std::string, DatapointAttribute> m_attributes;
+    std::shared_ptr<QTimer> timer;
+    bool checkBoundaries;
+signals:
+    void requestCheckpoint(DatapointRule *);
+
+public slots:
+    void triggerRequestCheckpoint();
+    void startCheckpointTimer();
+
+friend DatapointRule * readDatapointRule(const QDomElement &);
+friend std::vector<std::shared_ptr<DatapointRule>> readDatapointRules(QDomDocument &);
+};
+
+DatapointRule * readDatapointRule(const QDomElement & element);
+std::vector<std::shared_ptr<DatapointRule>> readDatapointRules(QDomDocument & document);
+
+#endif // DATAPOINT_RULE_H
+
diff --git a/MAC/WinCCDBBridge/src/Program.cpp b/MAC/WinCCDBBridge/src/Program.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85ddba1a1dea6793017afc40e3a10cd95b72b5fc
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/Program.cpp
@@ -0,0 +1,251 @@
+#include "Program.hpp"
+
+#include <QDebug>
+#include <QFile>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QJsonValue>
+
+#include <QDomDocument>
+#include <QDomElement>
+#include <QDomNode>
+
+#include <QXmlInputSource>
+
+#include <QtNetwork>
+#include <QUrl>
+#include <QSqlQuery>
+#include <QSqlError>
+
+
+#include <exception>
+
+using LOFAR::WINCCWRAPPER::WinCCWrapper;
+
+class ConfigurationFileReadError : public std::exception {
+private:
+    std::string message;
+public:
+    ConfigurationFileReadError(std::string path): message{"Error reading file " + path} {}
+
+    const char * what() const throw () {
+        return message.c_str();
+    }
+};
+
+Program::Program(QObject * parent, const QString& configurationFile, const QString & projectName): QObject{parent},
+    database{new QSqlDatabase}
+{
+    wrapper.reset(new LOFAR::WINCCWRAPPER::WinCCWrapper{"Program", projectName.toStdString(), 0});
+
+    try{
+        readConfigurationFile(configurationFile);
+
+        // Event connect
+        // -- WINCC side
+        wrapper->set_connect_datapoints_callback([=](std::map<std::string, std::string> values){
+                                              emit datapointChanged(values); });
+        // -- QT side
+        connect(this, &Program::datapointChanged, this, &Program::consolePrint);
+        connect(this, &Program::datapointChanged, this, &Program::messageDispatch);
+    }
+    catch (std::exception & e){
+        qCritical() << "An error occurred while reading the configuration file: "<< e.what();
+        qFatal("Cannot read configuration file");
+    }
+
+}
+
+Program::~Program()
+{
+}
+
+void Program::run()
+{
+    database->open();
+    connectToDatapoints();
+    emit startRulesCheckpointTriggers();
+
+    while(true){
+        qApp->processEvents();
+        wrapper->wait_for_event(0, 500);
+    }
+    wrapper->exit();
+    database->close();
+    qInfo()<< "Closing worker threads";
+
+}
+
+void Program::consolePrint(std::map<std::string, std::string> values)
+{
+
+    qDebug() << "Received: ";
+    for(const auto & value: values){
+        qDebug()<< value.first.c_str() << " " << value.second.c_str();
+    }
+}
+
+void Program::messageDispatch(std::map<std::string, std::string> values)
+{
+    if(values.size() == 0) return;
+
+    const std::string & uri = values.begin()->first;
+
+
+    DatapointRule * rule = uriToRule[uri];
+    if(rule == nullptr) qWarning() << "CANNOT FIND RULE FOR"<< uri.c_str();
+
+    qInfo() << "update has been triggered for" <<uri.c_str() << "with name " << rule->name();
+    if(rule->hasToTriggerUpdate(values)){
+
+        const QString queryString{rule->sqlStatement()};
+        QSqlDatabase db{QSqlDatabase::database()};
+
+        QSqlQuery query{db};
+        query.prepare(queryString);
+
+        QMapIterator<QString, QVariant> boundValuesIt(query.boundValues());
+
+        while(boundValuesIt.hasNext()){
+        	boundValuesIt.next();
+        	qDebug()<< "key" << boundValuesIt.key()<< "value" << boundValuesIt.value();
+        }
+
+        rule->bindValues(query, values);
+
+
+        if(query.exec() != true) qWarning() << query.lastError();
+
+        qInfo() << "Executed query " << query.executedQuery();
+        qInfo() << rule->name() << "  SQL:" << queryString;
+    }else{
+        qInfo() << "Skipping update of "<< uri.c_str() << " with rule name " << rule->name() << "values all in range";
+    }
+
+}
+
+const QJsonDocument readXMLFile(QFile & file){
+
+    QString buffer;
+
+    file.open(QIODevice::ReadOnly | QIODevice::Text);
+    try{
+        buffer = file.readAll();
+        file.close();
+    }catch(std::exception &e){
+        file.close();
+        qCritical()<<"An error occurred while reading configuration file"<< e.what();
+        throw e;
+    }
+    return QJsonDocument::fromJson(buffer.toUtf8());
+}
+
+void Program::readDBConfigurationSection(QSettings & configuration){
+
+    configuration.beginGroup("Database");
+    const QString name = configuration.value("name").toString();
+    const QString type = configuration.value("type").toString();
+    const QString address = configuration.value("address").toString();
+    const int port = configuration.value("port").toInt();
+    const QString user = configuration.value("user").toString();
+    const QString pass = configuration.value("pass").toString();
+    configuration.endGroup();
+
+    QSqlDatabase db = QSqlDatabase::addDatabase(type);
+
+    db.setHostName(address);
+    db.setDatabaseName(name);
+    db.setPort(port);
+    db.setUserName(user);
+    db.setPassword(pass);
+
+    qInfo()<<db;
+
+    *database = db;
+}
+
+void Program::readConfigurationFile(QString fileName){
+    QFile pathToConfigFile{fileName};
+
+    if(pathToConfigFile.exists()){
+        QSettings configuration{fileName, QSettings::IniFormat};
+        readDBConfigurationSection(configuration);
+
+    }else{
+        throw ConfigurationFileReadError{fileName.toStdString()};
+    }
+}
+
+void Program::connectToDatapoints(){
+
+    std::cout<< "Datapoint rules are " << dataPointRules.size();
+
+    for(std::shared_ptr<DatapointRule> & rule: dataPointRules){
+        std::vector<std::string> dataPoints{rule->getDatapointsURI()};
+
+        for(std::string name : dataPoints) qDebug()<< "Registering " << dataPoints.size() << "rule "<< name.c_str();
+        wrapper->connect_datapoints_multi(dataPoints);
+    }
+}
+
+void Program::checkpointRequested(DatapointRule * rule){
+
+    std::map<std::string, std::string> values;
+    qInfo() << "Processing rule " << rule->name();
+    try {
+
+        for(const std::string & uri: rule->getDatapointsURI()){
+                values[uri] = wrapper->get_formatted_datapoint(uri);
+                qDebug() << "uri:"<<uri.c_str() << "\t" << wrapper->get_formatted_datapoint(uri).c_str();
+        }
+
+        QSqlDatabase db = QSqlDatabase::database();
+        QSqlQuery query{db};
+        query.prepare(rule->sqlStatement());
+
+        rule->bindValues(query, values);
+        if(!query.exec()) qWarning() << query.lastError();
+        qInfo() << "Processed rule " << rule->name();
+
+        qDebug() << "Executed query " << query.executedQuery();
+    }catch (...) {
+        qWarning() << "cannot update rule " << rule->name();
+    }
+}
+
+
+
+QDomDocument readFile(QString fileName){
+    QFile datapointFile{fileName};
+
+    if(datapointFile.exists()){
+
+        QDomDocument parsedFile;
+        QXmlInputSource sourceXML{&datapointFile};
+        parsedFile.setContent(&sourceXML, true);
+        datapointFile.close();
+        return parsedFile;
+
+    }else{
+        throw ConfigurationFileReadError{fileName.toStdString()};
+    }
+}
+
+void Program::loadDatapointsConfiguration(QString configurationFile)
+{
+    QDomDocument parsedContent{readFile(configurationFile)};
+
+    dataPointRules = readDatapointRules(parsedContent);
+    for(std::shared_ptr<DatapointRule> & rule: dataPointRules){
+        connect(rule.get(), &DatapointRule::requestCheckpoint, this, &Program::checkpointRequested);
+        connect(this, &Program::startRulesCheckpointTriggers, rule.get(), &DatapointRule::startCheckpointTimer);
+
+        for(std::string uri: rule->getDatapointsURI()){
+            uriToRule[uri] = rule.get();
+            qDebug()<< "URI="<< uri.c_str() << "SQL: " << rule->sqlStatement();
+        }
+    }
+}
+
+#include "Program.moc"
diff --git a/MAC/WinCCDBBridge/src/Program.hpp b/MAC/WinCCDBBridge/src/Program.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..232e6c18a5b855c10360096ac7cf243a4bba6a21
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/Program.hpp
@@ -0,0 +1,50 @@
+#ifndef PROGRAM_H
+#define PROGRAM_H
+
+#include <QObject>
+#include <QSqlDatabase>
+
+#include <WinCCWrapper.h>
+#include <memory>
+
+#include <map>
+#include <QString>
+
+#include "DatapointRule.hpp"
+
+class QSettings;
+
+class Program : public QObject
+{
+    Q_OBJECT
+
+public:
+    Program(QObject * parent, const QString& configurationFile, const QString & projectName);
+    void loadDatapointsConfiguration(QString configurationFile);
+    virtual ~Program();
+    void run();
+
+public slots:
+    void consolePrint(std::map<std::string, std::string> datapoints); //!< Prints a log message for each changed datapoint name
+    void messageDispatch(std::map<std::string, std::string> datapoints); //!< Send the datapoint change value into the database
+    void checkpointRequested(DatapointRule *); //! < React when a rule triggers a checkpoint request
+
+signals:
+    void datapointChanged(std::map<std::string, std::string> datapoints); //!< This event is triggered when a registered datapoint value changes
+    void startRulesCheckpointTriggers();
+
+private:
+    std::shared_ptr<LOFAR::WINCCWRAPPER::WinCCWrapper> wrapper; //!< Pointer to an instance of the WinCCWrapper
+
+    void readConfigurationFile(QString fileName); //!< Read the configuration file
+    void readDBConfigurationSection(QSettings &); //!< Read the database section of the configuration file
+
+    void startWorkers(int num);
+    void connectToDatapoints(); //! Ask WinCC to get an update every time one of the specified datapoint changes
+    std::map<std::string, DatapointRule *> uriToRule;
+    std::vector<std::shared_ptr<DatapointRule>> dataPointRules;
+    std::shared_ptr<QSqlDatabase> database;
+
+};
+
+#endif // PROGRAM_H
diff --git a/MAC/WinCCDBBridge/src/main.cpp b/MAC/WinCCDBBridge/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b7ed825afac36f5f5fdd085655cec22e3fb64acb
--- /dev/null
+++ b/MAC/WinCCDBBridge/src/main.cpp
@@ -0,0 +1,69 @@
+#include <QCoreApplication>
+
+#include <QCommandLineParser>
+#include <QDebug>
+
+#include "Program.hpp"
+
+/**
+Defines the Qt application properties.
+*/
+void defineApplication(){
+    QCoreApplication::setApplicationName("WinCCDispatcher");
+
+}
+
+/**
+Defines the command line options
+*/
+void defineCommandLineOptions(QCommandLineParser & parser){
+    parser.setApplicationDescription("Stores the value directly in a chosen SQL database.\n");
+
+    parser.addHelpOption();
+    parser.addPositionalArgument("datapoint_config", "Configuration file containing the list of datapoints to connect to");
+    parser.addPositionalArgument("configuration", "Configuration file containing the database connection info");
+    parser.addPositionalArgument("WinCCProjectName", "Project name to connect to");
+}
+
+
+/**
+Execute the main program
+*/
+void executeProgram(const QStringList arguments){
+    const QString datapointXMLConfiguration = arguments.at(0);
+    const QString configuration = arguments.at(1);
+
+    const QString winccProjectName = arguments.at(2);
+
+    Program program{nullptr, configuration, winccProjectName};
+    program.loadDatapointsConfiguration(datapointXMLConfiguration);
+    program.run();
+}
+
+/**
+Main function. It execute in succession:
+  * parsing the program options
+  * handle required missing parameter
+  * constructing the main program
+  * executes the main loop
+*/
+int main(int argc, char * argv[]){
+    QCoreApplication app{argc, argv};
+    QCommandLineParser parser;
+
+    defineApplication();
+
+    defineCommandLineOptions(parser);
+
+    parser.process(app);
+    const QStringList arguments = parser.positionalArguments();
+
+    if(arguments.size() == 3) {
+        executeProgram(arguments);
+    }else{
+        qCritical()<< "Missing required arguments";
+        parser.showHelp();
+        exit(1);
+    }
+
+}
diff --git a/MAC/WinCCDBBridge/test/CMakeLists.txt b/MAC/WinCCDBBridge/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ddbb3222fc3143b96dbeeca40f7e0eb5e4d28f51
--- /dev/null
+++ b/MAC/WinCCDBBridge/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+include(LofarCTest)
+
+IF(HAVE_UNITTEST++)
+    find_package(Qt5 COMPONENTS Core Network Sql Xml REQUIRED)
+
+    include_directories(${Qt5_INCLUDE_DIRS})
+    set(TESTED_FILES
+     ../src/DatapointAttribute.cpp
+     ../src/DatapointRule.cpp
+     ../src/DatapointIndex.cpp)
+
+    set(TEST_NAMES
+        t_DatapointAttribute
+        t_DatapointIndex
+    )
+
+    foreach(TEST_NAME ${TEST_NAMES})
+        lofar_add_test("${TEST_NAME}" "${TEST_NAME}.cpp" ${TESTED_FILES})
+        target_link_libraries("${TEST_NAME}"
+            Qt5::Core
+            Qt5::Network
+            Qt5::Sql
+            Qt5::Xml
+        )
+    endforeach()
+
+ENDIF(HAVE_UNITTEST++)
diff --git a/MAC/WinCCDBBridge/test/t_DatapointAttribute.cpp b/MAC/WinCCDBBridge/test/t_DatapointAttribute.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5bd40df794b6f8af44aec0d7681c324cec695e6b
--- /dev/null
+++ b/MAC/WinCCDBBridge/test/t_DatapointAttribute.cpp
@@ -0,0 +1,98 @@
+#include "UnitTest++/UnitTest++.h"
+
+#include "../src/DatapointAttribute.hpp"
+#include "../src/DatapointRule.hpp"
+
+#include <QDomElement>
+#include <QDomDocument>
+
+
+#include <QDebug>
+TEST(test_formatValue)
+{
+    DatapointAttribute attr("datapointname", "alias_name", "STRING", "s", "12", 0, 15, true);
+    CHECK_EQUAL("\'12\'", attr.formatValue().toStdString());
+
+    attr.setType("INT");
+
+    CHECK_EQUAL("12", attr.formatValue().toStdString());
+}
+
+TEST(test_toString)
+{
+    DatapointAttribute attr("datapointname", "alias_name", "STRING", "s", "12", 0, 15, true);
+
+    CHECK_EQUAL("DatapointAttribute(datapointname, alias_name, STRING, s, 12)", attr.toString().toStdString());
+}
+
+TEST(test_isInRange)
+{
+    DatapointAttribute attr("datapointname", "alias_name", "STRING", "s", "12", 0, 15, true);
+    CHECK_EQUAL(true, attr.isInRange("12"));
+
+    CHECK_EQUAL(false, attr.isInRange("-1"));
+
+    CHECK_EQUAL(false, attr.isInRange("18"));
+
+    attr = DatapointAttribute{};
+    attr.setUpperLimit(12);
+    attr.setLowerLimit(0);
+
+    CHECK_EQUAL(false, attr.isBoundariesCheckEnabled());
+    CHECK_EQUAL(false, attr.isInRange("-1"));
+    CHECK_EQUAL(false, attr.isInRange("15"));
+    CHECK_EQUAL(false, attr.isInRange("5"));
+
+}
+
+TEST(test_URI){
+    DatapointAttribute attr("datapointname", "alias_name", "STRING", "s", "12", 0, 15, true);
+
+    DatapointRule rule("rule.", "ruleAlias", "mytestrule");
+
+    attr.setParentRule(rule);
+
+    CHECK_EQUAL("rule.datapointname", attr.URI().toStdString());
+}
+
+TEST(test_XMLParsing){
+    QDomDocument doc = QDomDocument("someParentElement");
+    QDomElement test = doc.createElement("attribute");
+    test.setAttribute("name", "myname");
+    test.setAttribute("alias", "alias");
+    test.setAttribute("unit", "s");
+    test.setAttribute("value", "5");
+
+    test.setAttribute("max", "12");
+    test.setAttribute("min", "12");
+
+    DatapointAttribute attr = readAttribute(test);
+
+    CHECK_EQUAL("myname", attr.name().toStdString());
+    CHECK_EQUAL("5", attr.value().toStdString());
+    CHECK_EQUAL("alias", attr.alias().toStdString());
+    CHECK_EQUAL("s", attr.unit().toStdString());
+    CHECK_EQUAL(12, attr.upperLimit());
+    CHECK_EQUAL(12, attr.lowerLimit());
+    CHECK_EQUAL(true, attr.isBoundariesCheckEnabled());
+
+
+    test = doc.createElement("attribute");
+    test.setAttribute("name", "myname");
+    test.setAttribute("alias", "alias");
+    test.setAttribute("unit", "s");
+
+    attr = readAttribute(test);
+
+    CHECK_EQUAL("myname", attr.name().toStdString());
+    CHECK_EQUAL("", attr.value().toStdString());
+    CHECK_EQUAL("alias", attr.alias().toStdString());
+    CHECK_EQUAL("s", attr.unit().toStdString());
+    CHECK_EQUAL(false, attr.isBoundariesCheckEnabled());
+
+}
+
+int main(int, const char *[])
+{
+   return UnitTest::RunAllTests();
+}
diff --git a/MAC/WinCCDBBridge/test/t_DatapointIndex.cpp b/MAC/WinCCDBBridge/test/t_DatapointIndex.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..47acc3b9208400570578409bce27ab1eb0d89ab4
--- /dev/null
+++ b/MAC/WinCCDBBridge/test/t_DatapointIndex.cpp
@@ -0,0 +1,11 @@
+#include "UnitTest++/UnitTest++.h"
+
+#include "../src/DatapointAttribute.hpp"
+#include "../src/DatapointIndex.hpp"
+#include "../src/DatapointRule.hpp"
+
+
+int main(int, const char *[])
+{
+   return UnitTest::RunAllTests();
+}