diff --git a/epicardium/ble/ble_adv.c b/epicardium/ble/ble_adv.c
new file mode 100644
index 0000000000000000000000000000000000000000..4aea57c5b651be0c680d4e4526b6f069f9b0229b
--- /dev/null
+++ b/epicardium/ble/ble_adv.c
@@ -0,0 +1,264 @@
+#include "ble_api.h"
+#include "epicardium.h"
+#include "modules/log.h"
+#include "modules/config.h"
+
+#include "wsf_types.h"
+#include "util/bstream.h"
+#include "wsf_msg.h"
+#include "wsf_trace.h"
+#include "app_api.h"
+#include "app_db.h"
+#include "svc_ch.h"
+#include "profiles/gap_api.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#define DEFAULT_ADV_INTERVAL_US 500000
+
+/*! configurable parameters for advertising */
+static appAdvCfg_t bleAdvCfg = {
+	{ 0, 0 }, /*! Advertising durations in ms */
+	{ DEFAULT_ADV_INTERVAL_US / 625,
+	  0 } /*! Advertising intervals in 0.625 ms units */
+};
+
+static bool tainted;
+
+/**************************************************************************************************
+  Advertising Data
+**************************************************************************************************/
+
+/* clang-format off */
+/*! advertising data, discoverable mode */
+static const uint8_t bleAdvDataDisc[] = {
+	/*! flags */
+	2,                        /*! length */
+	DM_ADV_TYPE_FLAGS,        /*! AD type */
+	DM_FLAG_LE_LIMITED_DISC | /*! flags */
+	DM_FLAG_LE_BREDR_NOT_SUP,
+
+	3,
+	DM_ADV_TYPE_APPEARANCE,
+	UINT16_TO_BYTES(CH_APPEAR_WATCH),
+
+	/*! service UUID list */
+	17,
+	DM_ADV_TYPE_128_UUID_PART,
+	CARD10_UUID_SUFFIX,
+	0x0,
+	CARD10_UUID_PREFIX,
+
+	2,                    /*! length */
+	DM_ADV_TYPE_TX_POWER, /*! AD type */
+	0,                    /*! tx power */
+};
+
+/*! advertising data, discoverable mode with HID service*/
+static const uint8_t bleAdvDataDiscHID[] = {
+	/*! flags */
+	2,                        /*! length */
+	DM_ADV_TYPE_FLAGS,        /*! AD type */
+	DM_FLAG_LE_LIMITED_DISC | /*! flags */
+	DM_FLAG_LE_BREDR_NOT_SUP,
+
+	3,
+	DM_ADV_TYPE_APPEARANCE,
+	UINT16_TO_BYTES(CH_APPEAR_WATCH),
+
+	/*! service UUID list */
+	17,
+	DM_ADV_TYPE_128_UUID_PART,
+	CARD10_UUID_SUFFIX,
+	0x0,
+	CARD10_UUID_PREFIX,
+
+	3,                        /*! length */
+	DM_ADV_TYPE_16_UUID_PART, /*! AD type */
+	UINT16_TO_BYTES(ATT_UUID_HID_SERVICE)
+};
+
+/*! scan data, discoverable mode */
+uint8_t bleScanDataDisc[] = {
+	/*! device name */
+	14,                     /*! length */
+	DM_ADV_TYPE_LOCAL_NAME, /*! AD type */
+	'c','a','r','d','1','0','-','0','0','0','0','0','0',
+
+	3,                      /*! length */
+	DM_ADV_TYPE_16_SOLICIT, /*! AD type */
+	UINT16_TO_BYTES(ATT_UUID_CURRENT_TIME_SERVICE),
+};
+/* clang-format on */
+
+/*! advertising data, connectable mode */
+static const uint8_t bleAdvDataConn[] = {
+	/*! flags */
+	2,                 /*! length */
+	DM_ADV_TYPE_FLAGS, /*! AD type */
+	DM_FLAG_LE_BREDR_NOT_SUP,
+};
+
+static uint8_t advertising_mode        = APP_MODE_NONE;
+static uint8_t advertising_mode_target = APP_MODE_NONE;
+
+void ble_adv_proc_msg(bleMsg_t *pMsg)
+{
+	switch (pMsg->hdr.event) {
+	case DM_ADV_START_IND:
+		LOG_INFO(
+			"ble",
+			"Advertisement started %u %u",
+			advertising_mode,
+			advertising_mode_target
+		);
+		if (advertising_mode != advertising_mode_target ||
+		    advertising_mode_target == APP_MODE_NONE) {
+			AppAdvStop();
+		}
+		break;
+
+	case DM_ADV_STOP_IND:
+		LOG_INFO(
+			"ble",
+			"Advertisement stopped %u %u",
+			advertising_mode,
+			advertising_mode_target
+		);
+		if (advertising_mode != advertising_mode_target) {
+			advertising_mode = advertising_mode_target;
+			AppAdvStart(advertising_mode);
+		}
+		break;
+	case DM_CONN_CLOSE_IND:
+		/* Stack overwrites advertising mode after connection close.
+		 * Force our desired mode.
+		 */
+		advertising_mode = APP_MODE_NONE;
+		AppAdvStop();
+		break;
+	};
+}
+
+void ble_adv_init(void)
+{
+	char buf[32];
+	char a, b, c, d, e, f, K;
+
+	/* clang-format off */
+	int result = epic_config_get_string("ble_mac", buf, sizeof(buf));
+	if (result == 0) {
+		if (sscanf(buf,
+			   "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c",
+			   &K, &K, &K, &K, &K, &K,
+			   &a, &b, &c, &d, &e, &f) == 12) {
+			bleScanDataDisc[9]  = a;
+			bleScanDataDisc[10] = b;
+			bleScanDataDisc[11] = c;
+			bleScanDataDisc[12] = d;
+			bleScanDataDisc[13] = e;
+			bleScanDataDisc[14] = f;
+		}
+	}
+	/* clang-format on */
+
+	pAppAdvCfg = (appAdvCfg_t *)&bleAdvCfg;
+}
+
+void ble_adv_setup(void)
+{
+	/* set advertising and scan response data for discoverable mode */
+	if (config_get_boolean_with_default("ble_hid_enable", false)) {
+		AppAdvSetData(
+			APP_ADV_DATA_DISCOVERABLE,
+			sizeof(bleAdvDataDiscHID),
+			(uint8_t *)bleAdvDataDiscHID
+		);
+	} else {
+		AppAdvSetData(
+			APP_ADV_DATA_DISCOVERABLE,
+			sizeof(bleAdvDataDisc),
+			(uint8_t *)bleAdvDataDisc
+		);
+	}
+
+	AppAdvSetData(
+		APP_SCAN_DATA_DISCOVERABLE,
+		sizeof(bleScanDataDisc),
+		(uint8_t *)bleScanDataDisc
+	);
+
+	/* set advertising and scan response data for connectable mode */
+	AppAdvSetData(
+		APP_ADV_DATA_CONNECTABLE,
+		sizeof(bleAdvDataConn),
+		(uint8_t *)bleAdvDataConn
+	);
+	AppAdvSetData(APP_SCAN_DATA_CONNECTABLE, 0, NULL);
+
+	bleAdvCfg.advInterval[0] = DEFAULT_ADV_INTERVAL_US / 625;
+}
+
+void ble_adv_set_interval(uint32_t interval_us)
+{
+	bleAdvCfg.advInterval[0] = interval_us / 625;
+	tainted                  = true;
+}
+
+void ble_adv_stop(void)
+{
+	if (advertising_mode != APP_MODE_NONE) {
+		advertising_mode_target = APP_MODE_NONE;
+		advertising_mode        = APP_MODE_NONE;
+		AppAdvStop();
+	}
+}
+
+static void adv_start(uint8_t mode)
+{
+	if (advertising_mode != APP_MODE_NONE) {
+		/* We need to stop advertising in between or the
+		* adv set will not be changed.
+		* Also need to wait for the stop operation to finish
+		* before we can start again
+		* Also need to set the variables first as we don't
+		* have a lock on the stack.*/
+		advertising_mode_target = mode;
+		advertising_mode        = APP_MODE_NONE;
+		AppAdvStop();
+	} else {
+		advertising_mode        = mode;
+		advertising_mode_target = mode;
+		AppAdvStart(advertising_mode);
+	}
+	tainted = false;
+}
+
+void ble_adv_start(uint8_t mode)
+{
+	adv_start(mode);
+	tainted = true;
+}
+
+void ble_adv_discoverable(bool discoverable)
+{
+	if (discoverable) {
+		if (advertising_mode != APP_MODE_DISCOVERABLE || tainted) {
+			LOG_INFO("ble", "Making bondable and discoverable");
+			adv_start(APP_MODE_DISCOVERABLE);
+		}
+	} else {
+		/* TODO: This does way more than the function name indicates */
+		if (AppDbCheckBonded()) {
+			if (advertising_mode != APP_MODE_CONNECTABLE ||
+			    tainted) {
+				LOG_INFO("ble", "Bonded. Making connectable");
+				adv_start(APP_MODE_CONNECTABLE);
+			}
+		} else {
+			LOG_INFO("ble", "Not bonded. Stop advertising");
+			ble_adv_stop();
+		}
+	}
+}
diff --git a/epicardium/ble/ble_api.h b/epicardium/ble/ble_api.h
index 38c60f731a27ada94022ad2dcbd11f467824f0d6..dd4ae3c6d590abf2029f7a8dbdc45c3af4e14505 100644
--- a/epicardium/ble/ble_api.h
+++ b/epicardium/ble/ble_api.h
@@ -12,6 +12,19 @@
 	0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23
 #define CARD10_UUID_PREFIX 0x02, 0x23, 0x42
 
