macOS上的应用软件如何接收诺为翻页笔的APP通道数据
诺为翻页笔对电脑来说,就是一个和鼠标、键盘一样的HID设备(Human Interface Device)。其所用的技术,和无线键盘、无线鼠标是一样的。
目前,如果想在电脑上获得翻页笔的按键信息,或者说想用翻页笔来控制电脑上的软件,有两种方法。一种是将翻页笔的按键功能自定义为电脑中的应用软件的快捷键,应用软件通过获得HID通道传来的快捷键,来触发相应的功能。这种自定义方法,请参见诺为官方网站上的Norwii Presenter软件的说明书,有比较详细的介绍。另一种是先自定义翻页笔按键的APP通道的数据,应用软件通过获取APP通道的数据来接受控制。本文讲述的是Windows上的应用软件如何自定义、读取诺为翻页笔的按键的APP通道数据,主要是程序实现。
可以把按键的短按、长按、双击自定义为APP通道的数据。本文所述的短按是按键抬起时触发,只要没有超过长按计时器的按键抬起,都是短按。长按是持续按住按键超过长按计时器时触发。长按计时器一般为1秒,不同的产品或按键可能会有差异。
一、诺为设备的设置和信息
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. Frameworks:IOKit.framework
2. 导入头文件
#import <IOKit/hid/IOHIDLib.h>
3. 初始化IOHIDManager
IOHIDManagerRef managerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
4. 进行配对设置,可以过滤其他USB设备
1) 无配对设备
IOHIDManagerSetDeviceMatching(managerRef, NULL);
2)单类设备配对(其中“pid”和“vid”分别替换为Presenter软件中查到的值)
NSMutableDictionary* dict= [NSMutableDictionary dictionary];
[dict setValue:[NSNumber numberWithLong:pid] forKey:[NSString stringWithCString:kIOHIDProductIDKey encoding:NSUTF8StringEncoding]];
[dict setValue:[NSNumber numberWithLong:vid] forKey:[NSString stringWithCString:kIOHIDVendorIDKey encoding:NSUTF8StringEncoding]];
IOHIDManagerSetDeviceMatching(managerRef, (__bridge CFMutableDictionaryRef)dict);
3)多种设备配对设置
NSMutableArray *arr = [NSMutableArray array];
[arr addObject:dict];
IOHIDManagerSetDeviceMatchingMultiple(managerRef, (__bridge CFMutableArrayRef)arr);
5. 注册插拔设备的callback
IOHIDManagerRegisterDeviceMatchingCallback(managerRef, &HandleDeviceMatchingCallback, NULL);
IOHIDManagerRegisterDeviceRemovalCallback(managerRef, &HandleDeviceRemovalCallback, NULL);
6. 加入RunLoop
IOHIDManagerScheduleWithRunLoop(managerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
7. 打开IOHIDManager
IOReturn IOReturn = IOHIDManagerOpen(managerRef, kIOHIDOptionsTypeNone);
if(kIOReturnSuccess != IOReturn) puts("IOHIDManagerOpen failed.");
8. 实现插拔callback
void Handle_DeviceMatchingCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef)
{
CFTypeRef ref;
int32_t nVendorID, nProductID;
ref = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey));
if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &nVendorID);
}
ref = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey));
if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &nProductID);
}
NSLog(@"insert hid, vid is:%d, pid is:%d",nVendorID, nProductID);
CFRelease(ref);
}
void Handle_DeviceRemovalCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef)
{
CFTypeRef ref;
int32_t nVendorID, nProductID;
ref = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey));
if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &nVendorID);
}
ref = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey));
if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &nProductID);
}
NSLog(@"remove hid, vid is:%d, pid is:%d",nVendorID, nProductID);
CFRelease(ref);
}
9. 插入设备获取到IOHIDDeviceRef inIOHIDDeviceRef后,打开IOHIDDeviceRef
IOReturn ret = IOHIDDeviceOpen(inIOHIDDeviceRef,options);
if (ret != kIOReturnSuccess)
{
printf("Open device failed!\n");return;
}
10. 注册接收数据方法,即可接收数据
cpp
unsigned char InputBuffer[64];
IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, InputBuffer, sizeof(InputBuffer), MyInputCallback, NULL);
11. 接收数据callback,当操作定义为“APP通道”数据的按键会收到设备发送来的数据
int appPath = 0;
void MyInputCallback(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t *report,CFIndex reportLength)
{
memcpy(ReceiveData,report,sizeof(ReceiveData));
if (report[0] == 0x03 && report[1] == 0x31 && report[2] == 0x00 &&
report[3] == 0x00 && report[4] == 0x02) {
appPath = report[5];
}
}
12. 向USB设备发送指令
IOReturn ret = IOHIDDeviceSetReport(inIOHIDDeviceRef, kIOHIDReportTypeOutput, 0, (uint8_t*)outbuffer, size);
if (ret != kIOReturnSuccess) {
PRINTMSG("发送hid数据失败,error number: %x\r\n",ret);
return ret;
}
13. 断开设备
IOReturn ret = IOHIDDeviceClose(inIOHIDDeviceRef,0L);
if (ret == kIOReturnSuccess) {
NSLog(@"断开成功");
}
14. 可能出现的问题
1)出现如下报错:
TCC deny IOHIDDeviceOpen
Open device failed!
需要在“启动台 - 系统设置 – 隐私与安全性”中赋予“输入监控”权限
2)赋予权限后依然不能打开hid时,需要在Xcode中,勾选“USB”选项来允许应用程序访问USB设备。以下是在Xcode中勾选“USB”选项的步骤:
打开Xcode。
打开你的项目(PROJECT)。
在项目导航面板中,选择你的目标(TARGETS)。
点击“Signing & Capabilities”标签页。
如果“App Sandbox”没有被添加到你的项目中,你需要点击“+ Capability”按钮来添加它。
在“App Sandbox”中,找到“Hardware”部分。
勾选“USB”选项。
请注意,从Xcode 13开始,苹果引入了新的App Sandbox特性,这可能会影响你如何配置App Sandbox。如果你使用的是Xcode 13或更高版本,并且找不到“USB”选项,可能是因为这个选项已经被移动或者更改了。在这种情况下,你可能需要查看苹果的官方文档或者Xcode的更新日志来获取最新的配置方法。