diff --git a/devices/devices/docker_device.py b/devices/devices/docker_device.py
index 5ff0ec366c436a2dfc75d4cd479219a04c6938d3..81abc26ee8ba4021b8e52bcefcf420a20cbdf28a 100644
--- a/devices/devices/docker_device.py
+++ b/devices/devices/docker_device.py
@@ -37,19 +37,6 @@ __all__ = ["Docker", "main"]
 
 @device_logging_to_python()
 class Docker(hardware_device):
-    """
-
-    **Properties:**
-
-    - Device Property
-        OPC_Server_Name
-            - Type:'DevString'
-        OPC_Server_Port
-            - Type:'DevULong'
-        OPC_Time_Out
-            - Type:'DevDouble'
-    """
-
     # -----------------
     # Device Properties
     # -----------------
@@ -103,19 +90,6 @@ class Docker(hardware_device):
     tango_rest_R = attribute_wrapper(comms_annotation={"container": "tango-rest"}, datatype=numpy.bool_)
     tango_rest_RW = attribute_wrapper(comms_annotation={"container": "tango-rest"}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
 
-    @log_exceptions()
-    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.debug_stream("Shutting down...")
-
-        self.Off()
-        self.debug_stream("Shut down.  Good bye.")
-
     # --------
     # overloaded functions
     # --------
@@ -124,9 +98,9 @@ class Docker(hardware_device):
         """ user code here. is called when the state is set to OFF """
         # Stop keep-alive
         try:
-            self.opcua_connection.stop()
+            self.docker_client.stop()
         except Exception as e:
-            self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e))
+            self.warn_stream("Exception while stopping docker client in configure_for_off function: {}. Exception ignored".format(e))
 
     @log_exceptions()
     def configure_for_initialise(self):
@@ -135,14 +109,9 @@ class Docker(hardware_device):
         # set up the Docker client
         self.docker_client = DockerClient(self.Docker_Base_URL, self.Fault, self)
 
-        # map an access helper class
+        # tie attributes to client
         for i in self.attr_list():
-            try:
-                i.set_comm_client(self.docker_client)
-            except Exception as e:
-                # use the pass function instead of setting read/write fails
-                i.set_pass_func()
-                self.warn_stream("error while setting the attribute {} read/write function. {}".format(i, e))
+            i.set_comm_client(self.docker_client)
 
         self.docker_client.start()
 
diff --git a/devices/devices/hardware_device.py b/devices/devices/hardware_device.py
index f8f6ca50d7e02f5a8694c2ec4f9135dd874cd516..1035f8460e524ecd4db8ed1d8e3d4aba2f014691 100644
--- a/devices/devices/hardware_device.py
+++ b/devices/devices/hardware_device.py
@@ -66,6 +66,7 @@ class hardware_device(Device, metaclass=AbstractDeviceMetas):
 
         self.value_dict = {i: i.initial_value() for i in self.attr_list()}
 
+    @log_exceptions()
     def init_device(self):
         """ Instantiates the device in the OFF state. """
 
@@ -74,6 +75,19 @@ class hardware_device(Device, metaclass=AbstractDeviceMetas):
 
         self.set_state(DevState.OFF)
 
