USB HID device access from Xcode part 1 

By Michael Dolinar - Wednesday, 2 May, 2012


There are several classes of USB device. One of these is the HID (Human Interface Device) class. The HID class of device includes things like keyboards, mice, joysticks and some data acquisition hardware!


In this article, we will be looking at Apple’s supplied interface for accessing the HID class devices attached to your system. Particularly, we’ll be using the HID Manager to detect I/O devices from ONTRAK Control Systems. If you don’t have any of ONTRAK’s USB I/O devices, don’t worry - the code will work for detecting HID devices from any manufacturer.


Note: retrieving and sending data to the USB device will be covered in part 2.


Apple’s HID Manager provides a very neat way to work with HID devices. Since the HID Manager interfaces with all of the system’s HID devices, we’ll use a matching dictionary to tell the HID Manager which devices we’re interested in. Then we’ll register some callback functions do detect the addition or removal of USB devices. Finally, we’ll query the HID Manager to get a device set that we’ll use to count the devices that match our dictionary.


Pseudocode for initialization of our app:

Create an HID Manager

Create a Matching Dictionary

Specify a device manufacturer in the Matching Dictionary

Register the Matching Dictionary to the HID Manager

Register a callback for USB device detection with the HID Manager

Register a callback fro USB device removal with the HID Manager

Register the HID Manager on our app’s run loop

 

Open the HID Manager

Let’s dive right into the implementation!


Create a new Cocoa application
- Crack open Xcode 4 & select Create a new Xcode project
-Under Mac OS X / Application select Cocoa Application
-Click Next
-For simplicity, use the same project name as me: USBHID
-Use the same value for class prefix too: USBHID
-Click Next
-Select a folder where you’d like to save your project
-Click Create


Ah, there’s nothing like a shiny new Cocoa app!


Before we begin coding, we need to add the IOKit framework:
-Click on the project root USBHID
-In the Linked Frameworks and Libraries section, click the +
-In the search field, type IOKit
-Select the IOKit.framework
-Click Add


Well done! Let’s start coding!


Click on the USBHIDAppDelegate.m file to begin coding. We start by importing IOHIDManager.h header file. Insert this line after the other #import statements:

#import "IOKit/hid/IOHIDManager.h"

Next, move your cursor down to the function named:
applicationDidFinishLaunching:


This is where you’ll write your initialization code. We’ll start coding right after the Apple-supplied comment:
// Insert code here to initialize your application


Enter the following code to accomplish the tasks listed in our pseudocode:

// 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(kIOHIDManufacturerKey),

                                           CFSTR("www.ontrak.net"));


// Register the Matching Dictionary to the HID Manager

IOHIDManagerSetDeviceMatching(HIDManager, matchDict);

No doubt you’ve noticed that we stopped short of registering the callback functions. Why you ask? Simply because we haven’t declared them yet, and Xcode will complain when we try to register a callback to a function that doesn’t exist.


At this point, it’s worth noting that we’ve specified a manufacturer name of “www.ontrak.net”. This tells the HID Manager to react only to devices with this manufacturer name. If you want to detect another manufacturer’s devices, just change this string to the device manufacturer’s name. For example, you can substitute “Apple Inc.” for “www.ontrak.net” and the HID Manager will detect the Apple USB HID devices connected to your system.

Editors Note:  An alternative way to detect a particular manufacturers device is to use the Manufacturer ID , which is HEX 0A07 for Ontrak Control Systems devices. All manufacturers of USB based products are assigned a 4 digit Hex code by usb.org


Let’s take a minute to declare our callback functions in the our application’s header file. Open the USBHIDAppDelegate.h by clicking on it. Start by importing the all important HIDManager.h file by entering the following line of code after the other #import statements:

#import "IOKit/hid/IOHIDManager.h"

 

Now we can declare our callback functions. Enter the following code just above the line that reads @end:

// USB device added callback function

static void Handle_DeviceMatchingCallback(void *inContext,

                                                 IOReturn inResult,

                                                 void *inSender,

                                                 IOHIDDeviceRef inIOHIDDeviceRef);


// USB device removed callback function

static void Handle_DeviceRemovalCallback(void *inContext,

                                                 IOReturn inResult,

                                                 void *inSender,

                                                 IOHIDDeviceRef inIOHIDDeviceRef);


// Counts the number of devices in the device set (includes all USB devices that match our dictionary)

