Characteristic with property "Write without response" sends response?

In the SDK (3.6) a GATTC_WRITE_CFM (ATT_WRITE_RSP?) is sent to the kernel after receiving an GATTC_WRITE_REQ_IND (ATT_WRITE_CMD handled as it).

  • Implemented in function void GATTC_WriteReqInd(...) within ble_gattc.h
  • Terminology according to Bluetooth Core Specification v5.2, see section 4.9 Characteristic Value Write

The specification does not expect an ATT_WRITE_RSP after an ATT_WRITE_CMD. Does the stack distinguish characteristics with property “Write without response” and characteristics with property “Write”? Is an ATT_WRITE_RSP only sent on air when the written characteristic had the property “Write”?

Hi @we.f ,
For your question, I have the answer.
GATTC_WRITE_REQ_IND, the confirm is required.
a. For write request that needs response, the stack will send a response to peer device
b. For write command that doesn’t need response, the stack is waiting for confirm, and then will free memory allocated for this message from the stack environment.

So that means response for write_without_response, the confirmation message is for local stack only and it does not send response to peer device.

Hi @larry.zhu,
thanks for your answer. Am I understanding you right, if I assume both of the questions are answered with yes?

Can we further conclude that the error code communicated in GATTC_WRITE_CFM (which is transmitted in the ATT_WRITE_RSP if applicable) is discarded in case the according characteristic has the property Write without response?

Hi @we.f ,
Does the stack distinguish characteristics with property “Write without response” and characteristics with property “Write”?
Answer: YES.
Is an ATT_WRITE_RSP only sent on air when the written characteristic had the property “Write”?
Answer: YES

Can we further conclude that the error code communicated in GATTC_WRITE_CFM (which is transmitted in the ATT_WRITE_RSP if applicable) is discarded in case the according characteristic has the property Write without response ?
Answer: since there is no response/acknowledge, transmitter side don’t know if there is error or not.
But on the receiver side still check the error and put into status.
That’s why I said both cases call GATTC_WriteReqInd().

Hi @larry.zhu,
thank you for being so precise. May I just ask to clarify what you mean by the following.

If the application sets the status already by setting an error code through a negative length, does the stack consider the error code in some way or does it discard the information?

Hi @we.f ,
I don’t fully understand your question.
Let me explain it again.
For both cases, they will call this GATTC_WriteReqInc(). and check. If there is no error, the received information will be copied/written and last step is to send confirmation to stack to free buffer.

/* ----------------------------------------------------------------------------
 * Function      : void GATTC_WriteReqInd(ke_msg_id_t const msg_id,
 *                                        struct gattc_write_req_ind
 *                                        const *param,
 *                                        ke_task_id_t const dest_id,
 *                                        ke_task_id_t const src_id)
 * ----------------------------------------------------------------------------
 * Description   : Handle received write request indication
 *                 from a GATT Controller
 * Inputs        : - msg_id     - Kernel message ID number
 *                 - param      - Message parameters in format of
 *                                struct gattc_write_req_ind
 *                 - dest_id    - Destination task ID number
 *                 - src_id     - Source task ID number
 * Outputs       : return value - Indicate if the message was consumed;
 *                                compare with KE_MSG_CONSUMED
 * Assumptions   : None
 * ------------------------------------------------------------------------- */
void GATTC_WriteReqInd(ke_msg_id_t const msg_id,
                          struct gattc_write_req_ind const *param,
                          ke_task_id_t const dest_id,
                          ke_task_id_t const src_id)
{
    /* Retrieve peer device index */
    signed int conidx = KE_IDX_GET(src_id);
    struct gattc_write_cfm *cfm =
        KE_MSG_ALLOC(GATTC_WRITE_CFM,
                     KE_BUILD_ID(TASK_GATTC, conidx),
                     TASK_APP, gattc_write_cfm);
    uint8_t status = GAP_ERR_NO_ERROR;
    uint16_t attnum;

    /* Verify the correctness of the write request. Set the attribute index if
     * the request is valid */
    attnum = (param->handle - gatt_env.start_hdl);
    if (param->offset)
    {
        status = ATT_ERR_INVALID_OFFSET;
    }
    else if (param->handle <= gatt_env.start_hdl)
    {
        status = ATT_ERR_INVALID_HANDLE;
    }
    else if ((attnum >= gatt_env.att_db_len) ||
             !(gatt_env.att_db[attnum].att.perm & (PERM(WRITE_REQ, ENABLE) | PERM(WRITE_COMMAND, ENABLE))))
    {
        status = ATT_ERR_WRITE_NOT_PERMITTED;
    }

    /* If there is no error, copy the requested attribute value, using the
     * callback function */
    if (status == GAP_ERR_NO_ERROR)
    {
        if (gatt_env.att_db[attnum].callback != NULL)
        {
            status = gatt_env.att_db[attnum].callback(conidx, attnum, param->handle, gatt_env.att_db[attnum].data, param->value,
                                                      MIN(param->length, gatt_env.att_db[attnum].length), GATTC_WRITE_REQ_IND);
        }
        else /* No callback function has been set for this attribute, just do a memcpy */
        {
            memcpy(gatt_env.att_db[attnum].data, param->value, MIN(param->length, gatt_env.att_db[attnum].length));
        }
    }
    cfm->handle = param->handle;
    cfm->status = status;

    /* Send the message */
    ke_msg_send(cfm);
}