+    @log_exceptions()
+    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).
+        """
+        logger.info("Shutting down...")
+
+        self.Off()
+        logger.info("Shut down.  Good bye.")
+
     # --------
     # Commands
     # --------
@@ -180,18 +194,6 @@ class hardware_device(Device, metaclass=AbstractDeviceMetas):
         """Method always executed before any TANGO command is executed."""
         pass
 
-    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.debug_stream("Shutting down...")
-
-        self.Off()
-        self.debug_stream("Shut down.  Good bye.")
-
     @command()
     @only_in_states([DevState.STANDBY, DevState.ON])
     @DebugIt()
diff --git a/devices/devices/opcua_device.py b/devices/devices/opcua_device.py
new file mode 100644
index 0000000000000000000000000000000000000000..d95a8426ed0dc260c0f6eb6d85149e3f5f0ec4ba
--- /dev/null
+++ b/devices/devices/opcua_device.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+#
+# This file represents a top-level device
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+""" Generic OPC-UA Device Server for LOFAR2.0
+
+"""
+
+# TODO(Corne): Remove sys.path.append hack once packaging is in place!
+import os, sys
+currentdir = os.path.dirname(os.path.realpath(__file__))
+parentdir = os.path.dirname(currentdir)
+sys.path.append(parentdir)
+
+# PyTango imports
+from tango import DebugIt
+from tango.server import device_property, attribute
+from tango import AttrWriteType
+import numpy
+# Additional import
+
+from devices.device_decorators import *
+
+from clients.opcua_client import OPCUAConnection
+from devices.hardware_device import hardware_device
+from common.lofar_logging import device_logging_to_python, log_exceptions
+from common.lofar_git import get_version
+
+__all__ = ["opcua_device", "main"]
+
+class opcua_device(hardware_device):
+    """
+
+    **Properties:**
+
+    - Device Property
+        OPC_Server_Name
+            - Type:'DevString'
+        OPC_Server_Port
+            - Type:'DevULong'
+        OPC_Time_Out
+            - Type:'DevDouble'
+    """
+
+    # -----------------
+    # 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
+    )
+
+    OPC_namespace = device_property(
+        dtype='DevString',
+        mandatory=False,
+        default_value="http://lofar.eu"
+    )
+
+    # ----------
+    # Attributes
+    # ----------
+
+    opcua_missing_attributes_R = attribute(max_dim_x=128, dtype=(str,), fget=lambda self: numpy.array(self.opcua_missing_attributes, dtype=str), doc="OPC-UA attributes that this device requested, but which are not exposed on the server. These attributes are replaced with a no-op and thus do not function as expected.")
+
+    # --------
+    # overloaded functions
+    # --------
+
+    @log_exceptions()
+    def configure_for_initialise(self):
+        """ user code here. is called when the state is set to INIT """
+
+        # set up the OPC ua client
+        self.opcua_connection = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), self.OPC_namespace, self.OPC_Time_Out, self.Fault, self)
+        self.opcua_missing_attributes = []
+
+        # map an access helper class
+        for i in self.attr_list():
+            try:
+                if not i.comms_id or i.comms_id == OPCUAConnection:
+                    i.set_comm_client(self.opcua_connection)
+            except Exception as e:
+                # use the pass function instead of setting read/write fails
+                i.set_pass_func()
+                self.opcua_missing_attributes.append(",".join(i.comms_annotation))
+
+                self.warn_stream("error while setting the attribute {} read/write function. {}".format(i, e))
+
+        self.opcua_connection.start()
+
+    @log_exceptions()
+    def configure_for_off(self):
+        """ user code here. is called when the state is set to OFF """
+        try:
+            # disconnect
+            self.opcua_connection.stop()
+        except Exception as e:
+            self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e))
+
diff --git a/devices/devices/recv.py b/devices/devices/recv.py
index 6f1de6aedc9e6db463c2edcd7a1a8bdf3daf7c2e..ddf834e14bfa9b8a6206ccc072ad4fee204c56ab 100644
--- a/devices/devices/recv.py
+++ b/devices/devices/recv.py
@@ -27,52 +27,19 @@ import numpy
 
 from device_decorators import *
 
-from clients.opcua_client import OPCUAConnection
 from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
+from devices.opcua_device import opcua_device
 from common.lofar_logging import device_logging_to_python, log_exceptions
 from common.lofar_git import get_version
 
 __all__ = ["RECV", "main"]
 
 @device_logging_to_python()
-class RECV(hardware_device):
-    """
-
-    **Properties:**
-
-    - Device Property
-        OPC_Server_Name
-            - Type:'DevString'
-        OPC_Server_Port
-            - Type:'DevULong'
-        OPC_Time_Out
-            - Type:'DevDouble'
-    """
-
+class RECV(opcua_device):
     # -----------------
     # 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
-    )
-    OPC_namespace = device_property(
-        dtype='DevString',
-        mandatory=False
-    )
-
     # ----------
     # Attributes
     # ----------
@@ -110,31 +77,9 @@ class RECV(hardware_device):
     RCU_translator_busy_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_translator_busy_R"], datatype=numpy.bool_)
     RCU_version_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_version_R"], datatype=numpy.str, dims=(32,))
 
-    @log_exceptions()
-    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.debug_stream("Shutting down...")
-
-        self.Off()
-        self.debug_stream("Shut down.  Good bye.")
-
     # --------
     # overloaded functions
     # --------
-    @log_exceptions()
-    def configure_for_off(self):
-        """ user code here. is called when the state is set to OFF """
-        # Stop keep-alive
-        try:
-            self.opcua_connection.stop()
-        except Exception as e:
-            self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e))
-
     @log_exceptions()
     def configure_for_initialise(self):
         """ user code here. is called when the state is set to INIT """
@@ -146,20 +91,7 @@ class RECV(hardware_device):
         self.function_mapping["CLK_on"] = {}
         self.function_mapping["CLK_off"] = {}
 
-        # set up the OPC ua client
-        self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self)
-
-        # map an access helper class
-        for i in self.attr_list():
-            try:
-                i.set_comm_client(self.OPCua_client)
-            except Exception as e:
-                # use the pass function instead of setting read/write fails
-                i.set_pass_func()
-                self.warn_stream("error while setting the RECV attribute {} read/write function. {}".format(i, e))
-
-        self.OPCua_client.start()
-
+        super().configure_for_initialise()
 
 
     # --------
diff --git a/devices/devices/sdp/sdp.py b/devices/devices/sdp/sdp.py
index 16f6d89737742f1f3d475c619a60513e4579a115..e39663c34767cc6fcbe74e6c2885b4825257a934 100644
--- a/devices/devices/sdp/sdp.py
+++ b/devices/devices/sdp/sdp.py
@@ -24,9 +24,8 @@ from tango.server import device_property, attribute
 from tango import AttrWriteType
 # Additional import
 
-from clients.opcua_client import OPCUAConnection
 from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
+from devices.opcua_device import opcua_device
 
 from common.lofar_logging import device_logging_to_python, log_exceptions
 from common.lofar_git import get_version
@@ -36,39 +35,11 @@ import numpy
 __all__ = ["SDP", "main"]
 
 @device_logging_to_python()
-class SDP(hardware_device):
-    """
-
-    **Properties:**
-
-    - Device Property
-        OPC_Server_Name
-            - Type:'DevString'
-        OPC_Server_Port
-            - Type:'DevULong'
-        OPC_Time_Out
-            - Type:'DevDouble'
-    """
-
+class SDP(opcua_device):
     # -----------------
     # 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
-    )
-
     FPGA_processing_enable_RW_default = device_property(
         dtype='DevVarBooleanArray',
         mandatory=False,
@@ -151,55 +122,9 @@ class SDP(hardware_device):
     TR_tod_R = attribute_wrapper(comms_annotation=["2:TR_tod_R"], datatype=numpy.int64, dims=(2,))
     TR_tod_pps_delta_R = attribute_wrapper(comms_annotation=["2:TR_tod_pps_delta_R"], datatype=numpy.double)
 
-    def always_executed_hook(self):
-        """Method always executed before any TANGO command is executed."""
-        pass
-
-    @log_exceptions()
-    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.debug_stream("Shutting down...")
-
-        self.Off()
-        self.debug_stream("Shut down.  Good bye.")
-
     # --------
     # overloaded functions
     # --------
-    @log_exceptions()
-    def configure_for_off(self):
-        """ user code here. is called when the state is set to OFF """
-
-        # Stop keep-alive
-        try:
-            self.OPCua_client.stop()
-        except Exception as e:
-            self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e))
-
-    @log_exceptions()
-    def configure_for_initialise(self):
-        """ user code here. is called when the sate is set to INIT """
-        """Initialises the attributes and properties of the SDP."""
-
-        # set up the OPC ua client
-        self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self)
-
-        # map an access helper class
-        for i in self.attr_list():
-            try:
-                i.set_comm_client(self.OPCua_client)
-            except Exception as e:
-                # use the pass function instead of setting read/write fails
-                i.set_pass_func()
-                self.warn_stream("error while setting the SDP attribute {} read/write function. {}".format(i, e))
-                pass
-
-        self.OPCua_client.start()
 
     # --------
     # Commands
diff --git a/devices/devices/sdp/statistics.py b/devices/devices/sdp/statistics.py
index 7d0b970b089ff29931bfc088f8b4b208d347402c..a884783ddd85c669e35a2230e72e3e4ca2f85d60 100644
--- a/devices/devices/sdp/statistics.py
+++ b/devices/devices/sdp/statistics.py
@@ -29,7 +29,7 @@ from clients.statistics_client import StatisticsClient
 from clients.opcua_client import OPCUAConnection
 from clients.attribute_wrapper import attribute_wrapper
 
-from devices.hardware_device import hardware_device
+from devices.opcua_device import opcua_device
 
 from common.lofar_git import get_version
 from common.lofar_logging import device_logging_to_python, log_exceptions
@@ -41,7 +41,7 @@ import numpy
 
 __all__ = ["Statistics"]
 
-class Statistics(hardware_device, metaclass=ABCMeta):
+class Statistics(opcua_device, metaclass=ABCMeta):
 
     # In derived classes, set this to a subclass of StatisticsCollector
     @property
@@ -53,21 +53,6 @@ class Statistics(hardware_device, metaclass=ABCMeta):
     # 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
-    )
-
     Statistics_Client_UDP_Port = device_property(
         dtype='DevUShort',
         mandatory=True
@@ -124,16 +109,15 @@ class Statistics(hardware_device, metaclass=ABCMeta):
         except Exception as e:
             logger.exception("Exception while stopping statistics_client in configure_for_off. Exception ignored")
 
-        try:
-            self.OPCUA_client.stop()
-        except Exception as e:
-            logger.exception("Exception while stopping OPC UA connection in configure_for_off. Exception ignored")
+        super().configure_for_off()
 
     @log_exceptions()
     def configure_for_initialise(self):
         """ user code here. is called when the sate is set to INIT """
         """Initialises the attributes and properties of the statistics device."""
 
+        super().configure_for_initialise()
+
         # Options for UDPReceiver
         udp_options = {
             "udp_port": self.Statistics_Client_UDP_Port,
@@ -148,27 +132,12 @@ class Statistics(hardware_device, metaclass=ABCMeta):
 
         self.statistics_collector = self.STATISTICS_COLLECTOR_CLASS()
         self.statistics_client = StatisticsClient(self.statistics_collector, udp_options, tcp_options, self.Fault, self)
-
-        self.OPCUA_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self)
-
-        # map an access helper class
-        for i in self.attr_list():
-            try:
-                if i.comms_id == StatisticsClient:
-                    i.set_comm_client(self.statistics_client)
-                elif i.comms_id == OPCUAConnection:
-                    i.set_comm_client(self.OPCUA_client)
-                else:
-                    raise ValueError("Cannot set comm client for attribute {}: Unknown comms_id {}".format(i, i.comms_id))
-            except Exception as e:
-                # use the pass function instead of setting read/write fails
-                i.set_pass_func()
-                self.warn_stream("error while setting the sst attribute {} read/write function. {}. using pass function instead".format(i, e))
-                pass
-
         self.statistics_client.start()
 
-        self.OPCUA_client.start()
+        # tie attributes to client
+        for i in self.attr_list():
+            if i.comms_id == StatisticsClient:
+                i.set_comm_client(self.statistics_client)
 
     # --------
     # Commands
diff --git a/devices/devices/unb2.py b/devices/devices/unb2.py
index 7c2575991605354de5bba608906fb9ea248f021b..83fb44ca93cb38795cc77b49354aa53dfafc5cf8 100644
--- a/devices/devices/unb2.py
+++ b/devices/devices/unb2.py
@@ -23,9 +23,8 @@ from tango.server import device_property, attribute
 from tango import AttrWriteType
 # Additional import
 
-from clients.opcua_client import OPCUAConnection
 from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
+from devices.opcua_device import opcua_device
 
 from common.lofar_logging import device_logging_to_python, log_exceptions
 from common.lofar_git import get_version
@@ -35,39 +34,11 @@ import numpy
 __all__ = ["UNB2", "main"]
 
 @device_logging_to_python()
-class UNB2(hardware_device):
-    """
-
-    **Properties:**
-
-    - Device Property
-        OPC_Server_Name
-            - Type:'DevString'
-        OPC_Server_Port
-            - Type:'DevULong'
-        OPC_Time_Out
-            - Type:'DevDouble'
-    """
-
+class UNB2(opcua_device):
     # -----------------
     # 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
     # ----------
@@ -167,49 +138,10 @@ class UNB2(hardware_device):
 
     # QualifiedName(2: UNB2_on)
     # QualifiedName(2: UNB2_off)
-    @log_exceptions()
-    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.debug_stream("Shutting down...")
-
-        self.Off()
-        self.debug_stream("Shut down.  Good bye.")
 
     # --------
     # overloaded functions
     # --------
-    @log_exceptions()
-    def configure_for_off(self):
-        """ user code here. is called when the state is set to OFF """
-        # Stop keep-alive
-        try:
-            self.opcua_connection.stop()
-        except Exception as e:
-            self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e))
-
-    @log_exceptions()
-    def configure_for_initialise(self):
-        """ user code here. is called when the sate is set to INIT """
-        """Initialises the attributes and properties of theRECV."""
-
-        # set up the OPC ua client
-        self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self)
-
-        # map an access helper class
-        for i in self.attr_list():
-            try:
-                i.set_comm_client(self.OPCua_client)
-            except Exception as e:
-                # use the pass function instead of setting read/write fails
-                i.set_pass_func()
-                self.warn_stream("error while setting the UNB2 attribute {} read/write function. {}".format(i, e))
-
-        self.OPCua_client.start()
 
     # --------
     # Commands