diff --git a/Documentation/card10-cfg.rst b/Documentation/card10-cfg.rst
index 8ad094c0e8c2a8de425990bb72c5eb8e05472698..881c3bd1c1a9b2bb8f61db6c1c536e1f96f8ba50 100644
--- a/Documentation/card10-cfg.rst
+++ b/Documentation/card10-cfg.rst
@@ -34,12 +34,14 @@ Float     A single-precision (32-bit) floating-point number in base 10. Example:
 Supported options
 -----------------
 
-=============== ========== ===========
-Option name     Type       Description
-=============== ========== ===========
-``execute_elf`` Boolean    Allow running of binary :ref:`l0dables`. These files can be nefarious, so this option is off by default.
---------------- ---------- -----------
-``timezone``    String     Timezone for card10; must be of format ``[+-]HHMM``.  Examples: ``+0800``, ``-0200``
---------------- ---------- -----------
-``default_app`` String     Full path to the exectutable file of the default application. If this option is not set,``apps/analog_clock/__init__.py`` is used.
-=============== ========== ===========
+================== ========== ===========
+Option name        Type       Description
+================== ========== ===========
+``execute_elf``    Boolean    Allow running of binary :ref:`l0dables`. These files can be nefarious, so this option is off by default.
+------------------ ---------- -----------
+``timezone``       String     Timezone for card10; must be of format ``[+-]HHMM``.  Examples: ``+0800``, ``-0200``
+------------------ ---------- -----------
+``default_app``    String     Full path to the exectutable file of the default application. If this option is not set,``apps/analog_clock/__init__.py`` is used.
+------------------ ---------- -----------
+``ble_log_enable`` Boolean    Activate HCI level logging of BLE data. Creates a new btsnoop compatible log file named ``ble.log`` in the ``logs`` folder after each boot if BLE is activated. Keeps the last 10 files.
+================== ========== ===========
diff --git a/epicardium/ble/ble.c b/epicardium/ble/ble.c
index 9fc6c1523d4f9ab25a66545ae49bd8c16753e563..c9faf528767a4fd520ed836a899c00a7b7c8c4f7 100644
--- a/epicardium/ble/ble.c
+++ b/epicardium/ble/ble.c
@@ -1,5 +1,6 @@
 #include "epicardium.h"
 #include "modules/log.h"
+#include "modules/config.h"
 
 #include "fs/fs_util.h"
 #include "wsf_types.h"
@@ -12,6 +13,7 @@
 #include "FreeRTOS.h"
 #include "timers.h"
 
+#include <machine/endian.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
@@ -20,6 +22,18 @@
 #define WSF_BUF_POOLS 6
 #define WSF_BUF_SIZE (0x1048 * FACTOR)
 
+struct log_packet_header {
+	uint32_t original_length;
+	uint32_t included_length;
+	uint32_t packet_flags;
+	uint32_t cumulative_drops;
+	uint32_t timestamp_us_h;
+	uint32_t timestamp_us_l;
+};
+static const uint8_t log_header[] = {
+	'b', 't', 's', 'n', 'o', 'o', 'p', 0, 0, 0, 0, 1, 0, 0, 0x03, 0xea
+};
+
 uint32_t SystemHeapSize = WSF_BUF_SIZE;
 uint32_t SystemHeap[WSF_BUF_SIZE / 4];
 uint32_t SystemHeapStart;
@@ -44,6 +58,11 @@ static StaticTimer_t x;
 static TimerHandle_t timerWakeup = NULL;
 static int lasttick              = 0;
 
+static int log_fd;
+static bool log_dirty        = false;
+static bool log_enabled      = false;
+static int log_lastflushtick = 0;
+
 /*! \brief  Stack initialization for app. */
 extern void StackInit(void);
 extern void AppInit(void);
@@ -70,6 +89,53 @@ static bool_t myTrace(const uint8_t *pBuf, uint32_t len)
 
 	return FALSE;
 }
+
+/*************************************************************************************************/
+void WsfPDump(wsfPDumpType_t pdType, uint16_t length, uint8_t *pBuffer)
+{
+	uint32_t direction;
+	uint8_t type;
+
+	if (log_enabled) {
+		switch (pdType) {
+		case WSF_PDUMP_TYPE_HCI_CMD:
+			direction = 0;
+			type      = 0x01;
+			break;
+		case WSF_PDUMP_TYPE_HCI_EVT:
+			direction = 1;
+			type      = 0x04;
+			break;
+		case WSF_PDUMP_TYPE_HCI_TX_ACL:
+			direction = 0;
+			type      = 0x02;
+			break;
+		case WSF_PDUMP_TYPE_HCI_RX_ACL:
+			direction = 1;
+			type      = 0x02;
+			break;
+		default:
+			break;
+		}
+
+		uint64_t tick         = xTaskGetTickCount();
+		uint64_t timestamp_us = tick * 1000;
+
+		struct log_packet_header header = {
+			.original_length  = __htonl(length + 1),
+			.included_length  = __htonl(length + 1),
+			.packet_flags     = __htonl(direction),
+			.cumulative_drops = __htonl(0),
+			.timestamp_us_h   = __htonl(timestamp_us >> 32),
+			.timestamp_us_l   = __htonl(timestamp_us & 0xFFFFFFFF)
+		};
+
+		epic_file_write(log_fd, &header, sizeof(header));
+		epic_file_write(log_fd, &type, sizeof(type));
+		epic_file_write(log_fd, pBuffer, length);
+		log_dirty = true;
+	}
+}
 /*************************************************************************************************/
 static void WsfInit(void)
 {
@@ -239,6 +305,48 @@ static void scheduleTimer(void)
 	}
 }
 /*************************************************************************************************/
