diff --git a/epicardium/ble/ble.c b/epicardium/ble/ble.c
index ddaf6f4e199de79ec6244f4b8a6193ea0dc4a32b..622ec4e8620b364be75f704cc0f6ace58b42fcd1 100644
--- a/epicardium/ble/ble.c
+++ b/epicardium/ble/ble.c
@@ -47,6 +47,7 @@ extern void StackInit(void);
 extern void AppInit(void);
 extern void bleuart_init(void);
 extern void bleCard10_init(void);
+extern void bleFileTransfer_init(void);
 
 /*************************************************************************************************/
 void PalSysAssertTrap(void)
@@ -199,6 +200,7 @@ void vBleTask(void *pvParameters)
 
 	bleuart_init();
 	bleCard10_init();
+	bleFileTransfer_init();
 
 	lasttick = xTaskGetTickCount();
 
diff --git a/epicardium/ble/filetransfer.c b/epicardium/ble/filetransfer.c
new file mode 100644
index 0000000000000000000000000000000000000000..03181390c573edb90e5be63ede43ed3b0b33b161
--- /dev/null
+++ b/epicardium/ble/filetransfer.c
@@ -0,0 +1,391 @@
+/*
+ * BLE (Bluetooth Low energy) file transfer service.
+ *
+ * This file provides a BLE service and a characteristic which allows to 
+ * write the content of the mytest.py file over BLE into the file system. 
+ * We haven't found any existing BLE service which provides such a 
+ * functionality so we implemented our own service.
+
+ * The service uses the UUID in fileTransSvc
+ * The characteristic uses the UUID in attTxChConfigUuid
+
+ * BLE point to point links use a pretty small MTU (min 23 bytes, max 244
+ * bytes) which is negotiated between the central and the peripheral (card10).
+ * 
+ * The first byte of each message is the type of message being send,
+ * different types are supported and based on this type the next bytes
+ * have different meaning.
+ * 
+ * TODOs:
+ * * File deletion
+ * * File read
+ * * Directory creation
+ * * Directory listing
+ */
+
+#include "wsf_types.h"
+#include "wsf_os.h"
+#include "wsf_buf.h"
+#include "wsf_timer.h"
+#include "wsf_trace.h"
+#include "app_ui.h"
+#include "fit/fit_api.h"
+#include "hci_vs.h"
+
+#include <epicardium.h>
+
+#include "util/bstream.h"
+#include "att_api.h"
+
+#include "FreeRTOS.h"
+#include "crc32.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <machine/endian.h>
+
+/*!< \brief Service start handle. */
+#define FILE_TRANS_START_HDL 0x820
+/*!< \brief Service end handle. */
+#define FILE_TRANS_END_HDL (FILE_TRANS_MAX_HDL - 1)
+
+#define FILE_TRANS_UUID_SUFFIX                                                 \
+	0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23
+#define FILE_TRANS_UUID_PREFIX 0x01, 0x23, 0x42
+
+/*
+ * This has to match in order and number of members to the functions
+ * called in fileTransAddGroupDyn() otherwise the stack breaks.
+ */
+enum {
+	/*!< \brief File transfer service declaration */
+	FILE_TRANS_SVC_HDL = FILE_TRANS_START_HDL,
+	/*!< \brief File transfer Central tx characteristic */
+	FILE_TRANS_CENTRAL_TX_CH_HDL,
+	/*!< \brief File transfer Central tx value */
+	FILE_TRANS_CENTRAL_TX_VAL_HDL,
+	/*!< \brief File transfer Central rx characteristic */
+	FILE_TRANS_CENTRAL_RX_CH_HDL,
+	/*!< \brief File transfer Central rx value */
+	FILE_TRANS_CENTRAL_RX_VAL_HDL,
+	/*!< \brief File transfer Central rx notification characteristic */
+	FILE_TRANS_CENTRAL_RX_CCC_HDL,
+	/*!< \brief Maximum handle. */
+	FILE_TRANS_MAX_HDL
+};
+
+/* BLE File transfer Service UUID */
+static const uint8_t fileTransSvc[] = { FILE_TRANS_UUID_SUFFIX,
+					0x00,
+					FILE_TRANS_UUID_PREFIX };
+
+/* BLE File transfer Central TX configuration */
+static const uint8_t txChConfig[] = { ATT_PROP_WRITE_NO_RSP,
+				      UINT16_TO_BYTES(
+					      FILE_TRANS_CENTRAL_TX_VAL_HDL),
+				      FILE_TRANS_UUID_SUFFIX,
+				      0x01,
+				      FILE_TRANS_UUID_PREFIX };
+/* BLE File transfer Central TX UUID */
+static const uint8_t attTxChConfigUuid[] = { FILE_TRANS_UUID_SUFFIX,
+					     0x01,
+					     FILE_TRANS_UUID_PREFIX };
+
+/* BLE File transfer Central RX configuration */
+static const uint8_t rxChConfig[] = { ATT_PROP_READ | ATT_PROP_NOTIFY,
+				      UINT16_TO_BYTES(
+					      FILE_TRANS_CENTRAL_RX_VAL_HDL),
+				      FILE_TRANS_UUID_SUFFIX,
+				      0x02,
+				      FILE_TRANS_UUID_PREFIX };
+/* BLE File transfer Central RX UUID */
+static const uint8_t attRxChConfigUuid[] = { FILE_TRANS_UUID_SUFFIX,
+					     0x02,
+					     FILE_TRANS_UUID_PREFIX };
+
+/* File descriptor of the currently transferred file */
+static int file_fd = -1;
+
+/*
+ * Create the BLE service description. 
+ */
+static void *fileTransAddGroupDyn(void)
+{
+	void *pSHdl;
+	uint8_t initCcc[] = { UINT16_TO_BYTES(0x0000) };
+
+	/* Create the service */
+	pSHdl = AttsDynCreateGroup(FILE_TRANS_START_HDL, FILE_TRANS_END_HDL);
+
+	if (pSHdl != NULL) {
+		/* Primary service */
+		AttsDynAddAttrConst(
+			pSHdl,
+			attPrimSvcUuid,
+			fileTransSvc,
+			sizeof(fileTransSvc),
+			0,
+			ATTS_PERMIT_READ
+		);
+
+		/* File transfer Central TX characteristic */
+		AttsDynAddAttrConst(
+			pSHdl,
+			attChUuid,
+			txChConfig,
+			sizeof(txChConfig),
+			0,
+			ATTS_PERMIT_READ
+		);
+
+		/* File transfer Central TX, this contains information about the real data */
+		AttsDynAddAttr(
+			pSHdl,
+			attTxChConfigUuid,
+			NULL,
+			0,
+			128,
+			ATTS_SET_WRITE_CBACK | ATTS_SET_VARIABLE_LEN,
+			ATTS_PERMIT_WRITE
+		);
+
+		/* File transfer Central RX characteristic */
+		AttsDynAddAttrConst(
+			pSHdl,
+			attChUuid,
+			rxChConfig,
+			sizeof(rxChConfig),
+			0,
+			ATTS_PERMIT_READ
+		);
+
+		/* File transfer Central RX, this contains information about the real data */
+		AttsDynAddAttr(
+			pSHdl,
+			attRxChConfigUuid,
+			NULL,
+			0,
+			64,
+			ATTS_SET_READ_CBACK,
+			ATTS_PERMIT_READ
+		);
+
+		/* File transfer Central RX notification channel */
+		AttsDynAddAttr(
+			pSHdl,
+			attCliChCfgUuid,
+			initCcc,
+			sizeof(uint16_t),
+			sizeof(uint16_t),
+			ATTS_SET_CCC,
+			ATTS_PERMIT_READ | ATTS_PERMIT_WRITE
+		);
+	}
+
+	return pSHdl;
+}
+
+/**
+ * Send a repose with an optional CRC.
+ * 
+ * The Central RX characteristic is filled and a notification is send in addition.
+ * If msg is given message is added in addition.
+ */
+static void sendCrcResponse(
+	dmConnId_t connId,
+	char code,
+	uint16_t crcLen,
+	uint8_t *crcData,
+	const char *msg
+) {
+	uint32_t crc;
+	uint16_t len = 1;
+	uint8_t answer[24];
+
+	answer[0] = code;
+
+	if (crcData) {
+		crc = __htonl(CalcCrc32(0xffffffff, crcLen, crcData));
+		memcpy(answer + len, &crc, sizeof(crc));
+		len += 4;
+	}
+
+	if (msg) {
+		if (strlen(msg) < sizeof(answer) - len) {
+			strncpy((char *)answer + len,
+				msg,
+				sizeof(answer) - len);
+			len += strlen(msg);
+			printf("BLE file transfer: %s\n", msg);
+		} else {
+			printf("error message \"%s\" too long\n", msg);
+		}
+	}
+
+	AttsSetAttr(FILE_TRANS_CENTRAL_RX_VAL_HDL, len, answer);
+	AttsHandleValueNtf(connId, FILE_TRANS_CENTRAL_RX_VAL_HDL, len, answer);
+}
+
+static uint8_t bleFileOpen(dmConnId_t connId, uint8_t *pValue, uint16_t len)
+{
+	char filepath[100];
+
+	if (len > sizeof(filepath)) {
+		sendCrcResponse(connId, 'e', 0, NULL, "path too long");
+		return ATT_ERR_RESOURCES;
+	}
+
+	/* Copy only file path and not type, make sure this is NULL terminated */
+	strncpy(filepath, (char *)pValue + 1, len - 1);
+
+	if (file_fd != -1)
+		epic_file_close(file_fd);
+
+	file_fd = epic_file_open(filepath, "w");
+	if (file_fd < 0) {
+		sendCrcResponse(connId, 'e', 0, NULL, "open failed");
+		return ATT_ERR_RESOURCES;
+	}
+	sendCrcResponse(connId, 'S', len, pValue, NULL);
+	return ATT_SUCCESS;
+}
+
+static uint8_t bleFileWrite(dmConnId_t connId, uint8_t *pValue, uint16_t len)
+{
+	uint32_t fileOffsetNet;
+	uint32_t fileOffset;
+	int res;
+
+	if (len < 6) {
+		sendCrcResponse(connId, 'e', 0, NULL, "msg too short");
+		return ATT_ERR_LENGTH;
+	}
+
+	memcpy(&fileOffsetNet, pValue + 1, sizeof(fileOffsetNet));
+	fileOffset = __ntohl(fileOffsetNet);
+
+	res = epic_file_seek(file_fd, fileOffset, SEEK_SET);
+	if (res) {
+		sendCrcResponse(connId, 'e', 0, NULL, "seek failed");
+		return ATT_ERR_RESOURCES;
+	}
+
+	res = epic_file_write(file_fd, pValue + 5, len - 5);
+	if (res != (len - 5)) {
+		sendCrcResponse(connId, 'e', 0, NULL, "write failed");
+		return ATT_ERR_RESOURCES;
+	}
+
+	sendCrcResponse(connId, 'C', len, pValue, NULL);
+	return ATT_SUCCESS;
+}
+
+/*
+ * Handle the Central TX characteristic which is used by the central to
+ * issues file transfer commands on this peripheral.
+ */
+static uint8_t handleCentralTX(
+	dmConnId_t connId,
+	uint16_t handle,
+	uint8_t operation,
+	uint16_t offset,
+	uint16_t len,
+	uint8_t *pValue,
+	attsAttr_t *pAttr
+) {
+	if (len < 1) {
+		sendCrcResponse(connId, 'e', 0, NULL, "msg too short");
+		return ATT_ERR_LENGTH;
+	}
+
+	if (operation == ATT_PDU_PREP_WRITE_REQ) {
+		/* In case of relaiable write, just return success and wait for exec. */
+		return ATT_SUCCESS;
+	} else if (
+		operation != ATT_PDU_EXEC_WRITE_REQ &&
+		operation != ATT_PDU_WRITE_CMD) {
+		printf("operation 0x%x not supported, try normal write\n",
+		       operation);
+		return ATT_ERR_INVALID_PDU;
+	}
+
+	switch (pValue[0]) {
+	case 's':
+		return bleFileOpen(connId, pValue, len);
+
+	case 'c':
+		return bleFileWrite(connId, pValue, len);
+
+	case 'f':
+		if (file_fd != -1) {
+			epic_file_close(file_fd);
+			file_fd = -1;
+		}
+		sendCrcResponse(connId, 'F', 0, NULL, NULL);
+		return ATT_SUCCESS;
+
+	case 'e':
+		if (file_fd != -1) {
+			epic_file_close(file_fd);
+			file_fd = -1;
+		}
+		sendCrcResponse(connId, 'E', 0, NULL, NULL);
+		return ATT_SUCCESS;
+
+	case 'E':
+		printf("Error was acked");
+		return ATT_SUCCESS;
+
+	default:
+		sendCrcResponse(connId, 'e', 0, NULL, "unkown command");
+		return ATT_ERR_NOT_SUP;
+	}
+	return ATT_ERR_NOT_SUP;
+}
+
+/*
+ * BLE file transfer write callback.
+ * 
+ * This gets called when data is written by a BLE central to our BLE
+ * peripheral. Here we take care of handling the received data.
+ */
+static uint8_t writeCallback(
+	dmConnId_t connId,
+	uint16_t handle,
+	uint8_t operation,
+	uint16_t offset,
+	uint16_t len,
+	uint8_t *pValue,
+	attsAttr_t *pAttr
+) {
+	switch (handle) {
+	case FILE_TRANS_CENTRAL_TX_VAL_HDL:
+		return handleCentralTX(
+			connId, handle, operation, offset, len, pValue, pAttr
+		);
+	default:
+		printf("unsupported characteristic: %c\n", handle);
+		return ATT_ERR_HANDLE;
+	}
+}
+
+static uint8_t readCallback(
+	dmConnId_t connId,
+	uint16_t handle,
+	uint8_t operation,
+	uint16_t offset,
+	attsAttr_t *pAttr
+) {
+	printf("read callback\n");
+	return ATT_SUCCESS;
+}
+
+/*
+ * This registers and starts the BLE file transfer service.
+ */
+void bleFileTransfer_init(void)
+{
+	void *pSHdl = fileTransAddGroupDyn();
+	AttsDynRegister(pSHdl, readCallback, writeCallback);
+}
diff --git a/epicardium/ble/meson.build b/epicardium/ble/meson.build
index b3571e64ffca91a111f8e26719e53eedfd13b134..c531ae9a95d310bc4fa3b80fbac513505cb09c8f 100644
--- a/epicardium/ble/meson.build
+++ b/epicardium/ble/meson.build
@@ -9,4 +9,5 @@ ble_sources = files(
   'app/common/app_ui.c',
   'uart.c',
   'card10.c',
+  'filetransfer.c',
 )