Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions include/bm/bluetooth/services/ble_mds.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2026 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

/** @file
*
* @defgroup ble_mds Bluetooth LE Memfault Diagnostic Service library
* @{
* @brief Library for exposing Memfault Diagnostic Service over Bluetooth LE.
*/

#ifndef BLE_MDS_H__
#define BLE_MDS_H__

#include <stdbool.h>
#include <stdint.h>

#include <ble.h>
#include <bm/bluetooth/ble_common.h>
#include <bm/softdevice_handler/nrf_sdh_ble.h>

#ifdef __cplusplus
extern "C" {
#endif

/** Vendor specific UUID base for Memfault Diagnostic Service. */
#define BLE_MDS_UUID_BASE \
{ \
0x36, 0x84, 0xBD, 0x4E, 0x2F, 0x72, 0x71, 0xA3, 0x07, 0x40, 0xA5, 0xF6, \
0x00, 0x00, 0x22, 0x54 \
}

#define BLE_UUID_MDS_SERVICE 0x0000
#define BLE_UUID_MDS_SUPPORTED_FEATURES_CHAR 0x0001
#define BLE_UUID_MDS_DEVICE_IDENTIFIER_CHAR 0x0002
#define BLE_UUID_MDS_DATA_URI_CHAR 0x0003
#define BLE_UUID_MDS_AUTHORIZATION_CHAR 0x0004
#define BLE_UUID_MDS_DATA_EXPORT_CHAR 0x0005

void ble_mds_on_ble_evt(const ble_evt_t *ble_evt, void *context);

/**
* @brief Macro for defining a ble_mds instance.
*
* @param _name Name of the instance.
* @hideinitializer
*/
#define BLE_MDS_DEF(_name) \
static struct ble_mds _name; \
NRF_SDH_BLE_OBSERVER(_name##_obs, ble_mds_on_ble_evt, &_name, HIGH)

/** @brief Default security configuration. */
#define BLE_MDS_CONFIG_SEC_MODE_DEFAULT \
{ \
.feature_char = { \
.read = BLE_GAP_CONN_SEC_MODE_OPEN, \
}, \
.device_id_char = { \
.read = BLE_GAP_CONN_SEC_MODE_OPEN, \
}, \
.data_uri_char = { \
.read = BLE_GAP_CONN_SEC_MODE_OPEN, \
}, \
.auth_char = { \
.read = BLE_GAP_CONN_SEC_MODE_OPEN, \
}, \
.data_export_char = { \
.write = BLE_GAP_CONN_SEC_MODE_OPEN, \
.cccd_write = BLE_GAP_CONN_SEC_MODE_OPEN, \
}, \
}

struct ble_mds_config {
struct {
struct {
ble_gap_conn_sec_mode_t read;
} feature_char;
struct {
ble_gap_conn_sec_mode_t read;
} device_id_char;
struct {
ble_gap_conn_sec_mode_t read;
} data_uri_char;
struct {
ble_gap_conn_sec_mode_t read;
} auth_char;
struct {
ble_gap_conn_sec_mode_t write;
ble_gap_conn_sec_mode_t cccd_write;
} data_export_char;
} sec_mode;
};

struct ble_mds {
uint8_t uuid_type;
uint16_t service_handle;
ble_gatts_char_handles_t supported_features_handles;
ble_gatts_char_handles_t device_id_handles;
ble_gatts_char_handles_t data_uri_handles;
ble_gatts_char_handles_t auth_handles;
ble_gatts_char_handles_t data_export_handles;
uint16_t conn_handle;
bool initialized;
bool subscriber_active;
bool streaming_enabled;
bool tx_blocked;
bool hvx_pending;
uint8_t seq_num;
int64_t next_empty_poll_ms;
int64_t next_log_collection_ms;
uint8_t pending_payload[CONFIG_NRF_SDH_BLE_GATT_MAX_MTU_SIZE - ATT_OPCODE_LEN -
ATT_HANDLE_LEN];
uint16_t pending_len;
};

/**
* @brief Initialize the Memfault Diagnostic Service.
*
* @retval NRF_SUCCESS On success.
* @retval NRF_ERROR_NULL If @p mds or @p cfg is NULL.
*/
uint32_t ble_mds_init(struct ble_mds *mds, const struct ble_mds_config *cfg);

/**
* @brief Pump pending Memfault chunks to an active MDS subscriber.
*
* Call from the application main loop.
*/
void ble_mds_process(struct ble_mds *mds);

/**
* @brief Get the SoftDevice UUID type assigned to the MDS UUID base.
*/
uint8_t ble_mds_service_uuid_type(const struct ble_mds *mds);

#ifdef __cplusplus
}
#endif

#endif /* BLE_MDS_H__ */

/** @} */
13 changes: 13 additions & 0 deletions samples/bluetooth/ble_mds/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#
# Copyright (c) 2026 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ble_mds)

target_sources(app PRIVATE src/main.c)

zephyr_include_directories(memfault_config)
31 changes: 31 additions & 0 deletions samples/bluetooth/ble_mds/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
# Copyright (c) 2026 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

menu "Bluetooth LE MDS sample"

config SAMPLE_BLE_DEVICE_NAME
string "Device name"
default "Nordic_Memfault"

config SAMPLE_BLE_MDS_BATTERY_LEVEL_MEAS_INTERVAL
int "Battery level measurement interval (ms)"
default 1000
help
Battery level measurement interval in milliseconds.

config SAMPLE_BLE_MDS_RUN_LED_BLINK_INTERVAL
int "Run LED blink interval (ms)"
default 1000
help
Run status LED blink interval in milliseconds.

module=SAMPLE_BLE_MDS
module-str=BLE Memfault Diagnostic Service Sample
source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config"

endmenu # "Bluetooth LE MDS sample"

source "Kconfig.zephyr"
138 changes: 138 additions & 0 deletions samples/bluetooth/ble_mds/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
.. _ble_mds_sample:

Bluetooth: Memfault Diagnostic Service (MDS)
############################################

.. contents::
:local:
:depth: 2

The Memfault Diagnostic Service sample demonstrates how to expose diagnostic data collected by the `Memfault SDK`_ over Bluetooth LE using |BMlong|.
The sample advertises the Memfault Diagnostic Service (MDS) and the Battery Service.
A Bluetooth gateway can connect to the device, read the Memfault upload information, and stream diagnostic chunks to the Memfault cloud.

Requirements
************

The sample supports the following development kits:

.. tabs::

.. group-tab:: Simple board variants

The following board variants do **not** have DFU capabilities:

.. include:: /includes/supported_boards_all_non-mcuboot_variants_s115.txt

.. include:: /includes/supported_boards_all_non-mcuboot_variants_s145.txt

.. group-tab:: MCUboot board variants

The following board variants have DFU capabilities:

.. include:: /includes/supported_boards_all_mcuboot_variants_s115.txt

.. include:: /includes/supported_boards_all_mcuboot_variants_s145.txt

Overview
********

The sample uses the bare-metal SoftDevice Bluetooth libraries to initialize advertising, the Battery Service, the Device Information Service, and MDS.
The Memfault SDK collects reboot information, logs, metrics, trace events, and coredumps.
When an MDS gateway subscribes to the Data Export characteristic and enables streaming, the application calls :c:func:`ble_mds_process` from the main loop to send pending Memfault chunks.

Metrics
=======

The sample defines the following application metrics in :file:`samples/bluetooth/ble_mds/memfault_config/memfault_metrics_heartbeat_config.def`:

* ``button_press_count`` - The number of **Button 2** presses.
* ``battery_soc_pct`` - The simulated battery level.
* ``button_elapsed_time_ms`` - The time measured between two **Button 0** presses.

Pressing **Button 0** the second time stops the timer and triggers a heartbeat collection.

Trace events
============

The sample defines the ``button_state_changed`` trace reason in :file:`samples/bluetooth/ble_mds/memfault_config/memfault_trace_reason_user_config.def`.
The event is collected when **Button 1** changes state.

Core dumps
==========

Press **Button 3** to trigger a hardfault exception by division by zero.
After reboot, reconnect with an MDS gateway to transfer the collected coredump data to Memfault.

User interface
**************

LED 0:
Blinks while the sample is running.

LED 1:
Lit when a Bluetooth LE central is connected.

Button 0:
Starts or stops the ``button_elapsed_time_ms`` metric timer.
The second press stops the timer and triggers a Memfault heartbeat.

Button 1:
Triggers the ``button_state_changed`` trace event on press and release.

Button 2:
Increments the ``button_press_count`` metric and triggers a Memfault heartbeat.

Button 3:
Simulates a crash by triggering a hardfault exception.

Configuration
*************

The sample configuration is split between the :file:`prj.conf` file and sample-specific Kconfig options in :file:`Kconfig`.

Set :kconfig:option:`CONFIG_MEMFAULT_NCS_PROJECT_KEY` to the project key for your Memfault project before using a real gateway.
The sample uses :kconfig:option:`CONFIG_MEMFAULT_NCS_DEVICE_ID_HW_ID` with :kconfig:option:`CONFIG_HW_ID_LIBRARY_SOURCE_DEVICE_ID` so each device reports a hardware-derived Memfault device ID.
It selects :kconfig:option:`CONFIG_MEMFAULT_REBOOT_REASON_GET_BASIC` for compatibility across the bare-metal board variants supported by this sample.
You can configure the advertising name with the :kconfig:option:`CONFIG_SAMPLE_BLE_DEVICE_NAME` Kconfig option.

The MDS library settings use the ``CONFIG_BLE_MDS`` prefix.
For Memfault SDK options that are not configurable through Kconfig, use :file:`samples/bluetooth/ble_mds/memfault_config/memfault_platform_config.h`.

Building and running
********************

This sample can be found under :file:`samples/bluetooth/ble_mds/` in the |BMshort| folder structure.

For details on how to create, configure, and program a sample, see :ref:`getting_started_with_the_samples`.

Testing
=======

You can test this sample using `nRF Connect Device Manager`_ or another MDS-compatible Bluetooth gateway.

1. Configure :kconfig:option:`CONFIG_MEMFAULT_NCS_PROJECT_KEY` for your Memfault project.
#. Compile and program the application.
#. Connect to the kit with a terminal emulator, for example the `Serial Terminal app`_.
#. Reset the kit.
#. In the terminal, observe that the ``BLE MDS sample initialized`` message is printed.
#. Observe that the ``Advertising as nRF_BM_MDS`` message is printed.
You can configure this name using the :kconfig:option:`CONFIG_SAMPLE_BLE_DEVICE_NAME` Kconfig option.
#. Open `nRF Connect Device Manager`_ and scan for devices.
#. Connect to the device and open the diagnostics view.
#. Use the buttons to generate metrics, trace events, and a coredump.
#. Upload the generated symbol file from the build directory to your Memfault project so that uploaded data can be decoded.

Dependencies
************

This sample uses the following |BMshort| libraries:

* :c:func:`ble_adv_init`
* :c:func:`ble_bas_init`
* :c:func:`ble_dis_init`
* :c:func:`ble_mds_init`
* :c:func:`bm_buttons_init`
* :c:func:`bm_timer_init`

In addition, it uses the `Memfault SDK`_.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* Application-specific metrics can be defined here.
* Please refer to https://docs.memfault.com/docs/embedded/metrics-api for more details.
*/

MEMFAULT_METRICS_KEY_DEFINE(button_press_count, kMemfaultMetricType_Unsigned)
MEMFAULT_METRICS_KEY_DEFINE(button_elapsed_time_ms, kMemfaultMetricType_Timer)
MEMFAULT_METRICS_KEY_DEFINE(battery_soc_pct, kMemfaultMetricType_Unsigned)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*
*
* Platform overrides for the default configuration settings in the
* memfault-firmware-sdk. Default configuration settings can be found in
* "<NCS folder>/modules/lib/memfault-firmware-sdk/components/include/memfault/default_config.h"
*/

/* SoftDevice owns HardFault_Handler for vector forwarding. Use the C hook that
* the SoftDevice forwarder calls when a fault belongs to the application.
*/
#define MEMFAULT_EXC_HANDLER_HARD_FAULT C_HardFault_Handler
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* Application-specific trace reasons can be defined here.
* Please refer to https://docs.memfault.com/docs/embedded/trace-events for more details.
*/

MEMFAULT_TRACE_REASON_DEFINE(button_state_changed)
Loading
Loading