I'm trying to get a grasp on IOKit and I feel I am close, but not there yet. So excuse my confusion.
I have managed to write code which detects my USB Device (a simple Button on the end of a USB cable which has a Windows Driver, but no Mac driver).
I'm trying to get some sort of callback when the button is pressed.
I manage to get a callback when the device is connected to USB or removed. Now, I am trying to find out how to get informed when the button is pressed, but I cannot figure it out. The documentation is very confusing to me, as IOKit seems to be available both a c++ and c depending on how you access it (Kernel Extension or user-space driver, or something similar. Not sure I have the right terminology.
I've tried adding a couple methods to get a callback when any Interrupt value changes as you will see in the code. But nothing happens.
Here is my current AppDelegate.m file as well as the USB Probe information on the device.
Low Speed device @ 5 (0x14100000): ............................................. Composite device: "DL100B Dream Cheeky Generic Controller"
Port Information: 0x101a
Number Of Endpoints (includes EP0):
Device Descriptor
Configuration Descriptor (current config)
Length (and contents): 34
Number of Interfaces: 1
Configuration Value: 1
Attributes: 0x80 (bus-powered)
MaxPower: 500 mA
Interface #0 - HID
Alternate Setting 0
Number of Endpoints 1
Interface Class: 3 (HID)
Interface Subclass; 0
Interface Protocol: 0
HID Descriptor
Endpoint 0x81 - Interrupt Input
Address: 0x81 (IN)
Attributes: 0x03 (Interrupt)
Max Packet Size: 8
Polling Interval: 10 ms
The App Delegate.m file:
//
// USBHIDAppDelegate.m
// USBHID
//
// Created by Michael Dolinar on 12-05-02.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "USBHIDAppDelegate.h"
#import "IOKit/hid/IOHIDManager.h"
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USBSpec.h>
@implementation USBHIDAppDelegate
@synthesize window = _window;
// New USB device specified in the matching dictionary has been added (callback function)
static void Handle_DeviceMatchingCallback(void *inContext,
IOReturn inResult,
void *inSender,
IOHIDDeviceRef inIOHIDDeviceRef){
// Retrieve the device name & serial number
NSString *devName = [NSString stringWithUTF8String:
CFStringGetCStringPtr(
IOHIDDeviceGetProperty(inIOHIDDeviceRef,
CFSTR("Product")),
kCFStringEncodingMacRoman)];
UInt32 serialString = CFStringGetCStringPtr(
IOHIDDeviceGetProperty(inIOHIDDeviceRef,
CFSTR("SerialNumber")),
kCFStringEncodingMacRoman);
NSString *devSerialNumber;
if (serialString == 0) {
devSerialNumber = @"No Serial Number";
} else {
devSerialNumber = [NSString stringWithUTF8String:serialString];
}
// Log the device reference, Name, Serial Number & device count
NSLog(@"\nDevice added: %p\nModel: %@\nSerial Number:%@\nDevice count: %ld",
inIOHIDDeviceRef,
devName,
devSerialNumber,
USBDeviceCount(inSender));
//Open the device (Was missing)
IOReturn err = IOHIDDeviceOpen(inIOHIDDeviceRef, 0);
//Should check for error here...
IOHIDDeviceRegisterInputValueCallback(inIOHIDDeviceRef, Handle_IOHIDDeviceInputValueCallback, NULL);
//Must also register for RunLoop again here... was also missing IOHIDDeviceScheduleWithRunLoop(inIOHIDDeviceRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
}
static void Handle_IOHIDDeviceInputValueCallback(void *inContext,
IOReturn inResult,
void *inSender,
IOHIDValueRef inIOHIDValueRef
)
{
NSLog(@"Value changed");
}
// USB device specified in the matching dictionary has been removed (callback function)
static void Handle_DeviceRemovalCallback(void *inContext,
IOReturn inResult,
void *inSender,
IOHIDDeviceRef inIOHIDDeviceRef){
// Log the device ID & device count
NSLog(@"\nDevice removed: %p\nDevice count: %ld",
(void *)inIOHIDDeviceRef,
USBDeviceCount(inSender));
IOHIDDeviceRegisterInputValueCallback(inIOHIDDeviceRef, NULL, NULL); //Remove callback
}
// Counts the number of devices in the device set (incudes all USB devices that match our dictionary)
static long USBDeviceCount(IOHIDManagerRef HIDManager){
// The device set includes all USB devices that match our matching dictionary. Fetch it.
CFSetRef devSet = IOHIDManagerCopyDevices(HIDManager);
// The devSet will be NULL if there are 0 devices, so only try to count the devices if devSet exists
if(devSet) return CFSetGetCount(devSet);
// There were no matching devices (devSet was NULL), so return a count of 0
return 0;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
SInt32 idVendor = 0x1D34;//0x062A;//0x1d34; //0x1AAD; //// set vendor id
SInt32 idProduct = 0x000D;//0x0000;//0x000d; //0x000F; //// set product id
// Create an HID Manager
IOHIDManagerRef HIDManager = IOHIDManagerCreate(kCFAllocatorDefault,
kIOHIDOptionsTypeNone);
// Create a Matching Dictionary
CFMutableDictionaryRef matchDict = CFDictionaryCreateMutable(kCFAllocatorDefault,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
// Specify a device manufacturer in the Matching Dictionary
CFDictionarySetValue(matchDict,
CFSTR(kIOHIDVendorIDKey),
CFNumberCreate(kCFAllocatorDefault,
kCFNumberSInt32Type, &idVendor));
CFDictionarySetValue(matchDict,
CFSTR(kIOHIDProductKey),
CFNumberCreate(kCFAllocatorDefault,
kCFNumberSInt32Type, &idProduct));
// Register the Matching Dictionary to the HID Manager
IOHIDManagerSetDeviceMatching(HIDManager, matchDict);
// Register a callback for USB device detection with the HID Manager
IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, &Handle_DeviceMatchingCallback, NULL);
// Register a callback fro USB device removal with the HID Manager
IOHIDManagerRegisterDeviceRemovalCallback(HIDManager, &Handle_DeviceRemovalCallback, NULL);
// Register the HID Manager on our app’s run loop
IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
// Open the HID Manager
IOReturn IOReturn = IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone);
if(IOReturn) NSLog(@"IOHIDManagerOpen failed."); // Couldn't open the HID manager! TODO: proper error handling
}
@end
I'm not even sure the device is sending anything... I try logging Using USB Logger, in Level 7, but only pressing the button doesn't seem to display anything... how can I make sure it is actually working?
UPDATE: Was able to detect button presses using this Ruby Open Source project for the BigRedButton I'm using to learn this. So I know it actually works. I also reworked my code to register for value changes only when the device is actually detected and removing it when the device is removed. Still nothing at this point.
UPDATE 2: Got this to work! Two issues... I was not opening the device for reading, and also not registering the device itself on the Current RunLoop which is also required. A great WWDC video on Userland device access from WWDC 2011 greatly helped. I also have to say that the BigRedBUtton I was using perhaps is not quite a standard device and that never worked as planed, but the actual device I wanted to work with works like a charm and is giving me input! So it is all good now, thanks to the WWDC videos!
In the end, the code above works now that I've Opened the device and scheduled the device on the current runloop. Also, changing devices allowed me to work with another device which seems to offer a more predictable experience changing its values. I suggest anyone looking into this to watch the Userland Device Access video from WWDC 2011 for a great understanding of how this works (about 30 minutes into the video)
UPDATE: The reason why devices react differently is a bit complex. First, each USB HID device can have multiple "Configurations" and a default one might not be enabled. You have to explicitly do this. Then, the second part is all about understanding the values being sent from the device. This is done by understanding "HID Device Report Descriptors" which describe in detail each kind of value being returned (called reports) and how its bits and bytes are arranged.
On OS X a great read for this is the New HID Manager APIs for Mac OS X version 10.5 TechNote. Although it dates to 10.5, it is the latest version of this and contains all the appropriate calls and gives a better understanding of how this all works.
Also, to learn about Descriptors, this tutorial was helpful.