Windows上的应用软件如何接收诺为翻页笔的APP通道数据
诺为翻页笔对电脑来说,就是一个和鼠标、键盘一样的HID(Human Interface Device)设备。其所用的技术,和无线键盘、无线鼠标是一样的。
目前,如果在电脑上想获得翻页笔的按键信息,或者说想用翻页笔来控制电脑上的软件,有两种方法。一种是设置快捷键来获得翻页笔的按键信息,另一种是使用翻页笔的APP通道来获得数据。本文讲述的是Windows上的应用软件如何接收诺为翻页笔的APP通道数据,主要是程序实现。
可以把按键的短按、长按、双击定义为APP通道。本文所述的短按是按键抬起时触发,只要没有超过长按计时器的按键抬起,都是短按。本文所述的长按是持续按住按键超过长按计时器时触发。长按计时器一般为1秒,不同的产品可能会有差异。
一、诺为设备使用Norwii Presenter软件设置APP通道
1.1. 如何把按键的设置为APP通道。
1)【功能自定义】页面,点击UI显示的翻页笔按键图标或这个图标对应的蓝色文字打开按键自定义窗口。
2) 点击“短按功能”处“下拉框箭头”-“APP通道”进入APP通道窗口,在编辑框输入1~255数字(十进制)的APP通道值,点击“确定”后,在上个窗口点击“保存”。
1.2. 如何确定设置为APP通道成功了。
在【对码】页面,短按已经把短按定义为“APP通道”的按键,右侧的窗口中出现“APP通道”的键值,说明APP通道的自定义设置成功了。
1.3. 如何获取诺为翻页笔的VID和PID。
在【关于】页面,找到显示“固件信息”的行,右侧显示诺为翻页笔的VID和PID,这两个值在USB HID通信会使用到。页面显示的这两个值是省略了0x的16进制,使用的时候记得在前面加上0x,下图中VID是0x3243,PID是 0x0341,不同的产品,VID相同,PID可能不同。
二、USB HID通信
2.1. 引用到的文件和库。
1. #include <windows.h>
2. #include <vector>
3. #include <string> // Includes the string functions.
4. //#include <tchar.h> // Includes the string functions.
5. #include <setupapi.h> // Includes the SetupAPI.
6. #include "hidapi.h"
7.
8. using namespace std;
9. #pragma comment (lib, "setupapi.lib")
2.2. 通过VID、PID获取设备的数据通道的HID地址关键词;如VID:0x3243,PID:0x0341,它的HID地址关键词是“#vid_3243&pid_0341&mi_02&col01#”。
1. string GetNorwiiKeyPath(const int vid, const int pid)
2. {
3. if (vid == 0x023243 & pid == 0x0352)
4. return "_dev_vid&023243_pid&0352_rev&0001_";
5. if (vid == 0x023243 & pid == 0x0361)
6. return "_dev_vid&023243_pid&0361_rev&0001_";
7. std::vector<string> vtHidPathKey;
8. vtHidPathKey.push_back("vid_3243&pid_0111&mi_00&col03");
9. vtHidPathKey.push_back("vid_3243&pid_0121&mi_01&col02");
10. vtHidPathKey.push_back("vid_3243&pid_0221&mi_01&col02");
11. vtHidPathKey.push_back("#vid_3243&pid_0131&mi_00&col03#");
12. vtHidPathKey.push_back("#vid_3243&pid_1234&mi_00&col03#");
13. vtHidPathKey.push_back("#vid_3243&pid_0112&mi_00&col03#");
14. vtHidPathKey.push_back("#vid_3243&pid_0212&mi_00&col03#");
15. vtHidPathKey.push_back("#vid_3243&pid_0122&mi_01&col03#");
16. vtHidPathKey.push_back("#vid_3243&pid_0122&mi_02#");
17. vtHidPathKey.push_back("#vid_3243&pid_0222&mi_01&col03#");
18. vtHidPathKey.push_back("#vid_3243&pid_0341&mi_02&col01#");
19. vtHidPathKey.push_back("#vid_3243&pid_0342&mi_02&col01#");
20. vtHidPathKey.push_back("#vid_3243&pid_0381&mi_02&col01#");
21. vtHidPathKey.push_back("#vid_3243&pid_0382&mi_02&col01#");
22.
23. string sKeyPath;
24. char temp_char[20] = { 0 };
25. sprintf(temp_char, "&pid_%04x&", pid);
26. std::vector<string>::iterator iter = vtHidPathKey.begin();
27. for (; iter != vtHidPathKey.end(); iter++)
28. {
29. string sTempKeyPath = *iter;
30. if (sTempKeyPath.find(temp_char) != string::npos)
31. {
32. sKeyPath = sTempKeyPath;
33. break;
34. }
35. }
36. return sKeyPath;
37. }
2.3. 遍历Windows系统所有USB和蓝牙设备的HID地址。
1. string GetDevicePath(HDEVINFO deviceInfoSet, SP_DEVICE_INTERFACE_DATA deviceInterfaceData)
2. {
3. DWORD bufferSize = 0;
4. SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData, NULL, 0, &bufferSize, NULL);
5.
6. SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL;
7. device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*)malloc(bufferSize);
8. device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
9.
10. string sDevicePath =
11. SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData, device_interface_detail_data, bufferSize, &bufferSize, NULL) ?
12. device_interface_detail_data->DevicePath : "";
13. free(device_interface_detail_data);
14. return sDevicePath;
15. }
16.
17. vector<string> EnumeratePath()
18. {
19. vector<string> devices;
20. GUID InterfaceClassGuid = { 0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };
21. //GUID InterfaceClassGuid = { 0xa5dcbf10L, 0x6530, 0x11d2,{0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed} };
22. HDEVINFO device_info_set = INVALID_HANDLE_VALUE;
23. //Get information for all the devices belonging to the HID class.
24. device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
25. if (device_info_set != INVALID_HANDLE_VALUE)
26. {
27. int device_index = 0;
28. SP_DEVINFO_DATA devinfo_data;
29. memset(&devinfo_data, 0x0, sizeof(devinfo_data));
30. devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
31.
32. while (SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data))
33. {
34. device_index += 1;
35.
36. SP_DEVICE_INTERFACE_DATA device_interface_data;
37. device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
38. int deviceInterfaceIndex = 0;
39.
40. while (SetupDiEnumDeviceInterfaces(device_info_set, &devinfo_data, &InterfaceClassGuid, deviceInterfaceIndex, &device_interface_data))
41. {
42. deviceInterfaceIndex++;
43. string devicePath = GetDevicePath(device_info_set, device_interface_data);
44.
45. devices.push_back(devicePath);
46. }
47. }
48. SetupDiDestroyDeviceInfoList(device_info_set);
49. }
50. return devices;
51. }
2.4. 获取诺为翻页笔的VID、PID通信的HID地址。
遍历Windows系统所有USB和蓝牙设备HID地址是否有一条含有前面介绍“HID地址关键词”信息,如果含有,该条HID地址就是诺为翻页笔的VID、PID通信的HID地址。如有一条HID地址“\\?\hid#vid_3243&pid_0341&mi_02&col01#7&34f72933&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}”含有HID地址关键词“#vid_3243&pid_0341&mi_02&col01#”。
1. string GetNorwiiHidPath(const string &sKeyPath)
2. {
3. string sCustomizedPath;
4. vector<string> vtAllPath = EnumeratePath();
5. for (int k = 0; k < vtAllPath.size(); k++)
6. {
7. string rString = vtAllPath[k];
8. if (rString.find(sKeyPath) != string::npos)
9. {
10. if (rString.find("_dev_vid&023243_pid&0352_rev&0001_") != string::npos ||
11. rString.find("_dev_vid&023243_pid&0361_rev&0001_") != string::npos)
12. {
13. if (rString.find("&col03#") == string::npos)
14. continue;
15. }
16. sCustomizedPath = rString;
17. break;
18. }
19. }
20. return sCustomizedPath;
21. }
2.5. 打开HID地址进行通信,调用到hidapi.h文件的类库。
1. string szHidPath = "\\?\hid#vid_3243&pid_0341&mi_02&col01#7&34f72933&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
2. hid_device *hidHandle = hid_open_path(szHidPath.c_str());
2.6. 关闭已打开的HID地址,调用到hidapi.h文件的类库。
1. if (hidHandle)
2. {
3. hid_close(hidHandle);
4. }
2.7.接收Hid数据,判断收到APP通道数据,调用到hidapi.h文件的类库。
1. size_t length = 8;
2. unsigned char data[8] = { 0 };
3. int res = hid_read_timeout(hidHandle, data, length, 5);
4. if (res > 0 && data[0] > 0x00)
5. {
6. if (data[0] == 0x03 && data[1] == 0x31 && data[2] == 0x00 && \
7. data[3] == 0x00 && data[4] == 0x02)
8. {
9. int appChannel = data[5];//获取到APP通道值
10. std::cout << "APP Channel" << appChannel << "\n";
11. }
12. }
2.8. 下面代码为整体通信代码。
1. struct hid_device_ {
2. HANDLE device_handle;
3. BOOL blocking;
4. USHORT output_report_length;
5. size_t input_report_length;
6. void *last_error_str;
7. DWORD last_error_num;
8. BOOL read_pending;
9. char *read_buf;
10. OVERLAPPED ol;
11. };
12.
13. int main()
14. {
15. string szKeyPath = GetNorwiiKeyPath(0x3243, 0x341);
16. if (!szKeyPath.empty())
17. {
18. string szHidPath = GetNorwiiHidPath(szKeyPath);
19. if (!szHidPath.empty())
20. {
21. hid_device *hidHandle = hid_open_path(szHidPath.c_str());
22. if (hidHandle)
23. {
24. std::cout << "connect success!\n";
25. int nPendingCount = 0;
26. size_t length = 8;
27. unsigned char data[8] = { 0 };
28. while (true)
29. {
30. int res = hid_read_timeout(hidHandle, data, length, 5);
31. if (res > 0 && data[0] > 0x00)
32. {
33. nPendingCount = 0;
34. if (data[0] == 0x03 && data[1] == 0x31 && data[2] == 0x00 && \
35. data[3] == 0x00 && data[4] == 0x02)
36. {
37. int appChannel = data[5];//获取到APP通道值
38. std::cout << "APP Channel" << appChannel << "\n";
39. }
40. else if (!hidHandle->read_pending)
41. {
42. nPendingCount++;
43. if (nPendingCount > 100)
44. break;
45. }
46. }
47. }
48. hid_close(hidHandle);
49. }
50. }
51. }
52.
53. std::cout << "Hello World!\n";
54. system("pause");
55. }
2.9. 参考代码地址:
https://www.norwii.com/downloads/presenter/windows/app_channel.zip