diff --git a/epicardium/ble/ble_api.h b/epicardium/ble/ble_api.h
index 7e2fbbf865e7bf07deea39d4403e4acb812a7f8e..a5d0b9ccf2c314d2eaae86d3e3ad26e133963ddd 100644
--- a/epicardium/ble/ble_api.h
+++ b/epicardium/ble/ble_api.h
@@ -57,3 +57,4 @@ void ble_adv_stop(void);
 void ble_adv_start(uint8_t mode);
 void ble_adv_discoverable(bool discoverable);
 void ble_adv_proc_msg(bleMsg_t *pMsg);
+void ble_close_connections_to_peripherals(void);
diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index 53a5a35cd13c6ec37a20bd481cc1024dd3a613d5..550e804e782e6c718c59659a2f1136cdc1f7edf9 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -258,6 +258,20 @@ static const char * const l2c_coc_events[] = {
 	"L2C_COC_DATA_IND",
 	"L2C_COC_DATA_CNF"
 };
+
+static const char * const att_internal_error_code[] = {
+	"ATT_ERR_MEMORY",
+	"ATT_ERR_TIMEOUT",
+	"ATT_ERR_OVERFLOW",
+	"ATT_ERR_INVALID_RSP",
+	"ATT_ERR_CANCELLED",
+	"ATT_ERR_UNDEFINED",
+	"ATT_ERR_REQ_NOT_FOUND",
+	"ATT_ERR_MTU_EXCEEDED",
+	"ATT_CONTINUING",
+	"ATT_RSP_PENDING"
+};
+
 /*************************************************************************************************/
 /*!
  *  \brief  Application DM callback.
@@ -392,6 +406,16 @@ static void bleSetup(bleMsg_t *pMsg)
   epic_ble_set_mode(false, false);
 }
 
+void ble_close_connections_to_peripherals(void)
+{
+	dmConnId_t connId = AppConnIsOpen();
+	if (connId != DM_CONN_ID_NONE) {
+		if(DmConnRole(connId) == DM_ROLE_MASTER) {
+			AppConnClose(connId);
+		}
+	}
+}
+
 void epic_ble_set_mode(bool bondable, bool scanner)
 {
 	if(!active) {
@@ -704,6 +728,7 @@ static void BleHandlerInit(void)
   ble_adv_init();
 
   /* Initialize application framework */
+  AppMasterInit();
   AppSlaveInit();
   AppDiscInit();
 
@@ -737,16 +762,27 @@ static void BleHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
       /* process security-related messages */
       AppSlaveSecProcDmMsg((dmEvt_t *) pMsg);
 
-      /* process discovery-related messages */
-      AppDiscProcDmMsg((dmEvt_t *) pMsg);
+      if(DmConnRole(pMsg->param) == DM_ROLE_SLAVE) {
+        /* process discovery-related messages only if we are peripheral */
+        AppDiscProcDmMsg((dmEvt_t *) pMsg);
+      }
 
       ble_epic_dm_api_event((dmEvt_t *)pMsg);
