diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py
index 6fcfafa22411a2f46b3c077afb4dbd06bd9ee14f..07ca38de73d81269b30d4ace30a09341e0085793 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation.py
@@ -6,88 +6,89 @@
 # See LICENSE.txt for more info.
 
 # PyTango imports
-from tango import DevState, AttrWriteType, DevString
-from tango.server import Device, command, attribute
+from tango import AttrWriteType
+from tango.server import attribute
+from tango import DevState
+
 import numpy
-from time import time
 
 from tangostationcontrol.common.entrypoint import entry
-from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
-from tangostationcontrol.common.lofar_version import get_version
-from tangostationcontrol.devices.device_decorators import only_in_states, only_when_on, fault_on_error
-
+from tangostationcontrol.common.lofar_logging import device_logging_to_python
+from tangostationcontrol.common.lofar_logging import log_exceptions
+from tangostationcontrol.devices.device_decorators import fault_on_error
+from tangostationcontrol.devices.device_decorators import only_when_on
+from tangostationcontrol.devices.device_decorators import only_in_states
+from tangostationcontrol.devices.lofar_device import lofar_device
+
+from datetime import datetime
 from json import loads
-
+from time import time
 import logging
+
 logger = logging.getLogger()
 
 __all__ = ["Observation", "main"]
 
+
 @device_logging_to_python()
-class Observation(Device):
+class Observation(lofar_device):
     """ Observation Device for LOFAR2.0
-    This Tango device is responsible for the set-up of hardware for a specific observation.  It will, if necessary keep tabs on HW MPs to signal issues that are not caught by MPs being outside their nominal range.
+    This Tango device is responsible for the set-up of hardware for a
+    specific observation.  It will, if necessary keep tabs on HW MPs to signal
+    issues that are not caught by MPs being outside their nominal range.
 
     The lifecycle of instances of this device is controlled by ObservationControl
     """
+
     # Attributes
-    version_R = attribute(dtype = str, access = AttrWriteType.READ, fget = lambda self: get_version())
-    observation_running_R = attribute(dtype = numpy.float, access = AttrWriteType.READ, polling_period = 1000, period = 1000, rel_change = "1.0")
-    observation_id_R = attribute(dtype = numpy.int64, access = AttrWriteType.READ)
-    stop_time_R = attribute(dtype = numpy.float, access = AttrWriteType.READ)
+    observation_running_R = attribute(dtype=numpy.float, access=AttrWriteType.READ, polling_period=1000, period=1000,
+                                      rel_change="1.0")
+    observation_id_R = attribute(dtype=numpy.int64, access=AttrWriteType.READ)
+    stop_time_R = attribute(dtype=numpy.float, access=AttrWriteType.READ)
+
+    observation_settings_RW = attribute(dtype=str, access=AttrWriteType.READ_WRITE)
 
-    # Core functions
-    @log_exceptions()
     def init_device(self):
-        Device.init_device(self)
-        self.set_state(DevState.OFF)
+        """Setup some class member variables for observation state"""
+
+        super().init_device()
+        self._observation_settings = loads("{}")
         self._observation_id = -1
-        self._stop_time = 0.0
+        self._stop_time = datetime.now()
+
+    def configure_for_initialise(self):
+        """Load the JSON from the attribute and configure member variables"""
+
+        super().configure_for_initialise()
 
-    @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.debug("Shutting down...")
-        if self.get_state() != DevState.OFF:
-            self.Off()
-        logger.debug("Shut down.  Good bye.")
-
-    # Lifecycle functions
-    @command(dtype_in = DevString)
-    @only_in_states([DevState.OFF])
-    @log_exceptions()
-    def Initialise(self, parameters: DevString = None):
-        self.set_state(DevState.INIT)
         # ObservationControl takes already good care of checking that the
         # parameters are in order and sufficient.  It is therefore unnecessary
         # at the moment to check the parameters here again.
         # This could change when the parameter check becomes depending on
         # certain aspects that only an Observation device can know.
-        self.observation_parameters = loads(parameters)
+        parameters = loads(self._observation_settings)
 
-        self._observation_id = int(self.observation_parameters.get("id"))
-        self._stop_time = float(self.observation_parameters.get("stop_time"))
-        self.set_state(DevState.STANDBY)
-        logger.info("The observation with ID={} is configured.  It will begin as soon as On() is called and it is supposed to stop at {}.".format(self._observation_id, self._stop_time))
+        self._observation_id = parameters["observation_id"]
+        self._stop_time = datetime.fromisoformat(parameters["stop_time"])
 
