SerialPort.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. import Foundation
  2. public class SerialPort {
  3. var path: String
  4. var fileDescriptor: Int32?
  5. private var isOpen: Bool { fileDescriptor != nil }
  6. private var pollSource: DispatchSourceRead?
  7. private var readDataStream: AsyncStream<Data>?
  8. private var readBytesStream: AsyncStream<UInt8>?
  9. private var readLinesStream: AsyncStream<String>?
  10. public init(path: String) {
  11. self.path = path
  12. }
  13. public func openPort() throws {
  14. try openPort(toReceive: true, andTransmit: true)
  15. }
  16. public func openPort(toReceive receive: Bool, andTransmit transmit: Bool) throws {
  17. guard !path.isEmpty else { throw PortError.invalidPath }
  18. guard isOpen == false else { throw PortError.instanceAlreadyOpen }
  19. guard receive || transmit else { throw PortError.mustReceiveOrTransmit }
  20. var readWriteParam : Int32
  21. if receive && transmit {
  22. readWriteParam = O_RDWR
  23. } else if receive {
  24. readWriteParam = O_RDONLY
  25. } else if transmit {
  26. readWriteParam = O_WRONLY
  27. } else {
  28. fatalError()
  29. }
  30. #if os(Linux)
  31. fileDescriptor = open(path, readWriteParam | O_NOCTTY)
  32. #elseif os(OSX)
  33. fileDescriptor = open(path, readWriteParam | O_NOCTTY | O_EXLOCK)
  34. #endif
  35. // Throw error if open() failed
  36. if fileDescriptor == PortError.failedToOpen.rawValue {
  37. throw PortError.failedToOpen
  38. }
  39. guard
  40. receive,
  41. let fileDescriptor
  42. else { return }
  43. let pollSource = DispatchSource.makeReadSource(fileDescriptor: fileDescriptor, queue: .global(qos: .default))
  44. let stream = AsyncStream<Data> { continuation in
  45. pollSource.setEventHandler {
  46. let bufferSize = 1024
  47. let buffer = UnsafeMutableRawPointer
  48. .allocate(byteCount: bufferSize, alignment: 8)
  49. let bytesRead = read(fileDescriptor, buffer, bufferSize)
  50. guard bytesRead > 0 else { return }
  51. let bytes = Data(bytes: buffer, count: bytesRead)
  52. continuation.yield(bytes)
  53. }
  54. pollSource.setCancelHandler {
  55. continuation.finish()
  56. }
  57. }
  58. pollSource.resume()
  59. self.pollSource = pollSource
  60. self.readDataStream = stream
  61. }
  62. public func setSettings(
  63. receiveRate: BaudRate,
  64. transmitRate: BaudRate,
  65. minimumBytesToRead: Int,
  66. timeout: Int = 0, /* 0 means wait indefinitely */
  67. parityType: ParityType = .none,
  68. sendTwoStopBits: Bool = false, /* 1 stop bit is the default */
  69. dataBitsSize: DataBitsSize = .bits8,
  70. useHardwareFlowControl: Bool = false,
  71. useSoftwareFlowControl: Bool = false,
  72. processOutput: Bool = false
  73. ) throws {
  74. guard let fileDescriptor = fileDescriptor else {
  75. throw PortError.mustBeOpen
  76. }
  77. // Set up the control structure
  78. var settings = termios()
  79. // Get options structure for the port
  80. tcgetattr(fileDescriptor, &settings)
  81. // Set baud rates
  82. cfsetispeed(&settings, receiveRate.speedValue)
  83. cfsetospeed(&settings, transmitRate.speedValue)
  84. // Enable parity (even/odd) if needed
  85. settings.c_cflag |= parityType.parityValue
  86. // Set stop bit flag
  87. if sendTwoStopBits {
  88. settings.c_cflag |= tcflag_t(CSTOPB)
  89. } else {
  90. settings.c_cflag &= ~tcflag_t(CSTOPB)
  91. }
  92. // Set data bits size flag
  93. settings.c_cflag &= ~tcflag_t(CSIZE)
  94. settings.c_cflag |= dataBitsSize.flagValue
  95. //Disable input mapping of CR to NL, mapping of NL into CR, and ignoring CR
  96. settings.c_iflag &= ~tcflag_t(ICRNL | INLCR | IGNCR)
  97. // Set hardware flow control flag
  98. #if os(Linux)
  99. if useHardwareFlowControl {
  100. settings.c_cflag |= tcflag_t(CRTSCTS)
  101. } else {
  102. settings.c_cflag &= ~tcflag_t(CRTSCTS)
  103. }
  104. #elseif os(OSX)
  105. if useHardwareFlowControl {
  106. settings.c_cflag |= tcflag_t(CRTS_IFLOW)
  107. settings.c_cflag |= tcflag_t(CCTS_OFLOW)
  108. } else {
  109. settings.c_cflag &= ~tcflag_t(CRTS_IFLOW)
  110. settings.c_cflag &= ~tcflag_t(CCTS_OFLOW)
  111. }
  112. #endif
  113. // Set software flow control flags
  114. let softwareFlowControlFlags = tcflag_t(IXON | IXOFF | IXANY)
  115. if useSoftwareFlowControl {
  116. settings.c_iflag |= softwareFlowControlFlags
  117. } else {
  118. settings.c_iflag &= ~softwareFlowControlFlags
  119. }
  120. // Turn on the receiver of the serial port, and ignore modem control lines
  121. settings.c_cflag |= tcflag_t(CREAD | CLOCAL)
  122. // Turn off canonical mode
  123. settings.c_lflag &= ~tcflag_t(ICANON | ECHO | ECHOE | ISIG)
  124. // Set output processing flag
  125. if processOutput {
  126. settings.c_oflag |= tcflag_t(OPOST)
  127. } else {
  128. settings.c_oflag &= ~tcflag_t(OPOST)
  129. }
  130. //Special characters
  131. //We do this as c_cc is a C-fixed array which is imported as a tuple in Swift.
  132. //To avoid hardcoding the VMIN or VTIME value to access the tuple value, we use the typealias instead
  133. #if os(Linux)
  134. 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)
  135. 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
  136. #elseif os(OSX)
  137. 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)
  138. 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
  139. #endif
  140. specialCharacters.VMIN = cc_t(minimumBytesToRead)
  141. specialCharacters.VTIME = cc_t(timeout)
  142. settings.c_cc = specialCharacters
  143. // Commit settings
  144. tcsetattr(fileDescriptor, TCSANOW, &settings)
  145. }
  146. public func closePort() {
  147. pollSource?.cancel()
  148. pollSource = nil
  149. readDataStream = nil
  150. readBytesStream = nil
  151. readLinesStream = nil
  152. if let fileDescriptor = fileDescriptor {
  153. close(fileDescriptor)
  154. }
  155. fileDescriptor = nil
  156. }
  157. }
  158. // MARK: Receiving
  159. extension SerialPort {
  160. @available(*, deprecated, message: "Use async reading methods")
  161. public func readBytes(into buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
  162. guard let fileDescriptor = fileDescriptor else {
  163. throw PortError.mustBeOpen
  164. }
  165. var s: stat = stat()
  166. fstat(fileDescriptor, &s)
  167. if s.st_nlink != 1 {
  168. throw PortError.deviceNotConnected
  169. }
  170. let bytesRead = read(fileDescriptor, buffer, size)
  171. return bytesRead
  172. }
  173. @available(*, deprecated, message: "Use async reading methods")
  174. public func readData(ofLength length: Int) throws -> Data {
  175. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: length)
  176. defer {
  177. buffer.deallocate()
  178. }
  179. let bytesRead = try readBytes(into: buffer, size: length)
  180. var data : Data
  181. if bytesRead > 0 {
  182. data = Data(bytes: buffer, count: bytesRead)
  183. } else {
  184. //This is to avoid the case where bytesRead can be negative causing problems allocating the Data buffer
  185. data = Data(bytes: buffer, count: 0)
  186. }
  187. return data
  188. }
  189. @available(*, deprecated, message: "Use async reading methods")
  190. public func readString(ofLength length: Int) throws -> String {
  191. var remainingBytesToRead = length
  192. var result = ""
  193. while remainingBytesToRead > 0 {
  194. let data = try readData(ofLength: remainingBytesToRead)
  195. if let string = String(data: data, encoding: String.Encoding.utf8) {
  196. result += string
  197. remainingBytesToRead -= data.count
  198. } else {
  199. return result
  200. }
  201. }
  202. return result
  203. }
  204. @available(*, deprecated, message: "Use async reading methods")
  205. public func readUntilChar(_ terminator: CChar) throws -> String {
  206. var data = Data()
  207. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
  208. defer {
  209. buffer.deallocate()
  210. }
  211. while true {
  212. let bytesRead = try readBytes(into: buffer, size: 1)
  213. if bytesRead > 0 {
  214. if ( buffer[0] > 127) {
  215. throw PortError.unableToConvertByteToCharacter
  216. }
  217. let character = CChar(buffer[0])
  218. if character == terminator {
  219. break
  220. } else {
  221. data.append(buffer, count: 1)
  222. }
  223. }
  224. }
  225. if let string = String(data: data, encoding: String.Encoding.utf8) {
  226. return string
  227. } else {
  228. throw PortError.stringsMustBeUTF8
  229. }
  230. }
  231. @available(*, deprecated, message: "Use async reading methods")
  232. public func readLine() throws -> String {
  233. let newlineChar = CChar(10) // Newline/Line feed character `\n` is 10
  234. return try readUntilChar(newlineChar)
  235. }
  236. @available(*, deprecated, message: "Use async reading methods")
  237. public func readByte() throws -> UInt8 {
  238. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
  239. defer {
  240. buffer.deallocate()
  241. }
  242. while true {
  243. let bytesRead = try readBytes(into: buffer, size: 1)
  244. if bytesRead > 0 {
  245. return buffer[0]
  246. }
  247. }
  248. }
  249. @available(*, deprecated, message: "Use async reading methods")
  250. public func readChar() throws -> UnicodeScalar {
  251. let byteRead = try readByte()
  252. let character = UnicodeScalar(byteRead)
  253. return character
  254. }
  255. public func asyncData() throws -> AsyncStream<Data> {
  256. guard
  257. isOpen,
  258. let readDataStream
  259. else {
  260. throw PortError.mustBeOpen
  261. }
  262. return readDataStream
  263. }
  264. public func asyncBytes() throws -> AsyncStream<UInt8> {
  265. guard
  266. isOpen,
  267. let readDataStream
  268. else {
  269. throw PortError.mustBeOpen
  270. }
  271. if let existing = readBytesStream {
  272. return existing
  273. } else {
  274. let new = AsyncStream<UInt8> { continuation in
  275. Task {
  276. for try await data in readDataStream {
  277. for byte in data {
  278. continuation.yield(byte)
  279. }
  280. }
  281. continuation.finish()
  282. }
  283. }
  284. readBytesStream = new
  285. return new
  286. }
  287. }
  288. public func asyncLines() throws -> AsyncStream<String> {
  289. guard isOpen else { throw PortError.mustBeOpen }
  290. if let existing = readLinesStream {
  291. return existing
  292. } else {
  293. let byteStream = try asyncBytes()
  294. let new = AsyncStream<String> { continuation in
  295. Task {
  296. var accumulator = Data()
  297. for try await byte in byteStream {
  298. accumulator.append(byte)
  299. guard
  300. UnicodeScalar(byte) == "\n".unicodeScalars.first
  301. else { continue }
  302. defer { accumulator = Data() }
  303. guard
  304. let string = String(data: accumulator, encoding: .utf8)
  305. else {
  306. continuation.yield("Error: Non string data. Perhaps you wanted data or bytes output?")
  307. continue
  308. }
  309. continuation.yield(string)
  310. }
  311. continuation.finish()
  312. }
  313. }
  314. readLinesStream = new
  315. return new
  316. }
  317. }
  318. }
  319. // MARK: Transmitting
  320. extension SerialPort {
  321. public func writeBytes(from buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
  322. guard let fileDescriptor = fileDescriptor else {
  323. throw PortError.mustBeOpen
  324. }
  325. let bytesWritten = write(fileDescriptor, buffer, size)
  326. return bytesWritten
  327. }
  328. public func writeData(_ data: Data) throws -> Int {
  329. let size = data.count
  330. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
  331. defer {
  332. buffer.deallocate()
  333. }
  334. data.copyBytes(to: buffer, count: size)
  335. let bytesWritten = try writeBytes(from: buffer, size: size)
  336. return bytesWritten
  337. }
  338. public func writeString(_ string: String) throws -> Int {
  339. guard let data = string.data(using: String.Encoding.utf8) else {
  340. throw PortError.stringsMustBeUTF8
  341. }
  342. return try writeData(data)
  343. }
  344. public func writeChar(_ character: UnicodeScalar) throws -> Int{
  345. let stringEquiv = String(character)
  346. let bytesWritten = try writeString(stringEquiv)
  347. return bytesWritten
  348. }
  349. }