This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

For a custom BLE service, write operation doesn't seem to work

Hello All,

I created a BLE service (relay service) to be able to turn ON or OFF a relay switch. The relay switch hardware is attached to Adafruit Bluefruit Feather (nRF52832 peripheral) at one of the GPIO pins. All seems fine there because I can discover the BLE service on the nRF connect android app and send write to turn the switch ON and OFF successfully and receive notification without any issues. Which means peripheral is not having any issues.

In parallel, I wrote a relay_client (based on NUS_C client) to be able to communicate to the relay peripheral using my nRF52832 based evb board. The issue is when I try to do a write from my relay client, this doesn't seem to work for some reason. sd_ble_gattc_write returns NRF_SUCCESS but the handler on the peripheral is not invoked because I do not see it printing the written value (The handler is invoked and prints the value when I use the nRF Connect Android App). My central device is based on nRF52 15.2 SDK and soft device 132. I am attaching the relay_client.c and corresponding header for your reference. The interesting point to notice is, I am able to receive the notifications from the peripheral device on my central device. Its only the write to the peripheral device from central that doesn't seem to work. Kindly help resolve the issue.

Following are excerpts from main file:

static void ble_relay_notification_received(uint8_t *p_data, uint16_t data_len, uint16_t connhandle) {

NRF_LOG_INFO("Relay Notification (connhandle): %d", connhandle);
NRF_LOG_INFO("Relay Notification (data_len): %d", data_len);
NRF_LOG_INFO("Relay Notification current state is : %d(%c)", *p_data, *p_data);
g_ble_sensor_dev[connhandle].relay_dev.relay_c_state = *p_data;
}