-    @command()
-    @only_in_states([DevState.STANDBY])
-    @log_exceptions()
-    def On(self):
-        self.set_state(DevState.ON)
-        logger.info("Started the observation with ID={}.".format(self._observation_id))
+        logger.info(
+            f"The observation with ID={self._observation_id} is "
+            "configured. It will begin as soon as On() is called and it is"
+            f"supposed to stop at {self._stop_time}")
 
-    @command()
-    @log_exceptions()
-    def Off(self):
-        self.stop_polling(True)
-        self.set_state(DevState.OFF)
-        logger.info("Stopped the observation with ID={}.".format(self._observation_id))
+    def configure_for_off(self):
+        """Indicate the observation has stopped"""
+
+        super().configure_for_off()
+
+        logger.info(f"Stopped the observation with ID={self._observation_id}.")
+
+    def configure_for_on(self):
+        """Indicate the observation has started"""
+
+        super().configure_for_on()
+
+        logger.info(f"Started the observation with ID={self._observation_id}.")
 
     @only_when_on()
     @fault_on_error()
@@ -101,13 +102,28 @@ class Observation(Device):
     @log_exceptions()
     def read_stop_time_R(self):
         """Return the stop_time_R attribute."""
-        return self._stop_time
+        return self._stop_time.timestamp()
+
+    @fault_on_error()
+    @log_exceptions()
+    def read_observation_settings_RW(self):
+        """Return current observation_parameters string"""
+        return self._observation_settings
+
+    @only_in_states([DevState.OFF])
+    @fault_on_error()
+    @log_exceptions()
+    def write_observation_settings_RW(self, parameters: str):
+        """No validation on configuring parameters as task of control device"""
+        self._observation_settings = parameters
 
     @only_when_on()
     @fault_on_error()
     @log_exceptions()
     def read_observation_running_R(self):
         """Return the observation_running_R attribute."""
+        # TODO(Corne): Actually keep track of the running time and perform proper
+        #              value
         return time()
 
 
diff --git a/tangostationcontrol/tangostationcontrol/devices/observation_control.py b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
index d6701bc38bf03074e0983f1dacf92d02f11676bb..cfb3f2f1785301c0eea96a3c65052000c7f3b1f9 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation_control.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
@@ -5,28 +5,30 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-# PyTango imports
+from datetime import datetime
+from json import loads
+import logging
+import time
+
+import numpy
 from tango import Except, DevFailed, DevState, AttrWriteType, DebugIt, DeviceProxy, Util, DevBoolean, DevString
 from tango.server import Device, command, attribute
 from tango import EventType
 
-import numpy
-import time
-from json import loads
-
 from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
 from tangostationcontrol.common.lofar_version import get_version
-from tangostationcontrol.devices.device_decorators import only_in_states, only_when_on, fault_on_error
+from tangostationcontrol.devices.device_decorators import only_when_on, fault_on_error
+from tangostationcontrol.devices.lofar_device import lofar_device
 from tangostationcontrol.devices.observation import Observation
 
-import logging
 logger = logging.getLogger()
 
 __all__ = ["ObservationControl", "main"]
 
+
 @device_logging_to_python()
-class ObservationControl(Device):
+class ObservationControl(lofar_device):
     """ Observation Control Device Server for LOFAR2.0
     The ObservationControl Tango device controls the instantiation of a Tango Dynamic Device from the Observation class.  ObservationControl then keeps a record of the Observation devices and if they are still alive.
 
@@ -104,49 +106,12 @@ class ObservationControl(Device):
         # device's name.
         self.myTangoDomain = self.get_name().split('/')[0]
 
-    @log_exceptions()
-    @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).
-        """
-        if self.get_state != DevState.OFF:
-            self.Off()
-
     # Lifecycle functions
-    @command()
-    @only_in_states([DevState.OFF])
-    @log_exceptions()
-    @DebugIt()
-    def Initialise(self):
-        self.set_state(DevState.INIT)
+    def configure_for_initialise(self):
         self.running_observations.clear()
-        self.set_state(DevState.STANDBY)
-
-    @command()
-    @only_in_states([DevState.STANDBY])
-    @log_exceptions()
-    @DebugIt()
-    def On(self):
-        self.set_state(DevState.ON)
 
