diff --git a/SDP/LICENSE.txt b/SDP/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ae533fce6dc75595f91290511273c7ff62312f76
--- /dev/null
+++ b/SDP/LICENSE.txt
@@ -0,0 +1,202 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2020 Stichting Nederlandse Wetenschappelijk Onderzoek Instituten,
+ASTRON Netherlands Institute for Radio Astronomy
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/SDP/MANIFEST.in b/SDP/MANIFEST.in
new file mode 100644
index 0000000000000000000000000000000000000000..d9d9aaba41c43f633c9a02635e72b4eb1b791f50
--- /dev/null
+++ b/SDP/MANIFEST.in
@@ -0,0 +1,9 @@
+recursive-include RCUSCC *.py
+recursive-include test *.py
+include *.rst
+include RCUSCC.xmi
+include *.txt
+graft docs
+
+global-exclude *.pyc
+global-exclude *.pyo
diff --git a/SDP/NOTICE b/SDP/NOTICE
new file mode 100644
index 0000000000000000000000000000000000000000..9c7867598e17de5d69b8c26656caa8316cd0a30f
--- /dev/null
+++ b/SDP/NOTICE
@@ -0,0 +1,8 @@
+Citation Notice version 1.0
+This Citation Notice is part of the LOFAR software suite.
+Parties that use ASTRON Software resulting in papers and/or publications are requested to
+refer to the DOI(s) that correspond(s) to the version(s) of the ASTRON Software used:
+<List of DOIs>
+Parties that use ASTRON Software for purposes that do not result in publications (e.g.
+commercial parties) are asked to inform ASTRON about their use of ASTRON Software, by
+sending an email to including the DOIs mentioned above in the message.
\ No newline at end of file
diff --git a/SDP/README.rst b/SDP/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..aafea1e3022ae9546fd62e1a2b1474dfc1bbef6a
--- /dev/null
+++ b/SDP/README.rst
@@ -0,0 +1,25 @@
+## SDP Device Server for LOFAR2.0
+
+
+## Requirement
+
+- PyTango >= 8.1.6
+- devicetest (for using tests)
+- sphinx (for building sphinx documentation)
+
+## Installation
+
+Run python setup.py install
+
+If you want to build sphinx documentation,
+run python setup.py build_sphinx
+
+If you want to pass the tests, 
+run python setup.py test
+
+## Usage
+
+Now you can start your device server in any
+Terminal or console by calling it :
+
+SDP instance_name
diff --git a/SDP/SDP/SDP.py b/SDP/SDP/SDP.py
new file mode 100644
index 0000000000000000000000000000000000000000..b98336472165d390a050a63c95d8dfb91a67be5a
--- /dev/null
+++ b/SDP/SDP/SDP.py
@@ -0,0 +1,319 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the SDP project
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+""" SDP Device Server for LOFAR2.0
+
+"""
+
+# PyTango imports
+from tango import DebugIt
+from tango.server import run
+from tango.server import Device
+from tango.server import attribute, command
+from tango.server import device_property
+from tango import AttrQuality, DispLevel, DevState
+from tango import AttrWriteType, PipeWriteType
+# Additional import
+import sys
+import opcua
+import numpy
+
+from wrappers import only_in_states, only_when_on, fault_on_error
+from opcua_connection import OPCUAConnection
+
+__all__ = ["SDP", "main"]
+
+class SDP(Device):
+    """
+
+    **Properties:**
+
+    - Device Property
+        OPC_Server_Name
+            - Type:'DevString'
+        OPC_Server_Port
+            - Type:'DevULong'
+        OPC_Time_Out
+            - Type:'DevDouble'
+
+    States are as follows:
+        INIT    = Device is initialising.
+        STANDBY = Device is initialised, but pends external configuration and an explicit turning on,
+        ON      = Device is fully configured, functional, controls the hardware, and is possibly actively running,
+        FAULT   = Device detected an unrecoverable error, and is thus malfunctional,
+        OFF     = Device is turned off, drops connection to the hardware,
+
+    The following state transitions are implemented:
+        boot -> OFF:     Triggered by tango.  Device will be instantiated,
+        OFF  -> INIT:    Triggered by device. Device will initialise (connect to hardware, other devices),
+        INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user,
+        STANDBY -> ON:   Triggered by user.   Device reports to be functional,
+        * -> FAULT:      Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost,
+        * -> FAULT:      Triggered by user.   Emulate a forced malfunction for integration testing purposes,
+        * -> OFF:        Triggered by user.   Device is turned off. Triggered by the Off() command,
+        FAULT -> INIT:   Triggered by user.   Device is reinitialised to recover from an error,
+
+        The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()).
+    """
+    client = 0
+    name_space_index = 0
+    obj = 0
+
+    # -----------------
+    # Device Properties
+    # -----------------
+
+    OPC_Server_Name = device_property(
+        dtype='DevString',
+        mandatory=True
+    )
+
+    OPC_Server_Port = device_property(
+        dtype='DevULong',
+        mandatory=True
+    )
+
+    OPC_Time_Out = device_property(
+        dtype='DevDouble',
+        mandatory=True
+    )
+
+    # ----------
+    # Attributes
+    # ----------
+
+    SDP_mask_RW = attribute(
+        dtype = ('DevBoolean',),
+        max_dim_x = 32,
+        access=AttrWriteType.READ_WRITE,
+    )
+
+    fpga_temp_R = attribute(
+        dtype = ('DevDouble',),
+        max_dim_x = 4,
+    )
+
+
+
+    # ---------------
+    # General methods
+    # ---------------
+
+    def get_sdp_node(self, node):
+        try:
+            return self.sdp_node.get_child(["{}:{}".format(self.name_space_index, node)])
+        except opcua.ua.uaerrors._auto.BadNoMatch:
+            self.error_stream("Could not find SDP node %s", node)
+
+            # Contract with hardware is broken --- cannot recover
+            raise
+
+    def _map_attributes(self):
+        try:
+            self.name_space_index = self.client.get_namespace_index("http://lofar.eu")
+        except Exception as e:
+            self.warn_stream("Cannot determine the OPC-UA name space index.  Will try and use the default = 2.")
+            self.name_space_index = 2
+
+        self.obj_node = self.client.get_objects_node()
+        self.sdp_node = self.obj_node.get_child(["{}:SDP".format(self.name_space_index)])
+
+        self.debug_stream("Mapping OPC-UA MP/CP to attributes...")
+
+        self.attribute_mapping["SDP_mask_RW"] = self.get_pcc_node("SDP_mask_RW")
+        self.attribute_mapping["fpga_temp_R"] = self.get_pcc_node("fpga_temp_R")
+
+        self.debug_stream("Mapping OPC-UA MP/CP to attributes done.")
+
+    def init_device(self):
+        """ Instantiates the device in the OFF state. """
+
+        # NOTE: Will delete_device first, if necessary
+        Device.init_device(self)
+
+        self.set_state(DevState.OFF)
+
+    def initialise(self):
+        """Initialises the attributes and properties of the SDP."""
+
+        self.set_state(DevState.INIT)
+
+        # Init the dict that contains attribute to OPC-UA MP/CP mappings.
+        self.attribute_mapping = {}
+
+        # Set default values in the RW/R attributes and add them to
+        # the mapping.
+        self._SDP_mask_RW = numpy.full(32, False)
+        self.attribute_mapping["SDP_mask_RW"] = {}
+        self._fpga_temp_R = numpy.full(4, False)
+        self.attribute_mapping["fpga_temp_R"] = {}
+
+        # Init the dict that contains function to OPC-UA function mappings.
+        self.function_mapping = {}
+
+        self.client = opcua.Client("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), self.OPC_Time_Out) # timeout in seconds
+
+        # Connect to OPC-UA -- will set ON state on success in case of a reconnect
+        self.opcua_connection = OPCUAConnection(self.client, self.Standby, self.Fault, self)
+
+        # Explicitly connect
+        if not self.opcua_connection.connect():
+            # hardware or infra is down -- needs fixing first
+            self.Fault()
+            return
+
+        # Retrieve and map server attributes
+        try:
+            self._map_attributes()
+        except Exception as e:
+            self.error_stream("Could not map server interface: %s", e)
+            self.Fault()
+            return
+
+        # Start keep-alive
+        self.opcua_connection.start()
+
+        # Set the masks.
+        #
+        # Attention!
+        # Set the masks only after the OPCUA connection has been
+        # established!  The setting of the masks needs to go through
+        # to the server.
+        #
+        # TODO
+        # Read default masks from config DB
+        #self.write_SDP_mask_RW(self._SDP_mask_R)
+
+        # Everything went ok -- go standby.
+        self.set_state(DevState.STANDBY)
+
+
+    def always_executed_hook(self):
+        """Method always executed before any TANGO command is executed."""
+        pass
+
+    @DebugIt()
+    def delete_device(self):
+        """Hook to delete resources allocated in init_device.
+
+        This method allows for any memory or other resources allocated in the
+        init_device method to be released.  This method is called by the device
+        destructor and by the device Init command (a Tango built-in).
+        """
+        self.Off()
+
+
+    # ------------------
+    # Attributes methods
+    # ------------------
+
+    @only_when_on
+    @fault_on_error
+    def read_SDP_mask_RW(self):
+        """Return the SDP_mask_RW attribute."""
+        return self._SDP_mask_RW
+
+    @only_when_on
+    @fault_on_error
+    def write_SDP_mask_RW(self, value):
+        """Set the SDP_mask_RW attribute."""
+        self.attribute_mapping["SDP_mask_RW"].set_value(value.tolist())
+        self._SDP_mask_RW = value
+
+    @only_when_on
+    @fault_on_error
+    def read_fpga_temp_R(self):
+        """Return the fpga_temp_R attribute."""
+        value = numpy.array(self.attribute_mapping["fpga_temp_R"].get_value())
+        self._fpga_temp_R = numpy.array(numpy.split(value, indices_or_sections = 4))
+        return self._fpga_temp_R
+
+
+    # --------
+    # Commands
+    # --------
+
+    @command()
+    @only_in_states([DevState.FAULT, DevState.OFF])
+    @DebugIt()
+    def Initialise(self):
+        """
+        Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
+
+        :return:None
+        """
+        self.initialise()
+
+    @only_in_states([DevState.INIT])
+    def Standby(self):
+        """
+        Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
+
+        :return:None
+        """
+        self.set_state(DevState.STANDBY)
+
+    @command()
+    @only_in_states([DevState.STANDBY])
+    @DebugIt()
+    def On(self):
+        """
+        Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
+
+        :return:None
+        """
+        self.set_state(DevState.ON)
+
+    @command()
+    @DebugIt()
+    def Off(self):
+        """
+        Command to ask for shutdown of this device.
+
+        :return:None
+        """
+        if self.get_state() == DevState.OFF:
+          # Already off. Don't complain.
+          return
+
+        # Turn off
+        self.set_state(DevState.OFF)
+
+        # Stop keep-alive
+        self.opcua_connection.stop()
+
+        # Turn off again, in case of race conditions through reconnecting
+        self.set_state(DevState.OFF)
+
+    @command()
+    @only_in_states([DevState.ON, DevState.INIT, DevState.STANDBY])
+    @DebugIt()
+    def Fault(self):
+        """
+        FAULT state is used to indicate our connection with the OPC-UA server is down.
+
+        This device will try to reconnect once, and transition to the ON state on success.
+
+        If reconnecting fails, the user needs to call Initialise() to retry to restart this device.
+
+        :return:None
+        """
+        self.set_state(DevState.FAULT)
+
+
+# ----------
+# Run server
+# ----------
+def main(args=None, **kwargs):
+    """Main function of the SDP module."""
+    return run((SDP,), args=args, **kwargs)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/SDP/SDP/__init__.py b/SDP/SDP/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf68b03729a1a4d562556c9765b9e7389fd49b18
--- /dev/null
+++ b/SDP/SDP/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the SDP project
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+"""SDP Device Server for LOFAR2.0
+
+"""
+
+from . import release
+from .SDP import SDP, main
+
+__version__ = release.version
+__version_info__ = release.version_info
+__author__ = release.author
diff --git a/SDP/SDP/__main__.py b/SDP/SDP/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ef0710c551b94138cfbe4b1c762af830dae9a62
--- /dev/null
+++ b/SDP/SDP/__main__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the SDP project
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+from SDP import main
+main()
diff --git a/SDP/SDP/opcua_connection.py b/SDP/SDP/opcua_connection.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfcfb74ab00416b79bfc2ccc8fbf263372c9f80c
--- /dev/null
+++ b/SDP/SDP/opcua_connection.py
@@ -0,0 +1,84 @@
+from threading import Thread
+import socket
+import time
+
+__all__ = ["OPCUAConnection"]
+
+class OPCUAConnection(Thread):
+    """
+      Connects to OPC-UA in the foreground or background, and sends HELLO
+      messages to keep a check on the connection. On connection failure, reconnects once.
+    """
+
+    def __init__(self, client, on_func, fault_func, streams, try_interval=2):
+        super().__init__(daemon=True)
+
+        self.client = client
+        self.on_func = on_func
+        self.fault_func = fault_func
+        self.try_interval = try_interval
+        self.streams = streams
+        self.stopping = False
+        self.connected = False
+
+    def _servername(self):
+        return self.client.server_url.geturl()
+
+    def connect(self):
+        try:
+            self.streams.debug_stream("Connecting to server %s", self._servername())
+            self.client.connect()
+            self.connected = True
+            self.streams.debug_stream("Connected to server. Initialising.")
+            return True
+        except socket.error as e:
+            self.streams.error_stream("Could not connect to server %s: %s", self._servername(), e)
+            return False
+
+    def disconnect(self):
+        self.connected = False # always force a reconnect, regardless of a successful disconnect
+
+        try:
+            self.client.disconnect()
+        except Exception as e:
+            self.streams.error_stream("Disconnect from OPC-UA server %s failed: %s", self._servername(), e)
+
+    def run(self):
+        while not self.stopping:
+            # keep trying to connect
+            if not self.connected:
+                if self.connect():
+                    self.on_func()
+                else:
+                    # we retry only once, to catch exotic network issues. if the infra or hardware is down,
+                    # our device cannot help, and must be reinitialised after the infra or hardware is fixed.
+                    self.fault_func()
+                    return
+
+            # keep checking if the connection is still alive
+            try:
+                while not self.stopping:
+                    self.client.send_hello()
+                    time.sleep(self.try_interval)
+            except Exception as e:
+                self.streams.error_stream("Lost connection to server %s: %s", self._servername(), e)
+
+                # technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect.
+                self.disconnect()
+
+                # signal that we're disconnected
+                self.fault_func()
+
+    def stop(self):
+        """
+          Stop connecting & disconnect. Can take a few seconds for the timeouts to hit.
+        """
+
+        if not self.ident:
+            # have not yet been started, so nothing to do
+            return
+
+        self.stopping = True
+        self.join()
+
+        self.disconnect()
diff --git a/SDP/SDP/release.py b/SDP/SDP/release.py
new file mode 100644
index 0000000000000000000000000000000000000000..74a9dd436a73d6acd8d9c7918c63dfc95b49ca09
--- /dev/null
+++ b/SDP/SDP/release.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the SDP project
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+"""Release information for Python Package"""
+
+name = """tangods-sdp"""
+version = "1.0.0"
+version_info = version.split(".")
+description = """"""
+author = "Thomas Juerges"
+author_email = "jurges at astron.nl"
+license = """APACHE"""
+url = """https://git.astron.nl/lofar2.0/tango.git"""
+copyright = """"""
diff --git a/SDP/SDP/wrappers.py b/SDP/SDP/wrappers.py
new file mode 100644
index 0000000000000000000000000000000000000000..9dbc45a68dc850b36bd30a0a5b8664d104b58e30
--- /dev/null
+++ b/SDP/SDP/wrappers.py
@@ -0,0 +1,53 @@
+from tango import DevState, Except
+from functools import wraps
+import traceback
+
+__all__ = ["only_in_states", "only_when_on", "fault_on_error"]
+
+def only_in_states(allowed_states):
+    """
+      Wrapper to call and return the wrapped function if the device is
+      in one of the given states. Otherwise a PyTango exception is thrown.
+    """
+    def wrapper(func):
+        @wraps(func)
+        def state_check_wrapper(self, *args, **kwargs):
+            if self.get_state() in allowed_states:
+                return func(self, *args, **kwargs)
+
+            self.warn_stream("Illegal command: Function %s can only be called in states %s. Current state: %s" % (func.__name__, allowed_states, self.get_state()))
+            Except.throw_exception("IllegalCommand", "Function can only be called in states %s. Current state: %s" % (allowed_states, self.get_state()), func.__name__)
+
+        return state_check_wrapper
+
+    return wrapper
+
+def only_when_on(func):
+    """
+      Wrapper to call and return the wrapped function if the device is
+      in the ON state. Otherwise None is returned and nothing
+      will be called.
+    """
+    @wraps(func)
+    def when_on_wrapper(self, *args, **kwargs):
+        if self.get_state() == DevState.ON:
+            return func(self, *args, **kwargs)
+
+        return None
+
+    return when_on_wrapper
+
+def fault_on_error(func):
+    """
+      Wrapper to catch exceptions. Sets the device in a FAULT state if any occurs.
+    """
+    @wraps(func)
+    def error_wrapper(self, *args, **kwargs):
+        try:
+            return func(self, *args, **kwargs)
+        except Exception as e:
+            self.error_stream("Function failed.  Trace: %s", traceback.format_exc())
+            self.Fault()
+            return None
+
+    return error_wrapper
diff --git a/SDP/requirements.txt b/SDP/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a0195dd42b98b0f3194e55e91cded17608ed6ee3
--- /dev/null
+++ b/SDP/requirements.txt
@@ -0,0 +1,2 @@
+opcua >= 0.98.9
+numpy
diff --git a/SDP/setup.py b/SDP/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..65c33ca7beb896c0f77e9acea2ad3dc311c33cd2
--- /dev/null
+++ b/SDP/setup.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This file is part of the SDP project
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+import os
+import sys
+from setuptools import setup
+
+setup_dir = os.path.dirname(os.path.abspath(__file__))
+
+# make sure we use latest info from local code
+sys.path.insert(0, setup_dir)
+
+readme_filename = os.path.join(setup_dir, 'README.rst')
+with open(readme_filename) as file:
+    long_description = file.read()
+
+release_filename = os.path.join(setup_dir, 'SDP', 'release.py')
+exec(open(release_filename).read())
+
+pack = ['SDP']
+
+setup(name=name,
+      version=version,
+      description='',
+      packages=pack,
+      include_package_data=True,
+      test_suite="test",
+      entry_points={'console_scripts':['SDP = SDP:main']},
+      author='jurges',
+      author_email='jurges at astron.nl',
+      license='APACHE',
+      long_description=long_description,
+      url='www.tango-controls.org',
+      platforms="Unix Like"
+      )