+/**************************************************************************************************
+  Data Types
+**************************************************************************************************/
+
+/*! Application message type */
+typedef union
+{
+  wsfMsgHdr_t     hdr;
+  dmEvt_t         dm;
+  attsCccEvt_t    ccc;
+  attEvt_t        att;
+} bleMsg_t;
+
 /**************************************************************************************************
   Function Declarations
 **************************************************************************************************/
@@ -36,15 +49,10 @@ void ble_epic_ble_api_trigger_event(enum epic_ble_event_type type, void *data);
 void ble_epic_ble_api_init(void);
 void ble_epic_dm_api_event(dmEvt_t *dm_event);
 
-/**************************************************************************************************
-  Data Types
-**************************************************************************************************/
-
-/*! Application message type */
-typedef union
-{
-  wsfMsgHdr_t     hdr;
-  dmEvt_t         dm;
-  attsCccEvt_t    ccc;
-  attEvt_t        att;
-} bleMsg_t;
+void ble_adv_init(void);
+void ble_adv_setup(void);
+void ble_adv_set_interval(uint32_t interval_ms);
+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);
diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index 7e96fc7cc5049b576c8794267634ff465a5f3c1d..795aaa41d8c51bd8af4e5dd02734747772d372ba 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -45,8 +45,6 @@
 #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 struct epic_scan_report scan_reports[SCAN_REPORTS_NUM];
 static int scan_reports_head;
 static int scan_reports_tail;