-    @command()
-    @log_exceptions()
-    @DebugIt()
-    def Off(self):
-        if self.get_state() != DevState.OFF:
-            self.stop_all_observations()
-            self.set_state(DevState.OFF)
-
-    @command()
-    @log_exceptions()
-    @DebugIt()
-    def Fault(self):
+    def configure_for_off(self):
         self.stop_all_observations()
-        self.set_state(DevState.FAULT)
 
     @only_when_on()
     @fault_on_error()
@@ -212,17 +177,17 @@ class ObservationControl(Device):
 
         # Parameter check, do not execute an observation in case
         # the parameters are not sufficiently defined.
-        obs_id = int(parameter_dict.get("obs_id"))
-        stop_time = float(parameter_dict.get("stop_time"))
-        # TODO(Once ticket https://support.astron.nl/jira/browse/L2SS-254 is
-        #  done, this needs to be replaced by a proper JSON verification
-        #  against a schema.)
+        obs_id = int(parameter_dict["observation_id"])
+        stop_datetime = datetime.fromisoformat(parameter_dict["stop_time"])
+        # TODO(Jan David): Once ticket https://support.astron.nl/jira/browse/L2SS-254
+        #                  is done, this needs to be replaced by a proper JSON
+        #                  verification against a schema.
         if obs_id is None or obs_id < 1:
             # Do not execute
             error = "Cannot start an observation with ID={} because the observation ID is invalid.  The ID must be any integer >= 1.".format(obs_id)
             Except.throw_exception("IllegalCommand", error, __name__)
-        elif stop_time is None or stop_time <= time.time():
-            error = "Cannot start an observation with ID={} because the parameter stop_time parameter value=\"{}\" is invalid.  It needs to be expressed as the number of seconds since the Unix epoch.".format(obs_id, stop_time)
+        elif stop_datetime is None or stop_datetime <= datetime.now():
+            error = "Cannot start an observation with ID={} because the parameter stop_time parameter value=\"{}\" is invalid.  It needs to be expressed in ISO 8601 format.".format(obs_id, stop_datetime)
             Except.throw_exception("IllegalCommand", error, __name__)
         elif len(parameters) == 0:
             error = "Cannot start an observation with ID={} because the parameter set is empty.".format(obs_id)
@@ -267,7 +232,7 @@ class ObservationControl(Device):
         # Store everything about the observation in this dict.  I store this
         # dict at the end in self.running_observations.
         observation = {"parameters": self.check_and_convert_parameters(parameters)}
-        obs_id = int(observation["parameters"].get("obs_id"))
+        observation_id = observation['parameters']['observation_id']
 
         # The class name of the Observation class is needed to create and
         # delete the device.
@@ -275,14 +240,14 @@ class ObservationControl(Device):
         observation["class_name"] = class_name
 
         # Generate the Tango DB name for the Observation device.
-        device_name = "{}/{}/{}".format(self.myTangoDomain, class_name, obs_id)
+        device_name = f"{self.myTangoDomain}/{class_name}/{observation_id}"
         observation["device_name"] = device_name
 
         try:
             # Create the Observation device and instantiate it.
             self.create_dynamic_device(class_name, device_name)
         except DevFailed as ex:
-            error_string = "Cannot create the Observation device instance {} for ID={}.  This means that the observation did not start.".format(device_name, obs_id)
+            error_string = "Cannot create the Observation device instance {} for ID={}.  This means that the observation did not start.".format(device_name, observation_id)
             logger.exception(error_string)
             Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
 
@@ -291,10 +256,14 @@ class ObservationControl(Device):
             device_proxy = DeviceProxy(device_name)
             observation["device_proxy"] = device_proxy
 
+            # Configure the dynamic device its attribute for the observation
+            # parameters.
+            device_proxy.observation_settings_RW = parameters
+
             # Take the Observation device through the motions.  Pass the
             # entire JSON set of parameters so that it can pull from it what it
             # needs.
-            device_proxy.Initialise(parameters)
+            device_proxy.Initialise()
 
             # The call to On will actually tell the Observation device to
             # become fully active.
@@ -302,7 +271,7 @@ class ObservationControl(Device):
         except DevFailed as ex:
             # Remove the device again.
             self.delete_dynamic_device(class_name, device_name)
-            error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}.  This means that the observation likely did not start but certainly cannot be controlled and/or forcefully be stopped.".format(obs_id, class_name, device_name)
+            error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}.  This means that the observation likely did not start but certainly cannot be controlled and/or forcefully be stopped.".format(observation_id, class_name, device_name)
             logger.exception(error_string)
             Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
 
