diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index c8fef1f669663a21453630743d6861ebee44ace1..ceb399bca70ab54c2015a3251ffa73226bc1b30b 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -45,10 +45,15 @@
 #include "api/interrupt-sender.h"
 #include "modules/log.h"
 
+#define SCAN_REPORTS_NUM	16
+
 static bool active;
 static uint8_t advertising_mode = APP_MODE_NONE;
 static uint8_t advertising_mode_target = APP_MODE_NONE;
 static enum ble_event_type ble_event;
+static struct epic_scan_report scan_reports[SCAN_REPORTS_NUM];
+static int scan_reports_head;
+static int scan_reports_tail;
 
 /**************************************************************************************************
   Macros
@@ -188,6 +193,15 @@ static const uint8_t bleAdvDataConn[] =
   DM_FLAG_LE_BREDR_NOT_SUP,
 };
 
+static const appMasterCfg_t scannerMasterCfg =
+{
+  420,                                     /*! The scan interval, in 0.625 ms units */
+  420,                                     /*! The scan window, in 0.625 ms units  */
+  0,                                       /*! The scan duration in ms */
+  DM_DISC_MODE_NONE,                       /*! The GAP discovery mode */
+  DM_SCAN_TYPE_PASSIVE
+                                           /*!< The scan type (active or passive) */
+};
 
 /**************************************************************************************************
   Client Characteristic Configuration Descriptors
@@ -472,15 +486,45 @@ static void bleSetup(bleMsg_t *pMsg)
 
   active = true;
   /* TODO: Sadly, not advertising leads to a higher current consumption... */
-  epic_ble_set_bondable(false);
+  epic_ble_set_mode(false, false);
 }
 
