From 6725140d0d9eab071882418bd210884862f2f3af Mon Sep 17 00:00:00 2001 From: hlord2000 Date: Thu, 14 May 2026 15:54:37 -0400 Subject: [PATCH 1/3] bluetooth: mds: add memfault data service Adds Memfault Data Service. Signed-off-by: hlord2000 --- include/bm/bluetooth/services/ble_mds.h | 144 +++++ subsys/bluetooth/services/CMakeLists.txt | 1 + subsys/bluetooth/services/Kconfig | 1 + .../bluetooth/services/ble_mds/CMakeLists.txt | 7 + subsys/bluetooth/services/ble_mds/Kconfig | 48 ++ subsys/bluetooth/services/ble_mds/mds.c | 530 ++++++++++++++++++ 6 files changed, 731 insertions(+) create mode 100644 include/bm/bluetooth/services/ble_mds.h create mode 100644 subsys/bluetooth/services/ble_mds/CMakeLists.txt create mode 100644 subsys/bluetooth/services/ble_mds/Kconfig create mode 100644 subsys/bluetooth/services/ble_mds/mds.c diff --git a/include/bm/bluetooth/services/ble_mds.h b/include/bm/bluetooth/services/ble_mds.h new file mode 100644 index 0000000000..6e66980dce --- /dev/null +++ b/include/bm/bluetooth/services/ble_mds.h @@ -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 +#include + +#include +#include +#include + +#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__ */ + +/** @} */ diff --git a/subsys/bluetooth/services/CMakeLists.txt b/subsys/bluetooth/services/CMakeLists.txt index 386ff1235b..e42ae7a873 100644 --- a/subsys/bluetooth/services/CMakeLists.txt +++ b/subsys/bluetooth/services/CMakeLists.txt @@ -14,4 +14,5 @@ add_subdirectory_ifdef(CONFIG_BLE_LBS ble_lbs) add_subdirectory_ifdef(CONFIG_BLE_NUS ble_nus) add_subdirectory_ifdef(CONFIG_BLE_NUS_CLIENT ble_nus_client) add_subdirectory_ifdef(CONFIG_BLE_MCUMGR ble_mcumgr) +add_subdirectory_ifdef(CONFIG_BLE_MDS ble_mds) add_subdirectory_ifdef(CONFIG_BLE_HRS_CLIENT ble_hrs_client) diff --git a/subsys/bluetooth/services/Kconfig b/subsys/bluetooth/services/Kconfig index a58ac5f29d..e2deccad09 100644 --- a/subsys/bluetooth/services/Kconfig +++ b/subsys/bluetooth/services/Kconfig @@ -15,3 +15,4 @@ rsource "ble_lbs/Kconfig" rsource "ble_nus/Kconfig" rsource "ble_nus_client/Kconfig" rsource "ble_mcumgr/Kconfig" +rsource "ble_mds/Kconfig" diff --git a/subsys/bluetooth/services/ble_mds/CMakeLists.txt b/subsys/bluetooth/services/ble_mds/CMakeLists.txt new file mode 100644 index 0000000000..c789cb6a98 --- /dev/null +++ b/subsys/bluetooth/services/ble_mds/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_sources(mds.c) diff --git a/subsys/bluetooth/services/ble_mds/Kconfig b/subsys/bluetooth/services/ble_mds/Kconfig new file mode 100644 index 0000000000..e165fd5638 --- /dev/null +++ b/subsys/bluetooth/services/ble_mds/Kconfig @@ -0,0 +1,48 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +menuconfig BLE_MDS + bool "Memfault Diagnostic Service" + depends on SOFTDEVICE_PERIPHERAL + depends on NRF_SDH_BLE + depends on BLE_CONN_PARAMS + depends on MEMFAULT + help + Expose the Memfault Diagnostic Service over Bluetooth LE so + gateway applications can read the upload URI/authorization and + stream Memfault chunks from the device. + +if BLE_MDS + +config BLE_MDS_DATA_URI_MAX_LEN + int "Maximum MDS data URI length" + default 192 + range 64 512 + +config BLE_MDS_EMPTY_POLL_INTERVAL_MS + int "MDS empty data poll interval in milliseconds" + default 1000 + range 100 60000 + +config BLE_MDS_TRIGGER_LOG_COLLECTION + bool "Trigger Memfault log collection while MDS data export is active" + default y + help + Periodically snapshot the Memfault RAM log buffer into the packetizer + while a gateway is subscribed and has enabled data export. This mirrors + the upload behavior used by the Memfault Zephyr periodic uploader. + +config BLE_MDS_LOG_COLLECTION_INTERVAL_MS + int "MDS log collection interval in milliseconds" + default 1000 + range 100 60000 + depends on BLE_MDS_TRIGGER_LOG_COLLECTION + +module=BLE_MDS +module-str=BLE Memfault Diagnostic Service +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +endif diff --git a/subsys/bluetooth/services/ble_mds/mds.c b/subsys/bluetooth/services/ble_mds/mds.c new file mode 100644 index 0000000000..49a88343c8 --- /dev/null +++ b/subsys/bluetooth/services/ble_mds/mds.c @@ -0,0 +1,530 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(ble_mds, CONFIG_BLE_MDS_LOG_LEVEL); + +#if defined(CONFIG_MEMFAULT_NCS_PROJECT_KEY) +#define BLE_MDS_PROJECT_KEY CONFIG_MEMFAULT_NCS_PROJECT_KEY +#elif defined(CONFIG_MEMFAULT_PROJECT_KEY) +#define BLE_MDS_PROJECT_KEY CONFIG_MEMFAULT_PROJECT_KEY +#elif defined(MEMFAULT_PROJECT_KEY) +#define BLE_MDS_PROJECT_KEY MEMFAULT_PROJECT_KEY +#else +#define BLE_MDS_PROJECT_KEY "" +#endif + +BUILD_ASSERT(sizeof(BLE_MDS_PROJECT_KEY) > 1, + "Set CONFIG_MEMFAULT_NCS_PROJECT_KEY or CONFIG_MEMFAULT_PROJECT_KEY for MDS"); + +#define BLE_MDS_ATT_OVERHEAD (ATT_OPCODE_LEN + ATT_HANDLE_LEN) +#define BLE_MDS_SEQ_NUM_COUNT 32 +#define BLE_MDS_SUPPORTED_FEATURES 0x00 +#define BLE_MDS_URI_BASE \ + MEMFAULT_HTTP_APIS_DEFAULT_SCHEME "://" MEMFAULT_HTTP_CHUNKS_API_HOST "/api/v0/chunks/" +#define BLE_MDS_AUTH_PREFIX MEMFAULT_HTTP_PROJECT_KEY_HEADER ":" + +static uint8_t supported_features = BLE_MDS_SUPPORTED_FEATURES; +static uint8_t data_export_mode; +static char device_id[MEMFAULT_DEVICE_INFO_MAX_STRING_SIZE + 1]; +static char data_uri[CONFIG_BLE_MDS_DATA_URI_MAX_LEN]; +static char authorization[sizeof(BLE_MDS_AUTH_PREFIX) + sizeof(BLE_MDS_PROJECT_KEY) - 1]; + +static uint32_t readable_char_add(uint16_t service_handle, uint8_t uuid_type, uint16_t uuid, + const ble_gap_conn_sec_mode_t *read_perm, const void *value, + uint16_t len, uint16_t max_len, + ble_gatts_char_handles_t *handles) +{ + ble_uuid_t char_uuid = { + .type = uuid_type, + .uuid = uuid, + }; + ble_gatts_char_md_t char_md = { + .char_props = { + .read = true, + }, + }; + ble_gatts_attr_md_t attr_md = { + .vloc = BLE_GATTS_VLOC_STACK, + .vlen = true, + .read_perm = *read_perm, + }; + ble_gatts_attr_t attr = { + .p_uuid = &char_uuid, + .p_attr_md = &attr_md, + .p_value = (uint8_t *)value, + .init_len = len, + .max_len = max_len, + }; + + return sd_ble_gatts_characteristic_add(service_handle, &char_md, &attr, handles); +} + +static uint32_t data_export_char_add(struct ble_mds *mds, const struct ble_mds_config *cfg) +{ + ble_uuid_t char_uuid = { + .type = mds->uuid_type, + .uuid = BLE_UUID_MDS_DATA_EXPORT_CHAR, + }; + ble_gatts_attr_md_t cccd_md = { + .vloc = BLE_GATTS_VLOC_STACK, + .read_perm = BLE_GAP_CONN_SEC_MODE_OPEN, + .write_perm = cfg->sec_mode.data_export_char.cccd_write, + }; + ble_gatts_char_md_t char_md = { + .char_props = { + .write = true, + .notify = true, + }, + .p_cccd_md = &cccd_md, + }; + ble_gatts_attr_md_t attr_md = { + .vloc = BLE_GATTS_VLOC_STACK, + .vlen = true, + .write_perm = cfg->sec_mode.data_export_char.write, + .wr_auth = true, + }; + ble_gatts_attr_t attr = { + .p_uuid = &char_uuid, + .p_attr_md = &attr_md, + .p_value = &data_export_mode, + .init_len = sizeof(data_export_mode), + .max_len = sizeof(mds->pending_payload), + }; + + return sd_ble_gatts_characteristic_add(mds->service_handle, &char_md, &attr, + &mds->data_export_handles); +} + +static uint32_t values_prepare(void) +{ + sMemfaultDeviceInfo info; + int len; + + memfault_platform_get_device_info(&info); + if ((info.device_serial == NULL) || (info.device_serial[0] == '\0')) { + LOG_ERR("Memfault device serial is empty"); + return NRF_ERROR_INVALID_PARAM; + } + + len = snprintf(device_id, sizeof(device_id), "%s", info.device_serial); + if ((len < 0) || ((size_t)len >= sizeof(device_id))) { + LOG_ERR("Memfault device serial too long"); + return NRF_ERROR_DATA_SIZE; + } + + len = snprintf(data_uri, sizeof(data_uri), "%s%s", BLE_MDS_URI_BASE, device_id); + if ((len < 0) || ((size_t)len >= sizeof(data_uri))) { + LOG_ERR("MDS data URI too long"); + return NRF_ERROR_DATA_SIZE; + } + + len = snprintf(authorization, sizeof(authorization), "%s%s", BLE_MDS_AUTH_PREFIX, + BLE_MDS_PROJECT_KEY); + if ((len < 0) || ((size_t)len >= sizeof(authorization))) { + LOG_ERR("MDS authorization value too long"); + return NRF_ERROR_DATA_SIZE; + } + + return NRF_SUCCESS; +} + +static bool notification_enabled(struct ble_mds *mds, uint16_t conn_handle) +{ + uint16_t cccd_value; + ble_gatts_value_t gatts_value = { + .p_value = (uint8_t *)&cccd_value, + .len = sizeof(cccd_value), + }; + uint32_t nrf_err; + + nrf_err = sd_ble_gatts_value_get(conn_handle, mds->data_export_handles.cccd_handle, + &gatts_value); + if (nrf_err) { + return false; + } + + return is_notification_enabled((const uint8_t *)&cccd_value); +} + +static void subscription_disable(struct ble_mds *mds) +{ + mds->subscriber_active = false; + mds->streaming_enabled = false; + mds->tx_blocked = false; + mds->hvx_pending = false; + mds->conn_handle = BLE_CONN_HANDLE_INVALID; + mds->seq_num = 0; + mds->pending_len = 0; + mds->next_log_collection_ms = 0; +} + +static void cccd_write_handle(struct ble_mds *mds, uint16_t conn_handle, + const ble_gatts_evt_write_t *write) +{ + if ((write->offset != 0) || (write->len != sizeof(uint16_t))) { + return; + } + + if (is_notification_enabled(write->data)) { + if (mds->subscriber_active && (mds->conn_handle != conn_handle)) { + LOG_WRN("Ignoring second MDS subscriber on handle %#x", conn_handle); + return; + } + + mds->subscriber_active = true; + mds->conn_handle = conn_handle; + mds->tx_blocked = false; + LOG_INF("MDS notifications enabled"); + } else if (mds->conn_handle == conn_handle) { + subscription_disable(mds); + LOG_INF("MDS notifications disabled"); + } +} + +static uint16_t data_export_write_apply(struct ble_mds *mds, uint16_t conn_handle, + const ble_gatts_evt_write_t *write) +{ + uint8_t mode; + + if (write->offset != 0) { + return BLE_GATT_STATUS_ATTERR_INVALID_OFFSET; + } + + if (write->len != sizeof(mode)) { + return BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH; + } + + if (!mds->subscriber_active || (mds->conn_handle != conn_handle) || + !notification_enabled(mds, conn_handle)) { + return BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR; + } + + mode = write->data[0]; + switch (mode) { + case 0x00: + mds->streaming_enabled = false; + mds->tx_blocked = false; + LOG_INF("MDS streaming disabled"); + return BLE_GATT_STATUS_SUCCESS; + case 0x01: + mds->streaming_enabled = true; + mds->tx_blocked = false; + mds->next_empty_poll_ms = 0; + mds->next_log_collection_ms = 0; + LOG_INF("MDS streaming enabled"); + return BLE_GATT_STATUS_SUCCESS; + default: + return BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED; + } +} + +static void rw_authorize_request_handle(struct ble_mds *mds, const ble_gatts_evt_t *gatts_evt) +{ + const ble_gatts_evt_rw_authorize_request_t *auth = + &gatts_evt->params.authorize_request; + ble_gatts_rw_authorize_reply_params_t reply = { + .type = auth->type, + }; + uint32_t nrf_err; + uint16_t status = BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND; + + if ((auth->type != BLE_GATTS_AUTHORIZE_TYPE_WRITE) || + (auth->request.write.handle != mds->data_export_handles.value_handle)) { + return; + } + + status = data_export_write_apply(mds, gatts_evt->conn_handle, &auth->request.write); + + reply.params.write.gatt_status = status; + reply.params.write.update = (status == BLE_GATT_STATUS_SUCCESS); + reply.params.write.offset = 0; + reply.params.write.len = auth->request.write.len; + reply.params.write.p_data = auth->request.write.data; + + nrf_err = sd_ble_gatts_rw_authorize_reply(gatts_evt->conn_handle, &reply); + if (nrf_err) { + LOG_ERR("MDS authorize reply failed, nrf_error %#x", nrf_err); + } +} + +static void write_handle(struct ble_mds *mds, const ble_gatts_evt_t *gatts_evt) +{ + const ble_gatts_evt_write_t *write = &gatts_evt->params.write; + + if (write->handle == mds->data_export_handles.cccd_handle) { + cccd_write_handle(mds, gatts_evt->conn_handle, write); + } else if (write->handle == mds->data_export_handles.value_handle) { + (void)data_export_write_apply(mds, gatts_evt->conn_handle, write); + } +} + +static bool can_send(const struct ble_mds *mds) +{ + return mds->initialized && mds->subscriber_active && mds->streaming_enabled && + !mds->tx_blocked && !mds->hvx_pending && + (mds->conn_handle != BLE_CONN_HANDLE_INVALID); +} + +static void log_collection_maybe_trigger(struct ble_mds *mds) +{ +#if MEMFAULT_LOG_DATA_SOURCE_ENABLED && IS_ENABLED(CONFIG_BLE_MDS_TRIGGER_LOG_COLLECTION) + const int64_t now = k_uptime_get(); + + if (now < mds->next_log_collection_ms) { + return; + } + + memfault_log_trigger_collection(); + mds->next_log_collection_ms = now + CONFIG_BLE_MDS_LOG_COLLECTION_INTERVAL_MS; +#else + ARG_UNUSED(mds); +#endif +} + +static bool pending_payload_prepare(struct ble_mds *mds) +{ + uint16_t att_mtu; + size_t chunk_len; + uint32_t nrf_err; + uint16_t value_len_max; + + if (mds->pending_len != 0) { + return true; + } + + log_collection_maybe_trigger(mds); + + if (!memfault_packetizer_data_available()) { + const int64_t now = k_uptime_get(); + + mds->next_empty_poll_ms = now + CONFIG_BLE_MDS_EMPTY_POLL_INTERVAL_MS; + return false; + } + + nrf_err = ble_conn_params_att_mtu_get(mds->conn_handle, &att_mtu); + if (nrf_err) { + LOG_WRN("Failed to get ATT MTU for MDS, nrf_error %#x", nrf_err); + att_mtu = BLE_GATT_ATT_MTU_DEFAULT; + } + + value_len_max = MIN((uint16_t)sizeof(mds->pending_payload), + (uint16_t)(att_mtu - BLE_MDS_ATT_OVERHEAD)); + if (value_len_max <= 1U) { + return false; + } + + mds->pending_payload[0] = mds->seq_num & 0x1f; + chunk_len = value_len_max - 1U; + if (!memfault_packetizer_get_chunk(&mds->pending_payload[1], &chunk_len)) { + mds->next_empty_poll_ms = k_uptime_get() + CONFIG_BLE_MDS_EMPTY_POLL_INTERVAL_MS; + return false; + } + + mds->pending_len = (uint16_t)(chunk_len + 1U); + return true; +} + +void ble_mds_process(struct ble_mds *mds) +{ + ble_gatts_hvx_params_t hvx; + uint16_t len; + uint32_t nrf_err; + + if ((mds == NULL) || !can_send(mds)) { + return; + } + + if ((mds->pending_len == 0) && (k_uptime_get() < mds->next_empty_poll_ms)) { + return; + } + + if (!pending_payload_prepare(mds)) { + return; + } + + len = mds->pending_len; + hvx = (ble_gatts_hvx_params_t){ + .handle = mds->data_export_handles.value_handle, + .type = BLE_GATT_HVX_NOTIFICATION, + .p_len = &len, + .p_data = mds->pending_payload, + }; + + nrf_err = sd_ble_gatts_hvx(mds->conn_handle, &hvx); + if (nrf_err == NRF_SUCCESS) { + mds->pending_len = 0; + mds->hvx_pending = true; + mds->seq_num = (mds->seq_num + 1U) % BLE_MDS_SEQ_NUM_COUNT; + return; + } + + if ((nrf_err == NRF_ERROR_RESOURCES) || (nrf_err == NRF_ERROR_INVALID_STATE)) { + mds->tx_blocked = true; + return; + } + + if (nrf_err == BLE_ERROR_GATTS_SYS_ATTR_MISSING) { + LOG_WRN("MDS notifications unavailable, nrf_error %#x", nrf_err); + mds->tx_blocked = true; + return; + } + + LOG_ERR("MDS notification failed, nrf_error %#x", nrf_err); + mds->pending_len = 0; + mds->tx_blocked = true; +} + +void ble_mds_on_ble_evt(const ble_evt_t *ble_evt, void *context) +{ + struct ble_mds *mds = context; + + if ((ble_evt == NULL) || (mds == NULL) || !mds->initialized) { + return; + } + + switch (ble_evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: + mds->tx_blocked = false; + mds->hvx_pending = false; + break; + case BLE_GAP_EVT_DISCONNECTED: + if (mds->conn_handle == ble_evt->evt.gap_evt.conn_handle) { + subscription_disable(mds); + } + break; + case BLE_GATTS_EVT_WRITE: + write_handle(mds, &ble_evt->evt.gatts_evt); + break; + case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST: + rw_authorize_request_handle(mds, &ble_evt->evt.gatts_evt); + break; + case BLE_GATTS_EVT_HVN_TX_COMPLETE: + mds->tx_blocked = false; + mds->hvx_pending = false; + break; + default: + break; + } +} + +uint32_t ble_mds_init(struct ble_mds *mds, const struct ble_mds_config *cfg) +{ + uint32_t nrf_err; + ble_uuid_t ble_uuid; + ble_uuid128_t uuid_base = { .uuid128 = BLE_MDS_UUID_BASE }; + + if ((mds == NULL) || (cfg == NULL)) { + return NRF_ERROR_NULL; + } + + memset(mds, 0, sizeof(*mds)); + mds->conn_handle = BLE_CONN_HANDLE_INVALID; + + nrf_err = values_prepare(); + if (nrf_err) { + return nrf_err; + } + + nrf_err = sd_ble_uuid_vs_add(&uuid_base, &mds->uuid_type); + if (nrf_err) { + LOG_ERR("Failed to add MDS UUID base, nrf_error %#x", nrf_err); + return nrf_err; + } + + ble_uuid.type = mds->uuid_type; + ble_uuid.uuid = BLE_UUID_MDS_SERVICE; + + nrf_err = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, + &mds->service_handle); + if (nrf_err) { + LOG_ERR("Failed to add MDS service, nrf_error %#x", nrf_err); + return nrf_err; + } + + nrf_err = readable_char_add(mds->service_handle, mds->uuid_type, + BLE_UUID_MDS_SUPPORTED_FEATURES_CHAR, + &cfg->sec_mode.feature_char.read, &supported_features, + sizeof(supported_features), sizeof(supported_features), + &mds->supported_features_handles); + if (nrf_err) { + LOG_ERR("Failed to add MDS supported features, nrf_error %#x", nrf_err); + return nrf_err; + } + + nrf_err = readable_char_add(mds->service_handle, mds->uuid_type, + BLE_UUID_MDS_DEVICE_IDENTIFIER_CHAR, + &cfg->sec_mode.device_id_char.read, device_id, + strlen(device_id), sizeof(device_id), + &mds->device_id_handles); + if (nrf_err) { + LOG_ERR("Failed to add MDS device identifier, nrf_error %#x", nrf_err); + return nrf_err; + } + + nrf_err = readable_char_add(mds->service_handle, mds->uuid_type, + BLE_UUID_MDS_DATA_URI_CHAR, + &cfg->sec_mode.data_uri_char.read, data_uri, + strlen(data_uri), sizeof(data_uri), + &mds->data_uri_handles); + if (nrf_err) { + LOG_ERR("Failed to add MDS data URI, nrf_error %#x", nrf_err); + return nrf_err; + } + + nrf_err = readable_char_add(mds->service_handle, mds->uuid_type, + BLE_UUID_MDS_AUTHORIZATION_CHAR, + &cfg->sec_mode.auth_char.read, authorization, + strlen(authorization), sizeof(authorization), + &mds->auth_handles); + if (nrf_err) { + LOG_ERR("Failed to add MDS authorization, nrf_error %#x", nrf_err); + return nrf_err; + } + + nrf_err = data_export_char_add(mds, cfg); + if (nrf_err) { + LOG_ERR("Failed to add MDS data export, nrf_error %#x", nrf_err); + return nrf_err; + } + + mds->initialized = true; + LOG_INF("MDS initialized for device %s", device_id); + + return NRF_SUCCESS; +} + +uint8_t ble_mds_service_uuid_type(const struct ble_mds *mds) +{ + if (mds == NULL) { + return BLE_UUID_TYPE_UNKNOWN; + } + + return mds->uuid_type; +} From 97c7d5d8d5a762b21940588b355f5f55a6345a33 Mon Sep 17 00:00:00 2001 From: hlord2000 Date: Thu, 14 May 2026 16:10:10 -0400 Subject: [PATCH 2/3] manifest: Add memfault-firmware-sdk to allowlist Adds memfault-firmware-sdk to allowlist for Memfault integration Signed-off-by: hlord2000 --- west.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/west.yml b/west.yml index 463b672fb8..a2ef278874 100644 --- a/west.yml +++ b/west.yml @@ -24,6 +24,7 @@ manifest: - lz4 - mbedtls - mcuboot + - memfault-firmware-sdk - nrfxlib - picolibc - oberon-psa-crypto From cfee351cef4f50e15de1450bfc0dc859d01435de Mon Sep 17 00:00:00 2001 From: hlord2000 Date: Thu, 14 May 2026 17:08:56 -0400 Subject: [PATCH 3/3] samples: bluetooth: add ble_mds sample Adds Memfault sample Signed-off-by: hlord2000 --- samples/bluetooth/ble_mds/CMakeLists.txt | 13 + samples/bluetooth/ble_mds/Kconfig | 31 + samples/bluetooth/ble_mds/README.rst | 138 +++++ .../memfault_metrics_heartbeat_config.def | 7 + .../memfault_platform_config.h | 15 + .../memfault_trace_reason_user_config.def | 5 + samples/bluetooth/ble_mds/prj.conf | 58 ++ samples/bluetooth/ble_mds/sample.yaml | 37 ++ samples/bluetooth/ble_mds/src/main.c | 528 ++++++++++++++++++ 9 files changed, 832 insertions(+) create mode 100644 samples/bluetooth/ble_mds/CMakeLists.txt create mode 100644 samples/bluetooth/ble_mds/Kconfig create mode 100644 samples/bluetooth/ble_mds/README.rst create mode 100644 samples/bluetooth/ble_mds/memfault_config/memfault_metrics_heartbeat_config.def create mode 100644 samples/bluetooth/ble_mds/memfault_config/memfault_platform_config.h create mode 100644 samples/bluetooth/ble_mds/memfault_config/memfault_trace_reason_user_config.def create mode 100644 samples/bluetooth/ble_mds/prj.conf create mode 100644 samples/bluetooth/ble_mds/sample.yaml create mode 100644 samples/bluetooth/ble_mds/src/main.c diff --git a/samples/bluetooth/ble_mds/CMakeLists.txt b/samples/bluetooth/ble_mds/CMakeLists.txt new file mode 100644 index 0000000000..0fd9b546bb --- /dev/null +++ b/samples/bluetooth/ble_mds/CMakeLists.txt @@ -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) diff --git a/samples/bluetooth/ble_mds/Kconfig b/samples/bluetooth/ble_mds/Kconfig new file mode 100644 index 0000000000..e0edc5ce0b --- /dev/null +++ b/samples/bluetooth/ble_mds/Kconfig @@ -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" diff --git a/samples/bluetooth/ble_mds/README.rst b/samples/bluetooth/ble_mds/README.rst new file mode 100644 index 0000000000..0d2e492c5e --- /dev/null +++ b/samples/bluetooth/ble_mds/README.rst @@ -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`_. diff --git a/samples/bluetooth/ble_mds/memfault_config/memfault_metrics_heartbeat_config.def b/samples/bluetooth/ble_mds/memfault_config/memfault_metrics_heartbeat_config.def new file mode 100644 index 0000000000..506774c10e --- /dev/null +++ b/samples/bluetooth/ble_mds/memfault_config/memfault_metrics_heartbeat_config.def @@ -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) diff --git a/samples/bluetooth/ble_mds/memfault_config/memfault_platform_config.h b/samples/bluetooth/ble_mds/memfault_config/memfault_platform_config.h new file mode 100644 index 0000000000..24b2c0d4d2 --- /dev/null +++ b/samples/bluetooth/ble_mds/memfault_config/memfault_platform_config.h @@ -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 + * "/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 diff --git a/samples/bluetooth/ble_mds/memfault_config/memfault_trace_reason_user_config.def b/samples/bluetooth/ble_mds/memfault_config/memfault_trace_reason_user_config.def new file mode 100644 index 0000000000..5a80f3b03a --- /dev/null +++ b/samples/bluetooth/ble_mds/memfault_config/memfault_trace_reason_user_config.def @@ -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) diff --git a/samples/bluetooth/ble_mds/prj.conf b/samples/bluetooth/ble_mds/prj.conf new file mode 100644 index 0000000000..382238442b --- /dev/null +++ b/samples/bluetooth/ble_mds/prj.conf @@ -0,0 +1,58 @@ +# Logging +CONFIG_LOG=y +CONFIG_LOG_BACKEND_BM_UARTE=y + +# SoftDevice +CONFIG_SOFTDEVICE=y +CONFIG_NRF_SDH_BLE_VS_UUID_COUNT=1 +CONFIG_NRF_SDH_BLE_GATT_MAX_MTU_SIZE=247 + +# Enable RNG +CONFIG_NRF_SECURITY=y +CONFIG_MBEDTLS_PSA_CRYPTO_C=y +CONFIG_PSA_WANT_GENERATE_RANDOM=y + +# Advertising library +CONFIG_BLE_ADV=y + +# BLE connection parameters +CONFIG_BLE_CONN_PARAMS=y +CONFIG_BLE_CONN_PARAMS_DATA_LENGTH_TX=251 +CONFIG_BLE_CONN_PARAMS_DATA_LENGTH_RX=251 +CONFIG_BLE_CONN_PARAMS_INITIATE_ATT_MTU_EXCHANGE=y +CONFIG_BLE_CONN_PARAMS_INITIATE_DATA_LENGTH_UPDATE=y + +# Device information service +CONFIG_BLE_DIS=y +CONFIG_BLE_DIS_SERIAL_NUMBER="ABCD" +CONFIG_BLE_DIS_HW_REVISION="hw 54.15.0" +CONFIG_BLE_DIS_FW_REVISION="fw 17.2.0" +CONFIG_BLE_DIS_SW_REVISION="sw 1.0.0" + +# Battery service +CONFIG_BLE_BAS=y + +# Memfault Diagnostic Service +CONFIG_BLE_MDS=y + +# Buttons and timers +CONFIG_BM_TIMER=y +CONFIG_BM_BUTTONS=y +CONFIG_BM_GPIOTE=y + +# Memfault +CONFIG_MEMFAULT=y +CONFIG_MEMFAULT_LOGGING_ENABLE=y +CONFIG_MEMFAULT_LOG_LEVEL_INF=y +CONFIG_MEMFAULT_NCS_STACK_METRICS=n +CONFIG_MEMFAULT_METRICS_DEFAULT_SET_ENABLE=n +CONFIG_MEMFAULT_METRICS_THREADS=n +CONFIG_MEMFAULT_METRICS_TIMER_CUSTOM=y +CONFIG_MEMFAULT_REBOOT_REASON_GET_BASIC=y +CONFIG_MEMFAULT_SOFTWARE_WATCHDOG_CUSTOM=y +CONFIG_MEMFAULT_COREDUMP_COLLECT_KERNEL_REGION=n +CONFIG_MEMFAULT_COREDUMP_COLLECT_TASKS_REGIONS=n + +CONFIG_MEMFAULT_NCS_PROJECT_KEY="" +CONFIG_MEMFAULT_NCS_DEVICE_ID_HW_ID=y +CONFIG_HW_ID_LIBRARY_SOURCE_DEVICE_ID=y diff --git a/samples/bluetooth/ble_mds/sample.yaml b/samples/bluetooth/ble_mds/sample.yaml new file mode 100644 index 0000000000..7b6d40296d --- /dev/null +++ b/samples/bluetooth/ble_mds/sample.yaml @@ -0,0 +1,37 @@ +sample: + name: Bluetooth LE Memfault Diagnostic Service Sample +tests: + sample.ble_mds: + sysbuild: true + build_only: true + extra_configs: + - CONFIG_MEMFAULT_NCS_PROJECT_KEY="dummy-key" + integration_platforms: + - bm_nrf54l15dk/nrf54l05/cpuapp/s145_softdevice + - bm_nrf54lm20dk/nrf54lm20a/cpuapp/s115_softdevice + platform_allow: + - bm_nrf54l15dk/nrf54l05/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l05/cpuapp/s115_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l05/cpuapp/s145_softdevice + - bm_nrf54l15dk/nrf54l05/cpuapp/s145_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l10/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l10/cpuapp/s115_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l10/cpuapp/s145_softdevice + - bm_nrf54l15dk/nrf54l10/cpuapp/s145_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l15/cpuapp/s115_softdevice + - bm_nrf54l15dk/nrf54l15/cpuapp/s115_softdevice/mcuboot + - bm_nrf54l15dk/nrf54l15/cpuapp/s145_softdevice + - bm_nrf54l15dk/nrf54l15/cpuapp/s145_softdevice/mcuboot + - bm_nrf54lm20dk/nrf54lm20a/cpuapp/s115_softdevice + - bm_nrf54lm20dk/nrf54lm20a/cpuapp/s115_softdevice/mcuboot + - bm_nrf54lm20dk/nrf54lm20a/cpuapp/s145_softdevice + - bm_nrf54lm20dk/nrf54lm20a/cpuapp/s145_softdevice/mcuboot + - bm_nrf54ls05dk/nrf54ls05b/cpuapp/s115_softdevice + - bm_nrf54ls05dk/nrf54ls05b/cpuapp/s115_softdevice/mcuboot + - bm_nrf54ls05dk/nrf54ls05b/cpuapp/s145_softdevice + - bm_nrf54ls05dk/nrf54ls05b/cpuapp/s145_softdevice/mcuboot + - bm_nrf54lv10dk/nrf54lv10a/cpuapp/s115_softdevice + - bm_nrf54lv10dk/nrf54lv10a/cpuapp/s115_softdevice/mcuboot + - bm_nrf54lv10dk/nrf54lv10a/cpuapp/s145_softdevice + - bm_nrf54lv10dk/nrf54lv10a/cpuapp/s145_softdevice/mcuboot + tags: ci_build diff --git a/samples/bluetooth/ble_mds/src/main.c b/samples/bluetooth/ble_mds/src/main.c new file mode 100644 index 0000000000..40d2871771 --- /dev/null +++ b/samples/bluetooth/ble_mds/src/main.c @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(sample, CONFIG_SAMPLE_BLE_MDS_LOG_LEVEL); + +#define RUN_STATUS_LED BOARD_PIN_LED_0 +#define CON_STATUS_LED BOARD_PIN_LED_1 +#define BATTERY_LEVEL_MAX 100U + +BLE_ADV_DEF(ble_adv); /* BLE advertising instance */ +BLE_BAS_DEF(ble_bas); /* BLE battery service instance */ +BLE_MDS_DEF(ble_mds); /* BLE Memfault Diagnostic Service instance */ + +static uint16_t conn_handle = BLE_CONN_HANDLE_INVALID; +static uint8_t battery_level = BATTERY_LEVEL_MAX; + +static struct bm_timer battery_timer; +static struct bm_timer run_led_timer; + +#if IS_ENABLED(CONFIG_MEMFAULT) && !IS_ENABLED(CONFIG_MULTITHREADING) +int z_impl_k_mutex_lock(struct k_mutex *mutex, k_timeout_t timeout) +{ + ARG_UNUSED(mutex); + ARG_UNUSED(timeout); + + return 0; +} + +int z_impl_k_mutex_unlock(struct k_mutex *mutex) +{ + ARG_UNUSED(mutex); + + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_MEMFAULT_METRICS_TIMER_CUSTOM) +static MemfaultPlatformTimerCallback *memfault_metrics_timer_cb; +static int64_t memfault_metrics_period_ms; +static int64_t memfault_metrics_next_ms; + +bool memfault_platform_metrics_timer_boot(uint32_t period_sec, + MemfaultPlatformTimerCallback callback) +{ + if ((period_sec == 0U) || (callback == NULL)) { + return false; + } + + memfault_metrics_timer_cb = callback; + memfault_metrics_period_ms = (int64_t)period_sec * 1000; + memfault_metrics_next_ms = k_uptime_get() + memfault_metrics_period_ms; + + return true; +} + +static void memfault_metrics_timer_process(void) +{ + int64_t now; + + if (memfault_metrics_timer_cb == NULL) { + return; + } + + now = k_uptime_get(); + if (now < memfault_metrics_next_ms) { + return; + } + + memfault_metrics_next_ms += memfault_metrics_period_ms; + if (now >= memfault_metrics_next_ms) { + memfault_metrics_next_ms = now + memfault_metrics_period_ms; + } + + memfault_metrics_timer_cb(); +} +#else +static void memfault_metrics_timer_process(void) +{ +} +#endif + +static void leds_init(void) +{ + nrf_gpio_cfg_output(RUN_STATUS_LED); + nrf_gpio_cfg_output(CON_STATUS_LED); + nrf_gpio_pin_write(RUN_STATUS_LED, !BOARD_LED_ACTIVE_STATE); + nrf_gpio_pin_write(CON_STATUS_LED, !BOARD_LED_ACTIVE_STATE); +} + +static void run_led_timeout_handler(void *context) +{ + static bool led_on; + + ARG_UNUSED(context); + + led_on = !led_on; + nrf_gpio_pin_write(RUN_STATUS_LED, led_on ? BOARD_LED_ACTIVE_STATE : + !BOARD_LED_ACTIVE_STATE); +} + +static void battery_level_timeout_handler(void *context) +{ + uint32_t nrf_err; + int err; + + ARG_UNUSED(context); + + if (battery_level == 0U) { + battery_level = BATTERY_LEVEL_MAX; + } else { + battery_level--; + } + + err = MEMFAULT_METRIC_SET_UNSIGNED(battery_soc_pct, battery_level); + if (err) { + LOG_ERR("Failed to set battery_soc_pct metric, err %d", err); + } + + nrf_err = ble_bas_battery_level_update(&ble_bas, conn_handle, battery_level); + if ((nrf_err != NRF_SUCCESS) && + (nrf_err != BLE_ERROR_INVALID_CONN_HANDLE) && + (nrf_err != NRF_ERROR_INVALID_STATE) && + (nrf_err != BLE_ERROR_GATTS_SYS_ATTR_MISSING)) { + LOG_ERR("Failed to update battery level, nrf_error %#x", nrf_err); + } +} + +static void on_ble_evt(const ble_evt_t *evt, void *ctx) +{ + uint32_t nrf_err; + + ARG_UNUSED(ctx); + + switch (evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: + LOG_INF("Peer connected"); + conn_handle = evt->evt.gap_evt.conn_handle; + nrf_gpio_pin_write(CON_STATUS_LED, BOARD_LED_ACTIVE_STATE); + + nrf_err = sd_ble_gatts_sys_attr_set(conn_handle, NULL, 0, 0); + if (nrf_err) { + LOG_ERR("Failed to set system attributes, nrf_error %#x", nrf_err); + } + break; + + case BLE_GAP_EVT_DISCONNECTED: + LOG_INF("Peer disconnected, reason %#x", + evt->evt.gap_evt.params.disconnected.reason); + if (conn_handle == evt->evt.gap_evt.conn_handle) { + conn_handle = BLE_CONN_HANDLE_INVALID; + } + nrf_gpio_pin_write(CON_STATUS_LED, !BOARD_LED_ACTIVE_STATE); + break; + + case BLE_GAP_EVT_AUTH_STATUS: + LOG_INF("Authentication status: %#x", + evt->evt.gap_evt.params.auth_status.auth_status); + break; + + case BLE_GAP_EVT_SEC_PARAMS_REQUEST: + /* Pairing is not required by this sample. */ + nrf_err = sd_ble_gap_sec_params_reply(evt->evt.gap_evt.conn_handle, + BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, + NULL, NULL); + if (nrf_err) { + LOG_ERR("Failed to reply with security params, nrf_error %#x", nrf_err); + } + break; + + case BLE_GATTS_EVT_SYS_ATTR_MISSING: + nrf_err = sd_ble_gatts_sys_attr_set(evt->evt.gatts_evt.conn_handle, NULL, 0, 0); + if (nrf_err) { + LOG_ERR("Failed to set missing system attributes, nrf_error %#x", + nrf_err); + } + break; + + default: + break; + } +} +NRF_SDH_BLE_OBSERVER(sdh_ble, on_ble_evt, NULL, USER_LOW); + +static void on_conn_params_evt(const struct ble_conn_params_evt *evt) +{ + uint32_t nrf_err; + + switch (evt->evt_type) { + case BLE_CONN_PARAMS_EVT_REJECTED: + nrf_err = sd_ble_gap_disconnect(evt->conn_handle, + BLE_HCI_CONN_INTERVAL_UNACCEPTABLE); + if (nrf_err) { + LOG_ERR("Failed to disconnect, nrf_error %#x", nrf_err); + } + break; + + case BLE_CONN_PARAMS_EVT_ATT_MTU_UPDATED: + LOG_INF("ATT MTU updated: %u", evt->att_mtu); + break; + + case BLE_CONN_PARAMS_EVT_DATA_LENGTH_UPDATED: + LOG_INF("Data length updated: tx %u rx %u", + evt->data_length.tx, + evt->data_length.rx); + break; + + case BLE_CONN_PARAMS_EVT_ERROR: + LOG_ERR("Connection parameter error, nrf_error %#x", evt->error.reason); + break; + + default: + break; + } +} + +static void ble_adv_evt_handler(struct ble_adv *adv, const struct ble_adv_evt *evt) +{ + ARG_UNUSED(adv); + + switch (evt->evt_type) { + case BLE_ADV_EVT_ERROR: + LOG_ERR("Advertising error, nrf_error %#x", evt->error.reason); + break; + default: + break; + } +} + +static void button_handler(uint8_t pin, enum bm_buttons_evt_type action) +{ + static bool time_measure_start; + int err; + + switch (pin) { + case BOARD_PIN_BTN_0: + if (action != BM_BUTTONS_PRESS) { + break; + } + + time_measure_start = !time_measure_start; + if (time_measure_start) { + err = MEMFAULT_METRIC_TIMER_START(button_elapsed_time_ms); + if (err) { + LOG_ERR("Failed to start button timer metric, err %d", err); + } else { + LOG_INF("button_elapsed_time_ms metric timer started"); + } + } else { + err = MEMFAULT_METRIC_TIMER_STOP(button_elapsed_time_ms); + if (err) { + LOG_ERR("Failed to stop button timer metric, err %d", err); + } else { + LOG_INF("button_elapsed_time_ms metric timer stopped"); + } + + memfault_metrics_heartbeat_debug_trigger(); + LOG_INF("Memfault heartbeat triggered"); + } + break; + + case BOARD_PIN_BTN_1: + MEMFAULT_TRACE_EVENT_WITH_LOG(button_state_changed, "Button state: %u", + (uint32_t)(action == BM_BUTTONS_PRESS)); + LOG_INF("button_state_changed event tracked, button state: %u", + (uint32_t)(action == BM_BUTTONS_PRESS)); + break; + + case BOARD_PIN_BTN_2: + if (action != BM_BUTTONS_PRESS) { + break; + } + + err = MEMFAULT_METRIC_ADD(button_press_count, 1); + if (err) { + LOG_ERR("Failed to increase button_press_count metric, err %d", err); + } else { + LOG_INF("button_press_count metric increased"); + memfault_metrics_heartbeat_debug_trigger(); + LOG_INF("Memfault heartbeat triggered"); + } + break; + + case BOARD_PIN_BTN_3: { + if (action != BM_BUTTONS_PRESS) { + break; + } + + volatile uint32_t div; + + LOG_INF("Division by zero will now be triggered"); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdiv-by-zero" + div = 1 / 0; +#pragma GCC diagnostic pop + ARG_UNUSED(div); + break; + } + + default: + break; + } +} + +int main(void) +{ + int err; + uint32_t nrf_err; + ble_gap_conn_sec_mode_t device_name_write_sec; + struct bm_buttons_config button_configs[] = { + { + .pin_number = BOARD_PIN_BTN_0, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + { + .pin_number = BOARD_PIN_BTN_1, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + { + .pin_number = BOARD_PIN_BTN_2, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + { + .pin_number = BOARD_PIN_BTN_3, + .active_state = BM_BUTTONS_ACTIVE_LOW, + .pull_config = BM_BUTTONS_PIN_PULLUP, + .handler = button_handler, + }, + }; + struct ble_bas_config bas_cfg = { + .can_notify = true, + .battery_level = battery_level, + .sec_mode = BLE_BAS_CONFIG_SEC_MODE_DEFAULT, + }; + struct ble_dis_config dis_cfg = { + .sec_mode = BLE_DIS_CONFIG_SEC_MODE_DEFAULT, + }; + struct ble_mds_config mds_cfg = { + .sec_mode = BLE_MDS_CONFIG_SEC_MODE_DEFAULT, + }; + + LOG_INF("BLE MDS sample started"); + + leds_init(); + + err = bm_timer_init(&battery_timer, BM_TIMER_MODE_REPEATED, + battery_level_timeout_handler); + if (err) { + LOG_ERR("Failed to initialize battery timer, err %d", err); + goto idle; + } + + err = bm_timer_init(&run_led_timer, BM_TIMER_MODE_REPEATED, run_led_timeout_handler); + if (err) { + LOG_ERR("Failed to initialize run LED timer, err %d", err); + goto idle; + } + + err = bm_buttons_init(button_configs, ARRAY_SIZE(button_configs), + BM_BUTTONS_DETECTION_DELAY_MIN_US); + if (err) { + LOG_ERR("Failed to initialize buttons, err %d", err); + goto idle; + } + + err = bm_buttons_enable(); + if (err) { + LOG_ERR("Failed to enable buttons, err %d", err); + goto idle; + } + + err = nrf_sdh_enable_request(); + if (err) { + LOG_ERR("Failed to enable SoftDevice, err %d", err); + goto idle; + } + + LOG_INF("SoftDevice enabled"); + + err = nrf_sdh_ble_enable(CONFIG_NRF_SDH_BLE_CONN_TAG); + if (err) { + LOG_ERR("Failed to enable BLE, err %d", err); + goto idle; + } + + LOG_INF("Bluetooth enabled"); + + BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&device_name_write_sec); + nrf_err = sd_ble_gap_device_name_set(&device_name_write_sec, CONFIG_SAMPLE_BLE_DEVICE_NAME, + strlen(CONFIG_SAMPLE_BLE_DEVICE_NAME)); + if (nrf_err) { + LOG_ERR("Failed to set device name, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_conn_params_evt_handler_set(on_conn_params_evt); + if (nrf_err) { + LOG_ERR("Failed to setup conn param event handler, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_bas_init(&ble_bas, &bas_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialize battery service, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_dis_init(&dis_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialize device information service, nrf_error %#x", nrf_err); + goto idle; + } + + nrf_err = ble_mds_init(&ble_mds, &mds_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialize Memfault Diagnostic Service, nrf_error %#x", nrf_err); + goto idle; + } + + ble_uuid_t adv_uuid_list[] = { + { + .uuid = BLE_UUID_MDS_SERVICE, + .type = ble_mds_service_uuid_type(&ble_mds), + }, + { + .uuid = BLE_UUID_BATTERY_SERVICE, + .type = BLE_UUID_TYPE_BLE, + }, + }; + struct ble_adv_config adv_cfg = { + .conn_cfg_tag = CONFIG_NRF_SDH_BLE_CONN_TAG, + .evt_handler = ble_adv_evt_handler, + .adv_data = { + .name_type = BLE_ADV_DATA_FULL_NAME, + .flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE, + }, + .sr_data.uuid_lists.complete = { + .len = ARRAY_SIZE(adv_uuid_list), + .uuid = &adv_uuid_list[0], + }, + }; + + nrf_err = ble_adv_init(&ble_adv, &adv_cfg); + if (nrf_err) { + LOG_ERR("Failed to initialize advertising, nrf_error %#x", nrf_err); + goto idle; + } + + LOG_INF("BLE MDS sample initialized"); + + err = bm_timer_start(&battery_timer, + BM_TIMER_MS_TO_TICKS( + CONFIG_SAMPLE_BLE_MDS_BATTERY_LEVEL_MEAS_INTERVAL), + NULL); + if (err) { + LOG_ERR("Failed to start battery timer, err %d", err); + goto idle; + } + + err = bm_timer_start(&run_led_timer, + BM_TIMER_MS_TO_TICKS(CONFIG_SAMPLE_BLE_MDS_RUN_LED_BLINK_INTERVAL), + NULL); + if (err) { + LOG_ERR("Failed to start run LED timer, err %d", err); + goto idle; + } + + nrf_err = ble_adv_start(&ble_adv, BLE_ADV_MODE_FAST); + if (nrf_err) { + LOG_ERR("Failed to start advertising, nrf_error %#x", nrf_err); + goto idle; + } + + LOG_INF("Advertising as %s", CONFIG_SAMPLE_BLE_DEVICE_NAME); + +idle: + while (true) { + memfault_metrics_timer_process(); + ble_mds_process(&ble_mds); + + log_flush(); + + k_cpu_idle(); + } + + return 0; +}