@@ -324,19 +293,19 @@ class ObservationControl(Device):
             # event.  And since the call back checks if the obs_id is in the dict
             # this triggers an error message if the ID is not already known.
             # There is no harm in copying the dict twice.
-            self.running_observations[obs_id] = observation
+            self.running_observations[observation_id] = observation
 
-            # Right.  Now subscribe to periodic events.
+            # Right. Now subscribe to periodic events.
             event_id = device_proxy.subscribe_event(attribute_name.split('/')[-1], EventType.PERIODIC_EVENT, self.observation_running_callback)
             observation["event_id"] = event_id
 
             # Finally update the self.running_observation dict's entry of this
             # observation with the complete set of info.
-            self.running_observations[obs_id] = observation
-            logger.info("Successfully started an observation with ID={}.".format(obs_id))
+            self.running_observations[observation_id] = observation
+            logger.info(f"Successfully started an observation with ID={observation_id}.")
         except DevFailed as ex:
             self.delete_dynamic_device(class_name, device_name)
-            error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}.  This means that the observation cannot be controlled and/or forcefully be stopped.".format(obs_id, Observation.__name__, device_name)
+            error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}.  This means that the observation cannot be controlled and/or forcefully be stopped.".format(observation_id, Observation.__name__, device_name)
             logger.exception(error_string)
             Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
 
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
index 47907110bd0be8576720b11aea8c9431f452ffdd..198a4cba29453a2e60665362edfa44b1cc52f63f 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
@@ -62,15 +62,11 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase):
         devices = [DeviceProxy("STAT/SDP/1"), DeviceProxy("STAT/UNB2/1"), DeviceProxy("STAT/RECV/1"),
                    DeviceProxy("STAT/APSCT/1"), DeviceProxy("STAT/APSPU/1"), DeviceProxy("STAT/PDU/1")]
 
-        # sleeping here to make sure we've dealt with any pre-existing events
-        time.sleep(2)
-
         # make sure none of the devices are in the OFF state. Any other state is fine
         for dev in devices:
             if dev.state() == DevState.OFF:
                 dev.initialise()
-                dev.on()
-                
+
         # toggle the attribute to make sure we get a change event to True
         self.recv_proxy.HBAT_LED_on_RW = [[False] * 32] * 96
         self.recv_proxy.HBAT_LED_on_RW = [[True] * 32] * 96
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/device_base.py b/tangostationcontrol/tangostationcontrol/test/devices/device_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..85c8c908ba03a93a137b30a19b07208cec99094e
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/test/devices/device_base.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+from tangostationcontrol.devices import lofar_device
+
+from tangostationcontrol.test import base
+
+import mock
+
+
+class DeviceTestCase(base.TestCase):
+    """BaseClass for device test cases to perform common DeviceProxy patching
+
+    Only to be used on devices inheriting lofar_device, use device_proxy_patch
+    to patch additional devices their proxy.
+    """
+
+    def setUp(self):
+        super(DeviceTestCase, self).setUp()
+
+        # Patch DeviceProxy to allow making the proxies during initialisation
+        # that we otherwise avoid using
+        for device in [lofar_device]:
+            self.device_proxy_patch(device)
+
+    def device_proxy_patch(self, device):
+        proxy_patcher = mock.patch.object(
+            device, 'DeviceProxy')
+        proxy_patcher.start()
+        self.addCleanup(proxy_patcher.stop)
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py
index c0e56a8439ce4fe244d8fd2c9b192205fe137b7a..b27c59e0f0deedd607398ad796b60550eea06e81 100644
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py
@@ -8,16 +8,16 @@
 # See LICENSE.txt for more info.
 
 import numpy
-import unittest
-import mock
 
 from tango.test_context import DeviceTestContext
 
-from tangostationcontrol.devices import antennafield, lofar_device
+from tangostationcontrol.devices import antennafield
 from tangostationcontrol.devices.antennafield import HBATToRecvMapper
 from tangostationcontrol.test import base
+from tangostationcontrol.test.devices import device_base
 
-class TestHBATToRecvMapper(unittest.TestCase):
+
+class TestHBATToRecvMapper(base.TestCase):
 
     # A mapping where HBATs are all not mapped to power RCUs
     power_not_connected = [[-1, -1]] * 48
