Windows上的应用软件如何接收诺为翻页笔的APP通道数据
诺为翻页笔对电脑来说,就是一个和鼠标、键盘一样的USB HID设备(Human Interface Device)。其所用的技术,和无线键盘、无线鼠标是一样的。
目前,如果想在电脑上获得翻页笔的按键信息,或者说想用翻页笔来控制电脑上的软件,有两种方法。一种是将翻页笔的按键功能自定义为电脑中的应用软件的快捷键,应用软件通过获得USB HID通道传来的快捷键,来触发相应的功能。这种自定义按键的方法,请参见诺为官方网站上的Norwii Presenter软件的说明书,有比较详细的介绍。另一种是先自定义翻页笔按键的APP通道的数据,应用软件通过获取APP通道的数据来接受控制。本文讲述的是Windows上的应用软件如何自定义按键的APP通道数据、读取诺为翻页笔的按键的APP通道数据,主要是程序实现。
这里的APP通道指的是一个用来和APP通信的诺为定义的HID通道。
可以把按键的短按、长按、双击自定义为APP通道的数据。本文所述的短按是按键抬起时触发,只要没有超过长按计时器的按键抬起,都是短按。长按是持续按住按键超过长按计时器时触发。长按计时器一般为1秒,不同的产品或按键可能会有差异。
一、诺为设备使用Norwii Presenter软件设置APP通道
1. 在Norwii Presenter软件的【功能自定义】页面,点击软件界面上显示的翻页笔按键图标或这个图标对应的蓝色文字,会打开按键自定义窗口。翻页笔的按键模式有3种或者4种,翻页笔的按键模式通过同时长按翻页笔的上、下翻页键进行切换,下图中有4种模式。用户可以选择按键模式中的一种进行自定义,自定义的结果也只在这种模式下有效。
2. 点击“短按功能”处“下拉框箭头”-“APP通道”进入APP通道窗口,在编辑框输入十进制数字1~255的一个数值作为APP通道的值,点击“确定”后,在上个窗口点击“保存”。
3. 如何确定将翻页笔按键的值自定义为APP通道的数据是成功的?
在【对码】页面,短按已经把短按功能自定义为“APP通道”的按键,窗口右下方白色框中出现“APP通道+数字”,如“APP通道 1”,说明翻页笔按键的APP通道的自定义设置成功了。按照这种方法,也可能对按键的长按功能进行自定义。
4. 如何获取诺为翻页笔的VID和PID。
在【关于】页面,找到显示“固件信息”的行,右侧显示诺为翻页笔的VID和PID,这两个值在USB HID通信中会使用到。页面显示的这两个值是省略了前缀0x的16进制数值,在程序中使用的时候记得加上前缀0x,下图中VID是16进制数值0x3243,PID是16进制数值 0x0341,诺为翻页笔的VID都是相同的,不同型号的产品的PID是不同的,同一型号的产品的PID一般是相同的,但同一型号的产品、不同的主控芯片也会有不同的PID。
二、USB HID通信
1. 引用到的文件和库。
1. #include <windows.h>
2. #include <vector>
3. #include <string> // Includes the string functions.
4. #include <setupapi.h> // Includes the SetupAPI.
5. #include "hidapi.h"
6. using namespace std;
7. #pragma comment (lib, "setupapi.lib")
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. }
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. }
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#”,这个HID设备接口的路径就是诺为翻页笔的VID、PID的HID通信地址。
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. }
5. 打开HID设备接口的路径进行通信,要调用hidapi.h文件中的类库函数hid_open_path
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());
6. 关闭已打开的HID设备接口的路径,要调用hidapi.h文件中的类库函数hid_close。
1. if (hidHandle)
2. {
3. hid_close(hidHandle);
4. }
7.接收HID数据,判断收到APP通道数据,要调用hidapi.h文件中的类库函数hid_read_timeout。
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 appPath = data[5];//获取到APP通道值
10. std::cout << "APP Path" << appPath << "\n";
11. }
12. }
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 appPath = data[5];//获取到APP通道值
38. std::cout << "APP Path" << appPath << "\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_path.zip