static long USBDeviceCount(IOHIDManagerRef HIDManager);

The first line of code declares the function that shall be called when a USB device is added. The second is for USB device removal.
The third line declares a counter function. We’re going to implement this counter function to query the HID Manager and return the number of matching devices. This function is not a callback function.


Let’s jump back to the implementation file and code these babies! Click on the USBHIDAppDelegate.m file and enter the following code just above the line that reads @end:

// 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){


// Log the device ID & device count

NSLog(@"\nONTRAK device added: %p\nONTRAK device count: %ld",

                      (void *)inIOHIDDeviceRef,

                      USBDeviceCount(inSender));

}






// 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(@"\nONTRAK device removed: %p\nONTRAK device count: %ld",

                   (void *)inIOHIDDeviceRef,

                   USBDeviceCount(inSender));

}






// Counts the number of devices in the device set (includes 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;

}

The first two functions look much the same: When a device is attached or removed from the system, the device’s identifier and the device count are printed to the debug window.


The USBDeviceCount function requests the device set from the HID Manager, counts the devices in the set and returns the result. This is where the device count is generated for the callback functions.

 
With the callback functions declared and implemented, we can continue with our application initialization. Scroll back up to the function:

                                                                                                 applicationDidFinishLaunching:

Append our previous code in this function with the following:

// 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!

That does it! You can now click Xcode’s Run button to compile & execute your project.


When an ONTRAK Control Systems USB device is attached, the debug window will show you the device ID and device count. The code can manage multiple devices. Finally, when disconnecting a device, another message will be posted to the debug window.


Complete Code Listing


Complete code listing for the project’s header file:


HIDManager.h

#import <Cocoa/Cocoa.h>

#import <IOKit/hid/IOHIDManager.h>


@interface USBHIDAppDelegate : NSObject <NSApplicationDelegate>


@property (assign) IBOutlet NSWindow *window;


// USB device added callback function

static void Handle_DeviceMatchingCallback(void *inContext,

                                  IOReturn inResult,

                                  void *inSender,

                                  IOHIDDeviceRef inIOHIDDeviceRef);


// USB device removed callback function

static void Handle_DeviceRemovalCallback(void *inContext,

                                  IOReturn inResult,

                                  void *inSender,

                                   IOHIDDeviceRef inIOHIDDeviceRef);


// Counts the number of devices in the device set (includes all USB devices that match our dictionary)

static long USBDeviceCount(IOHIDManagerRef HIDManager);


@end

Complete code listing for the project’s implementation file:


HIDManager.m

#import "USBHIDAppDelegate.h"

#import "IOKit/hid/IOHIDManager.h"


@implementation USBHIDAppDelegate


@synthesize window = _window;


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification

{

// Insert code here to initialize your application


// Create an HID Manager

IOHIDManagerRef HIDManager = IOHIDManagerCreate(kCFAllocatorDefault,

                                        kIOHIDOptionsTypeNone);


// Create a Matching Dictionary

CFMutableDictionaryRef matchDict = CFDictionaryCreateMutable(kCFAllocatorDefault,

                                        2,

                                         &kCFTypeDictionaryKeyCallBacks/span>,,

                                         &kCFTypeDictionaryValueCallBacks/span>);


// Specify a device manufacturer in the Matching Dictionary

CCFDictionarySetValue(matchDict,

                           CFSTR(span class="style_3">kIOHIDManufacturerKey)),

                           CFSTR(span class="style_9">"www.ontrak.net"));


// 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

}







// 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){


// Log the device ID & device countbr />

NSLog((@"\nONTRAK device added: %p\nONTRAK device count: %ld",

                (void/span> *)inIOHIDDeviceRef,

                USBDeviceCount(inSender));

}br />






// 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 countbr />

NSLog((@"\nONTRAK device removed: %p\nONTRAK device count: %ld",

              (void/span> *)inIOHIDDeviceRef,

              USBDeviceCount(inSender));


// TODO: make sure your application doesn't try to do anything with the removed devicebr />

}






// 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;

}


@@end



Download the Xcode 4 project file USBHID.zip.

View USB HID Device Access From XCODE Part 2


This project was created on OS X 10.7.3 using Xcode 4.2.1.


Thanks to Tom Fortin from ONTRAK Control Systems for providing the ADU208 and ADU200 USB I/O devices used in the development of this series of articles.

 

Back to Programming Page>