How to Implement iOServicematchingcallback in Swift

How to implement IOServiceMatchingCallBack in Swift

Here's a Swift 3 version, using closures instead of global functions (a closure w/o a context can be bridged to a C function pointer), using GCD instead of Runloops (much nicer API), using callbacks and dispatches to inform about events and using real objects instead of static objects or singletons:

import Darwin
import IOKit
import IOKit.usb
import Foundation


class IOUSBDetector {

enum Event {
case Matched
case Terminated
}

let vendorID: Int
let productID: Int

var callbackQueue: DispatchQueue?

var callback: (
( _ detector: IOUSBDetector, _ event: Event,
_ service: io_service_t
) -> Void
)?


private
let internalQueue: DispatchQueue

private
let notifyPort: IONotificationPortRef

private
var matchedIterator: io_iterator_t = 0

private
var terminatedIterator: io_iterator_t = 0


private
func dispatchEvent (
event: Event, iterator: io_iterator_t
) {
repeat {
let nextService = IOIteratorNext(iterator)
guard nextService != 0 else { break }
if let cb = self.callback, let q = self.callbackQueue {
q.async {
cb(self, event, nextService)
IOObjectRelease(nextService)
}
} else {
IOObjectRelease(nextService)
}
} while (true)
}


init? ( vendorID: Int, productID: Int ) {
self.vendorID = vendorID
self.productID = productID
self.internalQueue = DispatchQueue(label: "IODetector")

guard let notifyPort = IONotificationPortCreate(kIOMasterPortDefault) else {
return nil
}

self.notifyPort = notifyPort
IONotificationPortSetDispatchQueue(notifyPort, self.internalQueue)
}

deinit {
self.stopDetection()
}


func startDetection ( ) -> Bool {
guard matchedIterator == 0 else { return true }

let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
as NSMutableDictionary
matchingDict[kUSBVendorID] = NSNumber(value: vendorID)
matchingDict[kUSBProductID] = NSNumber(value: productID)

let matchCallback: IOServiceMatchingCallback = {
(userData, iterator) in
let detector = Unmanaged<IOUSBDetector>
.fromOpaque(userData!).takeUnretainedValue()
detector.dispatchEvent(
event: .Matched, iterator: iterator
)
};
let termCallback: IOServiceMatchingCallback = {
(userData, iterator) in
let detector = Unmanaged<IOUSBDetector>
.fromOpaque(userData!).takeUnretainedValue()
detector.dispatchEvent(
event: .Terminated, iterator: iterator
)
};

let selfPtr = Unmanaged.passUnretained(self).toOpaque()

let addMatchError = IOServiceAddMatchingNotification(
self.notifyPort, kIOFirstMatchNotification,
matchingDict, matchCallback, selfPtr, &self.matchedIterator
)
let addTermError = IOServiceAddMatchingNotification(
self.notifyPort, kIOTerminatedNotification,
matchingDict, termCallback, selfPtr, &self.terminatedIterator
)

guard addMatchError == 0 && addTermError == 0 else {
if self.matchedIterator != 0 {
IOObjectRelease(self.matchedIterator)
self.matchedIterator = 0
}
if self.terminatedIterator != 0 {
IOObjectRelease(self.terminatedIterator)
self.terminatedIterator = 0
}
return false
}

// This is required even if nothing was found to "arm" the callback
self.dispatchEvent(event: .Matched, iterator: self.matchedIterator)
self.dispatchEvent(event: .Terminated, iterator: self.terminatedIterator)

return true
}


func stopDetection ( ) {
guard self.matchedIterator != 0 else { return }
IOObjectRelease(self.matchedIterator)
IOObjectRelease(self.terminatedIterator)
self.matchedIterator = 0
self.terminatedIterator = 0
}
}

And here is some simple test code to test that class (set product and vendor ID as appropriate for your USB device):

let test = IOUSBDetector(vendorID: 0x4e8, productID: 0x1a23)
test?.callbackQueue = DispatchQueue.global()
test?.callback = {
(detector, event, service) in
print("Event \(event)")
};
_ = test?.startDetection()
while true { sleep(1) }

USB Connection Delegate on Swift

This answer worked for me https://stackoverflow.com/a/35788694 but it needed some adaptation, like creating a bridging header to import some specific IOKit parts.

First, add IOKit.framework to your project (click "+" in "Linked Frameworks and Libraries").

Then create a new empty ".m" file, whatever its name. Xcode will then ask if it should make a "bridging header". Say YES.

Ignore the ".m" file. In the new "YOURAPPNAME-Bridging-Header.h" file that Xcode just created, add the following lines:

#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/hid/IOHIDKeys.h>

Now you can use the code in the linked answer. Here's a simplified version:

