text: /**************************************************************************************************
   
    Phyplus Microelectronics Limited confidential and proprietary. 
    All rights reserved.
  
    IMPORTANT: All rights of this software belong to Phyplus Microelectronics 
    Limited ("Phyplus"). Your use of this Software is limited to those 
    specific rights granted under  the terms of the business contract, the 
    confidential agreement, the non-disclosure agreement and any other forms 
    of agreements as a customer or a partner of Phyplus. You may not use this 
    Software unless you agree to abide by the terms of these agreements. 
    You acknowledge that the Software may not be modified, copied, 
    distributed or disclosed unless embedded on a Phyplus Bluetooth Low Energy 
    (BLE) integrated circuit, either as a product or is integrated into your 
    products.  Other than for the aforementioned purposes, you may not use, 
    reproduce, copy, prepare derivative works of, modify, distribute, perform, 
    display or sell this Software and/or its documentation for any purposes.
  
    YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE
    PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
    INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE,
    NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
    PHYPLUS OR ITS SUBSIDIARIES BE LIABLE OR OBLIGATED UNDER CONTRACT,
    NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER
    LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES
    INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE
    OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT
    OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES
    (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.
    
  **************************************************************************************************/
  
  /**************************************************************************************************
    Filename:       heartrate.c
    Revised:        $Date $
    Revision:       $Revision $
  
  
  **************************************************************************************************/
  
  /*********************************************************************
   * INCLUDES
   */
  
  #include "bcomdef.h"
  #include "OSAL.h"
  #include "linkdb.h"
  #include "gatt.h"
  #include "gapgattserver.h"
  #include "gattservapp.h"
  #include "gatt_profile_uuid.h"
  #include "heartrateservice.h"
  #include "devinfoservice.h"
  #include "peripheral.h"
  #include "gapbondmgr.h"
  #include "heartrate.h"
  #include "log.h"
  
  /*********************************************************************
   * MACROS
   */
  
  // Convert BPM to RR-Interval for data simulation purposes
  #define HEARTRATE_BPM_TO_RR(bpm)              ((uint16) 60 * 1024 / (uint16) (bpm))
  
  /*********************************************************************
   * CONSTANTS
   */
  
  // Fast advertising interval in 625us units
  #define DEFAULT_FAST_ADV_INTERVAL             32
  
  // Duration of fast advertising duration in ms
  #define DEFAULT_FAST_ADV_DURATION             30000
  
  // Slow advertising interval in 625us units
  #define DEFAULT_SLOW_ADV_INTERVAL             1600
  
  // Duration of slow advertising duration in ms (set to 0 for continuous advertising)
  #define DEFAULT_SLOW_ADV_DURATION             0
  
  // How often to perform heart rate periodic event
  #define DEFAULT_HEARTRATE_PERIOD              2000
  
  // Whether to enable automatic parameter update request when a connection is formed
  #define DEFAULT_ENABLE_UPDATE_REQUEST         FALSE
  
  // Minimum connection interval (units of 1.25ms) if automatic parameter update request is enabled
  #define DEFAULT_DESIRED_MIN_CONN_INTERVAL     200
  
  // Maximum connection interval (units of 1.25ms) if automatic parameter update request is enabled
  #define DEFAULT_DESIRED_MAX_CONN_INTERVAL     1600
  
  // Slave latency to use if automatic parameter update request is enabled
  #define DEFAULT_DESIRED_SLAVE_LATENCY         1
  
  // Supervision timeout value (units of 10ms) if automatic parameter update request is enabled
  #define DEFAULT_DESIRED_CONN_TIMEOUT          1000
  
  
  
  // Some values used to simulate measurements
  #define BPM_DEFAULT                           73
  #define BPM_MAX                               80
  #define ENERGY_INCREMENT                      10
  #define FLAGS_IDX_MAX                         7
  
  /*********************************************************************
   * TYPEDEFS
   */
  
  /*********************************************************************
   * GLOBAL VARIABLES
   */
  
  /*********************************************************************
   * EXTERNAL VARIABLES
   */
  
  /*********************************************************************
   * EXTERNAL FUNCTIONS
   */
  
  /*********************************************************************
   * LOCAL VARIABLES
   */
  static uint8 heartRate_TaskID;   // Task ID for internal task/event processing
  
  static gaprole_States_t gapProfileState = GAPROLE_INIT;
  
  // GAP Profile - Name attribute for SCAN RSP data
  static uint8 scanData[] =
  {
    0x10,   // length of this data
    GAP_ADTYPE_LOCAL_NAME_COMPLETE,
    'P',
    'h',
    'y',
    '+',
    ' ',
    'H',
    'e',
    'a',
    'r',
    't',
    ' ',
    'R',
    'a',
    't',
    'e',
  };
  
  static uint8 advertData[] = 
  { 
    // flags
    0x02,
    GAP_ADTYPE_FLAGS,
    GAP_ADTYPE_FLAGS_GENERAL | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
    // service UUIDs
    0x03,
    GAP_ADTYPE_16BIT_MORE,
    LO_UINT16(HEARTRATE_SERV_UUID),
    HI_UINT16(HEARTRATE_SERV_UUID),
  };
  
  // Device name attribute value
  static uint8 attDeviceName[GAP_DEVICE_NAME_LEN] = "PhyHeartRate";
  
  // GAP connection handle
  static uint16 gapConnHandle;
  
  // Heart rate measurement value stored in this structure
  static attHandleValueNoti_t heartRateMeas;
  
  // Components of heart rate measurement structure
  static uint8 heartRateBpm = BPM_DEFAULT;
  static uint16 heartRateEnergy = 0;
  static uint16 heartRateRrInterval1 = HEARTRATE_BPM_TO_RR(BPM_DEFAULT);
  static uint16 heartRateRrInterval2 = HEARTRATE_BPM_TO_RR(BPM_DEFAULT);
  
  // flags for simulated measurements
  static const uint8 heartRateFlags[FLAGS_IDX_MAX] =
  {
    HEARTRATE_FLAGS_CONTACT_NOT_SUP,
    HEARTRATE_FLAGS_CONTACT_NOT_DET,
    HEARTRATE_FLAGS_CONTACT_DET | HEARTRATE_FLAGS_ENERGY_EXP,
    HEARTRATE_FLAGS_CONTACT_DET | HEARTRATE_FLAGS_RR,
    HEARTRATE_FLAGS_CONTACT_DET | HEARTRATE_FLAGS_ENERGY_EXP | HEARTRATE_FLAGS_RR,
    HEARTRATE_FLAGS_FORMAT_UINT16 | HEARTRATE_FLAGS_CONTACT_DET | HEARTRATE_FLAGS_ENERGY_EXP | HEARTRATE_FLAGS_RR,
    0x00
  };
  
  static uint8 heartRateFlagsIdx = 5;
  
  // Advertising user-cancelled state
  static bool heartRateAdvCancelled = FALSE;
  
  /*********************************************************************
   * LOCAL FUNCTIONS
   */
  static void heartRate_ProcessOSALMsg( osal_event_hdr_t *pMsg );
  static void HeartRateGapStateCB( gaprole_States_t newState );
  static void heartRatePeriodicTask( void );
  static void heartRateMeasNotify(void);
  static void heartRateCB(uint8 event);
  
  /*********************************************************************
   * PROFILE CALLBACKS
   */
  
  // GAP Role Callbacks
  static gapRolesCBs_t heartRatePeripheralCB =
  {
    HeartRateGapStateCB,  // Profile State Change Callbacks
    NULL                            // When a valid RSSI is read from controller
  };
  
  // Bond Manager Callbacks
  static const gapBondCBs_t heartRateBondCB =
  {
    NULL,                   // Passcode callback
    NULL                    // Pairing state callback
  };
  
  /*********************************************************************
   * PUBLIC FUNCTIONS
   */
  
  /*********************************************************************
   * @fn      HeartRate_Init
   *
   * @brief   Initialization function for the Heart Rate App Task.
   *          This is called during initialization and should contain
   *          any application specific initialization (ie. hardware
   *          initialization/setup, table initialization, power up
   *          notificaiton ... ).
   *
   * @param   task_id - the ID assigned by OSAL.  This ID should be
   *                    used to send messages and set timers.
   *
   * @return  none
   */
  void HeartRate_Init( uint8 task_id )
  {
    heartRate_TaskID = task_id;
  
    // Setup the GAP Peripheral Role Profile
    {
      uint8 initial_advertising_enable = TRUE;
      uint8 advChnMap = GAP_ADVCHAN_37 | GAP_ADVCHAN_38 | GAP_ADVCHAN_39; 
  
      // By setting this to zero, the device will go into the waiting state after
      // being discoverable for 30.72 second, and will not being advertising again
      // until the enabler is set back to TRUE
      uint16 gapRole_AdvertOffTime = 0;
        
      uint8 enable_update_request = DEFAULT_ENABLE_UPDATE_REQUEST;
      uint16 desired_min_interval = DEFAULT_DESIRED_MIN_CONN_INTERVAL;
      uint16 desired_max_interval = DEFAULT_DESIRED_MAX_CONN_INTERVAL;
      uint16 desired_slave_latency = DEFAULT_DESIRED_SLAVE_LATENCY;
      uint16 desired_conn_timeout = DEFAULT_DESIRED_CONN_TIMEOUT;
      uint8 peerPublicAddr[] = {
  			0x01,
  			0x02,
  			0x03,
  			0x04,
  			0x05,
  			0x06
  		};
  
      GAPRole_SetParameter(GAPROLE_ADV_DIRECT_ADDR, sizeof(peerPublicAddr), peerPublicAddr);
      // set adv channel map
      GAPRole_SetParameter(GAPROLE_ADV_CHANNEL_MAP, sizeof(uint8), &advChnMap);        
      
      // Set the GAP Role Parameters
      GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &initial_advertising_enable );
      GAPRole_SetParameter( GAPROLE_ADVERT_OFF_TIME, sizeof( uint16 ), &gapRole_AdvertOffTime );
      
      GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, sizeof ( scanData ), scanData );
      GAPRole_SetParameter( GAPROLE_ADVERT_DATA, sizeof( advertData ), advertData );
      
      GAPRole_SetParameter( GAPROLE_PARAM_UPDATE_ENABLE, sizeof( uint8 ), &enable_update_request );
      GAPRole_SetParameter( GAPROLE_MIN_CONN_INTERVAL, sizeof( uint16 ), &desired_min_interval );
      GAPRole_SetParameter( GAPROLE_MAX_CONN_INTERVAL, sizeof( uint16 ), &desired_max_interval );
      GAPRole_SetParameter( GAPROLE_SLAVE_LATENCY, sizeof( uint16 ), &desired_slave_latency );
      GAPRole_SetParameter( GAPROLE_TIMEOUT_MULTIPLIER, sizeof( uint16 ), &desired_conn_timeout );
    }
    
    // Set the GAP Characteristics
    GGS_SetParameter( GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName );
  
  
    // Setup the Heart Rate Characteristic Values
    {
      uint8 sensLoc = HEARTRATE_SENS_LOC_WRIST;
      HeartRate_SetParameter( HEARTRATE_SENS_LOC, sizeof ( uint8 ), &sensLoc );
    }
    
    // Set advertising interval
    {
        uint16 advInt = 400;   // actual time = advInt * 625us
    
        GAP_SetParamValue( TGAP_LIM_DISC_ADV_INT_MIN, advInt );
        GAP_SetParamValue( TGAP_LIM_DISC_ADV_INT_MAX, advInt );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MIN, advInt );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MAX, advInt );
    }
    
    // Initialize GATT attributes
    GGS_AddService( GATT_ALL_SERVICES );         // GAP
    GATTServApp_AddService( GATT_ALL_SERVICES ); // GATT attributes
    DevInfo_AddService( );
    HeartRate_AddService( GATT_ALL_SERVICES );
    
    // Register for Heart Rate service callback
    HeartRate_Register( heartRateCB );
    
    // Setup a delayed profile startup
    osal_set_event( heartRate_TaskID, START_DEVICE_EVT );
  }
  
  /*********************************************************************
   * @fn      HeartRate_ProcessEvent
   *
   * @brief   Heart Rate Application Task event processor.  This function
   *          is called to process all events for the task.  Events
   *          include timers, messages and any other user defined events.
   *
   * @param   task_id  - The OSAL assigned task ID.
   * @param   events - events to process.  This is a bit map and can
   *                   contain more than one event.
   *
   * @return  events not processed
   */
  uint16 HeartRate_ProcessEvent( uint8 task_id, uint16 events )
  {
    
    VOID task_id; // OSAL required parameter that isn't used in this function
    LOG("HeartRate_ProcessEvent: 0x%x\n",events);
    
    if ( events & SYS_EVENT_MSG )
    {
      uint8 *pMsg;
  
      if ( (pMsg = osal_msg_receive( heartRate_TaskID )) != NULL )
      {
        heartRate_ProcessOSALMsg( (osal_event_hdr_t *)pMsg );
  
        // Release the OSAL message
        VOID osal_msg_deallocate( pMsg );
      }
  
      // return unprocessed events
      return (events ^ SYS_EVENT_MSG);
    }
   
    
  
    if ( events & START_DEVICE_EVT )
    {
      // Start the Device
      VOID GAPRole_StartDevice( &heartRatePeripheralCB );
  
      // Register with bond manager after starting device
      GAPBondMgr_Register( (gapBondCBs_t *) &heartRateBondCB );
      
      return ( events ^ START_DEVICE_EVT );
    }
  
    if ( events & HEART_PERIODIC_EVT )
    {
      // Perform periodic heart rate task
      LOG("HEART_PERIODIC_EVT\n");
      heartRatePeriodicTask();
      
      return (events ^ HEART_PERIODIC_EVT);
    }  
  
    
    // Discard unknown events
    return 0;
  }
  
  /*********************************************************************
   * @fn      heartRate_ProcessOSALMsg
   *
   * @brief   Process an incoming task message.
   *
   * @param   pMsg - message to process
   *
   * @return  none
   */
  static void heartRate_ProcessOSALMsg( osal_event_hdr_t *pMsg )
  {
  }
  
  
  /*********************************************************************
   * @fn      heartRateMeasNotify
   *
   * @brief   Prepare and send a heart rate measurement notification
   *
   * @return  none
   */
  static void heartRateMeasNotify(void)
  {
    uint8 *p = heartRateMeas.value;
    uint8 flags = heartRateFlags[heartRateFlagsIdx];
    
    // build heart rate measurement structure from simulated values
    *p++ = flags;
    *p++ = heartRateBpm;
    if (flags & HEARTRATE_FLAGS_FORMAT_UINT16)
    {
      // additional byte for 16 bit format
      *p++ = 0;
    }
    if (flags & HEARTRATE_FLAGS_ENERGY_EXP)
    {
      *p++ = LO_UINT16(heartRateEnergy);
      *p++ = HI_UINT16(heartRateEnergy);
    }
    if (flags & HEARTRATE_FLAGS_RR)
    {
      *p++ = LO_UINT16(heartRateRrInterval1);
      *p++ = HI_UINT16(heartRateRrInterval1);  
      *p++ = LO_UINT16(heartRateRrInterval2);
      *p++ = HI_UINT16(heartRateRrInterval2);  
    }
    heartRateMeas.len = (uint8) (p - heartRateMeas.value);
    HeartRate_MeasNotify( gapConnHandle, &heartRateMeas );
    
    // update simulated values 
    heartRateEnergy += ENERGY_INCREMENT;
    if (++heartRateBpm == BPM_MAX)
    {
      heartRateBpm = BPM_DEFAULT;
    }
    heartRateRrInterval1 = heartRateRrInterval2 = HEARTRATE_BPM_TO_RR(heartRateBpm);
  }
  
  /*********************************************************************
   * @fn      HeartRateGapStateCB
   *
   * @brief   Notification from the profile of a state change.
   *
   * @param   newState - new state
   *
   * @return  none
   */
  static void HeartRateGapStateCB( gaprole_States_t newState )
  {
    LOG("HeartRateGapStateCB: %d", newState);
    // if connected
    if (newState == GAPROLE_CONNECTED)
    {
      // get connection handle
      GAPRole_GetParameter(GAPROLE_CONNHANDLE, &gapConnHandle);
    }
    // if disconnected
    else if (gapProfileState == GAPROLE_CONNECTED && 
             newState != GAPROLE_CONNECTED)
    {
      uint8 advState = TRUE;
  
      // stop periodic measurement
      osal_stop_timerEx( heartRate_TaskID, HEART_PERIODIC_EVT );
  
      // reset client characteristic configuration descriptors
      HeartRate_HandleConnStatusCB( gapConnHandle, LINKDB_STATUS_UPDATE_REMOVED );
  
      if ( newState == GAPROLE_WAITING_AFTER_TIMEOUT )
      {
        // link loss timeout-- use fast advertising
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MIN, DEFAULT_FAST_ADV_INTERVAL );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MAX, DEFAULT_FAST_ADV_INTERVAL );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_MIN, DEFAULT_FAST_ADV_DURATION );
      }
      else
      {
        // Else use slow advertising
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MIN, DEFAULT_SLOW_ADV_INTERVAL );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MAX, DEFAULT_SLOW_ADV_INTERVAL );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_MIN, DEFAULT_SLOW_ADV_DURATION );
      }
  
      // Enable advertising
      GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &advState );    
    }    
    // if advertising stopped
    else if ( gapProfileState == GAPROLE_ADVERTISING && 
              newState == GAPROLE_WAITING )
    {
      // if advertising stopped by user
      if ( heartRateAdvCancelled )
      {
        heartRateAdvCancelled = FALSE;
      }
      // if fast advertising switch to slow
      else if ( GAP_GetParamValue( TGAP_GEN_DISC_ADV_INT_MIN ) == DEFAULT_FAST_ADV_INTERVAL )
      {
        uint8 advState = TRUE;
        
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MIN, DEFAULT_SLOW_ADV_INTERVAL );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_INT_MAX, DEFAULT_SLOW_ADV_INTERVAL );
        GAP_SetParamValue( TGAP_GEN_DISC_ADV_MIN, DEFAULT_SLOW_ADV_DURATION );
        GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &advState );   
      }  
    }
    // if started
    else if (newState == GAPROLE_STARTED)
    {
      // Set the system ID from the bd addr
      uint8 systemId[DEVINFO_SYSTEM_ID_LEN];
      GAPRole_GetParameter(GAPROLE_BD_ADDR, systemId);
      
      // shift three bytes up
      systemId[7] = systemId[5];
      systemId[6] = systemId[4];
      systemId[5] = systemId[3];
      
      // set middle bytes to zero
      systemId[4] = 0;
      systemId[3] = 0;
      
      DevInfo_SetParameter(DEVINFO_SYSTEM_ID, DEVINFO_SYSTEM_ID_LEN, systemId);
    }
    
    gapProfileState = newState;
  }
  
  /*********************************************************************
   * @fn      heartRateCB
   *
   * @brief   Callback function for heart rate service.
   *
   * @param   event - service event
   *
   * @return  none
   */
  static void heartRateCB(uint8 event)
  {
    if (event == HEARTRATE_MEAS_NOTI_ENABLED)
    {
      // if connected start periodic measurement
      if (gapProfileState == GAPROLE_CONNECTED)
      {
        osal_start_timerEx( heartRate_TaskID, HEART_PERIODIC_EVT, DEFAULT_HEARTRATE_PERIOD );
      } 
    }
    else if (event == HEARTRATE_MEAS_NOTI_DISABLED)
    {
      // stop periodic measurement
      osal_stop_timerEx( heartRate_TaskID, HEART_PERIODIC_EVT );
    }
    else if (event == HEARTRATE_COMMAND_SET)
    {
      // reset energy expended
      heartRateEnergy = 0;
    }
  }
  
  
  /*********************************************************************
   * @fn      heartRatePeriodicTask
   *
   * @brief   Perform a periodic heart rate application task.
   *
   * @param   none
   *
   * @return  none
   */
  static void heartRatePeriodicTask( void )
  {
    if (gapProfileState == GAPROLE_CONNECTED)
    {
      // send heart rate measurement notification
      heartRateMeasNotify();
      
      // Restart timer
      osal_start_timerEx( heartRate_TaskID, HEART_PERIODIC_EVT, DEFAULT_HEARTRATE_PERIOD );
    }
  }
  
  
  
  /*********************************************************************
  *********************************************************************/
  