From f3c3a214e31d16e24b006cc656524e15460c0139 Mon Sep 17 00:00:00 2001 From: Yeo Kheng Meng Date: Sat, 24 Jun 2017 17:54:36 +0800 Subject: [PATCH] adjust readbyte documentation --- README.md | 7 +- Sources/SwiftSerial.swift | 1024 ++++++++++++++++++------------------- 2 files changed, 518 insertions(+), 513 deletions(-) diff --git a/README.md b/README.md index 9197c04..a3cc206 100644 --- a/README.md +++ b/README.md @@ -196,10 +196,15 @@ func readUntilChar(_ terminator: CChar) throws -> String ``` Keep reading until the specified CChar is encountered. Return the string read so far without that value. +```swift +func readByte() throws -> UInt8 +``` +Read only one byte. This works best if `minimumBytesToRead` has been set to `1` when opening the port. This function internally calls `readBytes()`. + ```swift func readChar() throws -> UnicodeScalar ``` -Read only one character. This works best if `minimumBytesToRead` has been set to `1` when opening the port. This function internally calls `readBytes()`. +Read only one character. This works best if `minimumBytesToRead` has been set to `1` when opening the port. This function internally calls `readByte()`. ### Writing data to the port diff --git a/Sources/SwiftSerial.swift b/Sources/SwiftSerial.swift index e08d672..3a8b33a 100644 --- a/Sources/SwiftSerial.swift +++ b/Sources/SwiftSerial.swift @@ -1,512 +1,512 @@ -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 ParityType { - case none - case even - case odd - - var parityValue: tcflag_t { - switch self { - case .none: - return 0 - case .even: - return tcflag_t(PARENB) - case .odd: - return tcflag_t(PARENB | PARODD) - } - } -} - -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 - } - - var readWriteParam : Int32 - - if receive && transmit { - readWriteParam = O_RDWR - } else if receive { - readWriteParam = O_RDONLY - } else if transmit { - readWriteParam = O_WRONLY - } else { - fatalError() - } - - #if os(Linux) - fileDescriptor = open(path, readWriteParam | O_NOCTTY) - #elseif os(OSX) - fileDescriptor = open(path, readWriteParam | O_NOCTTY | O_EXLOCK) - #endif - - // 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 */ - parityType: ParityType = .none, - 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) - - // Enable parity (even/odd) if needed - settings.c_cflag |= parityType.parityValue - - // 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 - //We do this as c_cc is a C-fixed array which is imported as a tuple in Swift. - //To avoid hardcoding the VMIN or VTIME value to access the tuple value, we use the typealias instead - #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, 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.allocate(capacity: length) - defer { - buffer.deallocate(capacity: length) - } - - let bytesRead = try readBytes(into: buffer, size: length) - - var data : Data - - if bytesRead > 0 { - data = Data(bytes: buffer, count: bytesRead) - } else { - //This is to avoid the case where bytesRead can be negative causing problems allocating the Data buffer - data = Data(bytes: buffer, count: 0) - } - - 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.allocate(capacity: 1) - defer { - buffer.deallocate(capacity: 1) - } - - while true { - let bytesRead = try readBytes(into: buffer, size: 1) - - if bytesRead > 0 { - let character = CChar(buffer[0]) - - if character == terminator { - break - } else { - data.append(buffer, count: 1) - } - } - } - - 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) - } - - public func readByte() throws -> UInt8 { - let buffer = UnsafeMutablePointer.allocate(capacity: 1) - - defer { - buffer.deallocate(capacity: 1) - } - - while true { - let bytesRead = try readBytes(into: buffer, size: 1) - - if bytesRead > 0 { - return buffer[0] - } - } - } - - public func readChar() throws -> UnicodeScalar { - let byteRead = readByte() - let character = UnicodeScalar(buffer[0]) - return character - } - -} - -// MARK: Transmitting - -extension SerialPort { - - public func writeBytes(from buffer: UnsafeMutablePointer, 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.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) - } - - public func writeChar(_ character: UnicodeScalar) throws -> Int{ - let stringEquiv = String(character) - let bytesWritten = try writeString(stringEquiv) - return bytesWritten - } -} +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 ParityType { + case none + case even + case odd + + var parityValue: tcflag_t { + switch self { + case .none: + return 0 + case .even: + return tcflag_t(PARENB) + case .odd: + return tcflag_t(PARENB | PARODD) + } + } +} + +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 + } + + var readWriteParam : Int32 + + if receive && transmit { + readWriteParam = O_RDWR + } else if receive { + readWriteParam = O_RDONLY + } else if transmit { + readWriteParam = O_WRONLY + } else { + fatalError() + } + + #if os(Linux) + fileDescriptor = open(path, readWriteParam | O_NOCTTY) + #elseif os(OSX) + fileDescriptor = open(path, readWriteParam | O_NOCTTY | O_EXLOCK) + #endif + + // 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 */ + parityType: ParityType = .none, + 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) + + // Enable parity (even/odd) if needed + settings.c_cflag |= parityType.parityValue + + // 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 + //We do this as c_cc is a C-fixed array which is imported as a tuple in Swift. + //To avoid hardcoding the VMIN or VTIME value to access the tuple value, we use the typealias instead + #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, 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.allocate(capacity: length) + defer { + buffer.deallocate(capacity: length) + } + + let bytesRead = try readBytes(into: buffer, size: length) + + var data : Data + + if bytesRead > 0 { + data = Data(bytes: buffer, count: bytesRead) + } else { + //This is to avoid the case where bytesRead can be negative causing problems allocating the Data buffer + data = Data(bytes: buffer, count: 0) + } + + 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.allocate(capacity: 1) + defer { + buffer.deallocate(capacity: 1) + } + + while true { + let bytesRead = try readBytes(into: buffer, size: 1) + + if bytesRead > 0 { + let character = CChar(buffer[0]) + + if character == terminator { + break + } else { + data.append(buffer, count: 1) + } + } + } + + 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) + } + + public func readByte() throws -> UInt8 { + let buffer = UnsafeMutablePointer.allocate(capacity: 1) + + defer { + buffer.deallocate(capacity: 1) + } + + while true { + let bytesRead = try readBytes(into: buffer, size: 1) + + if bytesRead > 0 { + return buffer[0] + } + } + } + + public func readChar() throws -> UnicodeScalar { + let byteRead = readByte() + let character = UnicodeScalar(buffer[0]) + return character + } + +} + +// MARK: Transmitting + +extension SerialPort { + + public func writeBytes(from buffer: UnsafeMutablePointer, 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.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) + } + + public func writeChar(_ character: UnicodeScalar) throws -> Int{ + let stringEquiv = String(character) + let bytesWritten = try writeString(stringEquiv) + return bytesWritten + } +}