class USBDetector {
class func monitorUSBEvent() {
var portIterator: io_iterator_t = 0
let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
let gNotifyPort: IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
let runLoopSource: Unmanaged<CFRunLoopSource>! = IONotificationPortGetRunLoopSource(gNotifyPort)
let gRunLoop: CFRunLoop! = CFRunLoopGetCurrent()
CFRunLoopAddSource(gRunLoop, runLoopSource.takeRetainedValue(), kCFRunLoopDefaultMode)
let observer = UnsafeMutablePointer<Void>(unsafeAddressOf(self))
_ = IOServiceAddMatchingNotification(gNotifyPort,
kIOMatchedNotification,
matchingDict,
deviceAdded,
observer,
&portIterator)
deviceAdded(nil, iterator: portIterator)
_ = IOServiceAddMatchingNotification(gNotifyPort,
kIOTerminatedNotification,
matchingDict,
deviceRemoved,
observer,
&portIterator)
deviceRemoved(nil, iterator: portIterator)
}
}

func deviceAdded(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) {
var kr: kern_return_t = KERN_FAILURE
while case let usbDevice = IOIteratorNext(iterator) where usbDevice != 0 {
let deviceNameAsCFString = UnsafeMutablePointer<io_name_t>.alloc(1)
defer {deviceNameAsCFString.dealloc(1)}
kr = IORegistryEntryGetName(usbDevice, UnsafeMutablePointer(deviceNameAsCFString))
if kr != KERN_SUCCESS {
deviceNameAsCFString.memory.0 = 0
}
let deviceName = String.fromCString(UnsafePointer(deviceNameAsCFString))
print("Active device: \(deviceName!)")
IOObjectRelease(usbDevice)
}
}

func deviceRemoved(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) {
// ...
}

Note: deviceAdded and deviceRemoved need to be functions (not methods).

To use this code, just launch the observer:

USBDetector.monitorUSBEvent()

This will list the currently plugged devices, and on every new USB device plug/unplug event it will print the device name.

USB device send/receive data

After a lot of questions on stackoverflow and learning sources i figure it out:

First define not implemented functions

import Foundation

import IOKit
import IOKit.usb
import IOKit.usb.IOUSBLib

//from IOUSBLib.h
let kIOUSBDeviceUserClientTypeID = CFUUIDGetConstantUUIDWithBytes(nil,
0x9d, 0xc7, 0xb7, 0x80, 0x9e, 0xc0, 0x11, 0xD4,
0xa5, 0x4f, 0x00, 0x0a, 0x27, 0x05, 0x28, 0x61)
let kIOUSBDeviceInterfaceID = CFUUIDGetConstantUUIDWithBytes(nil,
0x5c, 0x81, 0x87, 0xd0, 0x9e, 0xf3, 0x11, 0xD4,
0x8b, 0x45, 0x00, 0x0a, 0x27, 0x05, 0x28, 0x61)

//from IOCFPlugin.h
let kIOCFPlugInInterfaceID = CFUUIDGetConstantUUIDWithBytes(nil,
0xC2, 0x44, 0xE8, 0x58, 0x10, 0x9C, 0x11, 0xD4,
0x91, 0xD4, 0x00, 0x50, 0xE4, 0xC6, 0x42, 0x6F)


/*!
@defined USBmakebmRequestType
@discussion Macro to encode the bRequest field of a Device Request. It is used when constructing an IOUSBDevRequest.
*/

func USBmakebmRequestType(direction:Int, type:Int, recipient:Int) -> UInt8 {
return UInt8((direction & kUSBRqDirnMask) << kUSBRqDirnShift)|UInt8((type & kUSBRqTypeMask) << kUSBRqTypeShift)|UInt8(recipient & kUSBRqRecipientMask)
}

Then create our class:

extension Notification.Name {
static let dfuDeviceConnected = Notification.Name("DFUDeviceConnected")
static let dfuDeviceDisconnected = Notification.Name("DFUDeviceDisconnected")
}