@@ -302,22 +302,16 @@ class TestHBATToRecvMapper(unittest.TestCase):
         actual = mapper.map_write("HBAT_PWR_on_RW", set_values)
         numpy.testing.assert_equal(expected, actual)
 
-class TestAntennafieldDevice(base.TestCase):
+
+class TestAntennafieldDevice(device_base.DeviceTestCase):
 
     # some dummy values for mandatory properties
     at_properties = {'OPC_Server_Name': 'example.com', 'OPC_Server_Port': 4840, 'OPC_Time_Out': 5.0, 
     'Antenna_Field_Reference_ITRF' : [3.0, 3.0, 3.0], 'Antenna_Field_Reference_ETRS' : [7.0, 7.0, 7.0]}
 
     def setUp(self):
-        super(TestAntennafieldDevice, self).setUp()     
-
-        # Patch DeviceProxy to allow making the proxies during initialisation
-        # that we otherwise avoid using
-        for device in [lofar_device]:
-            proxy_patcher = mock.patch.object(
-                device, 'DeviceProxy')
-            proxy_patcher.start()
-            self.addCleanup(proxy_patcher.stop)
+        # DeviceTestCase setUp patches lofar_device DeviceProxy
+        super(TestAntennafieldDevice, self).setUp()
 
     def test_read_Antenna_Field_Reference(self):
         """Verify if Antenna coordinates are correctly provided"""
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py
index d79a22f31b24d954b7f672b83ffe29243064ae4f..7b84e6992d9dbe7f2c36db920ba18f53eb052e17 100644
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py
@@ -7,21 +7,11 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-from tangostationcontrol.devices import tilebeam, lofar_device
+from tangostationcontrol.test.devices import device_base
 
-import mock
 
-from tangostationcontrol.test import base
-
-class TestBeamDevice(base.TestCase):
+class TestBeamDevice(device_base.DeviceTestCase):
 
     def setUp(self):
+        # DeviceTestCase setUp patches lofar_device DeviceProxy
         super(TestBeamDevice, self).setUp()
-
-        # Patch DeviceProxy to allow making the proxies during initialisation
-        # that we otherwise avoid using
-        for device in [tilebeam, lofar_device]:
-            proxy_patcher = mock.patch.object(
-                device, 'DeviceProxy')
-            proxy_patcher.start()
-            self.addCleanup(proxy_patcher.stop)
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_device_temperature_manager.py b/tangostationcontrol/tangostationcontrol/test/devices/test_device_temperature_manager.py
index ea81159f1a5290f4e1eb80ad8b7ea8a79b52d387..ca54101ed14e8affee1dce064ea92bf5ba9065a9 100644
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_device_temperature_manager.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_device_temperature_manager.py
@@ -7,34 +7,34 @@
 #
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
-from tango.test_context import DeviceTestContext
-from tangostationcontrol.devices import temperature_manager, lofar_device
-
-import mock
 
-from tangostationcontrol.test import base
+import time
 
+from tango.test_context import DeviceTestContext
+from tangostationcontrol.devices import temperature_manager
 
+from tangostationcontrol.test.devices import device_base
 
 
-class TestTemperatureManagerDevice(base.TestCase):
+class TestTemperatureManagerDevice(device_base.DeviceTestCase):
 
     def setUp(self):
+        # DeviceTestCase setUp patches lofar_device DeviceProxy
         super(TestTemperatureManagerDevice, self).setUp()
 
-        # Patch DeviceProxy to allow making the proxies during initialisation
-        # that we otherwise avoid using
-        for device in [temperature_manager, lofar_device]:
-            proxy_patcher = mock.patch.object(
-                device, 'DeviceProxy')
-            proxy_patcher.start()
-            self.addCleanup(proxy_patcher.stop)
+        # Patch additional devices using baseclass device_proxy_patch
+        for device in [temperature_manager]:
+            self.device_proxy_patch(device)
 
     def test_alarm(self):
         with DeviceTestContext(temperature_manager.TemperatureManager, process=True, timeout=10) as proxy:
             proxy.initialise()
             proxy.on()
 
+            # TODO(Corne): Remove this once race condition in is_alarming_R
+            #              addressed. L2SS-816
+            time.sleep(1)
+
             self.assertFalse(proxy.is_alarming_R)
 
 
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py
index 15434810dd7bf9d3162ce64282661f3fa358b3de..d97cf7b9ebc6625cf9568fb2db24cab4edd51bc7 100644
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py
@@ -12,21 +12,14 @@ from tango.server import attribute
 
 from tangostationcontrol.devices import lofar_device
 
