|
@@ -0,0 +1,443 @@
|
|
|
|
|
+import Foundation
|
|
|
|
|
+
|
|
|
|
|
+#if os(Linux)
|
|
|
|
|
+public enum BaudRate {
|
|
|
|
|
+ case baud0
|
|
|
|
|
+ case baud50
|
|
|
|
|
+ case baud75
|
|
|
|
|
+ case baud110
|
|
|
|
|
+ case baud134
|
|
|
|
|
+ case baud150
|
|
|
|
|
+ case baud200
|
|
|
|
|
+ case baud300
|
|
|
|
|
+ case baud600
|
|
|
|
|
+ case baud1200
|
|
|
|
|
+ case baud1800
|
|
|
|
|
+ case baud2400
|
|
|
|
|
+ case baud4800
|
|
|
|
|
+ case baud9600
|
|
|
|
|
+ case baud19200
|
|
|
|
|
+ case baud38400
|
|
|
|
|
+ case baud57600
|
|
|
|
|
+ case baud115200
|
|
|
|
|
+ case baud230400
|
|
|
|
|
+ case baud460800
|
|
|
|
|
+ case baud500000
|
|
|
|
|
+ case baud576000
|
|
|
|
|
+ case baud921600
|
|
|
|
|
+ case baud1000000
|
|
|
|
|
+ case baud1152000
|
|
|
|
|
+ case baud1500000
|
|
|
|
|
+ case baud2000000
|
|
|
|
|
+ case baud2500000
|
|
|
|
|
+ case baud3500000
|
|
|
|
|
+ case baud4000000
|
|
|
|
|
+
|
|
|
|
|
+ var speedValue: speed_t {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .baud0:
|
|
|
|
|
+ return speed_t(B0)
|
|
|
|
|
+ case .baud50:
|
|
|
|
|
+ return speed_t(B50)
|
|
|
|
|
+ case .baud75:
|
|
|
|
|
+ return speed_t(B75)
|
|
|
|
|
+ case .baud110:
|
|
|
|
|
+ return speed_t(B110)
|
|
|
|
|
+ case .baud134:
|
|
|
|
|
+ return speed_t(B134)
|
|
|
|
|
+ case .baud150:
|
|
|
|
|
+ return speed_t(B150)
|
|
|
|
|
+ case .baud200:
|
|
|
|
|
+ return speed_t(B200)
|
|
|
|
|
+ case .baud300:
|
|
|
|
|
+ return speed_t(B300)
|
|
|
|
|
+ case .baud600:
|
|
|
|
|
+ return speed_t(B600)
|
|
|
|
|
+ case .baud1200:
|
|
|
|
|
+ return speed_t(B1200)
|
|
|
|
|
+ case .baud1800:
|
|
|
|
|
+ return speed_t(B1800)
|
|
|
|
|
+ case .baud2400:
|
|
|
|
|
+ return speed_t(B2400)
|
|
|
|
|
+ case .baud4800:
|
|
|
|
|
+ return speed_t(B4800)
|
|
|
|
|
+ case .baud9600:
|
|
|
|
|
+ return speed_t(B9600)
|
|
|
|
|
+ case .baud19200:
|
|
|
|
|
+ return speed_t(B19200)
|
|
|
|
|
+ case .baud38400:
|
|
|
|
|
+ return speed_t(B38400)
|
|
|
|
|
+ case .baud57600:
|
|
|
|
|
+ return speed_t(B57600)
|
|
|
|
|
+ case .baud115200:
|
|
|
|
|
+ return speed_t(B115200)
|
|
|
|
|
+ case .baud230400:
|
|
|
|
|
+ return speed_t(B230400)
|
|
|
|
|
+ case .baud460800:
|
|
|
|
|
+ return speed_t(B460800)
|
|
|
|
|
+ case .baud500000:
|
|
|
|
|
+ return speed_t(B500000)
|
|
|
|
|
+ case .baud576000:
|
|
|
|
|
+ return speed_t(B576000)
|
|
|
|
|
+ case .baud921600:
|
|
|
|
|
+ return speed_t(B921600)
|
|
|
|
|
+ case .baud1000000:
|
|
|
|
|
+ return speed_t(B1000000)
|
|
|
|
|
+ case .baud1152000:
|
|
|
|
|
+ return speed_t(B1152000)
|
|
|
|
|
+ case .baud1500000:
|
|
|
|
|
+ return speed_t(B1500000)
|
|
|
|
|
+ case .baud2000000:
|
|
|
|
|
+ return speed_t(B2000000)
|
|
|
|
|
+ case .baud2500000:
|
|
|
|
|
+ return speed_t(B2500000)
|
|
|
|
|
+ case .baud3500000:
|
|
|
|
|
+ return speed_t(B3500000)
|
|
|
|
|
+ case .baud4000000:
|
|
|
|
|
+ return speed_t(B4000000)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+#elseif os(OSX)
|
|
|
|
|
+public enum BaudRate {
|
|
|
|
|
+ case baud0
|
|
|
|
|
+ case baud50
|
|
|
|
|
+ case baud75
|
|
|
|
|
+ case baud110
|
|
|
|
|
+ case baud134
|
|
|
|
|
+ case baud150
|
|
|
|
|
+ case baud200
|
|
|
|
|
+ case baud300
|
|
|
|
|
+ case baud600
|
|
|
|
|
+ case baud1200
|
|
|
|
|
+ case baud1800
|
|
|
|
|
+ case baud2400
|
|
|
|
|
+ case baud4800
|
|
|
|
|
+ case baud9600
|
|
|
|
|
+ case baud19200
|
|
|
|
|
+ case baud38400
|
|
|
|
|
+ case baud57600
|
|
|
|
|
+ case baud115200
|
|
|
|
|
+ case baud230400
|
|
|
|
|
+
|
|
|
|
|
+ var speedValue: speed_t {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .baud0:
|
|
|
|
|
+ return speed_t(B0)
|
|
|
|
|
+ case .baud50:
|
|
|
|
|
+ return speed_t(B50)
|
|
|
|
|
+ case .baud75:
|
|
|
|
|
+ return speed_t(B75)
|
|
|
|
|
+ case .baud110:
|
|
|
|
|
+ return speed_t(B110)
|
|
|
|
|
+ case .baud134:
|
|
|
|
|
+ return speed_t(B134)
|
|
|
|
|
+ case .baud150:
|
|
|
|
|
+ return speed_t(B150)
|
|
|
|
|
+ case .baud200:
|
|
|
|
|
+ return speed_t(B200)
|
|
|
|
|
+ case .baud300:
|
|
|
|
|
+ return speed_t(B300)
|
|
|
|
|
+ case .baud600:
|
|
|
|
|
+ return speed_t(B600)
|
|
|
|
|
+ case .baud1200:
|
|
|
|
|
+ return speed_t(B1200)
|
|
|
|
|
+ case .baud1800:
|
|
|
|
|
+ return speed_t(B1800)
|
|
|
|
|
+ case .baud2400:
|
|
|
|
|
+ return speed_t(B2400)
|
|
|
|
|
+ case .baud4800:
|
|
|
|
|
+ return speed_t(B4800)
|
|
|
|
|
+ case .baud9600:
|
|
|
|
|
+ return speed_t(B9600)
|
|
|
|
|
+ case .baud19200:
|
|
|
|
|
+ return speed_t(B19200)
|
|
|
|
|
+ case .baud38400:
|
|
|
|
|
+ return speed_t(B38400)
|
|
|
|
|
+ case .baud57600:
|
|
|
|
|
+ return speed_t(B57600)
|
|
|
|
|
+ case .baud115200:
|
|
|
|
|
+ return speed_t(B115200)
|
|
|
|
|
+ case .baud230400:
|
|
|
|
|
+ return speed_t(B230400)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+public enum DataBitsSize {
|
|
|
|
|
+ case bits5
|
|
|
|
|
+ case bits6
|
|
|
|
|
+ case bits7
|
|
|
|
|
+ case bits8
|
|
|
|
|
+
|
|
|
|
|
+ var flagValue: tcflag_t {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .bits5:
|
|
|
|
|
+ return tcflag_t(CS5)
|
|
|
|
|
+ case .bits6:
|
|
|
|
|
+ return tcflag_t(CS6)
|
|
|
|
|
+ case .bits7:
|
|
|
|
|
+ return tcflag_t(CS7)
|
|
|
|
|
+ case .bits8:
|
|
|
|
|
+ return tcflag_t(CS8)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+public enum PortError: Int32, Error {
|
|
|
|
|
+ case failedToOpen = -1 // refer to open()
|
|
|
|
|
+ case invalidPath
|
|
|
|
|
+ case mustReceiveOrTransmit
|
|
|
|
|
+ case mustBeOpen
|
|
|
|
|
+ case stringsMustBeUTF8
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+public class SerialPort {
|
|
|
|
|
+
|
|
|
|
|
+ var path: String
|
|
|
|
|
+ var fileDescriptor: Int32?
|
|
|
|
|
+
|
|
|
|
|
+ public init(path: String) {
|
|
|
|
|
+ self.path = path
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func openPort() throws {
|
|
|
|
|
+ try openPort(toReceive: true, andTransmit: true)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func openPort(toReceive receive: Bool, andTransmit transmit: Bool) throws {
|
|
|
|
|
+ guard !path.isEmpty else {
|
|
|
|
|
+ throw PortError.invalidPath
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ guard receive || transmit else {
|
|
|
|
|
+ throw PortError.mustReceiveOrTransmit
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if receive && transmit {
|
|
|
|
|
+ fileDescriptor = open(path, O_RDWR | O_NOCTTY)
|
|
|
|
|
+ } else if receive {
|
|
|
|
|
+ fileDescriptor = open(path, O_RDONLY | O_NOCTTY)
|
|
|
|
|
+ } else if transmit {
|
|
|
|
|
+ fileDescriptor = open(path, O_WRONLY | O_NOCTTY)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fatalError()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Throw error if open() failed
|
|
|
|
|
+ if fileDescriptor == PortError.failedToOpen.rawValue {
|
|
|
|
|
+ throw PortError.failedToOpen
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func setSettings(receiveRate: BaudRate,
|
|
|
|
|
+ transmitRate: BaudRate,
|
|
|
|
|
+ minimumBytesToRead: Int,
|
|
|
|
|
+ timeout: Int = 0, /* 0 means wait indefinitely */
|
|
|
|
|
+ enableParity: Bool = false,
|
|
|
|
|
+ sendTwoStopBits: Bool = false, /* 1 stop bit is the default */
|
|
|
|
|
+ dataBitsSize: DataBitsSize = .bits8,
|
|
|
|
|
+ useHardwareFlowControl: Bool = false,
|
|
|
|
|
+ useSoftwareFlowControl: Bool = false,
|
|
|
|
|
+ processOutput: Bool = false) {
|
|
|
|
|
+ guard let fileDescriptor = fileDescriptor else {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set up the control structure
|
|
|
|
|
+ var settings = termios()
|
|
|
|
|
+
|
|
|
|
|
+ // Get options structure for the port
|
|
|
|
|
+ tcgetattr(fileDescriptor, &settings)
|
|
|
|
|
+
|
|
|
|
|
+ // Set baud rates
|
|
|
|
|
+ cfsetispeed(&settings, receiveRate.speedValue)
|
|
|
|
|
+ cfsetospeed(&settings, transmitRate.speedValue)
|
|
|
|
|
+
|
|
|
|
|
+ // Set parity enable flag
|
|
|
|
|
+ if enableParity {
|
|
|
|
|
+ settings.c_cflag |= ~tcflag_t(PARENB)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ settings.c_cflag &= ~tcflag_t(PARENB)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set stop bit flag
|
|
|
|
|
+ if sendTwoStopBits {
|
|
|
|
|
+ settings.c_cflag |= tcflag_t(CSTOPB)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ settings.c_cflag &= ~tcflag_t(CSTOPB)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set data bits size flag
|
|
|
|
|
+ settings.c_cflag &= ~tcflag_t(CSIZE)
|
|
|
|
|
+ settings.c_cflag |= dataBitsSize.flagValue
|
|
|
|
|
+
|
|
|
|
|
+ // Set hardware flow control flag
|
|
|
|
|
+ #if os(Linux)
|
|
|
|
|
+ if useHardwareFlowControl {
|
|
|
|
|
+ settings.c_cflag |= tcflag_t(CRTSCTS)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ settings.c_cflag &= ~tcflag_t(CRTSCTS)
|
|
|
|
|
+ }
|
|
|
|
|
+ #elseif os(OSX)
|
|
|
|
|
+ if useHardwareFlowControl {
|
|
|
|
|
+ settings.c_cflag |= tcflag_t(CRTS_IFLOW)
|
|
|
|
|
+ settings.c_cflag |= tcflag_t(CCTS_OFLOW)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ settings.c_cflag &= ~tcflag_t(CRTS_IFLOW)
|
|
|
|
|
+ settings.c_cflag &= ~tcflag_t(CCTS_OFLOW)
|
|
|
|
|
+ }
|
|
|
|
|
+ #endif
|
|
|
|
|
+
|
|
|
|
|
+ // Set software flow control flags
|
|
|
|
|
+ let softwareFlowControlFlags = tcflag_t(IXON | IXOFF | IXANY)
|
|
|
|
|
+ if useSoftwareFlowControl {
|
|
|
|
|
+ settings.c_iflag |= softwareFlowControlFlags
|
|
|
|
|
+ } else {
|
|
|
|
|
+ settings.c_iflag &= ~softwareFlowControlFlags
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Turn on the receiver of the serial port, and ignore modem control lines
|
|
|
|
|
+ settings.c_cflag |= tcflag_t(CREAD | CLOCAL)
|
|
|
|
|
+
|
|
|
|
|
+ // Turn off canonical mode
|
|
|
|
|
+ settings.c_lflag &= ~tcflag_t(ICANON | ECHO | ECHOE | ISIG)
|
|
|
|
|
+
|
|
|
|
|
+ // Set output processing flag
|
|
|
|
|
+ if processOutput {
|
|
|
|
|
+ settings.c_oflag |= tcflag_t(OPOST)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ settings.c_oflag &= ~tcflag_t(OPOST)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Special characters
|
|
|
|
|
+ #if os(Linux)
|
|
|
|
|
+ typealias specialCharactersTuple = (VINTR: cc_t, VQUIT: cc_t, VERASE: cc_t, VKILL: cc_t, VEOF: cc_t, VTIME: cc_t, VMIN: cc_t, VSWTC: cc_t, VSTART: cc_t, VSTOP: cc_t, VSUSP: cc_t, VEOL: cc_t, VREPRINT: cc_t, VDISCARD: cc_t, VWERASE: cc_t, VLNEXT: cc_t, VEOL2: cc_t, spare1: cc_t, spare2: cc_t, spare3: cc_t, spare4: cc_t, spare5: cc_t, spare6: cc_t, spare7: cc_t, spare8: cc_t, spare9: cc_t, spare10: cc_t, spare11: cc_t, spare12: cc_t, spare13: cc_t, spare14: cc_t, spare15: cc_t)
|
|
|
|
|
+ var specialCharacters: specialCharactersTuple = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // NCCS = 32
|
|
|
|
|
+ #elseif os(OSX)
|
|
|
|
|
+ typealias specialCharactersTuple = (VEOF: cc_t, VEOL: cc_t, VEOL2: cc_t, VERASE: cc_t, VWERASE: cc_t, VKILL: cc_t, VREPRINT: cc_t, spare1: cc_t, VINTR: cc_t, VQUIT: cc_t, VSUSP: cc_t, VDSUSP: cc_t, VSTART: cc_t, VSTOP: cc_t, VLNEXT: cc_t, VDISCARD: cc_t, VMIN: cc_t, VTIME: cc_t, VSTATUS: cc_t, spare: cc_t)
|
|
|
|
|
+ var specialCharacters: specialCharactersTuple = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // NCCS = 20
|
|
|
|
|
+ #endif
|
|
|
|
|
+
|
|
|
|
|
+ specialCharacters.VMIN = cc_t(minimumBytesToRead)
|
|
|
|
|
+ specialCharacters.VTIME = cc_t(timeout)
|
|
|
|
|
+ settings.c_cc = specialCharacters
|
|
|
|
|
+
|
|
|
|
|
+ // Commit settings
|
|
|
|
|
+ tcsetattr(fileDescriptor, TCSANOW, &settings)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func closePort() {
|
|
|
|
|
+ if let fileDescriptor = fileDescriptor {
|
|
|
|
|
+ close(fileDescriptor)
|
|
|
|
|
+ }
|
|
|
|
|
+ fileDescriptor = nil
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// MARK: Receiving
|
|
|
|
|
+
|
|
|
|
|
+extension SerialPort {
|
|
|
|
|
+
|
|
|
|
|
+ public func readBytes(into buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
|
|
|
|
|
+ guard let fileDescriptor = fileDescriptor else {
|
|
|
|
|
+ throw PortError.mustBeOpen
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let bytesRead = read(fileDescriptor, buffer, size)
|
|
|
|
|
+ return bytesRead
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func readData(ofLength length: Int) throws -> Data {
|
|
|
|
|
+ let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: length)
|
|
|
|
|
+ defer {
|
|
|
|
|
+ buffer.deallocate(capacity: length)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let bytesRead = try readBytes(into: buffer, size: length)
|
|
|
|
|
+ let data = Data(bytes: buffer, count: bytesRead)
|
|
|
|
|
+ return data
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func readString(ofLength length: Int) throws -> String {
|
|
|
|
|
+ var remainingBytesToRead = length
|
|
|
|
|
+ var result = ""
|
|
|
|
|
+
|
|
|
|
|
+ while remainingBytesToRead > 0 {
|
|
|
|
|
+ let data = try readData(ofLength: remainingBytesToRead)
|
|
|
|
|
+ if let string = String(data: data, encoding: String.Encoding.utf8) {
|
|
|
|
|
+ result += string
|
|
|
|
|
+ remainingBytesToRead -= data.count
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return result
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func readUntilChar(_ terminator: CChar) throws -> String {
|
|
|
|
|
+ var data = Data()
|
|
|
|
|
+ let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
|
|
|
|
|
+ defer {
|
|
|
|
|
+ buffer.deallocate(capacity: 1)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Read byte by byte
|
|
|
|
|
+ while try readBytes(into: buffer, size: 1) > 0 {
|
|
|
|
|
+ let character = CChar(buffer[0])
|
|
|
|
|
+ if character != terminator {
|
|
|
|
|
+ data.append(buffer, count: 1)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if let string = String(data: data, encoding: String.Encoding.utf8) {
|
|
|
|
|
+ return string
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw PortError.stringsMustBeUTF8
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func readLine() throws -> String {
|
|
|
|
|
+ let newlineChar = CChar(10) // Newline/Line feed character `\n` is 10
|
|
|
|
|
+ return try readUntilChar(newlineChar)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// MARK: Transmitting
|
|
|
|
|
+
|
|
|
|
|
+extension SerialPort {
|
|
|
|
|
+
|
|
|
|
|
+ public func writeBytes(from buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
|
|
|
|
|
+ guard let fileDescriptor = fileDescriptor else {
|
|
|
|
|
+ throw PortError.mustBeOpen
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let bytesWritten = write(fileDescriptor, buffer, size)
|
|
|
|
|
+ return bytesWritten
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func writeData(_ data: Data) throws -> Int {
|
|
|
|
|
+ let size = data.count
|
|
|
|
|
+ let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
|
|
|
|
|
+ defer {
|
|
|
|
|
+ buffer.deallocate(capacity: size)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ data.copyBytes(to: buffer, count: size)
|
|
|
|
|
+
|
|
|
|
|
+ let bytesWritten = try writeBytes(from: buffer, size: size)
|
|
|
|
|
+ return bytesWritten
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public func writeString(_ string: String) throws -> Int {
|
|
|
|
|
+ guard let data = string.data(using: String.Encoding.utf8) else {
|
|
|
|
|
+ throw PortError.stringsMustBeUTF8
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return try writeData(data)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|