class DFUDevice: NSObject {
let vendorId = 0x0483
let productId = 0xdf11

static let sharedInstance = DFUDevice()

var deviceInterfacePtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<IOUSBDeviceInterface>?>?
var plugInInterfacePtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<IOCFPlugInInterface>?>?
var interfacePtrPtr:UnsafeMutablePointer<UnsafeMutablePointer<IOUSBInterfaceInterface>?>?

private func rawDeviceAdded(iterator: io_iterator_t) {
var score:Int32 = 0
var kr:Int32 = 0

while case let usbDevice = IOIteratorNext(iterator), usbDevice != 0 {
// io_name_t imports to swift as a tuple (Int8, ..., Int8) 128 ints
// although in device_types.h it's defined:
// typedef char io_name_t[128];
var deviceNameCString: [CChar] = [CChar](repeating: 0, count: 128)
let deviceNameResult = IORegistryEntryGetName(usbDevice, &deviceNameCString)

if(deviceNameResult != kIOReturnSuccess) {
print("Error getting device name")
}

let deviceName = String.init(cString: &deviceNameCString)
print("usb Device Name: \(deviceName)")

// Get plugInInterface for current USB device
let plugInInterfaceResult = IOCreatePlugInInterfaceForService(
usbDevice,
kIOUSBDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterfacePtrPtr,
&score)

// USB device object is no longer needed.
IOObjectRelease(usbDevice)

// Dereference pointer for the plug-in interface
guard plugInInterfaceResult == kIOReturnSuccess,
let plugInInterface = plugInInterfacePtrPtr?.pointee?.pointee else {
print("Unable to get Plug-In Interface")
continue
}

// use plug in interface to get a device interface
let deviceInterfaceResult = withUnsafeMutablePointer(to: &deviceInterfacePtrPtr) {
$0.withMemoryRebound(to: Optional<LPVOID>.self, capacity: 1) {
plugInInterface.QueryInterface(
plugInInterfacePtrPtr,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
$0)
}
}

// dereference pointer for the device interface
guard deviceInterfaceResult == kIOReturnSuccess,
let deviceInterface = deviceInterfacePtrPtr?.pointee?.pointee else {
print("Unable to get Device Interface")
continue
}

kr = deviceInterface.USBDeviceOpen(deviceInterfacePtrPtr)

if (kr != kIOReturnSuccess)
{
print("Could not open device (error: \(kr))")
continue
}
else if (kr == kIOReturnExclusiveAccess)
{
// this is not a problem as we can still do some things
continue
}

self.connected()
}
}

private func rawDeviceRemoved(iterator: io_iterator_t) {
var kr:Int32 = 0

while case let usbDevice = IOIteratorNext(iterator), usbDevice != 0 {
// USB device object is no longer needed.
kr = IOObjectRelease(usbDevice)

if (kr != kIOReturnSuccess)
{
print("Couldn’t release raw device object (error: \(kr))")
continue
}

self.disconnected()

}
}

func getStatus() throws -> [UInt8] {
guard let deviceInterface = self.deviceInterfacePtrPtr?.pointee?.pointee else {
throw DFUDeviceError.DeviceInterfaceNotFound
}

var kr:Int32 = 0
let length:Int = 6
var requestPtr:[UInt8] = [UInt8](repeating: 0, count: length)
var request = IOUSBDevRequest(bmRequestType: USBmakebmRequestType(direction: kUSBIn, type: kUSBDevice, recipient: kUSBStandard),
bRequest: DFUREQUEST.GETSTATUS.rawValue,
wValue: 0,
wIndex: 0,
wLength: UInt16(length),
pData: &requestPtr,
wLenDone: 255)

kr = deviceInterface.DeviceRequest(self.deviceInterfacePtrPtr, &request)

if (kr != kIOReturnSuccess) {
throw DFUDeviceError.RequestError(desc: "Get device status request error: \(kr)")
}

return requestPtr
}



private func configureDevice() -> Int32 {
var kr:Int32 = 0

guard let deviceInterface = deviceInterfacePtrPtr?.pointee?.pointee else {
print("Unable to get Device Interface")
return -1
}

var numConfig:UInt8 = 0

kr = deviceInterface.GetNumberOfConfigurations(deviceInterfacePtrPtr, &numConfig)
if numConfig == 0 {
print("Device Number Of Configurations: 0")
return -1
}

var configPtr:IOUSBConfigurationDescriptorPtr?

// set first configuration as active
kr = deviceInterface.GetConfigurationDescriptorPtr(deviceInterfacePtrPtr, 0, &configPtr)
if (kr != kIOReturnSuccess)
{
print("Couldn’t get configuration descriptor for index (error: %x)\n", kr);
return -1
}

guard let config = configPtr?.pointee else {
return -1
}

//Set the device’s configuration. The configuration value is found in
//the bConfigurationValue field of the configuration descriptor

kr = deviceInterface.SetConfiguration(deviceInterfacePtrPtr, config.bConfigurationValue)
if (kr != kIOReturnSuccess)
{
print("Couldn’t set configuration to value (error: %x)\n", kr);
return -1
}

return kIOReturnSuccess
}


func connected() {
NotificationCenter.default.post(name: .dfuDeviceConnected, object: nil)
globalLogPost("DFU device has been device connected")
}

func disconnected() {
NotificationCenter.default.post(name: .dfuDeviceDisconnected, object: nil)
globalLogPost("DFU device has been disconnected")
}

func initUsb() {
var matchedIterator:io_iterator_t = 0
var removalIterator:io_iterator_t = 0
let notifyPort:IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
IONotificationPortSetDispatchQueue(notifyPort, DispatchQueue(label: "IODetector"))

let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
as NSMutableDictionary
matchingDict[kUSBVendorID] = NSNumber(value: self.vendorId)
matchingDict[kUSBProductID] = NSNumber(value: self.productId)

let matchingCallback:IOServiceMatchingCallback = { (userData, iterator) in
let this = Unmanaged<DFUDevice>
.fromOpaque(userData!).takeUnretainedValue()
this.rawDeviceAdded(iterator: iterator)
}

let removalCallback: IOServiceMatchingCallback = {
(userData, iterator) in
let this = Unmanaged<DFUDevice>
.fromOpaque(userData!).takeUnretainedValue()
this.rawDeviceRemoved(iterator: iterator)
}

let selfPtr = Unmanaged.passUnretained(self).toOpaque()

IOServiceAddMatchingNotification(notifyPort, kIOFirstMatchNotification, matchingDict, matchingCallback, selfPtr, &matchedIterator)
IOServiceAddMatchingNotification(notifyPort, kIOTerminatedNotification, matchingDict, removalCallback, selfPtr, &removalIterator)

self.rawDeviceAdded(iterator: matchedIterator)
self.rawDeviceRemoved(iterator: removalIterator)

RunLoop.current.run()
}
}

You can look on method getStatus where i create a USBRequest and send it to device. Then in requestPtr:[UInt8] i received answer from device. Thank you for helping guys.

We can use ore device pointer anywhere in project, for example:

func upload(value:UInt16, length:UInt16) throws -> [UInt8] {
guard let deviceInterface = DFUDevice.sharedInstance.deviceInterfacePtrPtr?.pointee?.pointee else {
throw DFUDeviceError.DeviceInterfaceNotFound
}
var kr:Int32 = 0
var requestPtr:[UInt8] = [UInt8](repeating: 0, count: Int(length))

var request = IOUSBDevRequest(bmRequestType: 161,
bRequest: DFUREQUEST.UPLOAD.rawValue,
wValue: value,
wIndex: 0,
wLength: length,
pData: &requestPtr,
wLenDone: 255)

kr = deviceInterface.DeviceRequest(DFUDevice.sharedInstance.deviceInterfacePtrPtr, &request)

if (kr != kIOReturnSuccess) {
throw DFUDeviceError.RequestError(desc: "Upload request error: \(kr), request data: \(request)")
}

return requestPtr
}

Example of detecting USB device with VID and PID

Thanks for answers, i build my own solution:

class DFUDevice: NSObject {
let vendorId = 0x0483
let productId = 0xdf11

static let sharedInstance = DFUDevice()

private func reloadMonitor(iterator:io_iterator_t) {
repeat {
let nextService = IOIteratorNext(iterator)
guard nextService != 0 else { break }
IOObjectRelease(nextService)

} while (true)
}

func connected(iterator:io_iterator_t) {
self.reloadMonitor(iterator: iterator)
}

func disconnected(iterator:io_iterator_t) {
self.reloadMonitor(iterator: iterator)
}

func initUsb() {
var matchedIterator:io_iterator_t = 0
var removalIterator:io_iterator_t = 0
let notifyPort:IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
IONotificationPortSetDispatchQueue(notifyPort, DispatchQueue(label: "IODetector"))

let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
as NSMutableDictionary
matchingDict[kUSBVendorID] = NSNumber(value: self.vendorId)
matchingDict[kUSBProductID] = NSNumber(value: self.productId)

let matchingCallback:IOServiceMatchingCallback = { (userData, iterator) in
let this = Unmanaged<DFUDevice>
.fromOpaque(userData!).takeUnretainedValue()
this.connected(iterator: iterator)
}

let removalCallback: IOServiceMatchingCallback = {
(userData, iterator) in
let this = Unmanaged<DFUDevice>
.fromOpaque(userData!).takeUnretainedValue()
this.disconnected(iterator: iterator)
};

let selfPtr = Unmanaged.passUnretained(self).toOpaque()

IOServiceAddMatchingNotification(notifyPort, kIOFirstMatchNotification, matchingDict, matchingCallback, selfPtr, &matchedIterator)
IOServiceAddMatchingNotification(notifyPort, kIOTerminatedNotification, matchingDict, removalCallback, selfPtr, &removalIterator)

if matchedIterator != 0 {
self.connected(iterator: matchedIterator)
matchedIterator = 0
}

if removalIterator != 0 {
self.reloadMonitor(iterator: removalIterator)
removalIterator = 0
}

self.reloadMonitor(iterator: matchedIterator)
self.reloadMonitor(iterator: removalIterator)

RunLoop.current.run();
}
}

To run it:

let DFUDeviceDaemon = Thread(target: DFUDevice.sharedInstance, selector:#selector(DFUDevice.initUsb), object: nil)
DFUDeviceDaemon.start()


Related Topics



Leave a reply



Submit