-import mock
+from tangostationcontrol.test.devices import device_base
 
-from tangostationcontrol.test import base
 
-class TestLofarDevice(base.TestCase):
+class TestLofarDevice(device_base.DeviceTestCase):
+
     def setUp(self):
+        # DeviceTestCase setUp patches lofar_device DeviceProxy
         super(TestLofarDevice, self).setUp()
-
-        # Patch DeviceProxy to allow making the proxies during initialisation
-        # that we otherwise avoid using
-        for device in [lofar_device]:
-            proxy_patcher = mock.patch.object(
-                device, 'DeviceProxy')
-            proxy_patcher.start()
-            self.addCleanup(proxy_patcher.stop)
     
     def test_read_attribute(self):
         """ Test whether read_attribute really returns the attribute. """
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_observation_base.py b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..f98a7c4754262e76cf3bb4d72630fcc3baf5f94a
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_base.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+
+class TestObservationBase:
+
+    # TODO(Corne): Use this once working on L2SS-774
+    VALID_JSON = '''
+            {
+              "observation_id": 12345,
+              "stop_time": "2106-02-07T00:00:00",
+              "antenna_mask": [0,1,2,9],
+              "filter": "HBA_110_190",
+              "SAPs": [{ 
+                    "subbands": [10, 20, 30],
+                    "pointing": {
+                        "angle1": 1.5, "angle2": 0, "direction_type": "J2000"
+                    }
+              }],
+              "tile_beam":
+                { "angle1": 1.5, "angle2": 0, "direction_type": "J2000" },
+              "first_beamlet": 0
+            }
+        '''
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_observation_control_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_control_device.py
new file mode 100644
index 0000000000000000000000000000000000000000..93420364a8e8d28f2cea790a9be177f590a9ffea
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_control_device.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+import mock
+
+from tango.test_context import DeviceTestContext
+from tango import DevState
+from tango import DevFailed
+
+import json
+from datetime import datetime
+from datetime import timedelta
+
+from tangostationcontrol.devices import observation_control
+
+from tangostationcontrol.test import base
+from tangostationcontrol.test.devices import test_observation_base
+
+
+class TestObservationControlDevice(base.TestCase, test_observation_base.TestObservationBase):
+
+    def setUp(self):
+        super(TestObservationControlDevice, self).setUp()
+
+    def on_device_assert(self, proxy):
+        """Transition the device to ON and assert intermediate states"""
+
+        proxy.Off()
+        self.assertEqual(DevState.OFF, proxy.state())
+        proxy.Initialise()
+        self.assertEqual(DevState.STANDBY, proxy.state())
+        proxy.On()
+        self.assertEqual(DevState.ON, proxy.state())
+
+    def mock_dynamic_devices(self):
+        observation_proxy = mock.patch.object(
+            observation_control, 'DeviceProxy')
+        self.m_observation_control = observation_proxy.start()
+        self.addCleanup(observation_proxy.stop)
+
+        observation_dynamic_device = mock.patch.object(
+            observation_control.ObservationControl, 'create_dynamic_device',
+            autospec=True)
+        self.m_observation_dynamic_device = observation_dynamic_device.start()
+        self.addCleanup(observation_dynamic_device.stop)
+
+    def test_device_on(self):
+        """Transition the ObservationControl device to ON state"""
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+    def test_no_observation_running(self):
+        """Assert no current observations on fresh boot"""
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            self.assertFalse(proxy.is_any_observation_running())
+            self.assertFalse(proxy.is_observation_running(12345))
+            self.assertFalse(proxy.is_observation_running(54321))
+
+    def test_check_and_convert_parameters_invalid_id(self):
+        """Test invalid parameter detection"""
+        self.mock_dynamic_devices()
+
+        parameters = json.loads(self.VALID_JSON)
+        parameters['observation_id'] = -1
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            self.assertRaises(
+                DevFailed, proxy.start_observation, json.dumps(parameters))
+
+    def test_check_and_convert_parameters_invalid_time(self):
+        """Test invalid parameter detection"""
+        self.mock_dynamic_devices()
+
+        parameters = json.loads(self.VALID_JSON)
+        parameters['stop_time'] = (datetime.now() - timedelta(seconds=1)).isoformat()
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            self.assertRaises(
+                DevFailed, proxy.start_observation, json.dumps(parameters))
+
+    def test_check_and_convert_parameters_invalid_empty(self):
+        """Test empty parameter detection"""
+        self.mock_dynamic_devices()
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            self.assertRaises(
+                DevFailed, proxy.start_observation, "{}")
+
+    @mock.patch.object(
+        observation_control.ObservationControl, 'delete_dynamic_device')
+    def test_start_observation(self, m_delete_device):
+        """Test starting an observation"""
+        self.mock_dynamic_devices()
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            proxy.start_observation(self.VALID_JSON)
+
+            self.assertTrue(proxy.is_any_observation_running())
+            self.assertTrue(proxy.is_observation_running(12345))
+
+    @mock.patch.object(
+        observation_control.ObservationControl, 'delete_dynamic_device')
+    def test_start_observation_multiple(self, m_delete_device):
+        """Test starting multiple observations"""
+        self.mock_dynamic_devices()
+
+        second_observation_json = json.loads(self.VALID_JSON)
+        second_observation_json['observation_id'] = 54321
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            proxy.start_observation(self.VALID_JSON)
+            proxy.start_observation(json.dumps(second_observation_json))
+
+            self.assertTrue(proxy.is_any_observation_running())
+            self.assertTrue(proxy.is_observation_running(12345))
+            self.assertTrue(proxy.is_observation_running(54321))
+
+    def test_stop_observation_invalid_id(self):
+        """Test stop_observation exceptions for invalid ids"""
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            self.assertRaises(DevFailed, proxy.stop_observation, -1)
+
+    def test_stop_observation_invalid_running(self):
+        """Test stop_observation exceptions for not running"""
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            self.assertRaises(DevFailed, proxy.stop_observation, 2)
+
+    def test_is_any_observation_running_after_stop_all_observations(self):
+        """Test whether is_any_observation_running conforms when we start & stop an observation"""
+        self.mock_dynamic_devices()
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            proxy.start_observation(self.VALID_JSON)
+            proxy.stop_all_observations()
+
+            # Test false
+            self.assertFalse(proxy.is_any_observation_running())
+
+    def test_start_stop_observation(self):
+        """Test starting and stopping an observation"""
+        self.mock_dynamic_devices()
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            # uses ID 12345
+            proxy.start_observation(self.VALID_JSON)
+            proxy.stop_observation(12345)
+
+            # Test false
+            self.assertFalse(proxy.is_observation_running(12345))
+
+    def test_start_multi_stop_all_observation(self):
+        """Test starting and stopping multiple observations"""
+        self.mock_dynamic_devices()
+
+        second_observation_json = json.loads(self.VALID_JSON)
+        second_observation_json['observation_id'] = 54321
+
+        with DeviceTestContext(observation_control.ObservationControl,
+                               process=True) as proxy:
+            self.on_device_assert(proxy)
+
+            # uses ID 12345
+            proxy.start_observation(self.VALID_JSON)
+            proxy.start_observation(json.dumps(second_observation_json))
+            proxy.stop_all_observations()
+
+            # Test false
+            self.assertFalse(proxy.is_observation_running(12345))
+            self.assertFalse(proxy.is_observation_running(54321))
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_observation_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_device.py
new file mode 100644
index 0000000000000000000000000000000000000000..1087fdd9f2fa927b7ca88867d246802c09ffc6aa
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_device.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+from json import loads
+from datetime import datetime
+
+from tango.test_context import DeviceTestContext
+from tango import DevFailed
+from tango import DevState
+
+from tangostationcontrol.devices import observation
+
+from tangostationcontrol.test.devices import device_base
+from tangostationcontrol.test.devices import test_observation_base
+
+
+class TestObservationDevice(device_base.DeviceTestCase, test_observation_base.TestObservationBase):
+
+    def setUp(self):
+        # DeviceTestCase setUp patches lofar_device DeviceProxy
+        super(TestObservationDevice, self).setUp()
+
+    def test_init_valid(self):
+        """Initialize an observation with valid JSON"""
+
+        with DeviceTestContext(observation.Observation, process=True) as proxy:
+            proxy.off()
+            proxy.observation_settings_RW = self.VALID_JSON
+            proxy.Initialise()
+            self.assertEqual(DevState.STANDBY, proxy.state())
+
+    def test_init_invalid(self):
+        """Initialize an observation with _invalid_ JSON"""
+
+        with DeviceTestContext(observation.Observation, process=True) as proxy:
+            proxy.off()
+            proxy.observation_settings_RW = "{}"
+            proxy.Initialise()
+            self.assertEqual(DevState.FAULT, proxy.state())
+
+    def test_prohibit_rewriting_settings(self):
+        """Test that changing observation settings is disallowed once init"""
+
+        with DeviceTestContext(observation.Observation, process=True) as proxy:
+            proxy.off()
+            proxy.observation_settings_RW = self.VALID_JSON
+            proxy.Initialise()
+
+            with self.assertRaises(DevFailed):
+                proxy.write_attribute(
+                    "observation_settings_RW", self.VALID_JSON)
+
+    def test_attribute_match(self):
+        """Test that JSON data is exposed to attributes"""
+
+        data = loads(self.VALID_JSON)
+        stop_timestamp = datetime.fromisoformat(data["stop_time"]).timestamp()
+
+        with DeviceTestContext(observation.Observation, process=True) as proxy:
+            proxy.off()
+            proxy.observation_settings_RW = self.VALID_JSON
+            proxy.Initialise()
+            proxy.On()
+
+            self.assertEqual(DevState.ON, proxy.state())
+            self.assertEqual(stop_timestamp, proxy.stop_time_R)
+            self.assertEqual(data["observation_id"], proxy.observation_id_R)
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_recv_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_recv_device.py
index 4ec6e4278e556f4617c433adb98b4e7d93917c4c..cece1ea75344d4f74543c332ca609d93000411ab 100644
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_recv_device.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_recv_device.py
@@ -9,28 +9,21 @@
 
 from tango.test_context import DeviceTestContext
 
