SerialPort.swift 12 KB

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