text: /******************************************************************************
  
     @file  led_service.c
  
     @brief   This file contains the implementation of the service.
  
      Group: CMCU, LPRF
      Target Device: cc2640r2
  
   ******************************************************************************
   
   Copyright (c) 2013-2020, Texas Instruments Incorporated
   All rights reserved.
  
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
  
   *  Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
  
   *  Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
  
   *  Neither the name of Texas Instruments Incorporated nor the names of
      its contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.
  
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
   OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
   EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   ******************************************************************************
   
   
   *****************************************************************************/
  
  /*********************************************************************
   * INCLUDES
   */
  #include <string.h>
  
  //#define xdc_runtime_Log_DISABLE_ALL 1  // Add to disable logs from this file
  #include <xdc/runtime/Diags.h>
  #include <uartlog/UartLog.h>
  
  #include <icall.h>
  #include "util.h"
  /* This Header file contains all BLE API and icall structure definition */
  #include "icall_ble_api.h"
  
  #include "led_service.h"
  
  /*********************************************************************
   * MACROS
   */
  
  /*********************************************************************
   * CONSTANTS
   */
  
  /*********************************************************************
   * TYPEDEFS
   */
  
  /*********************************************************************
  * GLOBAL VARIABLES
  */
  
  // LED_Service Service UUID
  CONST uint8_t LedServiceUUID[ATT_UUID_SIZE] =
  {
    LED_SERVICE_SERV_UUID_BASE128(LED_SERVICE_SERV_UUID)
  };
  
  // LED0 UUID
  CONST uint8_t ls_LED0UUID[ATT_UUID_SIZE] =
  {
    LS_LED0_UUID_BASE128(LS_LED0_UUID)
  };
  
  // LED1 UUID
  CONST uint8_t ls_LED1UUID[ATT_UUID_SIZE] =
  {
    LS_LED1_UUID_BASE128(LS_LED1_UUID)
  };
  
  // LED2 UUID
  CONST uint8_t ls_LED2UUID[ATT_UUID_SIZE] =
  {
   LS_LED1_UUID_BASE128(LS_LED2_UUID)
  };
  
  // Fade UUID
  CONST uint8_t ls_FADEUUID[ATT_UUID_SIZE] =
  {
   LS_FADE_UUID_BASE128(LS_FADE_UUID)
  };
  
  
  /*********************************************************************
   * LOCAL VARIABLES
   */
  
  static LedServiceCBs_t *pAppCBs = NULL;
  
  /*********************************************************************
  * Profile Attributes - variables
  */
  
  // Service declaration
  static CONST gattAttrType_t LedServiceDecl = { ATT_UUID_SIZE, LedServiceUUID };
  
  // Characteristic "LED0" Properties (for declaration)
  static uint8_t ls_LED0Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP;
  
  // Characteristic "LED0" Value variable
  static uint8_t ls_LED0Val[LS_LED0_LEN] = {0};
  
  // Length of data in characteristic "LED0" Value variable, initialized to minimal size.
  static uint16_t ls_LED0ValLen = LS_LED0_LEN_MIN;
  
  
  
  // Characteristic "LED1" Properties (for declaration)
  static uint8_t ls_LED1Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP;
  
  // Characteristic "LED1" Value variable
  static uint8_t ls_LED1Val[LS_LED1_LEN] = {0};
  
  // Length of data in characteristic "LED1" Value variable, initialized to minimal size.
  static uint16_t ls_LED1ValLen = LS_LED1_LEN_MIN;
  
  
  
  // Characteristic "LED2" Properties (for declaration)
  static uint8_t ls_LED2Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP;
  
  // Characteristic "LED2" Value variable
  static uint8_t ls_LED2Val[LS_LED2_LEN] = {0};
  
  // Length of data in characteristic "LED2" Value variable, initialized to minimal size.
  static uint16_t ls_LED2ValLen = LS_LED2_LEN_MIN;
  
  
  // Characteristic "FADE" Properties (for declaration)
  static uint8_t ls_FADEProps = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP;
  
  // Characteristic "FADE" Value variable
  static uint8_t ls_FADEVal[LS_FADE_LEN] = {0};
  
  // Length of data in characteristic "FADE" Value variable, initialized to minimal size.
  static uint16_t ls_FADEValLen = LS_FADE_LEN_MIN;
  
  
  
  /*********************************************************************
  * Profile Attributes - Table
  */
  
  static gattAttribute_t LED_ServiceAttrTbl[] =
  {
    // LED_Service Service Declaration
    {
      { ATT_BT_UUID_SIZE, primaryServiceUUID },
      GATT_PERMIT_READ,
      0,
      (uint8_t *)&LedServiceDecl
    },
      // LED0 Characteristic Declaration
      {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &ls_LED0Props
      },
        // LED0 Characteristic Value
        {
          { ATT_UUID_SIZE, ls_LED0UUID },
          GATT_PERMIT_READ | GATT_PERMIT_WRITE | GATT_PERMIT_WRITE,
          0,
          ls_LED0Val
        },
      // LED1 Characteristic Declaration
      {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &ls_LED1Props
      },
        // LED1 Characteristic Value
        {
          { ATT_UUID_SIZE, ls_LED1UUID },
          GATT_PERMIT_READ | GATT_PERMIT_WRITE | GATT_PERMIT_WRITE,
          0,
          ls_LED1Val
        },
        // LED2 Characteristic Declaration
        {
          { ATT_BT_UUID_SIZE, characterUUID },
          GATT_PERMIT_READ,
          0,
          &ls_LED2Props
        },
          // FADE Characteristic Value
          {
            { ATT_UUID_SIZE, ls_LED2UUID },
            GATT_PERMIT_READ | GATT_PERMIT_WRITE | GATT_PERMIT_WRITE,
            0,
            ls_LED2Val
          },
          // FADE Characteristic Declaration
          {
            { ATT_BT_UUID_SIZE, characterUUID },
            GATT_PERMIT_READ,
            0,
            &ls_FADEProps
          },
            // FADE Characteristic Value
            {
              { ATT_UUID_SIZE, ls_FADEUUID },
              GATT_PERMIT_READ | GATT_PERMIT_WRITE | GATT_PERMIT_WRITE,
              0,
              ls_FADEVal
            },
  };
  
  /*********************************************************************
   * LOCAL FUNCTIONS
   */
  static bStatus_t LED_Service_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 LED_Service_WriteAttrCB( uint16_t connHandle, gattAttribute_t *pAttr,
                                              uint8_t *pValue, uint16_t len, uint16_t offset,
                                              uint8_t method );
  
  /*********************************************************************
   * PROFILE CALLBACKS
   */
  // Simple Profile Service Callbacks
  CONST gattServiceCBs_t LED_ServiceCBs =
  {
    LED_Service_ReadAttrCB,  // Read callback function pointer
    LED_Service_WriteAttrCB, // Write callback function pointer
    NULL                       // Authorization callback function pointer
  };
  
  /*********************************************************************
  * PUBLIC FUNCTIONS
  */
  
  /*
   * LedService_AddService- Initializes the LedService service by registering
   *          GATT attributes with the GATT server.
   *
   *    rspTaskId - The ICall Task Id that should receive responses for Indications.
   */
  extern bStatus_t LedService_AddService( uint8_t rspTaskId )
  {
    uint8_t status;
  
    // Register GATT attribute list and CBs with GATT Server App
    status = GATTServApp_RegisterService( LED_ServiceAttrTbl,
                                          GATT_NUM_ATTRS( LED_ServiceAttrTbl ),
                                          GATT_MAX_ENCRYPT_KEY_SIZE,
                                          &LED_ServiceCBs );
    Log_info1("Registered service, %d attributes", (IArg)GATT_NUM_ATTRS( LED_ServiceAttrTbl ));
    return ( status );
  }
  
  /*
   * LedService_RegisterAppCBs - Registers the application callback function.
   *                    Only call this function once.
   *
   *    appCallbacks - pointer to application callbacks.
   */
  bStatus_t LedService_RegisterAppCBs( LedServiceCBs_t *appCallbacks )
  {
    if ( appCallbacks )
    {
      pAppCBs = appCallbacks;
      Log_info1("Registered callbacks to application. Struct %p", (IArg)appCallbacks);
      return ( SUCCESS );
    }
    else
    {
      Log_warning0("Null pointer given for app callbacks.");
      return ( FAILURE );
    }
  }
  
  /*
   * LedService_SetParameter - Set a LedService parameter.
   *
   *    param - Profile parameter ID
   *    len   - length of data to write
   *    value - pointer to data to write.  This is dependent on
   *            the parameter ID and may be cast to the appropriate
   *            data type (example: data type of uint16_t will be cast to
   *            uint16_t pointer).
   */
  bStatus_t LedService_SetParameter( uint8_t param, uint16_t len, void *value )
  {
    bStatus_t ret = SUCCESS;
    uint8_t  *pAttrVal;
    uint16_t *pValLen;
    uint16_t valMinLen;
    uint16_t valMaxLen;
  
    switch ( param )
    {
      case LS_LED0_ID:
        pAttrVal  =  ls_LED0Val;
        pValLen   = &ls_LED0ValLen;
        valMinLen =  LS_LED0_LEN_MIN;
        valMaxLen =  LS_LED0_LEN;
        Log_info2("SetParameter : %s len: %d", (IArg)"LED0", (IArg)len);
        break;
  
      case LS_LED1_ID:
        pAttrVal  =  ls_LED1Val;
        pValLen   = &ls_LED1ValLen;
        valMinLen =  LS_LED1_LEN_MIN;
        valMaxLen =  LS_LED1_LEN;
        Log_info2("SetParameter : %s len: %d", (IArg)"LED1", (IArg)len);
        break;
  
      case LS_LED2_ID:
        pAttrVal  =  ls_LED2Val;
        pValLen   = &ls_LED2ValLen;
        valMinLen =  LS_LED2_LEN_MIN;
        valMaxLen =  LS_LED2_LEN;
        Log_info2("SetParameter : %s len: %d", (IArg)"LED2", (IArg)len);
        break;
  
      case LS_FADE_ID:
        pAttrVal  =  ls_FADEVal;
        pValLen   = &ls_FADEValLen;
        valMinLen =  LS_FADE_LEN_MIN;
        valMaxLen =  LS_FADE_LEN;
        Log_info2("SetParameter : %s len: %d", (IArg)"FADE", (IArg)len);
        break;
  
      default:
        Log_error1("SetParameter: Parameter #%d not valid.", (IArg)param);
        return INVALIDPARAMETER;
    }
  
    // Check bounds, update value and send notification or indication if possible.
    if ( len <= valMaxLen && len >= valMinLen )
    {
      memcpy(pAttrVal, value, len);
      *pValLen = len; // Update length for read and get.
    }
    else
    {
      Log_error3("Length outside bounds: Len: %d MinLen: %d MaxLen: %d.", (IArg)len, (IArg)valMinLen, (IArg)valMaxLen);
      ret = bleInvalidRange;
    }
  
    return ret;
  }
  
  
  /*
   * LedService_GetParameter - Get a LedService parameter.
   *
   *    param - Profile parameter ID
   *    len   - pointer to a variable that contains the maximum length that can be written to *value.
                After the call, this value will contain the actual returned length.
   *    value - pointer to data to write.  This is dependent on
   *            the parameter ID and may be cast to the appropriate
   *            data type (example: data type of uint16_t will be cast to
   *            uint16_t pointer).
   */
  bStatus_t LedService_GetParameter( uint8_t param, uint16_t *len, void *value )
  {
    bStatus_t ret = SUCCESS;
    switch ( param )
    {
      case LS_LED0_ID:
        *len = MIN(*len, ls_LED0ValLen);
        memcpy(value, ls_LED0Val, *len);
        Log_info2("GetParameter : %s returning %d bytes", (IArg)"LED0", (IArg)*len);
        break;
  
      case LS_LED1_ID:
        *len = MIN(*len, ls_LED1ValLen);
        memcpy(value, ls_LED1Val, *len);
        Log_info2("GetParameter : %s returning %d bytes", (IArg)"LED1", (IArg)*len);
        break;
  
      case LS_LED2_ID:
        *len = MIN(*len, ls_LED2ValLen);
        memcpy(value, ls_LED2Val, *len);
        Log_info2("GetParameter : %s returning %d bytes", (IArg)"LED2", (IArg)*len);
        break;
  
      case LS_FADE_ID:
        *len = MIN(*len, ls_FADEValLen);
        memcpy(value, ls_FADEVal, *len);
        Log_info2("GetParameter : %s returning %d bytes", (IArg)"FADE", (IArg)*len);
        break;
  
      default:
        Log_error1("GetParameter: Parameter #%d not valid.", (IArg)param);
        ret = INVALIDPARAMETER;
        break;
    }
    return ret;
  }
  
  /*********************************************************************
   * @internal
   * @fn          LED_Service_findCharParamId
   *
   * @brief       Find the logical param id of an attribute in the service's attr table.
   *
   *              Works only for Characteristic Value attributes and
   *              Client Characteristic Configuration Descriptor attributes.
   *
   * @param       pAttr - pointer to attribute
   *
   * @return      uint8_t paramID (ref led_service.h) or 0xFF if not found.
   */
  static uint8_t LED_Service_findCharParamId(gattAttribute_t *pAttr)
  {
    // Is this a Client Characteristic Configuration Descriptor?
    if (ATT_BT_UUID_SIZE == pAttr->type.len && GATT_CLIENT_CHAR_CFG_UUID == *(uint16_t *)pAttr->type.uuid)
      return LED_Service_findCharParamId(pAttr - 1); // Assume the value attribute precedes CCCD and recurse
  
    // Is this attribute in "LED0"?
    else if ( ATT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, ls_LED0UUID, pAttr->type.len))
      return LS_LED0_ID;
  
    // Is this attribute in "LED1"?
    else if ( ATT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, ls_LED1UUID, pAttr->type.len))
      return LS_LED1_ID;
  
    // Is this attribute in "LED2"?
    else if ( ATT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, ls_LED2UUID, pAttr->type.len))
      return LS_LED2_ID;
  
    // Is this attribute in "FADE"?
    else if ( ATT_UUID_SIZE == pAttr->type.len && !memcmp(pAttr->type.uuid, ls_FADEUUID, pAttr->type.len))
      return LS_FADE_ID;
  
    else
      return 0xFF; // Not found. Return invalid.
  }
  
  /*********************************************************************
   * @fn          LED_Service_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
   */
  static bStatus_t LED_Service_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;
    uint16_t valueLen;
    uint8_t paramID = 0xFF;
  
    // Find settings for the characteristic to be read.
    paramID = LED_Service_findCharParamId( pAttr );
    switch ( paramID )
    {
      case LS_LED0_ID:
        valueLen = ls_LED0ValLen;
  
        Log_info4("ReadAttrCB : %s connHandle: %d offset: %d method: 0x%02x",
                   (IArg)"LED0",
                   (IArg)connHandle,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED0 can be inserted here */
        break;
  
      case LS_LED1_ID:
        valueLen = ls_LED1ValLen;
  
        Log_info4("ReadAttrCB : %s connHandle: %d offset: %d method: 0x%02x",
                   (IArg)"LED1",
                   (IArg)connHandle,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED1 can be inserted here */
        break;
  
      case LS_LED2_ID:
        valueLen = ls_LED2ValLen;
  
        Log_info4("ReadAttrCB : %s connHandle: %d offset: %d method: 0x%02x",
                   (IArg)"LED2",
                   (IArg)connHandle,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED1 can be inserted here */
        break;
  
      case LS_FADE_ID:
        valueLen = ls_FADEValLen;
  
        Log_info4("ReadAttrCB : %s connHandle: %d offset: %d method: 0x%02x",
                   (IArg)"FADE",
                   (IArg)connHandle,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED1 can be inserted here */
        break;
  
      default:
        Log_error0("Attribute was not found.");
        return ATT_ERR_ATTR_NOT_FOUND;
    }
    // Check bounds and return the value
    if ( offset > valueLen )  // Prevent malicious ATT ReadBlob offsets.
    {
      Log_error0("An invalid offset was requested.");
      status = ATT_ERR_INVALID_OFFSET;
    }
    else
    {
      *pLen = MIN(maxLen, valueLen - offset);  // Transmit as much as possible
      memcpy(pValue, pAttr->pValue + offset, *pLen);
    }
  
    return status;
  }
  
  /*********************************************************************
   * @fn      LED_Service_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
   */
  static bStatus_t LED_Service_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 = 0xFF;
    uint8_t   changeParamID = 0xFF;
    uint16_t writeLenMin;
    uint16_t writeLenMax;
    uint16_t *pValueLenVar;
  
    // Find settings for the characteristic to be written.
    paramID = LED_Service_findCharParamId( pAttr );
    switch ( paramID )
    {
      case LS_LED0_ID:
        writeLenMin  = LS_LED0_LEN_MIN;
        writeLenMax  = LS_LED0_LEN;
        pValueLenVar = &ls_LED0ValLen;
  
        Log_info5("WriteAttrCB : %s connHandle(%d) len(%d) offset(%d) method(0x%02x)",
                   (IArg)"LED0",
                   (IArg)connHandle,
                   (IArg)len,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED0 can be inserted here */
        break;
  
      case LS_LED1_ID:
        writeLenMin  = LS_LED1_LEN_MIN;
        writeLenMax  = LS_LED1_LEN;
        pValueLenVar = &ls_LED1ValLen;
  
        Log_info5("WriteAttrCB : %s connHandle(%d) len(%d) offset(%d) method(0x%02x)",
                   (IArg)"LED1",
                   (IArg)connHandle,
                   (IArg)len,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED1 can be inserted here */
        break;
  
      case LS_LED2_ID:
        writeLenMin  = LS_LED2_LEN_MIN;
        writeLenMax  = LS_LED2_LEN;
        pValueLenVar = &ls_LED2ValLen;
  
        Log_info5("WriteAttrCB : %s connHandle(%d) len(%d) offset(%d) method(0x%02x)",
                   (IArg)"LED2",
                   (IArg)connHandle,
                   (IArg)len,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED2 can be inserted here */
        break;
  
      case LS_FADE_ID:
        writeLenMin  = LS_FADE_LEN_MIN;
        writeLenMax  = LS_FADE_LEN;
        pValueLenVar = &ls_FADEValLen;
  
        Log_info5("WriteAttrCB : %s connHandle(%d) len(%d) offset(%d) method(0x%02x)",
                   (IArg)"FADE",
                   (IArg)connHandle,
                   (IArg)len,
                   (IArg)offset,
                   (IArg)method);
        /* Other considerations for LED2 can be inserted here */
        break;
  
      default:
        Log_error0("Attribute was not found.");
        return ATT_ERR_ATTR_NOT_FOUND;
    }
    // Check whether the length is within bounds.
    if ( offset >= writeLenMax )
    {
      Log_error0("An invalid offset was requested.");
      status = ATT_ERR_INVALID_OFFSET;
    }
    else if ( offset + len > writeLenMax )
    {
      Log_error0("Invalid value length was received.");
      status = ATT_ERR_INVALID_VALUE_SIZE;
    }
    else if ( offset + len < writeLenMin && ( method == ATT_EXECUTE_WRITE_REQ || method == ATT_WRITE_REQ ) )
    {
      // Refuse writes that are lower than minimum.
      // Note: Cannot determine if a Reliable Write (to several chars) is finished, so those will
      //       only be refused if this attribute is the last in the queue (method is execute).
      //       Otherwise, reliable writes are accepted and parsed piecemeal.
      Log_error0("Invalid value length was received.");
      status = ATT_ERR_INVALID_VALUE_SIZE;
    }
    else
    {
      // Copy pValue into the variable we point to from the attribute table.
      memcpy(pAttr->pValue + offset, pValue, len);
  
      // Only notify application and update length if enough data is written.
      //
      // Note: If reliable writes are used (meaning several attributes are written to using ATT PrepareWrite),
      //       the application will get a callback for every write with an offset + len larger than _LEN_MIN.
      // Note: For Long Writes (ATT Prepare + Execute towards only one attribute) only one callback will be issued,
      //       because the write fragments are concatenated before being sent here.
      if ( offset + len >= writeLenMin )
      {
        changeParamID = paramID;
        *pValueLenVar = offset + len; // Update data length.
      }
    }
  
    // Let the application know something changed (if it did) by using the
    // callback it registered earlier (if it did).
    if (changeParamID != 0xFF)
      if ( pAppCBs && pAppCBs->pfnChangeCb )
        pAppCBs->pfnChangeCb( connHandle, LED_SERVICE_SERV_UUID, paramID, pValue, len+offset ); // Call app function from stack task context.
  
    return status;
  }
  