-from tangostationcontrol.devices import recv, lofar_device
+from tangostationcontrol.devices import recv
 
-import mock
 import numpy
 
-from tangostationcontrol.test import base
+from tangostationcontrol.test.devices import device_base
 
-class TestRecvDevice(base.TestCase):
+
+class TestRecvDevice(device_base.DeviceTestCase):
 
     # some dummy values for mandatory properties
     recv_properties = {'OPC_Server_Name': 'example.com', 'OPC_Server_Port': 4840, 'OPC_Time_Out': 5.0}
 
     def setUp(self):
-        super(TestRecvDevice, self).setUp()     
-
-        # Patch DeviceProxy to allow making the proxies during initialisation
-        # that we otherwise avoid using
-        for device in [lofar_device]:
-            proxy_patcher = mock.patch.object(
-                device, 'DeviceProxy')
-            proxy_patcher.start()
-            self.addCleanup(proxy_patcher.stop)
+        # DeviceTestCase setUp patches lofar_device DeviceProxy
+        super(TestRecvDevice, self).setUp()
        
     def test_calculate_HBAT_bf_delay_steps(self):
         """Verify HBAT beamforming calculations are correctly executed"""
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_snmp_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_snmp_device.py
index 6289b2a33162031b01998aeeb84cc2119fd78860..23d81f6d5634b6e4cb1e7ca9ee935484efbdbcc7 100644
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_snmp_device.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_snmp_device.py
@@ -9,30 +9,22 @@
 
 from tango.test_context import DeviceTestContext
 
-from tangostationcontrol.devices import snmp_device, lofar_device
+from tangostationcontrol.devices import snmp_device
 
-import mock
 from os import path
 
-from tangostationcontrol.test import base
+from tangostationcontrol.test.devices import device_base
 
 
-class TestSNMPDevice(base.TestCase):
+class TestSNMPDevice(device_base.DeviceTestCase):
 
     # some dummy values for mandatory properties
     snmp_properties = {'SNMP_community': 'localhost', 'SNMP_host': 161, 'SNMP_rel_mib_dir': "SNMP_mib_loading", 'SNMP_timeout': 5.0}
 
     def setUp(self):
+        # DeviceTestCase setUp patches lofar_device DeviceProxy
         super(TestSNMPDevice, self).setUp()
 
-        # Patch DeviceProxy to allow making the proxies during initialisation
-        # that we otherwise avoid using
-        for device in [lofar_device]:
-            proxy_patcher = mock.patch.object(
-                device, 'DeviceProxy')
-            proxy_patcher.start()
-            self.addCleanup(proxy_patcher.stop)
-
     def test_get_mib_dir(self):
         with DeviceTestContext(snmp_device.SNMP, properties=self.snmp_properties, process=True) as proxy: