diff --git a/README.md b/README.md
index 91e5901aa16647c964b4bc40da5bb7d99f50896b..bc579103e6e432f0230cb42997b2f8f2a300a648 100644
--- a/README.md
+++ b/README.md
@@ -105,6 +105,7 @@ tox -e debug tests.requests.test_prometheus
 ```
 
 ## Releasenotes
+- 0.13  - Added lazy connection behavior to `devices.LofarDeviceProxy` class
 - 0.12.
     * Added `HDF5Writer` class for writing HDF5 statistics file
     * Added `Receiver` class for creating a file/TCP receiver for retrieving statistics data
diff --git a/VERSION b/VERSION
index 34a83616bb5aa9a70c5713bc45cd45498a50ba24..f3040840fd7058ec0e224314c609184fd4ec53f2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.12.1
+0.13
diff --git a/lofar_station_client/devices.py b/lofar_station_client/devices.py
index aac73f1b8f89f2c859c5f5fd42f9450b8116ad1f..b49468809fece20bba9ffd2ba86eb6b3e5c333ec 100644
--- a/lofar_station_client/devices.py
+++ b/lofar_station_client/devices.py
@@ -1,77 +1,116 @@
 """ Enhanced interfaces towards the station's Tango devices. """
 
+# inconsistent-return-statements
+# pylint: disable=R1710
+
 import ast
 from functools import lru_cache
+import logging
 
 import numpy
 
-from tango import DeviceProxy
+from tango import DeviceProxy, DevFailed
 from tango import ExtractAs
 
+logger = logging.getLogger()
+
 
 class LofarDeviceProxy(DeviceProxy):
     """A LOFAR-specific tango.DeviceProxy that provides
     a richer experience."""
 
+    def __init__(self, *args):
+        """Do not connect on device construction"""
+        self.__dict__["dev_name"] = str(args[0])
+        self.dev_name = str(args[0])
+        self.__dict__["connected"] = False
+        self.connected = False
+        logger.info("LOFARDeviceProxy %s not connected", self.dev_name)
+
+    def __str__(self):
+        if not self.connected:
+            return f"<LOFARDeviceProxy({self.dev_name})"
+        return super().__str__()
+
+    __repr__ = __str__
+    repr = __str__
+
+    def connect(self):
+        """Try to estabilish a connection when a device operation is called"""
+        if not self.connected:
+            try:
+                super().__init__(self.dev_name)
+                self.__dict__["connected"] = True
+                self.connected = True
+            except DevFailed as excep:
+                self.__dict__["connected"] = False
+                self.connected = False
+                reason = excep.args[0].desc.replace("\n", " ")
+                logger.warning("LOFARDeviceProxy not connected: %s", reason)
+
     @lru_cache()
     def get_attribute_config(self, name):
         """Get cached attribute configurations, as they are not expected to change."""
-
-        return super().get_attribute_config(name)
+        self.connect()
+        if self.connected:
+            return super().get_attribute_config(name)
 
     @lru_cache()
     def get_attribute_shape(self, name):
         """Get the shape of the requested attribute, as a tuple."""
-
-        config = self.get_attribute_config(name)
-
-        if config.format and config.format[0] == "(":
-            # For >2D arrays, the "format" property describes actual
-            # the dimensions as a tuple (x, y, z, ...),
-            # so reshape the value accordingly.
-            shape = ast.literal_eval(config.format)
-        elif config.max_dim_y > 0:
-            # 2D array
-            shape = (config.max_dim_y, config.max_dim_x)
-        elif config.max_dim_x > 1:
-            # 1D array
-            shape = (config.max_dim_x,)
-        else:
-            # scalar
-            shape = ()
-
-        return shape
+        self.connect()
+        if self.connected:
+            config = self.get_attribute_config(name)
+
+            if config.format and config.format[0] == "(":
+                # For >2D arrays, the "format" property describes actual
+                # the dimensions as a tuple (x, y, z, ...),
+                # so reshape the value accordingly.
+                shape = ast.literal_eval(config.format)
+            elif config.max_dim_y > 0:
+                # 2D array
+                shape = (config.max_dim_y, config.max_dim_x)
+            elif config.max_dim_x > 1:
+                # 1D array
+                shape = (config.max_dim_x,)
+            else:
+                # scalar
+                shape = ()
+
+            return shape
 
     def read_attribute(self, name, extract_as=ExtractAs.Numpy):
         """Read an attribute from the server."""
+        self.connect()
+        if self.connected:
+            attr = super().read_attribute(name, extract_as)
 
-        attr = super().read_attribute(name, extract_as)
-
-        # convert non-scalar values into their actual shape
-        shape = self.get_attribute_shape(name)
-        if shape != ():
-            attr.value = attr.value.reshape(shape)
+            # convert non-scalar values into their actual shape
+            shape = self.get_attribute_shape(name)
+            if shape != ():
+                attr.value = attr.value.reshape(shape)
 
-        return attr
+            return attr
 
     def write_attribute(self, name, value):
         """Write an attribute to the server."""
-
-        config = self.get_attribute_config(name)
-        shape = self.get_attribute_shape(name)
-
-        # 2D arrays also represent arrays of higher dimensionality. reshape them
-        # to fit their original Tango shape before writing.
-        if shape != ():
-            value = numpy.array(value)
-
-            if value.shape != shape:
-                raise ValueError(
-                    f"Invalid shape. Given: {value.shape} Expected: {shape}"
-                )
-
-            if len(shape) > 2:
-                # >2D arrays collapse into 2D
-                value = value.reshape((config.max_dim_y, config.max_dim_x))
-
-        return super().write_attribute(name, value)
+        self.connect()
+        if self.connected:
+            config = self.get_attribute_config(name)
+            shape = self.get_attribute_shape(name)
+
+            # 2D arrays also represent arrays of higher dimensionality. reshape them
+            # to fit their original Tango shape before writing.
+            if shape != ():
+                value = numpy.array(value)
+
+                if value.shape != shape:
+                    raise ValueError(
+                        f"Invalid shape. Given: {value.shape} Expected: {shape}"
+                    )
+
+                if len(shape) > 2:
+                    # >2D arrays collapse into 2D
+                    value = value.reshape((config.max_dim_y, config.max_dim_x))
+
+            return super().write_attribute(name, value)
diff --git a/tests/test_devices.py b/tests/test_devices.py
index 8db4f39178562a94ee6ddbadc151c728a173ddde..9cbf97dd0480b312f373c57e2c8e0c5e53dd9c9f 100644
--- a/tests/test_devices.py
+++ b/tests/test_devices.py
@@ -75,6 +75,7 @@ class LofarDeviceProxyTest(base.TestCase):
 
         cls.context.start()
         cls.proxy = LofarDeviceProxy(cls.context.get_device_access("STAT/MyDevice/1"))
+        cls.proxy.connect()  # necessary in the DeviceTestContext
 
     @classmethod
     def tearDownClass(cls):
@@ -137,6 +138,63 @@ class LofarDeviceProxyTest(base.TestCase):
         self.assertEqual((2, 3, 4), value.shape)
 
 
+class LazyLofarDeviceProxyTest(base.TestCase):
+    TEST_DEVICE_INFO = [
+        {
+            "class": MyDevice,
+            "devices": [{"name": "STAT/MyDevice/2", "properties": {}, "memorized": {}}],
+        }
+    ]
+
+    @classmethod
+    def setUpClass(cls):
+        # setting up the TestContext takes ~1 second, so do it only once
+        cls.context = MultiDeviceTestContext(
+            cls.TEST_DEVICE_INFO,
+            process=True,
+        )
+
+        cls.context.start()
+        cls.proxy = LofarDeviceProxy(cls.context.get_device_access("STAT/MyDevice/2"))
+        cls.proxy.connected = False
+
+    @classmethod
+    def tearDownClass(cls):
+        # In Python3.8+, we can use addClassCleanup instead
+        cls.context.stop()
+
+    @classmethod
+    def connect(cls):
+        # Simulate that a connection with the DB has been estabilished,
+        # i.e. Device has been created in the TangoDB
+        cls.proxy.connect()
+
+    @classmethod
+    def disconnect(cls):
+        # Simulate a disconnected device
+        cls.proxy.connected = False
+
+    def test_lazy_read_scalar(self):
+        self.disconnect()
+        # DeviceProxy not yet initialised
+        with self.assertRaises(AttributeError):
+            value = self.proxy.scalar
+        # Simulate connection with DB
+        self.connect()
+        value = self.proxy.scalar
+        self.assertEqual(bool, type(value))
+
+    def test_lazy_write_scalar(self):
+        self.disconnect()
+        with self.assertRaises(AttributeError):
+            self.proxy.scalar = True
+        # Simulate connection with DB
+        self.connect()
+        self.assertEqual(False, self.proxy.scalar)
+        self.proxy.scalar = True
+        self.assertEqual(True, self.proxy.scalar)
+
+
 class RecvDeviceTest(MyDevice):
 
     RCU_attenuator_dB_R = attribute(