+static void log_flush(void)
+{
+	int tick = xTaskGetTickCount();
+	if (tick - log_lastflushtick > 5000) {
+		log_lastflushtick = tick;
+		if (log_dirty) {
+			log_dirty = false;
+			LOG_INFO("ble", "Flushing log");
+			epic_file_flush(log_fd);
+		}
+	}
+}
+/*************************************************************************************************/
+static void log_rotate(void)
+{
+	int i;
+	char filename_old[16];
+	char filename_new[16];
+	struct epic_stat stat;
+
+	if (epic_file_stat("logs/", &stat) != 0) {
+		epic_file_mkdir("logs");
+	}
+
+	if (epic_file_stat("logs/ble9.log", &stat) == 0) {
+		epic_file_unlink("logs/ble9.log");
+	}
+
+	for (i = 8; i > 0; i--) {
+		sprintf(filename_old, "logs/ble%d.log", i);
+		sprintf(filename_new, "logs/ble%d.log", i + 1);
+
+		if (epic_file_stat(filename_old, &stat) == 0) {
+			epic_file_rename(filename_old, filename_new);
+		}
+	}
+
+	if (epic_file_stat("logs/ble.log", &stat) == 0) {
+		epic_file_rename("logs/ble.log", "logs/ble1.log");
+	}
+}
+/*************************************************************************************************/
 void vBleTask(void *pvParameters)
 {
 	ble_task_id = xTaskGetCurrentTaskHandle();
@@ -248,6 +356,15 @@ void vBleTask(void *pvParameters)
 	 */
 	vTaskDelay(pdMS_TO_TICKS(500));
 
+	log_enabled = config_get_boolean_with_default("ble_log_enable", false);
+
+	if (log_enabled) {
+		LOG_INFO("ble", "Log is enabled");
+		log_rotate();
+		log_fd = epic_file_open("logs/ble.log", "w");
+		epic_file_write(log_fd, log_header, sizeof(log_header));
+	}
+
 	/* We are going to execute FreeRTOS functions from callbacks
 	 * coming from this interrupt. Its priority needs to be
 	 * reduced to allow this. */
@@ -286,5 +403,8 @@ void vBleTask(void *pvParameters)
 		ulTaskNotifyTake(pdTRUE, portTICK_PERIOD_MS * 1000);
 		wsfOsDispatcher();
 		scheduleTimer();
+		if (log_enabled) {
+			log_flush();
+		}
 	}
 }
diff --git a/lib/sdk/Libraries/BTLE/wsf/include/wsf_trace.h b/lib/sdk/Libraries/BTLE/wsf/include/wsf_trace.h
index 6bb3d2cd2c5fa11071d7fedb1384884e5b0c378e..00e90ca24a157495bb2f775f0f642dae39e29b2c 100644
--- a/lib/sdk/Libraries/BTLE/wsf/include/wsf_trace.h
+++ b/lib/sdk/Libraries/BTLE/wsf/include/wsf_trace.h
@@ -293,14 +293,10 @@ bool_t WsfTokenService(void);
 /*! \brief 3 argument HCI error trace. */
 #define HCI_TRACE_ERR3(msg, var1, var2, var3)       WSF_TRACE3("HCI", "ERR",  msg, var1, var2, var3)
 
-/*! \brief HCI PDUMP on command. */
-#define HCI_PDUMP_CMD(len, pBuf)
-/*! \brief HCI PDUMP on event. */
-#define HCI_PDUMP_EVT(len, pBuf)
-/*! \brief HCI PDUMP on transmitted ACL message. */
-#define HCI_PDUMP_TX_ACL(len, pBuf)
-/*! \brief HCI PDUMP on Received ACL message. */
-#define HCI_PDUMP_RX_ACL(len, pBuf)
+#define HCI_PDUMP_CMD(len, pBuf)                    WsfPDump(WSF_PDUMP_TYPE_HCI_CMD, len, pBuf)
+#define HCI_PDUMP_EVT(len, pBuf)                    WsfPDump(WSF_PDUMP_TYPE_HCI_EVT, len, pBuf)
+#define HCI_PDUMP_TX_ACL(len, pBuf)                 WsfPDump(WSF_PDUMP_TYPE_HCI_TX_ACL, len, pBuf)
+#define HCI_PDUMP_RX_ACL(len, pBuf)                 WsfPDump(WSF_PDUMP_TYPE_HCI_RX_ACL, len, pBuf)
 
 /*! \brief 0 argument DM info trace. */
 #define DM_TRACE_INFO0(msg)                         WSF_TRACE0("DM", "INFO", msg)
@@ -622,6 +618,25 @@ bool_t WsfTokenService(void);
 #define LL_TRACE_ENABLE(ena)
 #endif
 
+/*! Protocol types */
+typedef enum
+{
+  WSF_PDUMP_TYPE_HCI_CMD    = (1 << 0),
+  WSF_PDUMP_TYPE_HCI_EVT    = (1 << 1),
+  WSF_PDUMP_TYPE_HCI_TX_ACL = (1 << 2),
+  WSF_PDUMP_TYPE_HCI_RX_ACL = (1 << 3),
+  WSF_PDUMP_TYPE_HCI_TX_ISO = (1 << 4),
+  WSF_PDUMP_TYPE_HCI_RX_ISO = (1 << 5),
+  WSF_PDUMP_TYPE_NONE       = 0,
+  WSF_PDUMP_TYPE_ALL        = 0xFFFF
+} wsfPDumpType_t;
+
+/**************************************************************************************************
+  Function Prototypes
+**************************************************************************************************/
+
+void WsfPDump(wsfPDumpType_t pdType, uint16_t length, uint8_t *pBuffer);
+
 /*! \} */    /* WSF_TRACE_API */
 
 #ifdef __cplusplus