Kaynağa Gözat

Initial commit of jayjun's improvements https://github.com/jayjun/SwiftLinuxSerial/tree/idiomatic-swift

Yeo Kheng Meng 9 yıl önce
ebeveyn
işleme
ecf296c107
3 değiştirilmiş dosya ile 452 ekleme ve 0 silme
  1. 4 0
      .gitignore
  2. 5 0
      Package.swift
  3. 443 0
      Sources/SwiftSerial.swift

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj

+ 5 - 0
Package.swift

@@ -0,0 +1,5 @@
+import PackageDescription
+
+let package = Package(
+    name: "SwiftSerial"
+)

+ 443 - 0
Sources/SwiftSerial.swift

@@ -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)
+    }
+}