/**@brief Handles events coming from the RELAY Button central module.
*/
static void ble_relay_c_evt_handler(ble_relay_c_t *p_relay_c, ble_relay_client_evt_t *p_relay_c_evt) {
switch (p_relay_c_evt->evt_type) {
case BLE_RELAY_C_EVT_DISCOVERY_COMPLETE: {
ret_code_t err_code;

err_code = ble_relay_client_handles_assign(p_relay_c,
p_relay_c_evt->conn_handle,
&p_relay_c_evt->peer_db);
APP_ERROR_CHECK(err_code);
NRF_LOG_INFO("RELAY service discovered on conn_handle 0x%x.", p_relay_c_evt->conn_handle);

err_code = ble_relay_c_state_notif_enable(p_relay_c);
APP_ERROR_CHECK(err_code);
ble_relay_c_action_send(p_relay_c, RELAY_STATE_UNOWN);

}

} break; // BLE_RELAY_SERVICE_CLIENT_EVT_DISCOVERY_COMPLETE
case BLE_RELAY_C_EVT_STATE_EVT: {
ble_relay_notification_received(p_relay_c_evt->p_data, p_relay_c_evt->data_len, p_relay_c_evt->conn_handle);

 relay_send_write(

#include "sdk_common.h"

#include "ble_db_discovery.h"
#include "ble_gattc.h"
#include "ble_srv_common.h"
#include "ble_types.h"
#include "relay_client.h"
#define NRF_LOG_MODULE_NAME relay_c
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();


/**@brief Function for creating a message for writing to the RELAY ACTION. */
void relay_send_write(ble_relay_c_t *p_ble_relay_c, uint16_t handle, uint8_t *p_string, uint16_t length) {
  uint32_t err_code;

  ble_gattc_write_params_t const write_params =
      {
          .write_op = BLE_GATT_OP_PREP_WRITE_REQ,
          .flags = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,
          .handle = handle,
          .offset = 0,
          .len = length,
          .p_value = p_string};

  err_code = sd_ble_gattc_write(p_ble_relay_c->conn_handle, &write_params);
  if (err_code == NRF_SUCCESS) {
    NRF_LOG_INFO("relay_send_write: WRITE_REQ(handle = %d, connhandle = %d, error code = %d)", handle, p_ble_relay_c->conn_handle, err_code);
  } else {
    NRF_LOG_INFO("relay_send_write: WRITE_REQ UNSUCCESSFUL(%d)",  err_code);
  }
}

static uint32_t cccd_configure(uint16_t conn_handle, uint16_t cccd_handle, bool enable) {
  uint8_t buf[BLE_CCCD_VALUE_LEN];

  buf[0] = enable ? BLE_GATT_HVX_NOTIFICATION : 0;
  buf[1] = 0;

  ble_gattc_write_params_t const write_params =
      {
          .write_op = BLE_GATT_OP_WRITE_REQ,
          .flags = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,
          .handle = cccd_handle,
          .offset = 0,
          .len = sizeof(buf),
          .p_value = buf};
  NRF_LOG_INFO("cccd_configure: cccd handle = %d", cccd_handle);
  return sd_ble_gattc_write(conn_handle, &write_params);
}

uint32_t ble_relay_c_state_notif_enable(ble_relay_c_t *p_ble_relay_c) {
  VERIFY_PARAM_NOT_NULL(p_ble_relay_c);

  if (p_ble_relay_c->conn_handle == BLE_CONN_HANDLE_INVALID) {
    return NRF_ERROR_INVALID_STATE;
  }
  NRF_LOG_INFO("cccd configure with: connection handle (0x%x), cccd handle(0x%x)", p_ble_relay_c->conn_handle, p_ble_relay_c->peer_relay_service_db.relay_cccd_handle);
  return cccd_configure(p_ble_relay_c->conn_handle, p_ble_relay_c->peer_relay_service_db.relay_cccd_handle, true);
}


/**@brief Function for handling Disconnected event received from the SoftDevice.
 *
 * @details This function check if the disconnect event is happening on the link
 *          associated with the current instance of the module, if so it will set its
 *          conn_handle to invalid.
 *
 * @param[in] p_ble_relay_c Pointer to the Relay Service Client structure.
 * @param[in] p_ble_evt                Pointer to the BLE event received.
 */
static void on_disconnected(ble_relay_c_t *p_ble_relay_c, ble_evt_t const *p_ble_evt) {
  if (p_ble_relay_c->conn_handle == p_ble_evt->evt.gap_evt.conn_handle) {
    p_ble_relay_c->conn_handle = BLE_CONN_HANDLE_INVALID;
    p_ble_relay_c->peer_relay_service_db.relay_action_handle = BLE_GATT_HANDLE_INVALID;
    p_ble_relay_c->peer_relay_service_db.relay_state_handle = BLE_GATT_HANDLE_INVALID;
    p_ble_relay_c->peer_relay_service_db.relay_cccd_handle = BLE_GATT_HANDLE_INVALID;
  }
}

void ble_relay_client_on_db_disc_evt(ble_relay_c_t *p_ble_relay_c, ble_db_discovery_evt_t const *p_evt) {
  // Check if the Relay Service was discovered.
  if (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE &&
      p_evt->params.discovered_db.srv_uuid.uuid == BLE_UUID_RELAY_SERVICE_UUID &&
      p_evt->params.discovered_db.srv_uuid.type == p_ble_relay_c->uuid_type) {
    ble_relay_client_evt_t evt;

    evt.evt_type = BLE_RELAY_C_EVT_DISCOVERY_COMPLETE;
    evt.conn_handle = p_evt->conn_handle;

    for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++) {
      const ble_gatt_db_char_t *p_char = &(p_evt->params.discovered_db.charateristics[i]);
      switch (p_char->characteristic.uuid.uuid) {
      case BLE_UUID_RELAY_ACTION_UUID:
        evt.peer_db.relay_action_handle = p_char->characteristic.handle_value;
        NRF_LOG_INFO("ble_relay_client_on_db_disc_evt: action handle = %d", evt.peer_db.relay_action_handle);
        break;
      case BLE_UUID_RELAY_STATE_UUID:
        evt.peer_db.relay_state_handle = p_char->characteristic.handle_value;
        evt.peer_db.relay_cccd_handle = p_char->cccd_handle;
        NRF_LOG_INFO("ble_relay_client_on_db_disc_evt: state handle = %d", evt.peer_db.relay_state_handle);
        NRF_LOG_INFO("ble_relay_client_on_db_disc_evt: cccd handle = %d", evt.peer_db.relay_cccd_handle);
        break;

      default:
        break;
      }
    }

    //If the instance has been assigned prior to db_discovery, assign the db_handles
    if (p_ble_relay_c->conn_handle != BLE_CONN_HANDLE_INVALID) {
      if (p_ble_relay_c->peer_relay_service_db.relay_action_handle == BLE_GATT_HANDLE_INVALID) {
        p_ble_relay_c->peer_relay_service_db = evt.peer_db;
      }
    }

    p_ble_relay_c->evt_handler(p_ble_relay_c, &evt);
  }
}

uint32_t ble_relay_client_init(ble_relay_c_t *p_ble_relay_c, ble_relay_c_init_t *p_ble_relay_c_init) {
  uint32_t err_code;
  ble_uuid_t relay_service_uuid;
  ble_uuid128_t relay_service_base_uuid = BLE_UUID_RELAY_SERVICE_BASE_UUID;

  VERIFY_PARAM_NOT_NULL(p_ble_relay_c);
  VERIFY_PARAM_NOT_NULL(p_ble_relay_c_init);
  VERIFY_PARAM_NOT_NULL(p_ble_relay_c_init->evt_handler);

  p_ble_relay_c->peer_relay_service_db.relay_action_handle = BLE_GATT_HANDLE_INVALID;
  p_ble_relay_c->peer_relay_service_db.relay_state_handle = BLE_GATT_HANDLE_INVALID;
  p_ble_relay_c->conn_handle = BLE_CONN_HANDLE_INVALID;
  p_ble_relay_c->evt_handler = p_ble_relay_c_init->evt_handler;

  err_code = sd_ble_uuid_vs_add(&relay_service_base_uuid, &p_ble_relay_c->uuid_type);
  if (err_code != NRF_SUCCESS) {
    return err_code;
  }
  VERIFY_SUCCESS(err_code);

  relay_service_uuid.type = p_ble_relay_c->uuid_type;
  relay_service_uuid.uuid = BLE_UUID_RELAY_SERVICE_UUID;

  return ble_db_discovery_evt_register(&relay_service_uuid);
}

static void on_hvx(ble_relay_c_t *p_ble_relay_c, ble_evt_t const *p_ble_evt) //(ble_nus_c_t * p_ble_relay_c, ble_evt_t const * p_ble_evt)
{
  // HVX can only occur from client sending.
  if ((p_ble_relay_c->peer_relay_service_db.relay_state_handle != BLE_GATT_HANDLE_INVALID) && (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_relay_c->peer_relay_service_db.relay_state_handle) && (p_ble_relay_c->evt_handler != NULL)) {
    ble_relay_client_evt_t ble_relay_c_evt; //ble_relay_c_evt;

    ble_relay_c_evt.evt_type = BLE_RELAY_C_EVT_STATE_EVT;
    ble_relay_c_evt.p_data = (uint8_t *)p_ble_evt->evt.gattc_evt.params.hvx.data;
    ble_relay_c_evt.data_len = p_ble_evt->evt.gattc_evt.params.hvx.len;
    ble_relay_c_evt.conn_handle = p_ble_evt->evt.gattc_evt.conn_handle;

    p_ble_relay_c->evt_handler(p_ble_relay_c, &ble_relay_c_evt);
    NRF_LOG_DEBUG("Client sending data.");
  }
}

void ble_relay_c_on_ble_evt(ble_evt_t const *p_ble_evt, void *p_context) {
  if ((p_context == NULL) || (p_ble_evt == NULL)) {
    return;
  }
  ble_relay_c_t *p_ble_relay_c = (ble_relay_c_t *)p_context;

  switch (p_ble_evt->header.evt_id) {
  case BLE_GATTC_EVT_WRITE_RSP:
    break;

  case BLE_GAP_EVT_DISCONNECTED:
    on_disconnected(p_ble_relay_c, p_ble_evt);
    break;
  case BLE_GATTC_EVT_HVX:
    on_hvx(p_ble_relay_c, p_ble_evt);
    break;

  default:
    break;
  }
}

uint32_t ble_relay_client_handles_assign(ble_relay_c_t *p_ble_relay_c,
    uint16_t conn_handle,
    const relay_service_db_t *p_peer_handles) {
  VERIFY_PARAM_NOT_NULL(p_ble_relay_c);

  p_ble_relay_c->conn_handle = conn_handle;
  if (p_peer_handles != NULL) {
    p_ble_relay_c->peer_relay_service_db.relay_action_handle = p_peer_handles->relay_action_handle;
    p_ble_relay_c->peer_relay_service_db.relay_state_handle = p_peer_handles->relay_state_handle;
    p_ble_relay_c->peer_relay_service_db.relay_cccd_handle = p_peer_handles->relay_cccd_handle;
  }
  return NRF_SUCCESS;
}
relay_client.h, p_relay_c->peer_relay_service_db.relay_action_handle, "test", strlen("test"));

} break; // BLE_RELAY_SERVICE_CLIENT_EVT_DISCOVERY_COMPLETE