@@ -55,13 +53,6 @@ static int scan_reports_tail;
   Configurable Parameters
 **************************************************************************************************/
 
-/*! configurable parameters for advertising */
-static const appAdvCfg_t bleAdvCfg =
-{
-  {0,             0},                  /*! Advertising durations in ms */
-  {500/0.625,     0}                   /*! Advertising intervals in 0.625 ms units */
-};
-
 /*! configurable parameters for slave */
 static const appSlaveCfg_t bleSlaveCfg =
 {
@@ -120,78 +111,6 @@ static const attCfg_t bleAttCfg =
   1                                    /* number of queued prepare writes supported by server */
 };
 
-/**************************************************************************************************
-  Advertising Data
-**************************************************************************************************/
-
-/*! advertising data, discoverable mode */
-static const uint8_t bleAdvDataDisc[] =
-{
-  /*! flags */
-  2,                                      /*! length */
-  DM_ADV_TYPE_FLAGS,                      /*! AD type */
-  DM_FLAG_LE_LIMITED_DISC |               /*! flags */
-  DM_FLAG_LE_BREDR_NOT_SUP,
-
-  3,
-  DM_ADV_TYPE_APPEARANCE,
-  UINT16_TO_BYTES(CH_APPEAR_WATCH),
-
-  /*! service UUID list */
-  17,
-  DM_ADV_TYPE_128_UUID_PART,
-  CARD10_UUID_SUFFIX, 0x0, CARD10_UUID_PREFIX,
-
-  2,                                      /*! length */
-  DM_ADV_TYPE_TX_POWER,                   /*! AD type */
-  0,                                      /*! tx power */
-};
-
-/*! advertising data, discoverable mode with HID service*/
-static const uint8_t bleAdvDataDiscHID[] =
-{
-  /*! flags */
-  2,                                      /*! length */
-  DM_ADV_TYPE_FLAGS,                      /*! AD type */
-  DM_FLAG_LE_LIMITED_DISC |               /*! flags */
-  DM_FLAG_LE_BREDR_NOT_SUP,
-
-  3,
-  DM_ADV_TYPE_APPEARANCE,
-  UINT16_TO_BYTES(CH_APPEAR_WATCH),
-
-  /*! service UUID list */
-  17,
-  DM_ADV_TYPE_128_UUID_PART,
-  CARD10_UUID_SUFFIX, 0x0, CARD10_UUID_PREFIX,
-
-  3,                                      /*! length */
-  DM_ADV_TYPE_16_UUID_PART,               /*! AD type */
-  UINT16_TO_BYTES(ATT_UUID_HID_SERVICE)
-};
-
-/*! scan data, discoverable mode */
-uint8_t bleScanDataDisc[] =
-{
-  /*! device name */
-  14,                                      /*! length */
-  DM_ADV_TYPE_LOCAL_NAME,                 /*! AD type */
-  'c','a','r','d','1','0','-','0','0','0','0','0','0',
-
-  3,                                      /*! length */
-  DM_ADV_TYPE_16_SOLICIT,                 /*! AD type */
-  UINT16_TO_BYTES(ATT_UUID_CURRENT_TIME_SERVICE),
-};
-
-/*! advertising data, connectable mode */
-static const uint8_t bleAdvDataConn[] =
-{
-  /*! flags */
-  2,                                      /*! length */
-  DM_ADV_TYPE_FLAGS,                      /*! AD type */
-  DM_FLAG_LE_BREDR_NOT_SUP,
-};
-
 static const appMasterCfg_t scannerMasterCfg =
 {
   420,                                     /*! The scan interval, in 0.625 ms units */
@@ -466,35 +385,7 @@ static void bleClose(bleMsg_t *pMsg)
 /*************************************************************************************************/
 static void bleSetup(bleMsg_t *pMsg)
 {
-  char buf[32];
-  char a, b, c, d, e, f, K;
-
-  int result = epic_config_get_string("ble_mac", buf, sizeof(buf));
-  if (result == 0)
-  {
-    if (sscanf(buf, "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c", &K,&K,&K,&K,&K,&K, &a, &b, &c, &d, &e, &f) == 12)
-    {
-      bleScanDataDisc[9]  = a;
-      bleScanDataDisc[10] = b;
-      bleScanDataDisc[11] = c;
-      bleScanDataDisc[12] = d;
-      bleScanDataDisc[13] = e;
-      bleScanDataDisc[14] = f;
-    }
-  }
-
-  /* set advertising and scan response data for discoverable mode */
-  if(config_get_boolean_with_default("ble_hid_enable", false)) {
-    AppAdvSetData(APP_ADV_DATA_DISCOVERABLE, sizeof(bleAdvDataDiscHID), (uint8_t *) bleAdvDataDiscHID);
-  } else {
-    AppAdvSetData(APP_ADV_DATA_DISCOVERABLE, sizeof(bleAdvDataDisc), (uint8_t *) bleAdvDataDisc);
-  }
-
-  AppAdvSetData(APP_SCAN_DATA_DISCOVERABLE, sizeof(bleScanDataDisc), (uint8_t *) bleScanDataDisc);
-
-  /* set advertising and scan response data for connectable mode */
-  AppAdvSetData(APP_ADV_DATA_CONNECTABLE, sizeof(bleAdvDataConn), (uint8_t *) bleAdvDataConn);
-  AppAdvSetData(APP_SCAN_DATA_CONNECTABLE, 0, NULL);
+  ble_adv_setup();
 
   active = true;
   /* TODO: Sadly, not advertising leads to a higher current consumption... */
@@ -513,11 +404,7 @@ void epic_ble_set_mode(bool bondable, bool scanner)
 	}
 
 	if(scanner) {
-		if(advertising_mode != APP_MODE_NONE) {
-			advertising_mode_target = APP_MODE_NONE;
-			advertising_mode = APP_MODE_NONE;
-			AppAdvStop();
-		}
+		ble_adv_stop();
 
 		dmConnId_t      connId;
 		if ((connId = AppConnIsOpen()) != DM_CONN_ID_NONE) {
@@ -538,48 +425,11 @@ void epic_ble_set_mode(bool bondable, bool scanner)
 	}
 
 	if(bondable) {
-		/* We need to stop advertising in between or the
-		 * adv set will not be changed.
-		 * Also need to wait for the stop operation to finish
-		 * before we can start again
-		 * Also need to set the variables first as we don't
-		 * have a lock on the stack.*/
 		AppSetBondable(TRUE);
-		if(advertising_mode != APP_MODE_DISCOVERABLE) {
-			LOG_INFO("ble", "Making bondable and discoverable");
-			if(advertising_mode != APP_MODE_NONE) {
-				advertising_mode_target = APP_MODE_DISCOVERABLE;
-				advertising_mode = APP_MODE_NONE;
-				AppAdvStop();
-			} else {
-				advertising_mode = APP_MODE_DISCOVERABLE;
-				advertising_mode_target = APP_MODE_DISCOVERABLE;
-				AppAdvStart(advertising_mode);
-			}
-		}
+		ble_adv_discoverable(true);
 	} else {
 		AppSetBondable(FALSE);
-		if(AppDbCheckBonded()) {
-			if(advertising_mode != APP_MODE_CONNECTABLE) {
-				LOG_INFO("ble", "Bonded. Making connectable");
-				if(advertising_mode != APP_MODE_NONE) {
-					advertising_mode_target = APP_MODE_CONNECTABLE;
-					advertising_mode = APP_MODE_NONE;
-					AppAdvStop();
-				} else {
-					advertising_mode = APP_MODE_CONNECTABLE;
-					advertising_mode_target = APP_MODE_CONNECTABLE;
-					AppAdvStart(advertising_mode);
-				}
-			}
-		} else {
-			if(advertising_mode != APP_MODE_NONE) {
-				LOG_INFO("ble", "Not bonded. Stop advertising");
-				advertising_mode = APP_MODE_NONE;
-				advertising_mode_target = APP_MODE_NONE;
-				AppAdvStop();
-			}
-		}
+		ble_adv_discoverable(false);
 	}
 }
 
@@ -708,18 +558,11 @@ static void bleProcMsg(bleMsg_t *pMsg)
       break;
 
     case DM_ADV_START_IND:
-      LOG_INFO("ble", "Advertisement started %u %u", advertising_mode, advertising_mode_target);
-      if(advertising_mode != advertising_mode_target || advertising_mode_target == APP_MODE_NONE) {
-        AppAdvStop();
-      }
+      ble_adv_proc_msg(pMsg);
       break;
 
     case DM_ADV_STOP_IND:
-      LOG_INFO("ble", "Advertisement stopped %u %u", advertising_mode, advertising_mode_target);
-      if(advertising_mode != advertising_mode_target) {
-        advertising_mode = advertising_mode_target;
-        AppAdvStart(advertising_mode);
-      }
+      ble_adv_proc_msg(pMsg);
       break;
 
     case DM_CONN_OPEN_IND:
@@ -761,11 +604,7 @@ static void bleProcMsg(bleMsg_t *pMsg)
                    pMsg->dm.connClose.reason);
           break;
       }
