diff --git a/Examples/SwiftLinuxSerialTest/Sources/main.swift b/Examples/SwiftLinuxSerialTest/Sources/main.swift index 692ffb0..6fd0516 100644 --- a/Examples/SwiftLinuxSerialTest/Sources/main.swift +++ b/Examples/SwiftLinuxSerialTest/Sources/main.swift @@ -1,61 +1,48 @@ -import Glibc +import Foundation import SwiftLinuxSerial print("You should do a loopback i.e short the TX and RX pins of the target serial port before testing.") -let testString : String = "The big brown fox jumps over the lazy dog 01234567890." +let testString: String = "The quick brown fox jumps over the lazy dog 01234567890." let arguments = CommandLine.arguments - -if(arguments.count < 2){ - print("Need Serial Port name example /dev/ttyUSB0 as first argument"); - exit(1) +guard arguments.count >= 2 else { + print("Need serial port name, e.g. /dev/ttyUSB0 as the first argument.") + exit(1) } let portName = arguments[1] +let serialPort: SerialPort = SerialPort(name: portName) -let serialHandler : SwiftLinuxSerial = SwiftLinuxSerial(serialPortName : portName) - -let status = serialHandler.openPort(receive : true, transmit : true) - -if(status.openSuccess){ - print("Serial port " + portName + " opened successfully") -} else { - print("Serial port " + portName + " failed to open. You might need root permissions") - exit(1) -} - -serialHandler.setPortSettings(receiveBaud : SwiftLinuxSerialBaud.BAUD_B9600, - transmitBaud : SwiftLinuxSerialBaud.BAUD_B9600, - charsToReadBeforeReturn : 1) - +do { + try serialPort.openPort() + print("Serial port \(portName) opened successfully.") + defer { + serialPort.closePort() + } -let stringCharacterCount = testString.characters.count + serialPort.setSettings(receiveRate: .baud9600, + transmitRate: .baud9600, + minimumBytesToRead: 1) -print("Writing test string <" + testString + "> of " + String(stringCharacterCount) + " characters to serial port") + print("Writing test string <\(testString)> of \(testString.characters.count) characters to serial port") -var bytesWritten = serialHandler.writeStringToPortBlocking(stringToWrite : testString) + var bytesWritten = try serialPort.writeString(testString) -print("Sucessfully wrote " + String(bytesWritten) + " bytes") + print("Successfully wrote \(bytesWritten) bytes") + print("Waiting to receive what was written...") -print("Waiting to receive what was written...") + let stringReceived = try serialPort.readString(ofLength: bytesWritten) -let stringReceived = serialHandler.readStringFromPortBlocking(bytesToReadFor : bytesWritten) + if testString == stringReceived { + print("Received string is the same as transmitted string. Test successful!") + } else { + print("Uh oh! Received string is not the same as what was transmitted. This was what we received,") + print("<\(stringReceived)>") + } -serialHandler.closePort() - -if(testString == stringReceived){ - print("Received String is the same as transmitted string. Test successful!") -} else { - print("Uh oh! Received String is not the same as what was transmitted. This was what we received") - print("<" + stringReceived + ">") +} catch PortError.failedToOpen { + print("Serial port \(portName) failed to open. You might need root permissions.") +} catch { + print("Error: \(error)") } - - - - - - - - - diff --git a/Sources/SwiftLinuxSerial.swift b/Sources/SwiftLinuxSerial.swift index 6df0cf1..81fa842 100644 --- a/Sources/SwiftLinuxSerial.swift +++ b/Sources/SwiftLinuxSerial.swift @@ -1,338 +1,443 @@ -import Glibc import Foundation -public let SERIAL_OPEN_FAIL : Int32 = -1 - -public enum SwiftLinuxSerialBaud : Int{ - case BAUD_0 - case BAUD_50 - case BAUD_B75 - case BAUD_B110 - case BAUD_B134 - case BAUD_B150 - case BAUD_B200 - case BAUD_B300 - case BAUD_B600 - case BAUD_B1200 - case BAUD_B1800 - case BAUD_B2400 - case BAUD_B4800 - case BAUD_B9600 - case BAUD_B19200 - case BAUD_B38400 - case BAUD_B57600 - case BAUD_B115200 - case BAUD_B230400 - case BAUD_B460800 - case BAUD_B500000 - case BAUD_B576000 - case BAUD_B921600 - case BAUD_B1000000 - case BAUD_B1152000 - case BAUD_B1500000 - case BAUD_B2000000 - case BAUD_B2500000 - case BAUD_B3500000 - case BAUD_B4000000 +#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) + } + } } - -public enum SwiftLinuxSerialDataBit : Int{ - case DATA_BIT_5 - case DATA_BIT_6 - case DATA_BIT_7 - case DATA_BIT_8 +#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) + } + } } - -public enum SwiftLinuxSerialStopBit : Int{ - case STOP_BIT_1 - case STOP_BIT_2 +#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 class SwiftLinuxSerial { - - var fileDescriptor : Int32 = SERIAL_OPEN_FAIL - var portName : String - - public init(serialPortName : String){ - portName = serialPortName - } - - public func openPort(receive : Bool = true, transmit : Bool = true) -> (openSuccess : Bool, descriptor : Int32){ - - if(portName.isEmpty || (receive == false && transmit == false)){ - return (false, SERIAL_OPEN_FAIL) - } - - //O_NOCTTY means that no terminal will control the process opening the serial port. - if(receive && transmit){ - - //C: open(portName, O_RDONLY | O_NOCTTY); - fileDescriptor = open(portName, O_RDWR | O_NOCTTY) - } else if(receive){ - fileDescriptor = open(portName, O_RDONLY | O_NOCTTY) - } else { - fileDescriptor = open(portName, O_WRONLY | O_NOCTTY) - } - - if(fileDescriptor == SERIAL_OPEN_FAIL){ - return (false, SERIAL_OPEN_FAIL) - } else { - return (true, fileDescriptor) - } - } - - public func setPortSettings(receiveBaud : SwiftLinuxSerialBaud, - transmitBaud : SwiftLinuxSerialBaud, - charsToReadBeforeReturn : UInt8, - parity : Bool = false, - dataBits : SwiftLinuxSerialDataBit = SwiftLinuxSerialDataBit.DATA_BIT_8, - stopBit : SwiftLinuxSerialStopBit = SwiftLinuxSerialStopBit.STOP_BIT_1, - hardwareFlowControl : Bool = false, - softwareFlowControl : Bool = false, - outputProcessing : Bool = false, - minimumTimeToWaitBeforeReturn : UInt8 = 0){ //0 means wait indefinitely - - if(fileDescriptor == SERIAL_OPEN_FAIL){ - return - } - - //Set up the control structure - //C: struct termios srSettings; - var srSettings : termios = termios() - - //Get options structure for the port - tcgetattr(fileDescriptor, &srSettings) - - let receiveBaudActual = convertSwiftLinuxSerialBaudRatesEnumToRequired(param : receiveBaud) - let transmitBaudActual = convertSwiftLinuxSerialBaudRatesEnumToRequired(param : transmitBaud) - - //C: cfsetispeed(&srSettings, B9600); - cfsetispeed(&srSettings, UInt32(receiveBaudActual)) - cfsetospeed(&srSettings, UInt32(transmitBaudActual)) - - if(parity){ - //C: srSettings.c_cflag |= PARENB; - srSettings.c_cflag |= ~UInt32(PARENB) - } else { - //C: srSettings.c_cflag &= ~PARENB; - srSettings.c_cflag &= ~UInt32(PARENB) - } - - if(stopBit == SwiftLinuxSerialStopBit.STOP_BIT_2){ - srSettings.c_cflag |= UInt32(CSTOPB) - } else { - srSettings.c_cflag &= ~UInt32(CSTOPB) - } - - //Disable mask - srSettings.c_cflag &= ~UInt32(CSIZE) - - switch(dataBits){ - case SwiftLinuxSerialDataBit.DATA_BIT_5 : srSettings.c_cflag |= UInt32(CS5) - case SwiftLinuxSerialDataBit.DATA_BIT_6 : srSettings.c_cflag |= UInt32(CS6) - case SwiftLinuxSerialDataBit.DATA_BIT_7 : srSettings.c_cflag |= UInt32(CS7) - case SwiftLinuxSerialDataBit.DATA_BIT_8 : srSettings.c_cflag |= UInt32(CS8) - } - - if(hardwareFlowControl){ - srSettings.c_cflag |= UInt32(CRTSCTS) - } else { - srSettings.c_cflag &= ~UInt32(CRTSCTS) - } - - if(softwareFlowControl){ - srSettings.c_iflag |= UInt32(IXON) | UInt32(IXOFF) | UInt32(IXANY) - } else { - srSettings.c_iflag &= ~(UInt32(IXON) | UInt32(IXOFF) | UInt32(IXANY)) - } - - //Turn on the receiver of the serial port (CREAD) - srSettings.c_cflag |= UInt32(CREAD) | UInt32(CLOCAL) - - //Turn off canonical mode - srSettings.c_lflag &= ~(UInt32(ICANON) | UInt32(ECHO) | UInt32(ECHOE) | UInt32(ISIG)) - - if(outputProcessing){ - srSettings.c_oflag |= UInt32(OPOST) - } else { - srSettings.c_oflag &= ~(UInt32(OPOST)) - } - - - //Wait for certain number of characters to come in before returning - //VMIN should be position 6 in the tuple. C fixed arrays are imported as tuples in Swift - //Use print(VMIN) to confirm the value for your platform - //C: srSettings.c_cc[VMIN] = charsToReadBeforeReturn; - srSettings.c_cc.6 = charsToReadBeforeReturn - - //VTIME is position 5 in the tuple. C fixed arrays are imported as tuples in Swift - //Use print(VTIME) to check the value for your platform - //C: srSettings.c_cc[VTIME] = minimumTimeToWaitBeforeReturn; - srSettings.c_cc.5 = minimumTimeToWaitBeforeReturn - - //Commit settings - tcsetattr(fileDescriptor, TCSANOW, &srSettings) - } - - public func convertSwiftLinuxSerialBaudRatesEnumToRequired(param : SwiftLinuxSerialBaud) -> Int32{ - switch(param){ - case SwiftLinuxSerialBaud.BAUD_0 : return B0 - case SwiftLinuxSerialBaud.BAUD_50 : return B50 - case SwiftLinuxSerialBaud.BAUD_B75 : return B75 - case SwiftLinuxSerialBaud.BAUD_B110 : return B110 - case SwiftLinuxSerialBaud.BAUD_B134 : return B134 - case SwiftLinuxSerialBaud.BAUD_B150 : return B150 - case SwiftLinuxSerialBaud.BAUD_B200 : return B200 - case SwiftLinuxSerialBaud.BAUD_B300 : return B300 - case SwiftLinuxSerialBaud.BAUD_B600 : return B600 - case SwiftLinuxSerialBaud.BAUD_B1200 : return B1200 - case SwiftLinuxSerialBaud.BAUD_B1800 : return B1800 - case SwiftLinuxSerialBaud.BAUD_B2400 : return B2400 - case SwiftLinuxSerialBaud.BAUD_B4800 : return B4800 - case SwiftLinuxSerialBaud.BAUD_B9600 : return B9600 - case SwiftLinuxSerialBaud.BAUD_B19200 : return B19200 - case SwiftLinuxSerialBaud.BAUD_B38400 : return B38400 - case SwiftLinuxSerialBaud.BAUD_B57600 : return B57600 - case SwiftLinuxSerialBaud.BAUD_B115200 : return B115200 - case SwiftLinuxSerialBaud.BAUD_B230400 : return B230400 - case SwiftLinuxSerialBaud.BAUD_B460800 : return B460800 - case SwiftLinuxSerialBaud.BAUD_B500000 : return B500000 - case SwiftLinuxSerialBaud.BAUD_B576000 : return B576000 - case SwiftLinuxSerialBaud.BAUD_B921600 : return B921600 - case SwiftLinuxSerialBaud.BAUD_B1000000 : return B1000000 - case SwiftLinuxSerialBaud.BAUD_B1152000 : return B1152000 - case SwiftLinuxSerialBaud.BAUD_B1500000 : return B1500000 - case SwiftLinuxSerialBaud.BAUD_B2000000 : return B2000000 - case SwiftLinuxSerialBaud.BAUD_B2500000 : return B2500000 - case SwiftLinuxSerialBaud.BAUD_B3500000 : return B3500000 - case SwiftLinuxSerialBaud.BAUD_B4000000 : return B4000000 - } - } - - //C: readBytesFromPortBlocking(char * buf, int size) - public func readBytesFromPortBlocking(buf : UnsafeMutablePointer, size : Int) -> Int { - if(fileDescriptor == SERIAL_OPEN_FAIL){ - return 0 - } - - let bytesRead : Int = read(fileDescriptor, buf, size) - return bytesRead - } - - public func writeBytesToPortBlocking(buf : UnsafeMutablePointer, size : Int) -> Int { - if(fileDescriptor == SERIAL_OPEN_FAIL){ - return 0 - } - - let bytesWritten : Int = write(fileDescriptor, buf, size) - return bytesWritten - } - - public func readDataFromPortBlocking(bytesToReadFor : Int) -> (dataRead : Data, bytesRead : Int) { - - //C: char * tempBuffer = (char*) malloc(sizeof(char) * bytesToReadFor); - let tempBuffer = UnsafeMutablePointer.allocate(capacity : bytesToReadFor) - - //C: free(tempBuffer) - defer { tempBuffer.deallocate(capacity : bytesToReadFor) } - - let bytesRead : Int = readBytesFromPortBlocking(buf : tempBuffer, size : bytesToReadFor) - let data = Data(bytes: tempBuffer, count: bytesRead) - - return (dataRead : data, bytesRead : bytesRead) - } - - public func writeDataToPortBlocking(dataToWrite : Data) -> Int { - - let sizeToWrite = dataToWrite.count - - let tempBuffer = UnsafeMutablePointer.allocate(capacity : sizeToWrite) - defer { tempBuffer.deallocate(capacity : sizeToWrite) } - - dataToWrite.copyBytes(to : tempBuffer, count : sizeToWrite) - - let bytesWritten : Int = writeBytesToPortBlocking(buf : tempBuffer, size : sizeToWrite) - return bytesWritten - } - - - public func writeStringToPortBlocking(stringToWrite : String) -> Int { - let data : Data? = stringToWrite.data(using : String.Encoding.utf8) - - if(data == nil){ - return 0 - } - - return writeDataToPortBlocking(dataToWrite : data!) - } - - - public func readStringFromPortBlocking(bytesToReadFor : Int) -> String { - - var bytesToReadRemaining : Int = bytesToReadFor - var bytesReadSoFar : Int = 0 - var stringReadSoFar : String = "" - - while(bytesReadSoFar < bytesToReadFor){ - - - let result = readDataFromPortBlocking(bytesToReadFor : bytesToReadRemaining) - - let dataRead = result.dataRead - let bytesRead = result.bytesRead - - let stringRead : String? = String(data: dataRead, encoding: String.Encoding.utf8) - - if(stringRead == nil){ - return stringReadSoFar - } else { - stringReadSoFar = stringReadSoFar + stringRead! - - bytesReadSoFar += bytesRead - bytesToReadRemaining -= bytesRead - } - +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, 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) + 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.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) + } +} - return stringReadSoFar +// MARK: Transmitting - } +extension SerialPort { - public func closePort(){ - if(fileDescriptor != SERIAL_OPEN_FAIL){ - close(fileDescriptor) - fileDescriptor = SERIAL_OPEN_FAIL - } - } + public func writeBytes(from buffer: UnsafeMutablePointer, size: Int) throws -> Int { + guard let fileDescriptor = fileDescriptor else { + throw PortError.mustBeOpen + } - public func readTillCharacterBlocking(characterRep : UnicodeScalar) -> String{ - var lineBuffer : String = "" - let tempBuffer = UnsafeMutablePointer.allocate(capacity : 1) - defer { tempBuffer.deallocate(capacity : 1) } + let bytesWritten = write(fileDescriptor, buffer, size) + return bytesWritten + } - while(true){ - //Read byte by byte so we pass 1 - let bytesRead : Int = readBytesFromPortBlocking(buf : tempBuffer, size : 1) + public func writeData(_ data: Data) throws -> Int { + let size = data.count + let buffer = UnsafeMutablePointer.allocate(capacity: size) + defer { + buffer.deallocate(capacity: size) + } - if(bytesRead > 0){ - let newestCharacter : UnicodeScalar = UnicodeScalar(tempBuffer[0]) + data.copyBytes(to: buffer, count: size) - if(newestCharacter == characterRep){ - return lineBuffer - } else { - lineBuffer = lineBuffer + String(newestCharacter) - } - } - } - } + let bytesWritten = try writeBytes(from: buffer, size: size) + return bytesWritten + } - public func readLineFromPortBlocking() -> String{ - //UnicodeScalar(10) is the newline \n character - return readTillCharacterBlocking(characterRep : UnicodeScalar(10)) - } + public func writeString(_ string: String) throws -> Int { + guard let data = string.data(using: String.Encoding.utf8) else { + throw PortError.stringsMustBeUTF8 + } + return try writeData(data) + } } - -