Skip to content

Serial device communication

Artem edited this page Mar 10, 2018 · 2 revisions

Create SerialDeviceMonitor object globally and run monitor in new thread for listen Serial devices


import Cocoa
import USBDeviceSwift

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    //make sure that cfDeviceMonitor always exist
    let cfDeviceMonitor = SerialDeviceMonitor()

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
        
        // Adding own function to filter serial devices that we need
        cfDeviceMonitor.filterDevices = {(devices: [SerialDevice]) -> [SerialDevice] in
            return devices.filter({$0.vendorId == 1155 && $0.productId == 22336})
        }
        
        let cfDeviceDaemon = Thread(target: self.cfDeviceMonitor, selector:#selector(self.cfDeviceMonitor.start), object: nil)
        cfDeviceDaemon.start()
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }


}

You can also specify a filter function for serial devices to do not receive not required notifications when serial device added or removed.

cfDeviceMonitor.filterDevices = {(devices: [SerialDevice]) -> [SerialDevice] in
    return devices.filter({$0.vendorId == 1155 && $0.productId == 22336})
}

Also you can set portUsageIntervalTime. For example:

let cfDeviceMonitor = SerialDeviceMonitor()
cfDeviceMonitor.portUsageIntervalTime = 1 // Monitoring will be every 1 sec, default: 0.25

note - start function using RunLoop that blocks thread don't run monitor in Main thread

There are two global notifications:

SerialDeviceAdded

SerialDeviceRemoved

Listen them in our ViewController:

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        NotificationCenter.default.addObserver(self, selector: #selector(self.serialDeviceAdded), name: .SerialDeviceAdded, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.serialDeviceRemoved), name: .SerialDeviceRemoved, object: nil)
        
    }

    @objc func serialDeviceAdded(notification: NSNotification) {
        guard let nobj = notification.object as? NSDictionary else {
            return
        }
        
        guard let deviceInfo:SerialDevice = nobj["device"] as? SerialDevice else {
            return
        }
    }

    @objc func serialDeviceRemoved(notification: NSNotification) {
        guard let nobj = notification.object as? NSDictionary else {
            return
        }
        
        guard let deviceInfo:SerialDevice = nobj["device"] as? SerialDevice else {
            return
        }
    }
    
}

SerialDeviceAdded and SerialDeviceRemoved notifications - return SerialDevice with all basic info

  • path - IOCalloutDeviceKey
  • name(optional) - USB Product Name
  • vendorName(optional) - USB Vendor Name
  • serialNumber(optional) - USB Serial Number
  • vendorId(optional) - USB Vendor id
  • productId(optional) - USB Product id

After this you need to open connection and manipulate with your device as you want. There are a several libraries that could help with this like:

Open/Close serial device example:

public enum PortError: Int32, Error {
    case failedToOpen = -1 // refer to open()
    case invalidPath
    case mustReceiveOrTransmit
    case mustBeOpen
    case stringsMustBeUTF8
}

class CleanFlightDevice {
    var deviceInfo:SerialDevice
    var fileDescriptor:Int32?
    
    required init(_ deviceInfo:SerialDevice) {
        self.deviceInfo = deviceInfo
    }
    
    func openPort(toReceive receive: Bool, andTransmit transmit: Bool) throws {
        guard !deviceInfo.path.isEmpty else {
            throw PortError.invalidPath
        }
        
        guard receive || transmit else {
            throw PortError.mustReceiveOrTransmit
        }

        var readWriteParam : Int32
        
        if receive && transmit {
            readWriteParam = O_RDWR
        } else if receive {
            readWriteParam = O_RDONLY
        } else if transmit {
            readWriteParam = O_WRONLY
        } else {
            fatalError()
        }
    
        fileDescriptor = open(deviceInfo.path, readWriteParam | O_NOCTTY | O_EXLOCK)
        
        // Throw error if open() failed
        if fileDescriptor == PortError.failedToOpen.rawValue {
            throw PortError.failedToOpen
        }
    }
    
    public func closePort() {
        if let fileDescriptor = fileDescriptor {
            close(fileDescriptor)
        }
        fileDescriptor = nil
    }
}

See full example in CleanFlightSerialExample folder

Clone this wiki locally