default:
break;
}
}

  • Please note that the .write_op of the write_params is BLE_GATT_OP_WRITE_REQ and not BLE_GATT_OP_PREP_WRITE_REQ as mentioned in the relay_client.c source. I modified it just to test and forgot to revert before posting this.

  • That's a lot of code to check through.

    Are you relying on a notification trigger in your peripheral to detect when the characteristic has been updated or are you polling it?

    This is the code from my current nRF52840 based project for updating and triggering a notification, I've only included the important bits:

        ble_gatts_hvx_params_t params;
    
        memset(&params, 0, sizeof(params));
        params.type = BLE_GATT_HVX_NOTIFICATION;
        params.p_len = &len;
    
        /* update and notify for the characteristic */
        params.handle = m_LiveDriveService.lds_char_result_handles.value_handle;
        params.p_data = m_scratch_buffer;
    
        err_code = sd_ble_gatts_hvx(m_LiveDriveService.conn_handle, &params);
        if (NRF_SUCCESS != err_code)
        { /* notifications may not be enabled, attempt to update data */
            ble_gatts_value_t valueData;
            valueData.len = len;
            valueData.offset = 0;
            valueData.p_value = m_scratch_buffer;
            NRF_LOG_HEXDUMP_DEBUG(m_scratch_buffer, len);
            err_code = sd_ble_gatts_value_set(m_LiveDriveService.conn_handle,
                                              m_LiveDriveService.lds_char_result_handles.value_handle,
                                              &valueData);
            if (NRF_SUCCESS != err_code)
            {
                return err_code;
            }
        }

    Oh I forgot to mention both devices central and peripheral must enable notification on any characteristic that they wish to receive a notification even for.

  • Yes. Notifications are enabled on both the central and peripheral. But notifications itself aren't the issue. It's the 'write' that is not getting to the peripheral.

  • Never-mind. This worked after changing the permissions of the relay service characteristics in the peripheral. 

  • Okay. I was just about to have a closer look.

    I'm glad that you've sorted it.

Related