* Copyright (C) 2023 Xiaomi Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
#include <stdlib.h>
#include <string.h>
#include "bt_hid_device.h"
#include "bt_tools.h"
static int register_cmd(void* handle, int argc, char* argv[]);
static int unregister_cmd(void* handle, int argc, char* argv[]);
static int connect_cmd(void* handle, int argc, char* argv[]);
static int disconnect_cmd(void* handle, int argc, char* argv[]);
static int send_report_cmd(void* handle, int argc, char* argv[]);
static int send_keyboard_cmd(void* handle, int argc, char* argv[]);
static int send_mouse_cmd(void* handle, int argc, char* argv[]);
static int send_consumer_cmd(void* handle, int argc, char* argv[]);
static int unplug_cmd(void* handle, int argc, char* argv[]);
static int dump_cmd(void* handle, int argc, char* argv[]);
static bt_command_t g_hidd_tables[] = {
{ "register", register_cmd, 0, "\"register HID app: <type>(1:KEYBOARD, 2:MOUSE, 3:KBMS_COMBO) <transport>(0:BLE, 1:BREDR)\"" },
{ "unregister", unregister_cmd, 0, "\"unregister HID app \"" },
{ "connect", connect_cmd, 0, "\"connect HID host param: <address> \"" },
{ "disconnect", disconnect_cmd, 0, "\"disconnect HID host param: <address>\"" },
{ "send_report", send_report_cmd, 0, "\"send report param: <address> <report id> <report data> \"" },
{ "send_keyboard", send_keyboard_cmd, 0, "\"send keyboard report: <address> <modifier key> <normal key>\"" },
{ "send_mouse", send_mouse_cmd, 0, "\"send mouse report: <address> <X axises>(-127~128) <Y axises>(-127~128)\"" },
{ "send_consumer", send_consumer_cmd, 0, "\"send consumer report: <address> <consumer key>\"" },
{ "unplug", unplug_cmd, 0, "\"virtual unplug param: <address> \"" },
{ "dump", dump_cmd, 0, "\"dump HID device current state\"" },
};
static void* hidd_callbacks = NULL;
const static uint8_t s_hid_KB_report_desc[] = {
0x05, 0x01,
0x09, 0x06,
0xA1, 0x01,
0x05, 0x07,
0x19, 0xE0,
0x29, 0xE7,
0x15, 0x00,
0x25, 0x01,
0x95, 0x08,
0x75, 0x01,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x05,
0x95, 0x05,
0x75, 0x01,
0x91, 0x02,
0x95, 0x01,
0x75, 0x03,
0x91, 0x01,
0x05, 0x07,
0x19, 0x00,
0x29, 0x65,
0x15, 0x00,
0x25, 0x65,
0x95, 0x06,
0x75, 0x08,
0x81, 0x00,
0xC0
};
const static uint8_t s_hid_MS_report_desc[] = {
0x05, 0x01,
0x09, 0x02,
0xA1, 0x01,
0x09, 0x01,
0xA1, 0x00,
0x05, 0x09,
0x19, 0x01,
0x29, 0x03,
0x15, 0x00,
0x25, 0x01,
0x95, 0x03,
0x75, 0x01,
0x81, 0x02,
0x95, 0x01,
0x75, 0x05,
0x81, 0x01,
0x05, 0x01,
0x09, 0x30,
0x09, 0x31,
0x09, 0x38,
0x15, 0x81,
0x25, 0x7F,
0x75, 0x08,
0x95, 0x03,
0x81, 0x06,
0xC0,
0xC0
};
const static uint8_t s_hid_combo_report_desc[] = {
0x05, 0x0C,
0x09, 0x01,
0xA1, 0x01,
0x85, 0x01,
0x15, 0x00,
0x25, 0x01,
0x95, 0x01,
0x75, 0x01,
0x09, 0xCD,
0x81, 0x06,
0x0A, 0x83, 0x01,
0x81, 0x06,
0x09, 0xB5,
0x81, 0x06,
0x09, 0xB6,
0x81, 0x06,
0x09, 0xEA,
0x81, 0x06,
0x09, 0xE9,
0x81, 0x06,
0x0A, 0x23, 0x02,
0x81, 0x06,
0x0A, 0x24, 0x02,
0x81, 0x06,
0xC0,
0x05, 0x01,
0x09, 0x02,
0xA1, 0x01,
0x09, 0x01,
0xA1, 0x00,
0x85, 0x02,
0x05, 0x09,
0x19, 0x01,
0x29, 0x03,
0x15, 0x00,
0x25, 0x01,
0x95, 0x03,
0x75, 0x01,
0x81, 0x02,
0x95, 0x01,
0x75, 0x05,
0x81, 0x01,
0x05, 0x01,
0x09, 0x30,
0x09, 0x31,
0x09, 0x38,
0x15, 0x81,
0x25, 0x7F,
0x75, 0x08,
0x95, 0x03,
0x81, 0x06,
0xC0,
0xC0
};
static void usage(void)
{
printf("Usage:\n");
printf("\taddress: peer device address like 00:01:02:03:04:05\n");
printf("Commands:\n");
for (int i = 0; i < ARRAY_SIZE(g_hidd_tables); i++) {
printf("\t%-8s\t%s\n", g_hidd_tables[i].cmd, g_hidd_tables[i].help);
}
}
static void hidd_app_state_cb(void* cookie, hid_app_state_t state)
{
PRINT("%s, state: %s", __func__, (state == HID_APP_STATE_REGISTERED) ? "registered" : "not registed");
}
static void hidd_connection_state_cb(void* cookie, bt_address_t* addr, bool le_hid,
profile_connection_state_t state)
{
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
bt_addr_ba2str(addr, addr_str);
PRINT("%s, addr:%s, transport: %s, state:%d", __func__, addr_str, le_hid ? "le" : "br", state);
}
static void hidd_get_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type,
uint8_t rpt_id, uint16_t buffer_size)
{
uint8_t rpt_data[] = { 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
bt_addr_ba2str(addr, addr_str);
PRINT("%s, addr:%s, buffer size: %d", __func__, addr_str, buffer_size);
if (rpt_id != 0)
rpt_data[0] = rpt_id;
bt_hid_device_response_report(cookie, addr, rpt_type, rpt_data, sizeof(rpt_data));
}
static void hidd_set_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type,
uint16_t rpt_size, uint8_t* rpt_data)
{
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
bt_addr_ba2str(addr, addr_str);
PRINT("%s, addr:%s, report type: %d", __func__, addr_str, rpt_type);
lib_dumpbuffer("report data:", rpt_data, rpt_size);
bt_hid_device_report_error(cookie, addr, HID_STATUS_OK);
}
static void hidd_receive_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type,
uint16_t rpt_size, uint8_t* rpt_data)
{
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
bt_addr_ba2str(addr, addr_str);
PRINT("%s, addr:%s, report type: %d", __func__, addr_str, rpt_type);
lib_dumpbuffer("report data:", rpt_data, rpt_size);
}
static void hidd_virtual_unplug_cb(void* cookie, bt_address_t* addr)
{
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
bt_addr_ba2str(addr, addr_str);
PRINT("%s, addr:%s", __func__, addr_str);
}
enum {
APP_HID_DEVICE_KEYBOARD = 1,
APP_HID_DEVICE_MOUSE = 2,
APP_HID_DEVICE_KBMS_COMBO = 3
};
static int register_cmd(void* handle, int argc, char* argv[])
{
hid_device_sdp_settings_t hidd_setting;
const uint8_t* desc_list;
uint16_t desc_len;
int app_type;
int transport;
if (argc < 1)
return CMD_PARAM_NOT_ENOUGH;
app_type = atoi(argv[0]);
transport = (argc < 2) ? BT_TRANSPORT_BREDR : atoi(argv[1]);
if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE)
return CMD_INVALID_PARAM;
memset(&hidd_setting, 0, sizeof(hid_device_sdp_settings_t));
hidd_setting.name = "HID_Device_Demo";
hidd_setting.description = "A demo of HID Device implementation";
hidd_setting.provider = "Xiaomi Vela";
hidd_setting.hids_info.attr_mask = HID_ATTR_MASK_VIRTUAL_CABLE | HID_ATTR_MASK_RECONNECT_INITIATE | HID_ATTR_MASK_NORMALLY_CONNECTABLE ;
switch (app_type) {
case APP_HID_DEVICE_KEYBOARD:
hidd_setting.hids_info.sub_class = (uint8_t)COD_PERIPHERAL_KEYBOARD;
desc_list = s_hid_KB_report_desc;
desc_len = sizeof(s_hid_KB_report_desc);
break;
case APP_HID_DEVICE_MOUSE:
hidd_setting.hids_info.sub_class = (uint8_t)COD_PERIPHERAL_POINT;
desc_list = s_hid_MS_report_desc;
desc_len = sizeof(s_hid_MS_report_desc);
break;
default:
hidd_setting.hids_info.sub_class = (uint8_t)COD_PERIPHERAL_KEYORPOINT;
desc_list = s_hid_combo_report_desc;
desc_len = sizeof(s_hid_combo_report_desc);
break;
}
hidd_setting.hids_info.dsc_list = malloc(desc_len + 3);
if (!hidd_setting.hids_info.dsc_list) {
return CMD_ERROR;
}
hidd_setting.hids_info.vendor_id = 0x038F;
hidd_setting.hids_info.product_id = 0x1234;
hidd_setting.hids_info.version = 0x100;
hidd_setting.hids_info.dsc_list_length = (uint16_t)(desc_len + 3);
hidd_setting.hids_info.dsc_list[0] = HID_SDP_DESCRIPTOR_REPORT;
hidd_setting.hids_info.dsc_list[1] = (uint8_t)(desc_len & 0xFF);
hidd_setting.hids_info.dsc_list[2] = (uint8_t)(desc_len >> 8);
memcpy(hidd_setting.hids_info.dsc_list + 3, desc_list, desc_len);
bt_status_t ret = bt_hid_device_register_app(handle, &hidd_setting, transport == BT_TRANSPORT_BLE);
free(hidd_setting.hids_info.dsc_list);
if (ret != BT_STATUS_SUCCESS) {
if (ret == BT_STATUS_NO_RESOURCES) {
PRINT("HID app has registed, please unregister then try again");
}
return CMD_ERROR;
}
PRINT("HID device register app, type:%s", argv[0]);
return CMD_OK;
}
static int unregister_cmd(void* handle, int argc, char* argv[])
{
bt_status_t ret = bt_hid_device_unregister_app(handle);
if (ret != BT_STATUS_SUCCESS) {
if (ret == BT_STATUS_NOT_FOUND) {
PRINT("HID app isn't registed, please register then try again");
}
return CMD_ERROR;
}
PRINT("HID device unregister app");
return CMD_OK;
}
static int connect_cmd(void* handle, int argc, char* argv[])
{
bt_address_t addr;
if (argc < 1)
return CMD_PARAM_NOT_ENOUGH;
if (bt_addr_str2ba(argv[0], &addr) < 0)
return CMD_INVALID_ADDR;
if (bt_hid_device_connect(handle, &addr) != BT_STATUS_SUCCESS)
return CMD_ERROR;
PRINT("HID device connect host, address:%s", argv[0]);
return CMD_OK;
}
static int disconnect_cmd(void* handle, int argc, char* argv[])
{
bt_address_t addr;
if (argc < 1)
return CMD_PARAM_NOT_ENOUGH;
if (bt_addr_str2ba(argv[0], &addr) < 0)
return CMD_INVALID_ADDR;
if (bt_hid_device_disconnect(handle, &addr) != BT_STATUS_SUCCESS)
return CMD_ERROR;
PRINT("HID device disconnect host, address:%s", argv[0]);
return CMD_OK;
}
static void hex2str(char* src_str, uint8_t* dest_buf, uint8_t hex_number)
{
uint8_t i;
uint8_t lb, hb;
for (i = 0; i < hex_number; i++) {
lb = src_str[(i << 1) + 1];
hb = src_str[i << 1];
if (hb >= '0' && hb <= '9') {
dest_buf[i] = hb - '0';
} else if (hb >= 'A' && hb < 'G') {
dest_buf[i] = hb - 'A' + 10;
} else if (hb >= 'a' && hb < 'g') {
dest_buf[i] = hb - 'a' + 10;
} else {
dest_buf[i] = 0;
}
dest_buf[i] <<= 4;
if (lb >= '0' && lb <= '9') {
dest_buf[i] += lb - '0';
} else if (lb >= 'A' && lb < 'G') {
dest_buf[i] += lb - 'A' + 10;
} else if (lb >= 'a' && lb < 'g') {
dest_buf[i] += lb - 'a' + 10;
}
}
}
static int send_report_cmd(void* handle, int argc, char* argv[])
{
bt_address_t addr;
uint8_t report_id;
uint8_t report_data[8];
int report_length;
char* buffer;
int size;
if (argc < 3)
return CMD_PARAM_NOT_ENOUGH;
PRINT("%s, address:%s", __func__, argv[0]);
if (bt_addr_str2ba(argv[0], &addr) < 0)
return CMD_INVALID_ADDR;
report_id = atoi(argv[1]);
size = strlen(argv[2]) + 1;
buffer = (char*)malloc(size);
if (!buffer)
return CMD_ERROR;
memcpy(buffer, argv[2], size);
buffer[size - 1] = 0;
report_length = size / 2;
if (report_length > sizeof(report_data))
report_length = sizeof(report_data);
memset(report_data, 0, sizeof(report_data));
hex2str(buffer, report_data, report_length);
bt_status_t ret = bt_hid_device_send_report(handle, &addr, report_id, report_data, report_length);
free(buffer);
if (ret != BT_STATUS_SUCCESS) {
return CMD_ERROR;
}
return CMD_OK;
}
static int send_keyboard_cmd(void* handle, int argc, char* argv[])
{
bt_address_t addr;
uint8_t rpt_data[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
if (argc < 3)
return CMD_PARAM_NOT_ENOUGH;
PRINT("%s, address:%s", __func__, argv[0]);
if (bt_addr_str2ba(argv[0], &addr) < 0)
return CMD_INVALID_ADDR;
rpt_data[0] = strtol(argv[1], NULL, 16);
rpt_data[2] = strtol(argv[2], NULL, 16);
PRINT("modifier key: 0x%02X, normal key: 0x%02X", rpt_data[0], rpt_data[2]);
if (bt_hid_device_send_report(handle, &addr, 0, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS)
return CMD_ERROR;
memset(rpt_data, 0, sizeof(rpt_data));
if (bt_hid_device_send_report(handle, &addr, 0, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS)
return CMD_ERROR;
return CMD_OK;
}
static int send_mouse_cmd(void* handle, int argc, char* argv[])
{
bt_address_t addr;
int8_t rpt_data[4] = { 0x00, 0x00, 0x00, 0x00 };
if (argc < 3)
return CMD_PARAM_NOT_ENOUGH;
PRINT("%s, address:%s", __func__, argv[0]);
if (bt_addr_str2ba(argv[0], &addr) < 0)
return CMD_INVALID_ADDR;
rpt_data[1] = atoi(argv[1]);
rpt_data[2] = atoi(argv[2]);
PRINT("X axises: %d, Y axises: %d", rpt_data[0], rpt_data[2]);
if (bt_hid_device_send_report(handle, &addr, 0, (uint8_t*)rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS)
return CMD_ERROR;
memset(rpt_data, 0, sizeof(rpt_data));
if (bt_hid_device_send_report(handle, &addr, 0, (uint8_t*)rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS)
return CMD_ERROR;
return CMD_OK;
}
static int send_consumer_cmd(void* handle, int argc, char* argv[])
{
bt_address_t addr;
uint8_t rpt_data[2] = { 0x01, 0x00 };
if (argc < 2)
return CMD_PARAM_NOT_ENOUGH;
PRINT("%s, address:%s", __func__, argv[0]);
if (bt_addr_str2ba(argv[0], &addr) < 0)
return CMD_INVALID_ADDR;
rpt_data[1] = strtol(argv[1], NULL, 16);
PRINT("consumer key: 0x%02X", rpt_data[1]);
if (bt_hid_device_send_report(handle, &addr, 1, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS)
return CMD_ERROR;
rpt_data[1] = 0;
if (bt_hid_device_send_report(handle, &addr, 1, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS)
return CMD_ERROR;
return CMD_OK;
}
static int unplug_cmd(void* handle, int argc, char* argv[])
{
bt_address_t addr;
if (argc < 1)
return CMD_PARAM_NOT_ENOUGH;
if (bt_addr_str2ba(argv[0], &addr) < 0)
return CMD_INVALID_ADDR;
if (bt_hid_device_virtual_unplug(handle, &addr) != BT_STATUS_SUCCESS)
return CMD_ERROR;
PRINT("HID device virtual unplug success, address:%s", argv[0]);
return CMD_OK;
}
static int dump_cmd(void* handle, int argc, char* argv[])
{
return CMD_OK;
}
static const hid_device_callbacks_t hidd_test_cbs = {
sizeof(hid_device_callbacks_t),
hidd_app_state_cb,
hidd_connection_state_cb,
hidd_get_report_cb,
hidd_set_report_cb,
hidd_receive_report_cb,
hidd_virtual_unplug_cb,
};
int hidd_command_init(void* handle)
{
hidd_callbacks = bt_hid_device_register_callbacks(handle, &hidd_test_cbs);
return 0;
}
void hidd_command_uninit(void* handle)
{
bt_hid_device_unregister_callbacks(handle, hidd_callbacks);
}
int hidd_command_exec(void* handle, int argc, char* argv[])
{
int ret = CMD_USAGE_FAULT;
if (argc > 0)
ret = execute_command_in_table(handle, g_hidd_tables, ARRAY_SIZE(g_hidd_tables), argc, argv);
if (ret < 0)
usage();
return ret;
}