Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

USB device send/receive data

I have implemented function to detect USB device. It works and now i need to send/read data.

I started look over a lot of obj-c sources and found only one good article in apple documentation, that describes how do we can send package to our USB device:

IOReturn WriteToDevice(IOUSBDeviceInterface **dev, UInt16 deviceAddress,
                        UInt16 length, UInt8 writeBuffer[])
{

    IOUSBDevRequest     request;
    request.bmRequestType = USBmakebmRequestType(kUSBOut, kUSBVendor,
                                                kUSBDevice);
    request.bRequest = 0xa0;
    request.wValue = deviceAddress;
    request.wIndex = 0;
    request.wLength = length;
    request.pData = writeBuffer;

    return (*dev)->DeviceRequest(dev, &request);
}

But I didn't find a way how to create and send data with Swift. The struct on Swift looks like:

public struct IOUSBDevRequest {
    public var bmRequestType: UInt8
    public var bRequest: UInt8
    public var wValue: UInt16
    public var wIndex: UInt16
    public var wLength: UInt16
    public var pData: UnsafeMutableRawPointer!
    public var wLenDone: UInt32
    public init()

    public init(bmRequestType: UInt8, bRequest: UInt8, wValue: UInt16, wIndex: UInt16, wLength: UInt16, pData: UnsafeMutableRawPointer!, wLenDone: UInt32)
}

I can't figure out what parameters is pData, zwLenDone.

This is data that i need to send:

{         
'direction':'in',         
'recipient':'device',
'requestType':  'standard',
'request':      6,         
'value':        0x300,         
'index':        0,         
'length':       255
}

The next question is: How i can receive data. I know the answer is in this article, but i can't convert it to Swift.

Here is what i could converted on Swift 3. My class detects USB device, get his configuration:

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

static let sharedInstance = DFUDevice()

var deviceName:String = ""

private func deviceAdded(iterator: io_iterator_t) {
    var plugInInterfacePtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<IOCFPlugInInterface>?>?
    var deviceInterfacePtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<IOUSBDeviceInterface>?>?
    var configPtr:IOUSBConfigurationDescriptorPtr?

    var score: 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")
        }

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

        // Get plugInInterface for current USB device

        let plugInInterfaceResult = IOCreatePlugInInterfaceForService(
            usbDevice,
            kIOUSBDeviceUserClientTypeID,
            kIOCFPlugInInterfaceID,
            &plugInInterfacePtrPtr,
            &score)

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

        var ret = deviceInterface.USBDeviceOpen(deviceInterfacePtrPtr)
        if (ret == kIOReturnSuccess)
        {
            // set first configuration as active
            ret = deviceInterface.GetConfigurationDescriptorPtr(deviceInterfacePtrPtr, 0, &configPtr)
            if (ret != kIOReturnSuccess)
            {
                print("Could not set active configuration (error: %x)\n", ret);
                continue
            }
            guard let config = configPtr?.pointee else {
                continue
            }

            if config.bLength > 0 {
                //HERE I NEED SEND DATA

            } else {
                print("ConfigurationDescriptor not valid")
            }
            print(config.bLength)
        }
        else if (ret == kIOReturnExclusiveAccess)
        {
            // this is not a problem as we can still do some things
        }
        else
        {
            print("Could not open device (error: %x)\n", ret)
            continue
        }

        IOObjectRelease(usbDevice)
    }
}


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.deviceAdded(iterator: iterator)
        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)

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

    RunLoop.current.run()
    }
}

I call it like:

let DFUDeviceDaemon = Thread(target: DFUDevice.sharedInstance, selector:#selector(DFUDevice.initUsb), object: nil)
DFUDeviceDaemon.start()
like image 707
Arti Avatar asked Dec 08 '16 11:12

Arti


2 Answers

The article you reference has a function called WriteToDevice. One of its parameters is

UInt8 writeBuffer[]

This writeBuffer, the data that you want to send, is a C array of bytes:

uint8_t msgLength = 3;
uint8_t writeBuffer[msgLength];
writeBuffer[0] = 0x41; // ASCII 'A'
writeBuffer[1] = 0x42; // ASCII 'B'
writeBuffer[2] = 0x43; // ASCII 'C'

What bytes do you need to send? That really depends on the device at the other end -- the technical data from the manufacturer should tell you that. To pass the C-array as NSData, which is probably what pData is, you'd use:

NSData *data = [NSData dataWithBytes:&writeBuffer length:3];

The (z)wLenDone is probably what I called the msgLength, 3. C-array's have no knowledge of their own length, so most functions require the length as a separate parameter.

As for receiving data, I would guess that happens in the matchingCallback: you use the iterator to receive the bytes and then parse them.

ANSWER TO COMMENT:

I'm not familiar with C#, and I'm no expert at this stuff, but maybe this will help:

var package = new UsbSetupPacket( 
(byte)(UsbCtrlFlags.Direction_In |
       UsbCtrlFlags.Recipient_Device | 
       UsbCtrlFlags.RequestType_Standard),  // Index
6,                      // length of data, second phase
0x200,                  // Request
0,                      // RequestType
(short)length);         // Value

A few observations: know nothing of C#, but should not the package be typed to struct? RequestType is 0, so you will receive no response -- is that what you want? Or did you want to send UsbCtrlFlags.RequestType_Standard as the fourth parameter? Why send the length as a value?

Anyway, what you do now is send the package to the USB device and see what happens.

like image 73
Elise van Looij Avatar answered Oct 19 '22 06:10

Elise van Looij


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
    }
like image 37
Arti Avatar answered Oct 19 '22 06:10

Arti