This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

GATT Valid Range Descriptor (Not working as expected)

Hello everyone,

I'm using simplelink_cc13x2_26x2_sdk_5_20_00_52 with an Launchxl eval board cc26x2 and the Project is based on CCS Projectzero. Creating and Using a custom gatt service.

GATT Valid Range Descriptor (Lower and Upper Range) is not working as expected. Am I doing something wrong? code snippet is a sample with float32 range 0.0 - 1.0.

range 0-25 / 0-4294967295 with uint8 / uint32 was tested and it was also not working. 

reading, writing the characteristic value and reading the format type work fine. The valid range descriptor is also found after making the service and characteristic discovery, but reading the valid range descriptor does not work. 

// Service declaration
static CONST gattAttrType_t Service0Decl = { ATT_UUID_SIZE, Service0UUID };


// Characteristic "Characteristic" Properties (for declaration)
static uint8_t Service_CharacteristicProps = GATT_PROP_READ | GATT_PROP_WRITE;
// Characteristic "Characteristic" Value variable
static uint8_t Service_CharacteristicVal[SERVICE_CHARACTERISTIC_LEN] = {0x3f, 0x80, 0x00, 0x00};
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Descriptors/org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
// Characteristic Presentation Format: 0x14: float32; 0x00: no exponent; 0x27AD: unit = percentage; 0x01: Bluetooth SIG namespace; 0x0000: No description
static uint8_t Service_CharacteristicFormatType[8] = {0x14, 0x00, 0xAD, 0x27, 0x01, 0x00, 0x00, 0x00};
// Valid range lower = 0.0; upper = 1.0;
static uint8_t Service_CharacteristicRange[8] = {0x00, 0x00, 0x00, 0x00, 0x3f, 0x80, 0x00, 0x00};


static gattAttribute_t Service0AttrTbl[] =
{
    // Service Declaration
    {
       { ATT_BT_UUID_SIZE, primaryServiceUUID },
       GATT_PERMIT_READ,
       0,
       (uint8_t *)&Service0Decl
    },


    // Characteristic Declaration
    {
       { ATT_BT_UUID_SIZE, characterUUID },
       GATT_PERMIT_READ,
       0,
       &Service_CharacteristicProps
    },
    // Characteristic Value
    {
       { ATT_UUID_SIZE, Service_CharacteristicUUID },
       GATT_PERMIT_READ | GATT_PERMIT_WRITE,
       0,
       Service_CharacteristicVal
    },
    // Characteristic Presentation Format
    {
       { ATT_BT_UUID_SIZE, charFormatUUID },
       GATT_PERMIT_READ,
       0,
       (uint8_t *)&Service_CharacteristicFormatType
    },
    // Characteristic Range
    {
       { ATT_BT_UUID_SIZE, validRangeUUID },
       GATT_PERMIT_READ | GATT_PERMIT_WRITE,
       0,
       (uint8_t *)&Service_CharacteristicRange
    },
    // Characteristic User Description
    {
       { ATT_BT_UUID_SIZE, charUserDescUUID},
       GATT_PERMIT_READ,
       0,
       "[0...1] : amplitude"
    },
}

Best regards,

