/********************************** (C) COPYRIGHT ******************************* * File Name : hidkbd.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 "devinfoservice.h" #include "battservice.h" #include "hidkbdservice.h" #include "hiddev.h" #include "hidkbd.h" /********************************************************************* * MACROS */ // HID keyboard input report length #define HID_KEYBOARD_IN_RPT_LEN 8 // HID LED output report length #define HID_LED_OUT_RPT_LEN 1 /********************************************************************* * CONSTANTS */ // Param update delay #define START_PARAM_UPDATE_EVT_DELAY 12800 // Param update delay #define START_PHY_UPDATE_DELAY 1600 // HID idle timeout in msec; set to zero to disable timeout #define DEFAULT_HID_IDLE_TIMEOUT 60000 // Minimum connection interval (units of 1.25ms) #define DEFAULT_DESIRED_MIN_CONN_INTERVAL 8 // Maximum connection interval (units of 1.25ms) #define DEFAULT_DESIRED_MAX_CONN_INTERVAL 8 // Slave latency to use if parameter update request #define DEFAULT_DESIRED_SLAVE_LATENCY 0 // Supervision timeout value (units of 10ms) #define DEFAULT_DESIRED_CONN_TIMEOUT 500 // Default passcode #define DEFAULT_PASSCODE 0 // Default GAP pairing mode #define DEFAULT_PAIRING_MODE GAPBOND_PAIRING_MODE_WAIT_FOR_REQ // Default MITM mode (TRUE to require passcode or OOB when pairing) #define DEFAULT_MITM_MODE FALSE // Default bonding mode, TRUE to bond #define DEFAULT_BONDING_MODE TRUE // Default GAP bonding I/O capabilities #define DEFAULT_IO_CAPABILITIES GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT // Battery level is critical when it is less than this % #define DEFAULT_BATT_CRITICAL_LEVEL 6 /********************************************************************* * TYPEDEFS */ /********************************************************************* * GLOBAL VARIABLES */ // Task ID static uint8_t hidEmuTaskId = INVALID_TASK_ID; /********************************************************************* * EXTERNAL VARIABLES */ /********************************************************************* * EXTERNAL FUNCTIONS */ /********************************************************************* * LOCAL VARIABLES */ // GAP Profile - Name attribute for SCAN RSP data static uint8_t scanRspData[] = { 0x0D, // length of this data GAP_ADTYPE_LOCAL_NAME_COMPLETE, // AD Type = Complete local name 'B', 'L', 'E', ' ', 'K', 'e', 'y', 'b', 'o', 'a', 'r', 'd', // connection interval range 0x05, // length of this data GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE, LO_UINT16(DEFAULT_DESIRED_MIN_CONN_INTERVAL), // 100ms HI_UINT16(DEFAULT_DESIRED_MIN_CONN_INTERVAL), LO_UINT16(DEFAULT_DESIRED_MAX_CONN_INTERVAL), // 1s HI_UINT16(DEFAULT_DESIRED_MAX_CONN_INTERVAL), // service UUIDs 0x05, // length of this data GAP_ADTYPE_16BIT_MORE, LO_UINT16(HID_SERV_UUID), HI_UINT16(HID_SERV_UUID), LO_UINT16(BATT_SERV_UUID), HI_UINT16(BATT_SERV_UUID), // Tx power level 0x02, // length of this data GAP_ADTYPE_POWER_LEVEL, 0 // 0dBm }; // Advertising data static uint8_t advertData[] = { // flags 0x02, // length of this data GAP_ADTYPE_FLAGS, GAP_ADTYPE_FLAGS_LIMITED | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED, // appearance 0x03, // length of this data GAP_ADTYPE_APPEARANCE, LO_UINT16(GAP_APPEARE_HID_KEYBOARD), HI_UINT16(GAP_APPEARE_HID_KEYBOARD)}; // Device name attribute value static CONST uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "HID Keyboard"; // HID Dev configuration static hidDevCfg_t hidEmuCfg = { DEFAULT_HID_IDLE_TIMEOUT, // Idle timeout HID_FEATURE_FLAGS // HID feature flags }; static uint16_t hidEmuConnHandle = GAP_CONNHANDLE_INIT; /********************************************************************* * LOCAL FUNCTIONS */ static void hidEmu_ProcessTMOSMsg(tmos_event_hdr_t *pMsg); static void hidEmuSendKbdReport(uint8_t keycode); static uint8_t hidEmuRcvReport(uint8_t len, uint8_t *pData); static uint8_t hidEmuRptCB(uint8_t id, uint8_t type, uint16_t uuid, uint8_t oper, uint16_t *pLen, uint8_t *pData); static void hidEmuEvtCB(uint8_t evt); static void hidEmuStateCB(gapRole_States_t newState, gapRoleEvent_t *pEvent); /********************************************************************* * PROFILE CALLBACKS */ static hidDevCB_t hidEmuHidCBs = { hidEmuRptCB, hidEmuEvtCB, NULL, hidEmuStateCB}; /********************************************************************* * PUBLIC FUNCTIONS */ /********************************************************************* * @fn HidEmu_Init * * @brief Initialization function for the HidEmuKbd 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 TMOS. This ID should be * used to send messages and set timers. * * @return none */ void HidEmu_Init() { hidEmuTaskId = TMOS_ProcessEventRegister(HidEmu_ProcessEvent); // Setup the GAP Peripheral Role Profile { uint8_t initial_advertising_enable = TRUE; // Set the GAP Role Parameters GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable); GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData); GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, sizeof(scanRspData), scanRspData); } // Set the GAP Characteristics GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, (void *)attDeviceName); // Setup the GAP Bond Manager { uint32_t passkey = DEFAULT_PASSCODE; uint8_t pairMode = DEFAULT_PAIRING_MODE; uint8_t mitm = DEFAULT_MITM_MODE; uint8_t ioCap = DEFAULT_IO_CAPABILITIES; uint8_t bonding = DEFAULT_BONDING_MODE; GAPBondMgr_SetParameter(GAPBOND_PERI_DEFAULT_PASSCODE, sizeof(uint32_t), &passkey); GAPBondMgr_SetParameter(GAPBOND_PERI_PAIRING_MODE, sizeof(uint8_t), &pairMode); GAPBondMgr_SetParameter(GAPBOND_PERI_MITM_PROTECTION, sizeof(uint8_t), &mitm); GAPBondMgr_SetParameter(GAPBOND_PERI_IO_CAPABILITIES, sizeof(uint8_t), &ioCap); GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(uint8_t), &bonding); } // Setup Battery Characteristic Values { uint8_t critical = DEFAULT_BATT_CRITICAL_LEVEL; Batt_SetParameter(BATT_PARAM_CRITICAL_LEVEL, sizeof(uint8_t), &critical); } // Set up HID keyboard service Hid_AddService(); // Register for HID Dev callback HidDev_Register(&hidEmuCfg, &hidEmuHidCBs); // Setup a delayed profile startup tmos_set_event(hidEmuTaskId, START_DEVICE_EVT); } /********************************************************************* * @fn HidEmu_ProcessEvent * * @brief HidEmuKbd 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 TMOS 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_t HidEmu_ProcessEvent(uint8_t task_id, uint16_t events) { static uint8_t send_char = 4; if(events & SYS_EVENT_MSG) { uint8_t *pMsg; if((pMsg = tmos_msg_receive(hidEmuTaskId)) != NULL) { hidEmu_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg); // Release the TMOS message tmos_msg_deallocate(pMsg); } // return unprocessed events return (events ^ SYS_EVENT_MSG); } if(events & START_DEVICE_EVT) { return (events ^ START_DEVICE_EVT); } if(events & START_PARAM_UPDATE_EVT) { // Send connect param update request GAPRole_PeripheralConnParamUpdateReq(hidEmuConnHandle, DEFAULT_DESIRED_MIN_CONN_INTERVAL, DEFAULT_DESIRED_MAX_CONN_INTERVAL, DEFAULT_DESIRED_SLAVE_LATENCY, DEFAULT_DESIRED_CONN_TIMEOUT, hidEmuTaskId); return (events ^ START_PARAM_UPDATE_EVT); } if(events & START_PHY_UPDATE_EVT) { // start phy update PRINT("Send Phy Update %x...\n", GAPRole_UpdatePHY(hidEmuConnHandle, 0, GAP_PHY_BIT_LE_2M, GAP_PHY_BIT_LE_2M, 0)); return (events ^ START_PHY_UPDATE_EVT); } if(events & START_REPORT_EVT) { hidEmuSendKbdReport(send_char); send_char += 3; if(send_char >= 29) send_char = 4; hidEmuSendKbdReport(0x00); tmos_start_task(hidEmuTaskId, START_REPORT_EVT, 2000); return (events ^ START_REPORT_EVT); } return 0; } /********************************************************************* * @fn hidEmu_ProcessTMOSMsg * * @brief Process an incoming task message. * * @param pMsg - message to process * * @return none */ static void hidEmu_ProcessTMOSMsg(tmos_event_hdr_t *pMsg) { switch(pMsg->event) { default: break; } } /********************************************************************* * @fn hidEmuSendKbdReport * * @brief Build and send a HID keyboard report. * * @param keycode - HID keycode. * * @return none */ static void hidEmuSendKbdReport(uint8_t keycode) { uint8_t buf[HID_KEYBOARD_IN_RPT_LEN]; buf[0] = 0; // Modifier keys buf[1] = 0; // Reserved buf[2] = keycode; // Keycode 1 buf[3] = 0; // Keycode 2 buf[4] = 0; // Keycode 3 buf[5] = 0; // Keycode 4 buf[6] = 0; // Keycode 5 buf[7] = 0; // Keycode 6 HidDev_Report(HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT, HID_KEYBOARD_IN_RPT_LEN, buf); } /********************************************************************* * @fn hidEmuStateCB * * @brief GAP state change callback. * * @param newState - new state * * @return none */ static void hidEmuStateCB(gapRole_States_t newState, gapRoleEvent_t *pEvent) { switch(newState & GAPROLE_STATE_ADV_MASK) { case GAPROLE_STARTED: { uint8_t ownAddr[6]; GAPRole_GetParameter(GAPROLE_BD_ADDR, ownAddr); GAP_ConfigDeviceAddr(ADDRTYPE_STATIC, ownAddr); PRINT("Initialized..\n"); } break; case GAPROLE_ADVERTISING: if(pEvent->gap.opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT) { PRINT("Advertising..\n"); } break; case GAPROLE_CONNECTED: if(pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT) { gapEstLinkReqEvent_t *event = (gapEstLinkReqEvent_t *)pEvent; // get connection handle hidEmuConnHandle = event->connectionHandle; tmos_start_task(hidEmuTaskId, START_PARAM_UPDATE_EVT, START_PARAM_UPDATE_EVT_DELAY); PRINT("Connected..\n"); } break; case GAPROLE_CONNECTED_ADV: if(pEvent->gap.opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT) { PRINT("Connected Advertising..\n"); } break; case GAPROLE_WAITING: if(pEvent->gap.opcode == GAP_END_DISCOVERABLE_DONE_EVENT) { PRINT("Waiting for advertising..\n"); } else if(pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT) { PRINT("Disconnected.. Reason:%x\n", pEvent->linkTerminate.reason); } else if(pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT) { PRINT("Advertising timeout..\n"); } // Enable advertising { uint8_t initial_advertising_enable = TRUE; // Set the GAP Role Parameters GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable); } break; case GAPROLE_ERROR: PRINT("Error %x ..\n", pEvent->gap.opcode); break; default: break; } } /********************************************************************* * @fn hidEmuRcvReport * * @brief Process an incoming HID keyboard report. * * @param len - Length of report. * @param pData - Report data. * * @return status */ static uint8_t hidEmuRcvReport(uint8_t len, uint8_t *pData) { // verify data length if(len == HID_LED_OUT_RPT_LEN) { // set LEDs return SUCCESS; } else { return ATT_ERR_INVALID_VALUE_SIZE; } } /********************************************************************* * @fn hidEmuRptCB * * @brief HID Dev report callback. * * @param id - HID report ID. * @param type - HID report type. * @param uuid - attribute uuid. * @param oper - operation: read, write, etc. * @param len - Length of report. * @param pData - Report data. * * @return GATT status code. */ static uint8_t hidEmuRptCB(uint8_t id, uint8_t type, uint16_t uuid, uint8_t oper, uint16_t *pLen, uint8_t *pData) { uint8_t status = SUCCESS; // write if(oper == HID_DEV_OPER_WRITE) { if(uuid == REPORT_UUID) { // process write to LED output report; ignore others if(type == HID_REPORT_TYPE_OUTPUT) { status = hidEmuRcvReport(*pLen, pData); } } if(status == SUCCESS) { status = Hid_SetParameter(id, type, uuid, *pLen, pData); } } // read else if(oper == HID_DEV_OPER_READ) { status = Hid_GetParameter(id, type, uuid, pLen, pData); } // notifications enabled else if(oper == HID_DEV_OPER_ENABLE) { tmos_start_task(hidEmuTaskId, START_REPORT_EVT, 500); } return status; } /********************************************************************* * @fn hidEmuEvtCB * * @brief HID Dev event callback. * * @param evt - event ID. * * @return HID response code. */ static void hidEmuEvtCB(uint8_t evt) { // process enter/exit suspend or enter/exit boot mode return; } /********************************************************************* *********************************************************************/