/********************************** (C) COPYRIGHT ******************************* * File Name : battservice.c * Author : WCH * Version : V1.0 * Date : 2018/12/10 * Description : 电池服务 ********************************************************************************* * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. * Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics. *******************************************************************************/ /********************************************************************* * INCLUDES */ #include "CONFIG.h" #include "hiddev.h" #include "battservice.h" /********************************************************************* * MACROS */ /********************************************************************* * CONSTANTS */ // ADC voltage levels #define BATT_ADC_LEVEL_3V 409 #define BATT_ADC_LEVEL_2V 273 #define BATT_LEVEL_VALUE_IDX 2 // Position of battery level in attribute array #define BATT_LEVEL_VALUE_CCCD_IDX 3 // Position of battery level CCCD in attribute array #define BATT_LEVEL_VALUE_LEN 1 /********************************************************************* * TYPEDEFS */ /********************************************************************* * GLOBAL VARIABLES */ // Battery service const uint8_t battServUUID[ATT_BT_UUID_SIZE] = { LO_UINT16(BATT_SERV_UUID), HI_UINT16(BATT_SERV_UUID)}; // Battery level characteristic const uint8_t battLevelUUID[ATT_BT_UUID_SIZE] = { LO_UINT16(BATT_LEVEL_UUID), HI_UINT16(BATT_LEVEL_UUID)}; /********************************************************************* * EXTERNAL VARIABLES */ /********************************************************************* * EXTERNAL FUNCTIONS */ /********************************************************************* * LOCAL VARIABLES */ // Application callback static battServiceCB_t battServiceCB; // Measurement setup callback static battServiceSetupCB_t battServiceSetupCB = NULL; // Measurement teardown callback static battServiceTeardownCB_t battServiceTeardownCB = NULL; // Measurement calculation callback static battServiceCalcCB_t battServiceCalcCB = NULL; static uint16_t battMinLevel = BATT_ADC_LEVEL_2V; // For VDD/3 measurements static uint16_t battMaxLevel = BATT_ADC_LEVEL_3V; // For VDD/3 measurements // Critical battery level setting static uint8_t battCriticalLevel; // ADC channel to be used for reading //static uint8_t battServiceAdcCh = HAL_ADC_CHANNEL_VDD; /********************************************************************* * Profile Attributes - variables */ // Battery Service attribute static const gattAttrType_t battService = {ATT_BT_UUID_SIZE, battServUUID}; // Battery level characteristic static uint8_t battLevelProps = GATT_PROP_READ | GATT_PROP_NOTIFY; static uint8_t battLevel = 100; static gattCharCfg_t battLevelClientCharCfg[GATT_MAX_NUM_CONN]; // HID Report Reference characteristic descriptor, battery level static uint8_t hidReportRefBattLevel[HID_REPORT_REF_LEN] = { HID_RPT_ID_BATT_LEVEL_IN, HID_REPORT_TYPE_INPUT}; /********************************************************************* * Profile Attributes - Table */ static gattAttribute_t battAttrTbl[] = { // Battery Service { {ATT_BT_UUID_SIZE, primaryServiceUUID}, /* type */ GATT_PERMIT_READ, /* permissions */ 0, /* handle */ (uint8_t *)&battService /* pValue */ }, // Battery Level Declaration { {ATT_BT_UUID_SIZE, characterUUID}, GATT_PERMIT_READ, 0, &battLevelProps}, // Battery Level Value { {ATT_BT_UUID_SIZE, battLevelUUID}, GATT_PERMIT_READ, 0, &battLevel}, // Battery Level Client Characteristic Configuration { {ATT_BT_UUID_SIZE, clientCharCfgUUID}, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, (uint8_t *)&battLevelClientCharCfg}, // HID Report Reference characteristic descriptor, batter level input { {ATT_BT_UUID_SIZE, reportRefUUID}, GATT_PERMIT_READ, 0, hidReportRefBattLevel} }; /********************************************************************* * LOCAL FUNCTIONS */ static bStatus_t battReadAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t *pLen, uint16_t offset, uint16_t maxLen, uint8_t method); static bStatus_t battWriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method); static void battNotifyCB(linkDBItem_t *pLinkItem); static uint8_t battMeasure(void); static void battNotifyLevel(void); /********************************************************************* * PROFILE CALLBACKS */ // Battery Service Callbacks gattServiceCBs_t battCBs = { battReadAttrCB, // Read callback function pointer battWriteAttrCB, // Write callback function pointer NULL // Authorization callback function pointer }; /********************************************************************* * PUBLIC FUNCTIONS */ /********************************************************************* * @fn Batt_AddService * * @brief Initializes the Battery Service by registering * GATT attributes with the GATT server. * * @return Success or Failure */ bStatus_t Batt_AddService(void) { uint8_t status = SUCCESS; // Initialize Client Characteristic Configuration attributes GATTServApp_InitCharCfg(INVALID_CONNHANDLE, battLevelClientCharCfg); // Register GATT attribute list and CBs with GATT Server App status = GATTServApp_RegisterService(battAttrTbl, GATT_NUM_ATTRS(battAttrTbl), GATT_MAX_ENCRYPT_KEY_SIZE, &battCBs); return (status); } /********************************************************************* * @fn Batt_Register * * @brief Register a callback function with the Battery Service. * * @param pfnServiceCB - Callback function. * * @return None. */ extern void Batt_Register(battServiceCB_t pfnServiceCB) { battServiceCB = pfnServiceCB; } /********************************************************************* * @fn Batt_SetParameter * * @brief Set a Battery Service parameter. * * @param param - Profile parameter ID * @param len - length of data to right * @param 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_t will be cast to * uint16_t pointer). * * @return bStatus_t */ bStatus_t Batt_SetParameter(uint8_t param, uint8_t len, void *value) { bStatus_t ret = SUCCESS; switch(param) { case BATT_PARAM_CRITICAL_LEVEL: battCriticalLevel = *((uint8_t *)value); // If below the critical level and critical state not set, notify it if(battLevel < battCriticalLevel) { battNotifyLevel(); } break; default: ret = INVALIDPARAMETER; break; } return (ret); } /********************************************************************* * @fn Batt_GetParameter * * @brief Get a Battery Service parameter. * * @param param - Profile parameter ID * @param value - pointer to data to get. This is dependent on * the parameter ID and WILL be cast to the appropriate * data type (example: data type of uint16_t will be cast to * uint16_t pointer). * * @return bStatus_t */ bStatus_t Batt_GetParameter(uint8_t param, void *value) { bStatus_t ret = SUCCESS; switch(param) { case BATT_PARAM_LEVEL: *((uint8_t *)value) = battLevel; break; case BATT_PARAM_CRITICAL_LEVEL: *((uint8_t *)value) = battCriticalLevel; break; case BATT_PARAM_SERVICE_HANDLE: *((uint16_t *)value) = GATT_SERVICE_HANDLE(battAttrTbl); break; case BATT_PARAM_BATT_LEVEL_IN_REPORT: { hidRptMap_t *pRpt = (hidRptMap_t *)value; pRpt->id = hidReportRefBattLevel[0]; pRpt->type = hidReportRefBattLevel[1]; pRpt->handle = battAttrTbl[BATT_LEVEL_VALUE_IDX].handle; pRpt->cccdHandle = battAttrTbl[BATT_LEVEL_VALUE_CCCD_IDX].handle; pRpt->mode = HID_PROTOCOL_MODE_REPORT; } break; default: ret = INVALIDPARAMETER; break; } return (ret); } /********************************************************************* * @fn Batt_MeasLevel * * @brief Measure the battery level and update the battery * level value in the service characteristics. If * the battery level-state characteristic is configured * for notification and the battery level has changed * since the last measurement, then a notification * will be sent. * * @return Success */ bStatus_t Batt_MeasLevel(void) { uint8_t level; level = battMeasure(); // If level has gone down if(level < battLevel) { // Update level battLevel = level; // Send a notification battNotifyLevel(); } return SUCCESS; } /********************************************************************* * @fn Batt_Setup * * @brief Set up which ADC source is to be used. Defaults to VDD/3. * * @param adc_ch - ADC Channel, e.g. HAL_ADC_CHN_AIN6 * @param minVal - max battery level * @param maxVal - min battery level * @param sCB - HW setup callback * @param tCB - HW tear down callback * @param cCB - percentage calculation callback * * @return none. */ void Batt_Setup(uint8_t adc_ch, uint16_t minVal, uint16_t maxVal, battServiceSetupCB_t sCB, battServiceTeardownCB_t tCB, battServiceCalcCB_t cCB) { //battServiceAdcCh = adc_ch; battMinLevel = minVal; battMaxLevel = maxVal; battServiceSetupCB = sCB; battServiceTeardownCB = tCB; battServiceCalcCB = cCB; } /********************************************************************* * @fn battReadAttrCB * * @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 * * @return Success or Failure */ static bStatus_t battReadAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t *pLen, uint16_t offset, uint16_t maxLen, uint8_t method) { uint16_t uuid; bStatus_t status = SUCCESS; // Make sure it's not a blob operation (no attributes in the profile are long) if(offset > 0) { return (ATT_ERR_ATTR_NOT_LONG); } uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]); // Measure battery level if reading level if(uuid == BATT_LEVEL_UUID) { uint8_t level; level = battMeasure(); // If level has gone down if(level < battLevel) { // Update level battLevel = level; } *pLen = 1; pValue[0] = battLevel; } else if(uuid == GATT_REPORT_REF_UUID) { *pLen = HID_REPORT_REF_LEN; tmos_memcpy(pValue, pAttr->pValue, HID_REPORT_REF_LEN); } else { status = ATT_ERR_ATTR_NOT_FOUND; } return (status); } /********************************************************************* * @fn battWriteAttrCB * * @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 * * @return Success or Failure */ static bStatus_t battWriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method) { bStatus_t status = SUCCESS; uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]); switch(uuid) { case GATT_CLIENT_CHAR_CFG_UUID: status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len, offset, GATT_CLIENT_CFG_NOTIFY); if(status == SUCCESS) { uint16_t charCfg = BUILD_UINT16(pValue[0], pValue[1]); if(battServiceCB) { (*battServiceCB)((charCfg == GATT_CFG_NO_OPERATION) ? BATT_LEVEL_NOTI_DISABLED : BATT_LEVEL_NOTI_ENABLED); } } break; default: status = ATT_ERR_ATTR_NOT_FOUND; break; } return (status); } /********************************************************************* * @fn battNotifyCB * * @brief Send a notification of the level state characteristic. * * @param connHandle - linkDB item * * @return None. */ static void battNotifyCB(linkDBItem_t *pLinkItem) { if(pLinkItem->stateFlags & LINK_CONNECTED) { uint16_t value = GATTServApp_ReadCharCfg(pLinkItem->connectionHandle, battLevelClientCharCfg); if(value & GATT_CLIENT_CFG_NOTIFY) { attHandleValueNoti_t noti; noti.pValue = GATT_bm_alloc(pLinkItem->connectionHandle, ATT_HANDLE_VALUE_NOTI, BATT_LEVEL_VALUE_LEN, NULL, 0); if(noti.pValue != NULL) { noti.handle = battAttrTbl[BATT_LEVEL_VALUE_IDX].handle; noti.len = BATT_LEVEL_VALUE_LEN; noti.pValue[0] = battLevel; if(GATT_Notification(pLinkItem->connectionHandle, ¬i, FALSE) != SUCCESS) { GATT_bm_free((gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI); } } } } } /********************************************************************* * @fn battMeasure * * @brief Measure the battery level with the ADC and return * it as a percentage 0-100%. * * @return Battery level. */ static uint8_t battMeasure(void) { uint16_t adc; uint8_t percent; // Call measurement setup callback if(battServiceSetupCB != NULL) { battServiceSetupCB(); } // Configure ADC and perform a read adc = 300; // Call measurement teardown callback if(battServiceTeardownCB != NULL) { battServiceTeardownCB(); } if(adc >= battMaxLevel) { percent = 100; } else if(adc <= battMinLevel) { percent = 0; } else { if(battServiceCalcCB != NULL) { percent = battServiceCalcCB(adc); } else { uint16_t range = battMaxLevel - battMinLevel + 1; // optional if you want to keep it even, otherwise just take floor of divide // range += (range & 1); range >>= 2; // divide by 4 percent = (uint8_t)((((adc - battMinLevel) * 25) + (range - 1)) / range); } } return percent; } /********************************************************************* * @fn battNotifyLevelState * * @brief Send a notification of the battery level state * characteristic if a connection is established. * * @return None. */ static void battNotifyLevel(void) { // Execute linkDB callback to send notification linkDB_PerformFunc(battNotifyCB); } /********************************************************************* * @fn Batt_HandleConnStatusCB * * @brief Battery Service link status change handler function. * * @param connHandle - connection handle * @param changeType - type of change * * @return none */ void Batt_HandleConnStatusCB(uint16_t connHandle, uint8_t changeType) { // Make sure this is not loopback connection if(connHandle != LOOPBACK_CONNHANDLE) { // Reset Client Char Config if connection has dropped if((changeType == LINKDB_STATUS_UPDATE_REMOVED) || ((changeType == LINKDB_STATUS_UPDATE_STATEFLAGS) && (!linkDB_Up(connHandle)))) { GATTServApp_InitCharCfg(connHandle, battLevelClientCharCfg); } } } /********************************************************************* *********************************************************************/