+
+      if(DmConnRole(pMsg->param) == DM_ROLE_MASTER && pMsg->event == DM_CONN_OPEN_IND) {
+        /* fake the discovery so the epic api can forward the connection open */
+        ble_epic_disc_cfg_complete();
+      }
     }
     else if (pMsg->event >= ATT_CBACK_START && pMsg->event <= ATT_CBACK_END)
     {
       /* Don't spam the console with successful notfication/indications */
       if (!(pMsg->event == ATTS_HANDLE_VALUE_CNF && pMsg->status == ATT_SUCCESS)) {
-        LOG_INFO("ble", "Ble got evt %d (%s): %d %d", pMsg->event, att_events[pMsg->event - ATT_CBACK_START], ((bleMsg_t *)pMsg)->att.handle, pMsg->status);
+        if(pMsg->status >= ATT_ERR_MEMORY && pMsg->status <= ATT_RSP_PENDING) {
+          LOG_INFO("ble", "Ble got evt %d (%s): %d %d (%s)", pMsg->event, att_events[pMsg->event - ATT_CBACK_START], ((bleMsg_t *)pMsg)->att.handle, pMsg->status, att_internal_error_code[pMsg->status-ATT_ERR_MEMORY]);
+        } else {
+          LOG_INFO("ble", "Ble got evt %d (%s): %d %d", pMsg->event, att_events[pMsg->event - ATT_CBACK_START], ((bleMsg_t *)pMsg)->att.handle, pMsg->status);
+        }
       }
       /* process discovery-related ATT messages */
       AppDiscProcAttMsg((attEvt_t *) pMsg);
diff --git a/epicardium/ble/epic_att_api.c b/epicardium/ble/epic_att_api.c
index 54ee97361edecb22b294ed01bfbcff0d6a3ebf72..19928067f9d7661e871e2ec06093cc8fcce6730f 100644
--- a/epicardium/ble/epic_att_api.c
+++ b/epicardium/ble/epic_att_api.c
@@ -31,6 +31,8 @@ void ble_epic_att_api_event(attEvt_t *att_event)
 		size_t value_len = 0;
 		if (att_event->hdr.event == ATTC_READ_BY_GROUP_TYPE_RSP ||
 		    att_event->hdr.event == ATTC_READ_BY_TYPE_RSP ||
+		    att_event->hdr.event == ATTC_READ_RSP ||
+		    att_event->hdr.event == ATTC_READ_LONG_RSP ||
 		    att_event->hdr.event == ATTC_FIND_INFO_RSP ||
 		    att_event->hdr.event == ATTC_HANDLE_VALUE_NTF ||
 		    att_event->hdr.event == ATTC_HANDLE_VALUE_IND) {
diff --git a/epicardium/ble/epic_ble_api.c b/epicardium/ble/epic_ble_api.c
index 92bb398b5cd0c8d41d1f27873784ab8e7bc37e65..11dbeb573f8834e6a60c78e4dbf1e63187f61fd5 100644
--- a/epicardium/ble/epic_ble_api.c
+++ b/epicardium/ble/epic_ble_api.c
@@ -53,6 +53,7 @@ void ble_epic_ble_api_trigger_event(enum epic_ble_event_type type, void *data)
 
 	struct epic_ble_event e = { .type = type, .data = data };
 
+	bool bypass = false;
 	if (type == BLE_EVENT_DM_EVENT) {
 		dmEvt_t *dm_event = data;
 		if (dm_event->hdr.event == DM_CONN_OPEN_IND) {
@@ -62,10 +63,15 @@ void ble_epic_ble_api_trigger_event(enum epic_ble_event_type type, void *data)
 		if (dm_event->hdr.event == DM_CONN_CLOSE_IND) {
 			connection_open = false;
 		}
+
+		if (dm_event->hdr.event == DM_SCAN_STOP_IND) {
+			bypass = true;
+		}
 	}
 
 	if (!connection_open &&
-	    (type == BLE_EVENT_ATT_EVENT || type == BLE_EVENT_DM_EVENT)) {
+	    (type == BLE_EVENT_ATT_EVENT || type == BLE_EVENT_DM_EVENT) &&
+		!bypass) {
 		// Don't forward DM and ATT events until epicardium is done setting up
 		// the connection
 		epic_ble_free_event(&e);
@@ -126,6 +132,10 @@ static void send_dm_event(dmEvt_t *dm_event)
 
 void ble_epic_dm_api_event(dmEvt_t *dm_event)
 {
+	if (dm_event->hdr.event == DM_CONN_CLOSE_IND) {
+		xTimerStop(dm_timer, 0);
+	}
+
 	if (dm_event->hdr.event == DM_CONN_OPEN_IND) {
 		/* Cache the connection open indication until
 		 * epicardium is done dicovering services. */
@@ -246,3 +256,8 @@ int epic_ble_advertise_stop(void)
 	ble_adv_stop();
 	return 0;
 }
+
+void epic_ble_connect(uint8_t addr_type, const uint8_t *addr)
+{
+	AppConnOpen(addr_type, (uint8_t *)addr, APP_DB_HDL_NONE);
+}
diff --git a/epicardium/ble/stack.c b/epicardium/ble/stack.c
index 4bf4696fd8639c1bf9b3101f3b58101e4220962b..f89cd1d45e665e4d35b3d1e3a2323ca2b8bac2af 100644
--- a/epicardium/ble/stack.c
+++ b/epicardium/ble/stack.c
@@ -168,6 +168,7 @@ void StackInit(void)
   DmAdvInit();
   DmScanInit();
   DmConnInit();
+  DmConnMasterInit();
   DmConnSlaveInit();
   DmSecInit();
   DmSecLescInit();
@@ -178,6 +179,7 @@ void StackInit(void)
   handlerId = WsfOsSetNextHandler(L2cSlaveHandler);
   L2cSlaveHandlerInit(handlerId);
   L2cInit();
+  L2cMasterInit();
   L2cSlaveInit();
 
   handlerId = WsfOsSetNextHandler(AttHandler);
@@ -189,7 +191,9 @@ void StackInit(void)
 
   handlerId = WsfOsSetNextHandler(SmpHandler);
   SmpHandlerInit(handlerId);
+  SmpiInit();
   SmprInit();
+  SmpiScInit();
   SmprScInit();
 
   /*TODO card10: Probably want to adjust this */
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 6957b43bdcab90ddfde116bf0155bb998e502a9f..9ffd81d131cbeed585f740259942a7511803f2ee 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -193,6 +193,8 @@ typedef _Bool bool;
 #define API_BLE_INIT                          0x190
 #define API_BLE_DEINIT                        0x191
 
+#define API_BLE_CONNECT                       0x1A0
+
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -2742,5 +2744,6 @@ API(API_BLE_ATTC_WRITE_NO_RSP, int epic_ble_attc_write_no_rsp(uint8_t connId, ui
 /** Private API call for Pycardium BLE support. */
 API(API_BLE_ATTC_WRITE, int epic_ble_attc_write(uint8_t connId, uint16_t value_handle, const uint8_t *value, uint16_t value_len));
 
+API(API_BLE_CONNECT, void epic_ble_connect(uint8_t addr_type, const uint8_t *addr));
 #endif /* _EPICARDIUM_H */
 
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index 75c4672ef7d05a0588e7a4d479ce3758f7c9ca72..0f36b1e3fea3e6e4d23c96542162d2d93b50701f 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -296,6 +296,8 @@ int hardware_reset(void)
 	/* Reset advertisement data */
 	ble_adv_setup();
 
+	ble_close_connections_to_peripherals();
+
 	/* Start advertising again if needed */
 	epic_ble_set_mode(false, false);
 
diff --git a/lib/sdk/Libraries/BTLE/meson.build b/lib/sdk/Libraries/BTLE/meson.build
index 2bf03dbb6af2f7d34b6e6c376f9c632a28006d09..072c947c2c80574471418d8fb7d5f17d10ea772e 100644
--- a/lib/sdk/Libraries/BTLE/meson.build
+++ b/lib/sdk/Libraries/BTLE/meson.build
@@ -427,6 +427,7 @@ ble_compileargs = [
   '-DINIT_PERIPHERAL',
   '-DINIT_ENCRYPTED',
   '-DINIT_OBSERVER',
+  '-DINIT_CENTRAL',
   '-DINIT_PHY',
 ]
 
diff --git a/pycardium/modules/modbluetooth_card10.c b/pycardium/modules/modbluetooth_card10.c
index 932930d8b959c78a0f4bc04ae42f2688de6a71d3..98df2982c633d50b55b436c39c9911f0f0d26e00 100644
--- a/pycardium/modules/modbluetooth_card10.c
+++ b/pycardium/modules/modbluetooth_card10.c
@@ -43,6 +43,7 @@ typedef struct {
 
 static bool active = false;
 static mp_obj_bluetooth_uuid_t uuid_filter;
+static uint8_t roles[8];
 
 const char *const not_implemented_message =
 	"Not (yet) implemented on card10. See https://git.card10.badge.events.ccc.de/card10/firmware/-/issues/8";
@@ -353,11 +354,21 @@ static void handle_att_event(struct epic_att_event *att_event)
 static void handle_dm_event(struct epic_dm_event *dm_event)
 {
 	struct epic_wsf_header *hdr = (struct epic_wsf_header *)dm_event;
+
 	if (hdr->event == DM_CONN_OPEN_IND) {
 		struct epic_hciLeConnCmpl_event *e =
 			(struct epic_hciLeConnCmpl_event *)dm_event;
+		roles[e->hdr.param] = e->role;
+
+		uint8_t event;
+		if(e->role == 1) {
+			event = MP_BLUETOOTH_IRQ_CENTRAL_CONNECT;
+		} else {
+			event = MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT;
+		}
+
 		mp_bluetooth_gap_on_connected_disconnected(
-			MP_BLUETOOTH_IRQ_CENTRAL_CONNECT,
+			event,
 			e->hdr.param,
 			e->addrType,
 			e->peerAddr
@@ -366,12 +377,22 @@ static void handle_dm_event(struct epic_dm_event *dm_event)
 		struct epic_hciDisconnectCmpl_event *e =
 			(struct epic_hciDisconnectCmpl_event *)dm_event;
 		uint8_t addr[6] = {};
+
+		uint8_t event;
+		if(roles[e->hdr.param] == 1) {
+			event = MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT;
+		} else {
+			event = MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT;
+		}
+
 		mp_bluetooth_gap_on_connected_disconnected(
-			MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT,
+			event,
 			e->hdr.param,
 			0xFF,
 			addr
 		);
+	} else if (hdr->event == DM_SCAN_STOP_IND) {
+		mp_bluetooth_gap_on_scan_complete();
 	}
 }
 
@@ -397,6 +418,17 @@ static void handle_att_write(struct epic_att_write *att_write)
 	mp_bluetooth_gatts_on_write(att_write->hdr.param, att_write->handle);
 }
 
+static void handle_scan_report(void)
+{
+	struct epic_scan_report scan_report;
+
+	while(epic_ble_get_scan_report(&scan_report) == 0) {
+		uint8_t adv_type = scan_report.eventType;
+		mp_bluetooth_gap_on_scan_result(scan_report.addrType, scan_report.addr,
+				adv_type, scan_report.rssi, scan_report.data, scan_report.len);
+	}
+}
+
 static mp_obj_t mp_ble_poll_events(mp_obj_t interrupt_id)
 {
 	struct epic_ble_event ble_event;
@@ -414,6 +446,9 @@ static mp_obj_t mp_ble_poll_events(mp_obj_t interrupt_id)
 			if (ble_event.type == BLE_EVENT_ATT_WRITE) {
 				handle_att_write(ble_event.att_write);
 			}
+			if (ble_event.type == BLE_EVENT_SCAN_REPORT) {
+				handle_scan_report();
+			}
 			epic_ble_free_event(&ble_event);
 		}
 	} while (ret >= 0);
@@ -802,14 +837,15 @@ int mp_bluetooth_gap_scan_start(
 	int32_t window_us,
 	bool active_scan
 ) {
-	raise();
+	if(active_scan) raise();
+	epic_ble_set_mode(false, true);
 	return 0;
 }
 
 // Stop discovery (if currently active).
 int mp_bluetooth_gap_scan_stop(void)
 {
-	raise();
+	epic_ble_set_mode(false, false);
 	return 0;
 }
 
@@ -817,7 +853,7 @@ int mp_bluetooth_gap_scan_stop(void)
 int mp_bluetooth_gap_peripheral_connect(
 	uint8_t addr_type, const uint8_t *addr, int32_t duration_ms
 ) {
-	raise();
+	epic_ble_connect(addr_type, addr);
 	return 0;
 }
 
diff --git a/tools/pycard10.py b/tools/pycard10.py
index 5f0a870c2db5e5bf92f6b96a3e5531da63f9f95a..306529aca14fd698632c99ae1747e19d4dbff6a5 100755
--- a/tools/pycard10.py
+++ b/tools/pycard10.py
@@ -117,10 +117,20 @@ class PyCard10(Pyboard):
 
         self.serial.write(b"\x04")
 
-        # check if we could exec command
-        data = self.serial.read(2)
-        if data != b"OK":
-            raise PyboardError("could not exec command (response: %r)" % data)
+        while True:
+            # check if we could exec command
+            data = self.serial.read(2)
+
+            # Catch log lines from epicardium
+            if data == b'\x1b[':
+                logline = self.serial.readline()
+                stdout_write_bytes(data + logline)
+                continue
+
+            if data != b"OK":
+                raise PyboardError("could not exec command (response: %r)" % data)
+            else:
+                break
 
     def enter_raw_repl(self):
         """