-void epic_ble_set_bondable(bool bondable)
+void epic_ble_set_mode(bool bondable, bool scanner)
 {
 	if(!active) {
 		return;
 	}
 
+	if(scanner && bondable) {
+		/* TODO: return error */
+		return;
+	}
+
+	if(scanner) {
+		if(advertising_mode != APP_MODE_NONE) {
+			advertising_mode_target = APP_MODE_NONE;
+			advertising_mode = APP_MODE_NONE;
+			AppAdvStop();
+		}
+
+		dmConnId_t      connId;
+		if ((connId = AppConnIsOpen()) != DM_CONN_ID_NONE) {
+			AppConnClose(connId);
+		}
+
+		/* Normal scanning filters out duplicates. We don't
+		* want that for now... */
+		//AppScanStart(scannerMasterCfg.discMode, scannerMasterCfg.scanType, scannerMasterCfg.scanDuration);
+		DmScanSetInterval(HCI_SCAN_PHY_LE_1M_BIT, &pAppMasterCfg->scanInterval,
+				&pAppMasterCfg->scanWindow);
+		DmScanStart(HCI_SCAN_PHY_LE_1M_BIT, scannerMasterCfg.discMode,
+				&scannerMasterCfg.scanType, FALSE, scannerMasterCfg.scanDuration, 0);
+
+		return;
+	} else {
+		AppScanStop();
+	}
+
 	if(bondable) {
 		/* We need to stop advertising in between or the
 		 * adv set will not be changed.
@@ -576,6 +620,48 @@ static void bleHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
 	trigger_event(BLE_EVENT_HANDLE_NUMERIC_COMPARISON);
 }
 
+int epic_ble_get_scan_report(struct epic_scan_report *rpt)
+{
+	if(scan_reports_head == scan_reports_tail) {
+		return -ENOENT;
+	}
+
+	int new_tail = (scan_reports_tail + 1) % SCAN_REPORTS_NUM;
+	*rpt = scan_reports[new_tail];
+	scan_reports_tail = new_tail;
+	return 0;
+}
+
+static void scannerScanReport(dmEvt_t *pMsg)
+{
+	struct epic_scan_report *scan_report;
+
+	int next_head = (scan_reports_head + 1) % SCAN_REPORTS_NUM;
+	if(next_head == scan_reports_tail) {
+		trigger_event(BLE_EVENT_SCAN_REPORT);
+		return;
+	}
+	scan_reports_head = next_head;
+	scan_report = &scan_reports[scan_reports_head];
+
+	memset(scan_report->data, 0, sizeof(scan_report->data));
+	memccpy(scan_report->data, pMsg->scanReport.pData, pMsg->scanReport.len, sizeof(scan_report->data));
+	scan_report->len = pMsg->scanReport.len;
+	scan_report->rssi = pMsg->scanReport.rssi;
+	scan_report->eventType = pMsg->scanReport.eventType;
+	scan_report->addrType = pMsg->scanReport.addrType;
+	memcpy(scan_report->addr, pMsg->scanReport.addr, BDA_ADDR_LEN);
+
+	scan_report->directAddrType = pMsg->scanReport.directAddrType;
+	memcpy(scan_report->directAddr, pMsg->scanReport.directAddr, BDA_ADDR_LEN);
+	trigger_event(BLE_EVENT_SCAN_REPORT);
+
+	if((scan_reports_head + 1) % SCAN_REPORTS_NUM == scan_reports_tail) {
+		LOG_WARN("ble", "Application missing scan results");
+	}
+}
+
+
 /*************************************************************************************************/
 /*!
  *  \brief  Process messages from the event handler.
@@ -610,10 +696,9 @@ static void bleProcMsg(bleMsg_t *pMsg)
 
     case DM_ADV_START_IND:
       LOG_INFO("ble", "Advertisement started %u %u", advertising_mode, advertising_mode_target);
-      if(advertising_mode != advertising_mode_target) {
+      if(advertising_mode != advertising_mode_target || advertising_mode_target == APP_MODE_NONE) {
         AppAdvStop();
       }
-
       break;
 
     case DM_ADV_STOP_IND:
@@ -719,6 +804,10 @@ static void bleProcMsg(bleMsg_t *pMsg)
       bleHandleNumericComparison(&pMsg->dm.cnfInd);
       break;
 
+    case DM_SCAN_REPORT_IND:
+      scannerScanReport((dmEvt_t *)pMsg);
+      break;
+
     case DM_HW_ERROR_IND:
       LOG_ERR("ble", "HW Error");
       break;
@@ -749,6 +838,7 @@ static void BleHandlerInit(void)
   pAppSlaveCfg = (appSlaveCfg_t *) &bleSlaveCfg;
   pAppSecCfg = (appSecCfg_t *) &bleSecCfg;
   pAppUpdateCfg = (appUpdateCfg_t *) &bleUpdateCfg;
+  pAppMasterCfg = (appMasterCfg_t *) &scannerMasterCfg;
 
   /* Initialize application framework */
   AppSlaveInit();
@@ -779,7 +869,7 @@ static void BleHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
 
     if (pMsg->event >= DM_CBACK_START && pMsg->event <= DM_CBACK_END)
     {
-      LOG_INFO("ble", "Ble got evt %d: %s", pMsg->event, dm_events[pMsg->event - DM_CBACK_START]);
+      if(pMsg->event != DM_SCAN_REPORT_IND) LOG_INFO("ble", "Ble got evt %d: %s", pMsg->event, dm_events[pMsg->event - DM_CBACK_START]);
       /* process advertising and connection-related messages */
       AppSlaveProcDmMsg((dmEvt_t *) pMsg);
 
diff --git a/epicardium/ble/stack.c b/epicardium/ble/stack.c
index a7896b68fa7c35a9d85a173b67d495ae95204e8d..3d24b0c5957b2df1195cbf22638c0db90560ac82 100644
--- a/epicardium/ble/stack.c
+++ b/epicardium/ble/stack.c
@@ -130,11 +130,7 @@ void StackInit(void)
       .freeMemAvail = LL_MEMORY_FOOTPRINT
   };
 
-#ifdef DATS_APP_USE_LEGACY_API
   memUsed = LlInitControllerExtInit(&ll_init_cfg);
-#else /* DATS_APP_USE_LEGACY_API */
-  memUsed = LlInitControllerExtInit(&ll_init_cfg);
-#endif /* DATS_APP_USE_LEGACY_API */
   if(memUsed != LL_MEMORY_FOOTPRINT)
   {
       printf("Controller memory mismatch 0x%x != 0x%x\n", (unsigned int)memUsed,
@@ -142,6 +138,11 @@ void StackInit(void)
   }
 #endif
 
+  SecInit();
+  SecRandInit();
+  SecAesInit();
+  SecCmacInit();
+  SecEccInit();
 
   /* card10:
    * These calls register a queue for callbacks in the OS abstraction
@@ -153,14 +154,10 @@ void StackInit(void)
   handlerId = WsfOsSetNextHandler(HciHandler);
   HciHandlerInit(handlerId);
 
-  SecInit();
-  SecAesInit();
-  SecCmacInit();
-  SecEccInit();
-
   handlerId = WsfOsSetNextHandler(DmHandler);
   DmDevVsInit(0);
   DmAdvInit();
+  DmScanInit();
   DmConnInit();
   DmConnSlaveInit();
   DmSecInit();
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 2ab3126f3400a95b92a8765389dd0173218b9904..2ad19134960d6ec35e110266ea0abab3754b255a 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -149,10 +149,11 @@ typedef _Bool bool;
 #define API_CONFIG_GET_BOOLEAN     0x132
 #define API_CONFIG_SET_STRING      0x133
 
-#define API_BLE_GET_COMPARE_VALUE  0x140
-#define API_BLE_COMPARE_RESPONSE   0x141
-#define API_BLE_SET_BONDABLE       0x142
-#define API_BLE_GET_EVENT          0x143
+#define API_BLE_GET_COMPARE_VALUE     0x140
+#define API_BLE_COMPARE_RESPONSE      0x141
+#define API_BLE_SET_MODE              0x142
+#define API_BLE_GET_EVENT             0x143
+#define API_BLE_GET_SCAN_REPORT       0x144
 
 /* clang-format on */
 
@@ -2036,7 +2037,7 @@ API(API_USB_CDCACM, int epic_usb_cdcacm(void));
 
 /**
  * Takes a gpio pin specified with the gpio module and transmits
- * the led data. The format `GG:RR:BB` is expected.
+ * the led data. The format ``GG:RR:BB`` is expected.
  *
  * :param uint8_t pin: The gpio pin to be used for data.
  * :param uint8_t * pixels: The buffer, in which the pixel data is stored.
@@ -2135,9 +2136,29 @@ enum ble_event_type {
 	BLE_EVENT_PAIRING_FAILED                          = 2,
 	/** A pairing procedure has successfully completed */
 	BLE_EVENT_PAIRING_COMPLETE                        = 3,
+	/** New scan data is available  */
+	BLE_EVENT_SCAN_REPORT                             = 4,
 };
 
 
+/**
+ * Scan report data. Bases on ``hciLeAdvReportEvt_t`` from BLE stack.
+ *
+ * TODO: 64 bytes for data is an arbitrary number ATM */
+struct epic_scan_report
+{
+  uint8_t             data[64];       /*!< \brief advertising or scan response data. */
+  uint8_t             len;            /*!< \brief length of advertising or scan response data. */
+  int8_t              rssi;           /*!< \brief RSSI. */
+  uint8_t             eventType;      /*!< \brief Advertising event type. */
+  uint8_t             addrType;       /*!< \brief Address type. */
+  uint8_t             addr[6];        /*!< \brief Device address. */
+
+  /* \brief direct fields */
+  uint8_t             directAddrType; /*!< \brief Direct advertising address type. */
+  uint8_t             directAddr[6];  /*!< \brief Direct advertising address. */
+};
+
 /**
  * **Interrupt Service Routine** for :c:data:`EPIC_INT_BLE`
  *
@@ -2202,22 +2223,44 @@ API(API_BLE_GET_COMPARE_VALUE, uint32_t epic_ble_get_compare_value(void));
 API(API_BLE_COMPARE_RESPONSE, void epic_ble_compare_response(bool confirmed));
 
 /**
- * Allow or disallow new bondings to happen
+ * Set the desired mode of the BLE stack.
+ *
+ * There are three allowed modes:
+ *
+ *  - Peripheral which is not bondable (bondable = ``false``, scanner = ``false``).
+ *  - Peripheral which is bondable (bondable = ``true``, scanner = ``false``).
+ *  - Observer which scans for advertisements (bondable = ``false``, scanner = ``true``).
  *
  * By default the card10 will not allow new bondings to be made. New
  * bondings have to explicitly allowed by calling this function.
  *
- * While bonadable the card10 will change its advertisements to
+ * While bondable the card10 will change its advertisements to
  * indicate to scanning hosts that it is available for discovery.
  *
+ * When scanning is active, :c:data:`BLE_EVENT_SCAN_REPORT` events will be sent
+ * and the scan reports can be fetched using :c:func:`epic_ble_get_scan_report`.
+ *
  * When switching applications new bondings are automatically
- * disallowed.
+ * disallowed and scanning is stopped.
  *
  * :param bool bondable: `true` if new bondings should be allowed.
+ * :param bool scanner: `true` if scanning should be turned on.
  *
  * .. versionadded:: 1.16
  */
-API(API_BLE_SET_BONDABLE, void epic_ble_set_bondable(bool bondable));
+API(API_BLE_SET_MODE, void epic_ble_set_mode(bool bondable, bool scanner));
 
+/**
+ * Retrieve a scan report from the queue of scan reports.
+ *
+ * :param struct epic_scan_report* rpt: Pointer where the report will be stored.
+ *
+ * :return: `0` on success or a negative value if an error occured. Possible
+ *    errors:
+ *
+ *    - ``-ENOENT``: No scan report available
+ *
+ */
+API(API_BLE_GET_SCAN_REPORT, int epic_ble_get_scan_report(struct epic_scan_report *rpt));
 #endif /* _EPICARDIUM_H */
 
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index 1168f5a756d81ee3e3d16a15e5e4c703314b2b7a..92dff16eadfdeea87f65fd47c956d1d67057ab44 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -292,7 +292,7 @@ int hardware_reset(void)
 	/*
 	 * BLE
 	 */
-	epic_ble_set_bondable(false);
+	epic_ble_set_mode(false, false);
 
 	return 0;
 }
diff --git a/lib/sdk/Libraries/BTLE/meson.build b/lib/sdk/Libraries/BTLE/meson.build
index 6dd10ee6a04dc62c8036da9681b739fa64ecf328..0f301cf1823469e2434d214d88ddf964423216c1 100644
--- a/lib/sdk/Libraries/BTLE/meson.build
+++ b/lib/sdk/Libraries/BTLE/meson.build
@@ -421,6 +421,7 @@ ble_compileargs = [
   '-DINIT_BROADCASTER',
   '-DINIT_PERIPHERAL',
   '-DINIT_ENCRYPTED',
+  '-DINIT_OBSERVER',
 ]
 
 if get_option('ble_trace')
diff --git a/pycardium/modules/sys_ble.c b/pycardium/modules/sys_ble.c
index d262ce4100889f5c8489384a448b12b14ced653a..34278be17cb7916d7500bbd63ba5b27dbd2c3b0f 100644
--- a/pycardium/modules/sys_ble.c
+++ b/pycardium/modules/sys_ble.c
@@ -33,7 +33,7 @@ static MP_DEFINE_CONST_FUN_OBJ_0(ble_get_event_obj, mp_ble_get_event);
 static mp_obj_t mp_ble_set_bondable(mp_obj_t bondable_obj)
 {
 	bool bondable = mp_obj_is_true(bondable_obj);
-	epic_ble_set_bondable(bondable);
+	epic_ble_set_mode(bondable, false);
 	return mp_const_none;
 }
 static MP_DEFINE_CONST_FUN_OBJ_1(ble_set_bondable_obj, mp_ble_set_bondable);