I'm using IOKit to connect to a custom USB HID device. I'm using XCode 14 and running Swift/SwiftUI. So far I have had great success reading from the device with IOHIDDeviceGetReport and this can be done repeatedly with no issues. However, when I use IOHIDDeviceSetReport, I can only successfully set the report to the device one time correctly but any subsequent call to this function would just end up with an I/O Timeout. Any calls using IOHIDDeviceGetReport still works fine so the USB Device is still functioning correctly, but I couldn't receive any additional IOHIDDeviceSetReport call. If I unplug the USB and plug it in again, I can once again successfully send a command. This is, of course, not very practical for the end user to have to unplug and plug the device in after a single set command, and I don't quite understand what's going on with this.
Here's my SetOutputReport function to call the IOHIDDeviceSetReport. The IOHIDDevice must already be connected and opened before calling this function so it's not nil.
I don't have this "one shot send command" problem on the PC (Windows 7, 10, 11) or Android (v.11,12,13,14) implementation of this Custom USB HID device. It seems like there's something at the lower level of IOHIDDeviceSetReport on macOS which might be done differently than what's available on the PC or Android.
Many searches on the web yielded no useful results. There's an IOHIDDeviceSetReportWithCallback function and it also seems only to work one time as well.
private func SetOutputReport(dev: IOHIDDevice? ,reportID: Int, data:[UInt8], reportLength: Int) -> String{
let inputReportID = reportID
var buffer = data
buffer[0] = UInt8(inputReportID)
let bufferPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: buffer.count)
bufferPointer.initialize(from: &buffer, count: buffer.count)
//print(bufferPointer[0])
let bufferLength: CFIndex = buffer.count
var success: IOReturn = kIOReturnError
if(dev != nil){
success = IOHIDDeviceSetReport(dev!, kIOHIDReportTypeOutput, CFIndex(buffer[0]), bufferPointer, bufferLength)
}
print("Set report result \(krToString(success))")
return krToString(success)
}
I started messing around with various things to see if there was anything I could do. Then I remembered reading about USB HID somewhere that when you send a command to set an Output Report, an Input Report is generated. You are supposed to acknowledge the Input report with a receipt. That makes me wonder whether my set command didn't finish the process because the generated input report wasn't received back. The custom device we have doesn't work like that since the USB HID reports are used more like a USB HID Feature Report rather than the proper device reports to pass back-and-forth communications.
Since our custom device will never generate an input report when the output report is sent to it, I decided to pretend that everything is an input report so that it doesn't generate any response. I made one small change to the code and now it works.
success = IOHIDDeviceSetReport(dev!, kIOHIDReportTypeInput, CFIndex(buffer[0]), bufferPointer, bufferLength). //a way to force no report generation
At the end of the day, our custom USB HID device is not implementing the usage properly, but this is probably true for many micro-controller-based USB HID devices that people were implementing based on Jan Axelson's USB HID examples on PC decades ago.