Ben A.

  • Hi Ben,

    I will ask an engineer to comment.

    Could you please provide snippets showing all the changes made into your code?

    Thanks and regards,

  • Additionally, could you tell us what tool you used to read the value here? For instance, BTool or a smartphone app? I'd recommend trying with BTool if you haven't already.

    You can also reference SimpleLink Academy Custom Profile training here.

    https://dev.ti.com/tirex/content/simplelink_academy_cc13x2_26x2sdk_5_20_02_00/modules/ble5stack/ble_01_custom_profile/ble_01_custom_profile.html

  • Hi Nathan, 

    thank you for your response.
    I used different tools (BTool with host_test cc26x2, Android Device with simpleLink App or Nordic nRF Connect App) with the same result.

    Observation:
    -Valid range descriptor read request is not handled BLE stack internally -> "ReadAttrCB" is called and will return with "ATTR_NOT_FOUND" 
    -Characteristic user Description and Characteristic Presentation Format are handled BLE stack internally



    btool Logs:

    test_btool.log

    The basic gatt handling was generated by using the generator from SimpleLink Academy Custom Profile training and some modification were made.

    /*********************************************************************
     * INCLUDES
     */
    
    /*********************************************************************
     * MACROS
     */
    
    /*********************************************************************
     * CONSTANTS
     */
    
    /*********************************************************************
     * TYPEDEFS
     */
    
    /*********************************************************************
    * GLOBAL VARIABLES
    */
    
    // EmsChannelService0 Service UUID
    CONST uint8_t EmsChannelService0UUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_SERV_UUID
    };
    
    // ChannelGain UUID
    CONST uint8_t EmsChannelService0_ChannelGainUUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_CHANNELGAIN_UUID
    };
    // BasePulsW UUID
    CONST uint8_t EmsChannelService0_BasePulsWUUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_BASEPULSW_UUID
    };
    // BasePulsP UUID
    CONST uint8_t EmsChannelService0_BasePulsPUUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_BASEPULSP_UUID
    };
    // BasePulsT UUID
    CONST uint8_t EmsChannelService0_BasePulsTUUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_BASEPULST_UUID
    };
    // EnvelopeWaveform UUID
    CONST uint8_t EmsChannelService0_EnvelopeWaveformUUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_ENVELOPEWAVEFORM_UUID
    };
    // EnvelopeFrequency UUID
    CONST uint8_t EmsChannelService0_EnvelopeFrequencyUUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_ENVELOPEFREQUENCY_UUID
    };
    // ChannelState UUID
    CONST uint8_t EmsChannelService0_ChannelStateUUID[ATT_UUID_SIZE] =
    {
      EMSCHANNELSERVICE0_CHANNELSTATE_UUID
    };
    
    /*********************************************************************
     * LOCAL VARIABLES
     */
    
    static EmsChannelServiceCBs_t *pAppCBs = NULL;
    static sFunctionTableEntry_t sEmsChannelService0ParamFunctionTable[EMSCHANNELSERVICE0_NUM_OF_CHARACTERISTICS];
    /*********************************************************************
    * Profile Attributes - variables
    */
    
    // Service declaration
    static CONST gattAttrType_t EmsChannelService0Decl = { ATT_UUID_SIZE, EmsChannelService0UUID };
    
    
    // Characteristic "ChannelGain" Properties (for declaration)
    static uint8_t EmsChannelService0_ChannelGainProps = GATT_PROP_READ | GATT_PROP_WRITE;
    // Characteristic "ChannelGain" Value variable
    static uint8_t EmsChannelService0_ChannelGainVal[EMSCHANNELSERVICE0_CHANNELGAIN_LEN] = {0x3f, 0x80, 0x00, 0x00};
    // https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Descriptors/org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
    // Characteristic Presentation Format: 0x14: float32; 0x00: no exponent; 0x27AD: unit = percentage; 0x01: Bluetooth SIG namespace; 0x0000: No description
    static uint8_t EmsChannelService0_ChannelGainFormatType[8] = {0x14, 0x00, 0xAD, 0x27, 0x01, 0x00, 0x00, 0x00};
    // Valid range lower = 0.0; upper = 1.0;
    static uint8_t EmsChannelService0_ChannelGainRange[8] = {0x00, 0x00, 0x00, 0x00, 0x3f, 0x80, 0x00, 0x00};
    
    
    // Characteristic "BasePulsW" Properties (for declaration)
    static uint8_t EmsChannelService0_BasePulsWProps = GATT_PROP_READ | GATT_PROP_WRITE;
    // Characteristic "BasePulsW" Value variable
    static uint8_t EmsChannelService0_BasePulsWVal[EMSCHANNELSERVICE0_BASEPULSW_LEN] = {0};
    // Characteristic Presentation Format: 0x08: uint32; 0x00: no exponent; 0x2700: unit = unitless; 0x01: Bluetooth SIG namespace; 0x0000: No description
    static uint8_t EmsChannelService0_BasePulsWFormatType[8] = {0x08, 0x00, 0x00, 0x27, 0x01, 0x00, 0x00, 0x00};
    // Valid range lower = 0; upper = 4294967295;
    static uint8_t EmsChannelService0_BasePulsWValidRange[8] = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
    
    
    // Characteristic "BasePulsP" Properties (for declaration)
    static uint8_t EmsChannelService0_BasePulsPProps = GATT_PROP_READ | GATT_PROP_WRITE;
    // Characteristic "BasePulsP" Value variable
    static uint8_t EmsChannelService0_BasePulsPVal[EMSCHANNELSERVICE0_BASEPULSP_LEN] = {0};
    // Characteristic Presentation Format: 0x08: uint32; 0x00: no exponent; 0x2700: unit = unitless; 0x01: Bluetooth SIG namespace; 0x0000: No description
    static uint8_t EmsChannelService0_BasePulsPFormatType[8] = {0x08, 0x00, 0x00, 0x27, 0x01, 0x00, 0x00, 0x00};
    // Valid range lower = 0; upper = 4294967295;
    static uint8_t EmsChannelService0_BasePulsPValidRange[8] = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
    
    
    // Characteristic "BasePulsT" Properties (for declaration)
    static uint8_t EmsChannelService0_BasePulsTProps = GATT_PROP_READ | GATT_PROP_WRITE;
    // Characteristic "BasePulsT" Value variable
    static uint8_t EmsChannelService0_BasePulsTVal[EMSCHANNELSERVICE0_BASEPULST_LEN] = {0};
    // Characteristic Presentation Format: 0x08: uint32; 0x00: no exponent; 0x2700: unit = unitless; 0x01: Bluetooth SIG namespace; 0x0000: No description
    static uint8_t EmsChannelService0_BasePulsTFormatType[8] = {0x08, 0x00, 0x00, 0x27, 0x01, 0x00, 0x00, 0x00};
    // Valid range lower = 0; upper = 4294967295;
    static uint8_t EmsChannelService0_BasePulsTValidRange[8] = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
    
    
    // Characteristic "EnvelopeWaveform" Properties (for declaration)
    static uint8_t EmsChannelService0_EnvelopeWaveformProps = GATT_PROP_READ | GATT_PROP_WRITE;
    // Characteristic "EnvelopeWaveform" Value variable
    static uint8_t EmsChannelService0_EnvelopeWaveformVal[EMSCHANNELSERVICE0_ENVELOPEWAVEFORM_LEN] = {0};
    // Characteristic Presentation Format: 0x04: uint8; 0x00: no exponent; 0x2700: unit = unitless; 0x01: Bluetooth SIG namespace; 0x0000: No description
    static uint8_t EmsChannelService0_EnvelopeWaveformFormatType[8] = {0x04, 0x00, 0x00, 0x27, 0x01, 0x00, 0x00, 0x00};
    // Valid range lower = 0; upper = 255;
    static uint8_t EmsChannelService0_EnvelopeWaveformValidRange[2] = {0x00, 0xFF};
    
    
    // Characteristic "EnvelopeFrequency" Properties (for declaration)
    static uint8_t EmsChannelService0_EnvelopeFrequencyProps = GATT_PROP_READ | GATT_PROP_WRITE;
    // Characteristic "EnvelopeFrequency" Value variable
    static uint8_t EmsChannelService0_EnvelopeFrequencyVal[EMSCHANNELSERVICE0_ENVELOPEFREQUENCY_LEN] = {0};
    // Characteristic Presentation Format: 0x04: uint8; 0x00: no exponent; 0x2722: unit = Hz; 0x01: Bluetooth SIG namespace; 0x0000: No description
    static uint8_t EmsChannelService0_EnvelopeFrequencyFormatType[8] = {0x04, 0x00, 0x22, 0x27, 0x01, 0x00, 0x00, 0x00};
    // Valid range lower = 5; upper = 120;
    static uint8_t EmsChannelService0_EnvelopeFrequencyValidRange[2] = {0x05, 0x78};
    
    
    // Characteristic "ChannelState" Properties (for declaration)
    static uint8_t EmsChannelService0_ChannelStateProps = GATT_PROP_READ | GATT_PROP_NOTIFY;
    // Characteristic "ChannelState" Value variable
    static uint8_t EmsChannelService0_ChannelStateVal[EMSCHANNELSERVICE0_CHANNELSTATE_LEN] = {0};
    // Characteristic Presentation Format: 0x08: uint32; 0x00: no exponent; 0x2700: unit = unitless; 0x01: Bluetooth SIG namespace; 0x0000: No description
    static uint8_t EmsChannelService0_ChannelStateFormatType[8] = {0x08, 0x00, 0x00, 0x27, 0x01, 0x00, 0x00, 0x00};
    // Valid range lower = 0; upper = 4294967295;
    static uint8_t EmsChannelService0_ChannelStateValidRange[8] = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
    // Characteristic "ChannelState" CCCD
    static gattCharCfg_t *EmsChannelService0_ChannelStateConfig;
    
    
    /*********************************************************************
    * Profile Attributes - Table
    */
    
    static gattAttribute_t EmsChannelService0AttrTbl[] =
    {
        // EmsChannelService Service Declaration
        {
           { ATT_BT_UUID_SIZE, primaryServiceUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0Decl
        },
    
    
        // ChannelGain Characteristic Declaration
        {
           { ATT_BT_UUID_SIZE, characterUUID },
           GATT_PERMIT_READ,
           0,
           &EmsChannelService0_ChannelGainProps
        },
        // ChannelGain Characteristic Value
        {
           { ATT_UUID_SIZE, EmsChannelService0_ChannelGainUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           EmsChannelService0_ChannelGainVal
        },
        // ChannelGain Characteristic User Description
        {
           { ATT_BT_UUID_SIZE, charUserDescUUID},
           GATT_PERMIT_READ,
           0,
           "[0...1] : Channel amplitude"
        },
        // ChannelGain Characteristic Presentation Format
        {
           { ATT_BT_UUID_SIZE, charFormatUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0_ChannelGainFormatType
        },
        // ChannelGain Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_ChannelGainRange
        },
    
    
    
        // BasePulsW Characteristic Declaration
        {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &EmsChannelService0_BasePulsWProps
        },
        // BasePulsW Characteristic Value
        {
        { ATT_UUID_SIZE, EmsChannelService0_BasePulsWUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        EmsChannelService0_BasePulsWVal
        },
        // BasePulsW Characteristic Presentation Format
        {
           { ATT_BT_UUID_SIZE, charFormatUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0_BasePulsWFormatType
        },
        // BasePulsW Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_BasePulsWValidRange
        },
        // BasePulsW Characteristic User Description
        {
           { ATT_BT_UUID_SIZE, charUserDescUUID},
           GATT_PERMIT_READ,
           0,
           "Basic waveform: Pulse width"
        },
    
    
        // BasePulsP Characteristic Declaration
        {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &EmsChannelService0_BasePulsPProps
        },
        // BasePulsP Characteristic Value
        {
        { ATT_UUID_SIZE, EmsChannelService0_BasePulsPUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        EmsChannelService0_BasePulsPVal
        },
        // BasePulsP Characteristic Presentation Format
        {
           { ATT_BT_UUID_SIZE, charFormatUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0_BasePulsPFormatType
        },
        // BasePulsP Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_BasePulsPValidRange
        },
        // BasePulsP Characteristic User Description
        {
           { ATT_BT_UUID_SIZE, charUserDescUUID},
           GATT_PERMIT_READ,
           0,
           "Basic waveform: Pause between high and low pulse"
        },
    
    
        // BasePulsT Characteristic Declaration
        {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &EmsChannelService0_BasePulsTProps
        },
        // BasePulsT Characteristic Value
        {
        { ATT_UUID_SIZE, EmsChannelService0_BasePulsTUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        EmsChannelService0_BasePulsTVal
        },
        // BasePulsT Characteristic Presentation Format
        {
           { ATT_BT_UUID_SIZE, charFormatUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0_BasePulsTFormatType
        },
        // BasePulsT Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_BasePulsTValidRange
        },
        // BasePulsT Characteristic User Description
        {
           { ATT_BT_UUID_SIZE, charUserDescUUID},
           GATT_PERMIT_READ,
           0,
           "Basic waveform: Period pulse to pulse"
        },
    
    
        // EnvelopeWaveform Characteristic Declaration
        {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &EmsChannelService0_EnvelopeWaveformProps
        },
        // EnvelopeWaveform Characteristic Value
        {
        { ATT_UUID_SIZE, EmsChannelService0_EnvelopeWaveformUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        EmsChannelService0_EnvelopeWaveformVal
        },
        // EnvelopeWaveform Characteristic Presentation Format
        {
           { ATT_BT_UUID_SIZE, charFormatUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0_EnvelopeWaveformFormatType
        },
        // EnvelopeWaveform Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_EnvelopeWaveformValidRange
        },
        // EnvelopeWaveform Characteristic User Description
        {
           { ATT_BT_UUID_SIZE, charUserDescUUID},
           GATT_PERMIT_READ,
           0,
           "Envelope waveform for specific channel"
        },
    
    
        // EnvelopeFrequency Characteristic Declaration
        {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &EmsChannelService0_EnvelopeFrequencyProps
        },
        // EnvelopeFrequency Characteristic Value
        {
        { ATT_UUID_SIZE, EmsChannelService0_EnvelopeFrequencyUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        EmsChannelService0_EnvelopeFrequencyVal
        },
        // EnvelopeFrequency Characteristic Presentation Format
        {
           { ATT_BT_UUID_SIZE, charFormatUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0_EnvelopeFrequencyFormatType
        },
        // EnvelopeFrequency Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_EnvelopeFrequencyValidRange
        },
        // EnvelopeFrequency Characteristic User Description
        {
           { ATT_BT_UUID_SIZE, charUserDescUUID},
           GATT_PERMIT_READ,
           0,
           "Frequency of envelope waveform"
        },
    
    
        // ChannelState Characteristic Declaration
        {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &EmsChannelService0_ChannelStateProps
        },
        // ChannelState Characteristic Value
        {
        { ATT_UUID_SIZE, EmsChannelService0_ChannelStateUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        EmsChannelService0_ChannelStateVal
        },
        // ChannelState Characteristic Presentation Format
        {
           { ATT_BT_UUID_SIZE, charFormatUUID },
           GATT_PERMIT_READ,
           0,
           (uint8_t *)&EmsChannelService0_ChannelStateFormatType
        },
        // ChannelState Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_ChannelStateValidRange
        },
        // ChannelState CCCD
        {
        { ATT_BT_UUID_SIZE, clientCharCfgUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        (uint8 *)&EmsChannelService0_ChannelStateConfig
        },
        // ChannelGain Characteristic User Description
        {
           { ATT_BT_UUID_SIZE, charUserDescUUID},
           GATT_PERMIT_READ,
           0,
           "On / Off / Error"
        },
    };
    
    /*********************************************************************
     * LOCAL FUNCTIONS
     */
    static bStatus_t EmsChannelService0_ReadAttrCB( uint16_t connHandle, gattAttribute_t *pAttr,
                                               uint8_t *pValue, uint16_t *pLen, uint16_t offset,
                                               uint16_t maxLen, uint8_t method );
    static bStatus_t EmsChannelService0_WriteAttrCB( uint16_t connHandle, gattAttribute_t *pAttr,
                                                uint8_t *pValue, uint16_t len, uint16_t offset,
                                                uint8_t method );
    static const sFunctionTableEntry_t *EmsChannelService0_psGetTableEntryByUuid(
            const uint8_t *pui8Uuid, const size_t xUuidDLen);
    static const sFunctionTableEntry_t *EmsChannelService0_psGetTableEntryByParamId(
            const eCharacteristicsID_t eParamId);
    /*********************************************************************
     * PROFILE CALLBACKS
     */
    // Simple Profile Service Callbacks
    CONST gattServiceCBs_t EmsChannelService0CBs =
    {
         EmsChannelService0_ReadAttrCB,  // Read callback function pointer
         EmsChannelService0_WriteAttrCB, // Write callback function pointer
         NULL                       // Authorization callback function pointer
    };
    
    //! \date       2021-10-08 (BA) - Creation
    void EmsChannelService0_vInitServiceTable(void)
    {
        sFunctionTableEntry_t sParamFunctionTable[] =
        {   // UUID,                                    PARAM_VAL,                                  PARAM_CONFIG                            PARAM_LEN,                                  PARAM_ID
            {NULL,                                      NULL,                                       NULL,                                   0,                                          eCharacteristicsID_Invalid                              },
            {EmsChannelService0_ChannelGainUUID,        EmsChannelService0_ChannelGainVal,          NULL,                                   EMSCHANNELSERVICE0_CHANNELGAIN_LEN,         eCharacteristicsID_EmsChannelService0_ChannelGain       },
            {EmsChannelService0_BasePulsWUUID,          EmsChannelService0_BasePulsWVal,            NULL,                                   EMSCHANNELSERVICE0_BASEPULSW_LEN,           eCharacteristicsID_EmsChannelService0_BasePulseW        },
            {EmsChannelService0_BasePulsPUUID,          EmsChannelService0_BasePulsPVal,            NULL,                                   EMSCHANNELSERVICE0_BASEPULSP_LEN,           eCharacteristicsID_EmsChannelService0_BasePulseP        },
            {EmsChannelService0_BasePulsTUUID,          EmsChannelService0_BasePulsTVal,            NULL,                                   EMSCHANNELSERVICE0_BASEPULST_LEN,           eCharacteristicsID_EmsChannelService0_BasePulseT        },
            {EmsChannelService0_EnvelopeWaveformUUID,   EmsChannelService0_EnvelopeWaveformVal,     NULL,                                   EMSCHANNELSERVICE0_ENVELOPEWAVEFORM_LEN,    eCharacteristicsID_EmsChannelService0_EnvelopeWaveform  },
            {EmsChannelService0_EnvelopeFrequencyUUID,  EmsChannelService0_EnvelopeFrequencyVal,    NULL,                                   EMSCHANNELSERVICE0_ENVELOPEFREQUENCY_LEN,   eCharacteristicsID_EmsChannelService0_EnvelopeFrequency },
            {EmsChannelService0_ChannelStateUUID,       EmsChannelService0_ChannelStateVal,         EmsChannelService0_ChannelStateConfig,  EMSCHANNELSERVICE0_CHANNELSTATE_LEN,        eCharacteristicsID_EmsChannelService0_ChannelState      },
        };
    
        assert(numberof(sParamFunctionTable) == EMSCHANNELSERVICE0_NUM_OF_CHARACTERISTICS);
    
        for (uint8_t ui8Counter = 0; ui8Counter < numberof(sParamFunctionTable); ui8Counter++)
        {
            memcpy(&sEmsChannelService0ParamFunctionTable[ui8Counter], &sParamFunctionTable[ui8Counter], sizeof(sFunctionTableEntry_t));
        }
    }
    
    /*********************************************************************
    * PUBLIC FUNCTIONS
    */
    
    /*
     * EmsChannelService0_AddService- Initializes the EmsChannelService0 service by registering
     *          GATT attributes with the GATT server.
     *
     */
    //! \date       2021-10-08 (BA) - Creation
    bStatus_t EmsChannelService0_AddService( void )
    {
        uint8_t status;
    
        // Allocate Client Characteristic Configuration table
        EmsChannelService0_ChannelStateConfig = (gattCharCfg_t *)ICall_malloc( sizeof(gattCharCfg_t) * linkDBNumConns );
        if ( EmsChannelService0_ChannelStateConfig == NULL )
        {
            return ( bleMemAllocError );
        }
    
        // Initialize Client Characteristic Configuration attributes
        GATTServApp_InitCharCfg( LINKDB_CONNHANDLE_INVALID, EmsChannelService0_ChannelStateConfig );
    
        // Register GATT attribute list and CBs with GATT Server App
        status = GATTServApp_RegisterService( EmsChannelService0AttrTbl,
                                            GATT_NUM_ATTRS( EmsChannelService0AttrTbl ),
                                            GATT_MAX_ENCRYPT_KEY_SIZE,
                                            &EmsChannelService0CBs );
    
        EmsChannelService0_vInitServiceTable();
    
        Log_info0("EMS Channel Service 0 Initialized");
    
        return ( status );
    }
    
    /*
     * EmsChannelService0_RegisterAppCBs - Registers the application callback function.
     *                    Only call this function once.
     *
     *    appCallbacks - pointer to application callbacks.
     */
    //! \date       2021-10-08 (BA) - Creation
    bStatus_t EmsChannelService0_RegisterAppCBs( EmsChannelServiceCBs_t *appCallbacks )
    {
        if ( appCallbacks )
        {
            pAppCBs = appCallbacks;
    
            return ( SUCCESS );
        }
        else
        {
            return ( bleAlreadyInRequestedMode );
        }
    }
    
    /*
     * EmsChannelService0_SetParameter - Set a EmsChannelService0 parameter.
     *
     *    param - Profile parameter ID
     *    len - length of data to right
     *    value - pointer to data to write.  This is dependent on
     *          the parameter ID and WILL be cast to the appropriate
     *          data type (example: data type of uint16 will be cast to
     *          uint16 pointer).
     */
    //! \date       2021-10-08 (BA) - Creation
    bStatus_t EmsChannelService0_SetParameter(uint8_t param, int len, void *value)
    {
        bStatus_t ret = SUCCESS;
    
        const sFunctionTableEntry_t *psTableEntry = EmsChannelService0_psGetTableEntryByParamId((eCharacteristicsID_t)param);
    
        if (psTableEntry->eParamId == eCharacteristicsID_Invalid)
        {
            ret = INVALIDPARAMETER;
            Log_info0("EmsChannelService0_SetParameter - INVALIDPARAMETER");
        }
        else
        {
            if (len == psTableEntry->xParamLen)
            {
                memcpy(psTableEntry->pui8ParamVal, value, len);
                Log_info2("EmsChannelService0_SetParameter - paramId: %d, len: %d", param, len);
                emsUtil_printParamValueByLength(value, len);
    
                if (psTableEntry->pvParamConfig != NULL)
                {
                    // Try to send notification.
                    ret = GATTServApp_ProcessCharCfg((gattCharCfg_t *)psTableEntry->pvParamConfig, (uint8_t *)&psTableEntry->pui8ParamVal, FALSE,
                                                  EmsChannelService0AttrTbl, GATT_NUM_ATTRS( EmsChannelService0AttrTbl ),
                                                  INVALID_TASK_ID,  EmsChannelService0_ReadAttrCB);
                }
            }
            else
            {
                ret = bleInvalidRange;
                Log_info0("EmsChannelService0_SetParameter - bleInvalidRange");
            }
        }
    
        return ret;
    }
    
    
    /*
     * EmsChannelService0_GetParameter - Get a EmsChannelService0 parameter.
     *
     *    param - Profile parameter ID
     *    value - pointer to data to write.  This is dependent on
     *          the parameter ID and WILL be cast to the appropriate
     *          data type (example: data type of uint16 will be cast to
     *          uint16 pointer).
     */
    //! \date       2021-10-08 (BA) - Creation
    bStatus_t EmsChannelService0_GetParameter(uint8_t param, void *value)
    {
        bStatus_t ret = SUCCESS;
    
        const sFunctionTableEntry_t *psTableEntry = EmsChannelService0_psGetTableEntryByParamId((eCharacteristicsID_t)param);
    
        if (psTableEntry->eParamId == eCharacteristicsID_Invalid)
        {
            ret = INVALIDPARAMETER;
            Log_info0("EmsChannelService0_GetParameter - INVALIDPARAMETER");
        }
        else
        {
            memcpy(value, psTableEntry->pui8ParamVal, psTableEntry->xParamLen);
            Log_info2("EmsChannelService0_GetParameter - paramId: %d, len: %d", param, psTableEntry->xParamLen);
            emsUtil_printParamValueByLength(value, psTableEntry->xParamLen);
        }
    
        return ret;
    }
    
    
    /*********************************************************************
     * @fn          EmsChannelService0_ReadAttrCB
     *
     * @brief       Read an attribute.
     *
     * @param       connHandle - connection message was received on
     * @param       pAttr - pointer to attribute
     * @param       pValue - pointer to data to be read
     * @param       pLen - length of data to be read
     * @param       offset - offset of the first octet to be read
     * @param       maxLen - maximum length of data to be read
     * @param       method - type of read message
     *
     * @return      SUCCESS, blePending or Failure
     */
    //! \date       2021-10-08 (BA) - Creation
    static bStatus_t EmsChannelService0_ReadAttrCB( uint16_t connHandle, gattAttribute_t *pAttr,
                                           uint8_t *pValue, uint16_t *pLen, uint16_t offset,
                                           uint16_t maxLen, uint8_t method )
    {
        bStatus_t status = SUCCESS;
    
        const sFunctionTableEntry_t *psTableEntry = EmsChannelService0_psGetTableEntryByUuid(pAttr->type.uuid, pAttr->type.len);
    
        if (psTableEntry->pui8Uuid == NULL)
        {
            // If we get here, that means you've forgotten to add an table entry for a
            // characteristic value attribute in the attribute table that has READ permissions.
            *pLen = 0;
            status = ATT_ERR_ATTR_NOT_FOUND;
            Log_info0("EmsChannelService0_ReadAttrCB - ATT_ERR_ATTR_NOT_FOUND");
        }
        // Prevent malicious ATT ReadBlob offsets.
        else if (offset > psTableEntry->xParamLen)
        {
            status = ATT_ERR_INVALID_OFFSET;
            Log_info1("EmsChannelService0_ReadAttrCB - ParamID: %d, ATT_ERR_INVALID_OFFSET", psTableEntry->eParamId);
        }
        else
        {
            *pLen = MIN(maxLen, psTableEntry->xParamLen - offset);  // Transmit as much as possible
            memcpy(pValue, pAttr->pValue + offset, *pLen);
            Log_info2("EmsChannelService0_ReadAttrCB - ParamID: %d, len: %d", psTableEntry->eParamId, psTableEntry->xParamLen);
            emsUtil_printParamValueByLength(pValue, psTableEntry->xParamLen);
        }
    
        return status;
    }
    
    
    /*********************************************************************
     * @fn      EmsChannelService0_WriteAttrCB
     *
     * @brief   Validate attribute data prior to a write operation
     *
     * @param   connHandle - connection message was received on
     * @param   pAttr - pointer to attribute
     * @param   pValue - pointer to data to be written
     * @param   len - length of data
     * @param   offset - offset of the first octet to be written
     * @param   method - type of write message
     *
     * @return  SUCCESS, blePending or Failure
     */
    //! \date       2021-10-08 (BA) - Creation
    static bStatus_t EmsChannelService0_WriteAttrCB( uint16_t connHandle, gattAttribute_t *pAttr,
                                            uint8_t *pValue, uint16_t len, uint16_t offset,
                                            uint8_t method )
    {
        bStatus_t status  = SUCCESS;
        uint8_t   paramID = eCharacteristicsID_Invalid;
    
        // See if request is regarding a Client Characteristic Configuration
        if (! memcmp(pAttr->type.uuid, clientCharCfgUUID, pAttr->type.len))
        {
            // Allow only notifications.
            status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,
                                                     offset, GATT_CLIENT_CFG_NOTIFY);
        }
        else
        {
            const sFunctionTableEntry_t *psTableEntry = EmsChannelService0_psGetTableEntryByUuid(pAttr->type.uuid, pAttr->type.len);
    
            if (psTableEntry->pui8Uuid == NULL)
            {
                // If we get here, that means you've forgotten to add an table entry for a
                // characteristic value attribute in the attribute table that has WRITE permissions.
                status = ATT_ERR_ATTR_NOT_FOUND;
                Log_info0("EmsChannelService0_WriteAttrCB - ATT_ERR_ATTR_NOT_FOUND");
            }
            else if (offset + len > psTableEntry->xParamLen)
            {
                status = ATT_ERR_INVALID_OFFSET;
                Log_info1("EmsChannelService0_WriteAttrCB - ParamID: %d, ATT_ERR_INVALID_OFFSET", psTableEntry->eParamId );
            }
            else
            {
                // Copy pValue into the variable we point to from the attribute table.
                memcpy(pAttr->pValue + offset, pValue, len);
    
                // Only notify application if entire expected value is written
                if ( offset + len <= psTableEntry->xParamLen)
                {
                    paramID = psTableEntry->eParamId;
                    Log_info2("EmsChannelService0_WriteAttrCB - ParamID: %d, len: %d", psTableEntry->eParamId, psTableEntry->xParamLen);
                    emsUtil_printParamValueByLength(pValue, psTableEntry->xParamLen);
                }
            }
        }
    
        // Let the application know something changed (if it did) by using the
        // callback it registered earlier (if it did).
        if (paramID != eCharacteristicsID_Invalid)
        {
            if ( pAppCBs && pAppCBs->pfnChangeCb )
            {
                pAppCBs->pfnChangeCb(connHandle, paramID, len, pValue);    // Call app function from stack task context.
            }
        }
    
        return status;
    }
    
    
    //! \brief      Get entry for specific UUID
    //! \param[in]  pui8Uuid:   pointer to parameter UUID
    //! \paran[in]  xUuidDLen:  size of UUID
    //! \retval     parameter handler object
    //! \date       2021-10-08 (BA) - Creation
    static const sFunctionTableEntry_t *EmsChannelService0_psGetTableEntryByUuid(const uint8_t *pui8Uuid, const size_t xUuidDLen)
    {
        const uint_fast8_t ui8Num = numberof(sEmsChannelService0ParamFunctionTable);
    
        for (uint_fast8_t i = 0; i < ui8Num; ++i)
        {
            if (0 == memcmp(pui8Uuid, sEmsChannelService0ParamFunctionTable[i].pui8Uuid, xUuidDLen))
            {
                return &sEmsChannelService0ParamFunctionTable[i];
            }
        }
        return &sEmsChannelService0ParamFunctionTable[0]; // unknown Parameter, first entry is returned. in this function error handling is included
    }
    
    
    //! \brief      Get entry for specific parameter ID
    //! \param[in]  eParamId parameter ID
    //! \retval     parameter handler object
    //! \date       2021-10-08 (BA) - Creation
    static const sFunctionTableEntry_t *EmsChannelService0_psGetTableEntryByParamId(const eCharacteristicsID_t eParamId)
    {
        const uint_fast8_t ui8Num = numberof(sEmsChannelService0ParamFunctionTable);
    
        for (uint_fast8_t i = 0; i < ui8Num; ++i)
        {
            if (eParamId == sEmsChannelService0ParamFunctionTable[i].eParamId)
            {
                return &sEmsChannelService0ParamFunctionTable[i];
            }
        }
        return &sEmsChannelService0ParamFunctionTable[0]; // unknown Parameter, first entry is returned. in this function error handling is included
    }
    
    
    //! @}
    


    Best regards, 

    Ben A.

  • Hi Ben,

    From the code you've sent, it looks like you can trigger this failure when psTableEntry->pui8Uuid is NULL. Is it possible you never set this value for this field in another part of your code?

    Best,

    Nate

  • Hi Nathan,

    in the service/characteristic table "EmsChannelService0AttrTbl" the valid range descriptor ist definitely set and hast a valid value.

        LINE 193 
        // ChannelGain Characteristic Range
        {
           { ATT_BT_UUID_SIZE, validRangeUUID },
           GATT_PERMIT_READ | GATT_PERMIT_WRITE,
           0,
           (uint8_t *)&EmsChannelService0_ChannelGainRange
        },

    I'm not sure why the valid range discriptor needs to be handled separately or specially,  the other gatt decsriptor (e.g. characteristic declaration, user description, client characteristic configuration and fomat type descriptor) are handled internally and the read callback "EmsChannelService0_ReadAttrCB" is not called. The other gatt descriptor are also not in the psTableEntry (except client characteristic configuration) and they are working fine (read request is responsed with a valid/declared value)

  • Hi Benjamin,

    Is the callback being called when you try to read the range descriptor? Perhaps it would be a good idea to set a breakpoint where psTableEntry->pui8Uuid is checked and seeing if for some reason that is what's triggering the error.

    Best,

    Nate

  • Hi Nathan, 

    as mentioned in the my last reply: yes, the callback is being called after trying to read the range discriptor and the error will be triggered after pui8Uuid check.
    The psTableEntry only contains 128 bit uuids for custom characteristics. The range descriptor is a common gatt decriptor with a 16 bit uuid and isn't held in the table. 

    The question was -> Why does the range descriptor needs to be handled separately in the callback? Trying reading other common gatt descriptors are skipping the callback.

    Best regards, 

    Ben A.

  • Hi Benjamin,

    Thank you for clarifying your question

    It does not make sense to me that the readattrcb function is not being triggered when you try to read for example the characteristic value. You should be able to confirm this by commenting our the code in the readattrcb function and observing that you can no longer read the value. Is this the case?

    Best,

    Nate

  • Hi Nathan,

    reading the characteristic value works fine and the readattrcb is triggerd. (-> Was never a issue)

    Commenting out code in readattrcb and reading valid range discriptor value will always return 0 and not the defined value.

  • Ok, if the readattrcb is always triggered, then you'll need to figure out why you are not finding the UUID in the EmsChannelService0_psGetTableEntryByUuid code. You mentioned above that 

    The psTableEntry only contains 128 bit uuids for custom characteristics. The range descriptor is a common gatt decriptor with a 16 bit uuid and isn't held in the table. 

    The code you've provided searches through this table to find the range descriptor UUID, and returns NULL because it does not find it. You could make a separate case for it if you don't wish to add it to the table. Or you could cast it to a 128 bit UUID to make it fit.

  • Hi Nathan, 

    you suggestion would not solve the issue. I have about 80 characterics values with a valid range descriptor (0x2906). I'm pretty sure it should be handled internaly by the stack. e.g. Characteristic Presentation Format (0x2904) is also initialisied once and doesn't need to be added in the table or to be casted to a 128 bit UUID or handle specially in readattrcb.

    Only quick and dirty solution I see is to find the the characteristic to which the the valid range descriptor is attached by the returned handle and then get the specific range. -> extra list with handles and characteristic needed

  • Hi Benjamin,

    Can you provide evidence that the Characteristic Presentation Format is not handled in readattrcb? That does not align with my understanding of the BLE Profiles on our devices. As I understand it, the stack does not handle the read operation for any attributes, not even the Characteristic Presentation Format. You should have to handle the read operation for every single attribute in your code. The SimpleLink Academy lab on Custom Profile shows this in extensive detail.

    Best,

    Nate

  • Hi Nathan,
    reading a characteristic presentation format of a custom characteristic with an external tool, won't halt at any breakpoint in readattrcb and no debug prints are generated. Perhaps the response is the characteristc defined presentation format. ->This was already mentioned in the previous post. 

  • Hi Benjamin,

    I have looked into the source code, and it looks like you are correct. The TI source should in fact handle the Characteristic Presentation Format. I apologize for the misinformation above. It seems the stack handles GATT_PRIMARY_SERVICE_UUID, GATT_SECONDARY_SERVICE_UUID, GATT_CHARACTER_UUID, GATT_INCLUDE_UUID, GATT_CLIENT_CHAR_CFG_UUID, GATT_CHAR_EXT_PROPS_UUID, GATT_CHAR_EXT_PROPS_UUID, GATT_SERV_CHAR_CFG_UUID, GATT_CHAR_USER_DESC_UUID, GATT_CHAR_FORMAT_UUID and GATT_CHAR_USER_DESC_UUID

    However, the TI source does not handle the range descriptor. You should still handle that in your code as discussed above.

    Best,

    Nate