-      /* Stack overwrites advertising mode after connection close.
-       * Force our desired mode.
-       */
-      advertising_mode = APP_MODE_NONE;
-      AppAdvStop();
+      ble_adv_proc_msg(pMsg);
       bleClose(pMsg);
       break;
 
@@ -856,13 +695,14 @@ static void BleHandlerInit(void)
   bleHandlerId =WsfOsSetNextHandler(BleHandler);
 
   /* Set configuration pointers */
-  pAppAdvCfg = (appAdvCfg_t *) &bleAdvCfg;
   pAppSlaveCfg = (appSlaveCfg_t *) &bleSlaveCfg;
   pAppSecCfg = (appSecCfg_t *) &bleSecCfg;
   pAppUpdateCfg = (appUpdateCfg_t *) &bleUpdateCfg;
   pAppDiscCfg = (appDiscCfg_t *) &bleDiscCfg;
   pAppMasterCfg = (appMasterCfg_t *) &scannerMasterCfg;
 
+  ble_adv_init();
+
   /* Initialize application framework */
   AppSlaveInit();
   AppDiscInit();
diff --git a/epicardium/ble/epic_ble_api.c b/epicardium/ble/epic_ble_api.c
index 726d26a949ece4dbefbebc4d7e9f0b8b8c9e818e..cf27adc0f237f650c3acabeaa38e4aab6df53f34 100644
--- a/epicardium/ble/epic_ble_api.c
+++ b/epicardium/ble/epic_ble_api.c
@@ -13,6 +13,7 @@
 
 #include <stdint.h>
 #include <string.h>
