diff --git a/CDB/LOFAR_ConfigDb.json b/CDB/LOFAR_ConfigDb.json
index 5b7465a5b52eb7dce306cf5bb69eaf7d108d3c17..ff3a7b7d11f93f1fc3e4754d75a6cc625d61e1b0 100644
--- a/CDB/LOFAR_ConfigDb.json
+++ b/CDB/LOFAR_ConfigDb.json
@@ -14,6 +14,13 @@
                 }
             }
         },
+        "station_control": {
+            "LTS": {
+                "StationControl": {
+                    "LTS/StationControl/1": {}
+                }
+            }
+        },
         "RECV": {
             "LTS": {
                 "RECV": {
diff --git a/devices/devices/station_control.py b/devices/devices/station_control.py
new file mode 100644
index 0000000000000000000000000000000000000000..27bca1714ef409f3a6c18730c04b1bc86c7a144b
--- /dev/null
+++ b/devices/devices/station_control.py
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the RECV project
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+""" StationControl 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 run, command
+from tango.server import device_property, attribute
+from tango import AttrWriteType, DeviceProxy, DevState
+# Additional import
+
+from device_decorators import *
+
+from clients.attribute_wrapper import attribute_wrapper
+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__ = ["StationControl", "main"]
+
+
+class InitialisationException(Exception):
+    pass
+
+@device_logging_to_python()
+class StationControl(hardware_device):
+    """
+
+    **Properties:**
+
+    - Device Property
+        OPC_Server_Name
+            - Type:'DevString'
+        OPC_Server_Port
+            - Type:'DevULong'
+        OPC_Time_Out
+            - Type:'DevDouble'
+    """
+
+    # -----------------
+    # Device Properties
+    # -----------------
+
+    DeviceProxy_Time_Out = device_property(
+        dtype='DevDouble',
+        mandatory=False,
+        default=3.0,
+    )
+
+    # By default, we assume any device is not available
+    # because its docker container was not started, which
+    # is an explicit and thus intentional action.
+    # We ignore such devices when initialising the station.
+    Ignore_Unavailable_Devices = device_property(
+        dtype='DevBoolean',
+        mandatory=False,
+        default=True,
+    )
+
+    # ----------
+    # Attributes
+    # ----------
+    version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version())
+    initialising_station_R = attribute(dtype=numpy.bool_, access=AttrWriteType.READ, fget=lambda self: self.initialising_station)
+    initialisation_progress_R = attribute(dtype=numpy.int, access=AttrWriteType.READ, fget=lambda self: numpy.int(self.initialisation_progress))
+
+    @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:
+            pass
+        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):
+        # all devices we're controlling
+        self.devices = {
+            "recv":   DeviceProxy("LTS/RECV/1"),
+            "unb2":   DeviceProxy("LTS/UNB2/1"),
+
+            "sdp":    DeviceProxy("LTS/SDP/1"),
+            "sst":    DeviceProxy("LTS/SST/1"),
+            "xst":    DeviceProxy("LTS/XST/1"),
+
+            "docker": DeviceProxy("LTS/Docker/1"),
+        }
+
+        # restart these before any others, and in this order
+        self.restart_first = [
+            "docker", # needed to do a deep restart of devices in case of malfunction
+            "sdp", # we need the TR_fpga_mask to be set before starting the statistics devices
+        ]
+
+        # set the timeout for all deviceproxies
+        for device in self.devices:
+            device.set_timeout_millis(self.DeviceProxy_Time_Out * 1000)
+
+        # setup initial state
+        self.initialising_station = False
+        self.initialisation_progress = 0
+
+    @command()
+    @DebugIt()
+    @only_when_on()
+    @fault_on_error()
+    def initialise_devices(self):
+        """
+        Initialise or re-initialise all devices on the station.
+
+        This command will take a while to execute, so should be called asynchronously.
+
+        :return:None
+        """
+
+        try:
+            # mark us as busy
+            self.set_state(DevState.RUNNING)
+
+            # reset initialisation parameters
+            self.initialising_station = True
+            self.initialisation_progress = 0
+            num_restarted_devices = 0
+
+            # determine initialisation order
+            devices_ordered = self.restart_first + [d for d in self.devices.keys() if d not in self.restart_first]
+
+            # First, stop all devices, to get a well defined state
+            for device in devices_ordered:
+                if self.is_available(device) or not self.Ignore_Unavailable_Devices:
+                    self.stop_device(device)
+
+            # restart devices in order
+            for device in devices_ordered:
+                if self.is_available(device) or not self.Ignore_Unavailable_Devices:
+                    self.start_device(device)
+
+                num_restarted_devices += 1
+                self.initialisation_progress = 100.0 * num_restarted_devices / len(self.devices)
+
+            # make sure we always finish at 100% in case of success
+            self.initialisation_progress = 100
+
+        except InitialisationException as e:
+            logger.log_exception("Error initialising station")
+
+            # Just because they go to FAULT, doesn't mean we have to.
+            # Note that the user can query the state of the devices from the devices themselves.
+            # The condition (initialisation_progress < 100 and not initialising_station)
+            # will be an indicator initialisation went wrong.
+        finally:
+            self.initialising_station = False
+
+        # revert to ON, which is the state we were called in. if an Exception happened by now,
+        # we'll go to FAULT. Both are guaranteed by our decorators.
+        self.set_state(DevState.ON)
+
+    def is_available(self, device_name: str):
+        """ Return whether the device 'device_name' is actually available on this server. """
+
+        proxy = self.devices[device_name]
+        try:
+            proxy.state()
+        except Exception as e:
+            return False
+
+        return True
+
+    @command()
+    @DebugIt()
+    @only_when_on()
+    def stop_device(self, device_name: str):
+        """ Stop device 'device_name'. """
+
+        if proxy.state() != DevState.OFF:
+            self.set_status(f"[restarting {device_name}] Turning off device.")
+            proxy.Off()
+            if proxy.state() != DevState.OFF:
+                raise InitialisationException(f"Could not turn off device {device_name}")
+
+
+    @command()
+    @DebugIt()
+    @only_when_on()
+    def start_device(self, device_name: str):
+        """ Run the startup sequence for device 'device_name'. """
+
+        proxy = self.devices[device_name]
+
+        # go to a well-defined state, which may be needed if the user calls
+        # this function explicitly.
+        self.stop(device_name)
+
+        # setup connections to hardware
+        self.set_status(f"[restarting {device_name}] Initialising device.")
+        proxy.Initialise()
+        if proxy.state() != DevState.INIT:
+            raise InitialisationException(f"Could not initialise device {device_name}")
+
+        # configure the device
+        try:
+            self.set_status(f"[restarting {device_name}] Setting defaults.")
+            proxy.set_defaults()
+
+            self.set_status(f"[restarting {device_name}] Initialising hardware.")
+            proxy.initialise_hardware()
+        except Exception as e:
+            raise InitialisationException(f"Could not configure device {device_name}") from e
+
+        # mark as ready for service
+        self.set_status(f"[restarting {device_name}] Turning on device.")
+        proxy.On()
+        if proxy.state() != DevState.ON:
+            raise InitialisationException(f"Could not turn on device {device_name}")
+
+        self.set_status(f"[restarting {device_name}] Succesfully restarted.")
+
+# ----------
+# Run server
+# ----------
+def main(args=None, **kwargs):
+    """Main function of the RECV module."""
+
+    from common.lofar_logging import configure_logger
+    configure_logger()
+
+    return run((StationControl,), args=args, **kwargs)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/docker-compose/device-station_control.yml b/docker-compose/device-station_control.yml
new file mode 100644
index 0000000000000000000000000000000000000000..02cf41e26dea31f90c1e294e61e5142b79044eed
--- /dev/null
+++ b/docker-compose/device-station_control.yml
@@ -0,0 +1,41 @@
+#
+# Docker compose file that launches a LOFAR2.0 station's
+# ObservationControl device. It also runs the dynamically
+# created Observation devices.
+#
+# Defines:
+#   - device-observation_control: LOFAR2.0 station ObvservationControl
+#
+# Requires:
+#   - lofar-device-base.yml
+#
+version: '2'
+
+services:
+  device-station_control:
+    image: device-station_control
+    # build explicitly, as docker-compose does not understand a local image
+    # being shared among services.
+    build:
+        context: lofar-device-base
+        args:
+            SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
+    container_name: ${CONTAINER_NAME_PREFIX}device-station_control
+    networks:
+      - control
+    ports:
+      - "5708:5708" # unique port for this DS
+    volumes:
+        - ${TANGO_LOFAR_CONTAINER_MOUNT}
+    environment:
+      - TANGO_HOST=${TANGO_HOST}
+    entrypoint:
+      - /usr/local/bin/wait-for-it.sh
+      - ${TANGO_HOST}
+      - --timeout=30
+      - --strict
+      - --
+      # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
+      # can't know about our Docker port forwarding
+      - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/station_control.py LTS -v -ORBendPoint giop:tcp:0:5708 -ORBendPointPublish giop:tcp:${HOSTNAME}:5708
+    restart: on-failure
diff --git a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
index df75d5962a1327041995aa04c41d6d1e1c2ae914..ba7ade76a915001e8ded8d7b3474c73fc2d25a1c 100644
--- a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
+++ b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
@@ -4,6 +4,7 @@ sdp = DeviceProxy("LTS/SDP/1")
 sst = DeviceProxy("LTS/SST/1")
 xst = DeviceProxy("LTS/XST/1")
 unb2 = DeviceProxy("LTS/UNB2/1")
+sc = DeviceProxy("LTS/StationControl/1")
 
 # Put them in a list in case one wants to iterate
-devices = [recv, sdp, sst, xst, unb2]
+devices = [recv, sdp, sst, xst, unb2, sc]