+#include <stdio.h>
 
 #define BLE_EVENT_QUEUE_SIZE 10
 
@@ -21,6 +22,9 @@ static uint8_t ble_event_queue_buffer
 	[sizeof(struct epic_ble_event) * BLE_EVENT_QUEUE_SIZE];
 static StaticQueue_t ble_event_queue_data;
 
+static uint8_t adv_data_buf[HCI_ADV_DATA_LEN];
+static uint8_t sr_data_buf[HCI_ADV_DATA_LEN];
+
 int epic_ble_free_event(struct epic_ble_event *e)
 {
 	if (e->data) {
@@ -113,3 +117,50 @@ int epic_ble_get_device_name(uint8_t **buf, uint16_t *len)
 	uint8_t ret = AttsGetAttr(GAP_DN_HDL, len, buf);
 	return ret;
 }
+
+int epic_ble_advertise(
+	int interval_us,
+	const uint8_t *adv_data,
+	size_t adv_data_len,
+	const uint8_t *sr_data,
+	size_t sr_data_len,
+	bool connectable
+) {
+	if (adv_data_len > sizeof(adv_data_buf)) {
+		adv_data_len = sizeof(adv_data_buf);
+	}
+
+	if (sr_data_len > sizeof(sr_data_buf)) {
+		sr_data_len = sizeof(sr_data_buf);
+	}
+
+	memcpy(adv_data_buf, adv_data, adv_data_len);
+	memcpy(sr_data_buf, sr_data, sr_data_len);
+
+	ble_adv_set_interval(interval_us);
+
+	if (connectable) {
+		AppAdvSetData(
+			APP_ADV_DATA_CONNECTABLE, adv_data_len, adv_data_buf
+		);
+		AppAdvSetData(
+			APP_SCAN_DATA_CONNECTABLE, sr_data_len, sr_data_buf
+		);
+		ble_adv_start(APP_MODE_CONNECTABLE);
+	} else {
+		AppAdvSetData(
+			APP_ADV_DATA_DISCOVERABLE, adv_data_len, adv_data_buf
+		);
+		AppAdvSetData(
+			APP_SCAN_DATA_DISCOVERABLE, sr_data_len, sr_data_buf
+		);
+		ble_adv_start(APP_MODE_DISCOVERABLE);
+	}
+	return 0;
+}
+
+int epic_ble_advertise_stop(void)
+{
+	ble_adv_stop();
+	return 0;
+}
diff --git a/epicardium/ble/meson.build b/epicardium/ble/meson.build
index 2372440b4e5416aeb6fa57c52154afce12608b7d..b426592c0e8cbbf82c30a74333f47e0bd57b5b5e 100644
--- a/epicardium/ble/meson.build
+++ b/epicardium/ble/meson.build
@@ -4,6 +4,7 @@ ble_sources = files(
   'epic_att_api.c',
   'stack.c',
   'ble_main.c',
+  'ble_adv.c',
   'ble_attc.c',
   'profiles/tipc_main.c',
   'profiles/gap_main.c',
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 3b93ea5bd1cdf3718867bc52986e34728f5e38c2..75901978e91b0c8104bd4d70a3c4c21f5669a585 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -180,6 +180,8 @@ typedef _Bool bool;
 #define API_BLE_SET_DEVICE_NAME               0x182
 #define API_BLE_GET_DEVICE_NAME               0x183
 #define API_BLE_GET_ADDRESS                   0x184
+#define API_BLE_ADVERTISE                     0x185
+#define API_BLE_ADVERTISE_STOP                0x186
 
 /* clang-format on */
 
@@ -2661,5 +2663,8 @@ API(API_BLE_SET_DEVICE_NAME, int epic_ble_set_device_name(const uint8_t *buf, ui
 API(API_BLE_GET_DEVICE_NAME, int epic_ble_get_device_name(uint8_t **buf, uint16_t *len));
 API(API_BLE_GET_ADDRESS, void epic_ble_get_address(uint8_t *addr));
 
+API(API_BLE_ADVERTISE, int epic_ble_advertise(int interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len, bool connectable));
+API(API_BLE_ADVERTISE_STOP, int epic_ble_advertise_stop(void));
+
 #endif /* _EPICARDIUM_H */
 
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index fb413958ee0e090251e423e3661426c6fe3dfa85..de0d9d215c833b886ea89aeafa46043e5e38fa19 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -14,6 +14,7 @@
 #include "pmic.h"
 #include "portexpander.h"
 #include "max86150.h"
+#include "ble/ble_api.h"
 
 #include "gpio.h"
 #include "i2c.h"
@@ -288,6 +289,11 @@ int hardware_reset(void)
 	/*
 	 * BLE
 	 */
+
+	/* Reset advertisement data */
+	ble_adv_setup();
+
+	/* Start advertising again if needed */
 	epic_ble_set_mode(false, false);
 
 	return 0;
diff --git a/pycardium/modules/modbluetooth_card10.c b/pycardium/modules/modbluetooth_card10.c
index a13d3f7934cc10080b041995807cf3d04a3264ac..4a659f93d42fa430af82e18d5ed8bc2c7b08248d 100644
--- a/pycardium/modules/modbluetooth_card10.c
+++ b/pycardium/modules/modbluetooth_card10.c
@@ -12,9 +12,6 @@ enum notification_status {
 	NOTIFICATION_STATUS_OVERFLOW
 };
 
-const char *const not_implemented_message =
-	"Not (yet) implemented on card10. See https://git.card10.badge.events.ccc.de/card10/firmware/-/issues/8";
-
 typedef struct _mp_bluetooth_card10_root_pointers_t {
 	// Characteristic (and descriptor) value storage.
 	mp_gatts_db_t gatts_db;
@@ -54,11 +51,6 @@ gatts_status_lookup(mp_gatts_db_t db, uint16_t handle)
 	return MP_OBJ_TO_PTR(elem->value);
 }
 
-static void raise(void)
-{
-	mp_raise_NotImplementedError(not_implemented_message);
-}
-
 static void clear_events(void)
 {
 	struct epic_ble_event ble_event;
@@ -220,18 +212,25 @@ int mp_bluetooth_gap_advertise_start(
 	size_t sr_data_len
 ) {
 	// Dropping any current connection starts advertising on the card10
-	// TODO: modify the advertising data
 	int connection = epic_ble_is_connection_open();
 	if (connection > 0) {
 		epic_ble_close_connection(connection);
 	}
-	return 0;
+
+	return epic_ble_advertise(
+		interval_us,
+		adv_data,
+		adv_data_len,
+		sr_data,
+		sr_data_len,
+		connectable
+	);
 }
 
 // Stop advertisement. No-op when already stopped.
 void mp_bluetooth_gap_advertise_stop(void)
 {
-	raise();
+	epic_ble_advertise_stop();
 }
 
 // Start adding services. Must be called before mp_bluetooth_register_service.
@@ -499,6 +498,14 @@ int mp_bluetooth_gap_disconnect(uint16_t conn_handle)
 }
 
 #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
+const char *const not_implemented_message =
+	"Not (yet) implemented on card10. See https://git.card10.badge.events.ccc.de/card10/firmware/-/issues/8";
+
+static void raise(void)
+{
+	mp_raise_NotImplementedError(not_implemented_message);
+}
+
 // Start a discovery (scan). Set duration to zero to run continuously.
 int mp_bluetooth_gap_scan_start(
 	int32_t duration_ms, int32_t interval_us, int32_t window_us