SwiftSerial.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. import Foundation
  2. public enum BaudRate {
  3. case baud0
  4. case baud50
  5. case baud75
  6. case baud110
  7. case baud134
  8. case baud150
  9. case baud200
  10. case baud300
  11. case baud600
  12. case baud1200
  13. case baud1800
  14. case baud2400
  15. case baud4800
  16. case baud9600
  17. case baud19200
  18. case baud38400
  19. case baud57600
  20. case baud115200
  21. case baud230400
  22. #if os(Linux)
  23. case baud460800
  24. case baud500000
  25. case baud576000
  26. case baud921600
  27. case baud1000000
  28. case baud1152000
  29. case baud1500000
  30. case baud2000000
  31. case baud2500000
  32. case baud3500000
  33. case baud4000000
  34. #endif
  35. var speedValue: speed_t {
  36. switch self {
  37. case .baud0:
  38. return speed_t(B0)
  39. case .baud50:
  40. return speed_t(B50)
  41. case .baud75:
  42. return speed_t(B75)
  43. case .baud110:
  44. return speed_t(B110)
  45. case .baud134:
  46. return speed_t(B134)
  47. case .baud150:
  48. return speed_t(B150)
  49. case .baud200:
  50. return speed_t(B200)
  51. case .baud300:
  52. return speed_t(B300)
  53. case .baud600:
  54. return speed_t(B600)
  55. case .baud1200:
  56. return speed_t(B1200)
  57. case .baud1800:
  58. return speed_t(B1800)
  59. case .baud2400:
  60. return speed_t(B2400)
  61. case .baud4800:
  62. return speed_t(B4800)
  63. case .baud9600:
  64. return speed_t(B9600)
  65. case .baud19200:
  66. return speed_t(B19200)
  67. case .baud38400:
  68. return speed_t(B38400)
  69. case .baud57600:
  70. return speed_t(B57600)
  71. case .baud115200:
  72. return speed_t(B115200)
  73. case .baud230400:
  74. return speed_t(B230400)
  75. #if os(Linux)
  76. case .baud460800:
  77. return speed_t(B460800)
  78. case .baud500000:
  79. return speed_t(B500000)
  80. case .baud576000:
  81. return speed_t(B576000)
  82. case .baud921600:
  83. return speed_t(B921600)
  84. case .baud1000000:
  85. return speed_t(B1000000)
  86. case .baud1152000:
  87. return speed_t(B1152000)
  88. case .baud1500000:
  89. return speed_t(B1500000)
  90. case .baud2000000:
  91. return speed_t(B2000000)
  92. case .baud2500000:
  93. return speed_t(B2500000)
  94. case .baud3500000:
  95. return speed_t(B3500000)
  96. case .baud4000000:
  97. return speed_t(B4000000)
  98. #endif
  99. }
  100. }
  101. }
  102. public enum DataBitsSize {
  103. case bits5
  104. case bits6
  105. case bits7
  106. case bits8
  107. var flagValue: tcflag_t {
  108. switch self {
  109. case .bits5:
  110. return tcflag_t(CS5)
  111. case .bits6:
  112. return tcflag_t(CS6)
  113. case .bits7:
  114. return tcflag_t(CS7)
  115. case .bits8:
  116. return tcflag_t(CS8)
  117. }
  118. }
  119. }
  120. public enum ParityType {
  121. case none
  122. case even
  123. case odd
  124. var parityValue: tcflag_t {
  125. switch self {
  126. case .none:
  127. return 0
  128. case .even:
  129. return tcflag_t(PARENB)
  130. case .odd:
  131. return tcflag_t(PARENB | PARODD)
  132. }
  133. }
  134. }
  135. public enum PortError: Int32, Error {
  136. case failedToOpen = -1 // refer to open()
  137. case invalidPath
  138. case mustReceiveOrTransmit
  139. case mustBeOpen
  140. case stringsMustBeUTF8
  141. case unableToConvertByteToCharacter
  142. case deviceNotConnected
  143. }
  144. public class SerialPort {
  145. var path: String
  146. var fileDescriptor: Int32?
  147. private var pollSource: DispatchSourceRead?
  148. private var readDataStream: AsyncStream<Data>?
  149. private var readBytesStream: AsyncStream<UInt8>?
  150. private var readLinesStream: AsyncStream<String>?
  151. public init(path: String) {
  152. self.path = path
  153. }
  154. public func openPort() throws {
  155. try openPort(toReceive: true, andTransmit: true)
  156. }
  157. public func openPort(toReceive receive: Bool, andTransmit transmit: Bool) throws {
  158. guard !path.isEmpty else {
  159. throw PortError.invalidPath
  160. }
  161. guard receive || transmit else {
  162. throw PortError.mustReceiveOrTransmit
  163. }
  164. var readWriteParam : Int32
  165. if receive && transmit {
  166. readWriteParam = O_RDWR
  167. } else if receive {
  168. readWriteParam = O_RDONLY
  169. } else if transmit {
  170. readWriteParam = O_WRONLY
  171. } else {
  172. fatalError()
  173. }
  174. #if os(Linux)
  175. fileDescriptor = open(path, readWriteParam | O_NOCTTY)
  176. #elseif os(OSX)
  177. fileDescriptor = open(path, readWriteParam | O_NOCTTY | O_EXLOCK)
  178. #endif
  179. // Throw error if open() failed
  180. if fileDescriptor == PortError.failedToOpen.rawValue {
  181. throw PortError.failedToOpen
  182. }
  183. guard
  184. receive,
  185. let fileDescriptor
  186. else { return }
  187. let pollSource = DispatchSource.makeReadSource(fileDescriptor: fileDescriptor, queue: .global(qos: .default))
  188. let stream = AsyncStream<Data> { continuation in
  189. pollSource.setEventHandler {
  190. let bufferSize = 1024
  191. let buffer = UnsafeMutableRawPointer
  192. .allocate(byteCount: bufferSize, alignment: 8)
  193. let bytesRead = read(fileDescriptor, buffer, bufferSize)
  194. guard bytesRead > 0 else { return }
  195. let bytes = Data(bytes: buffer, count: bytesRead)
  196. continuation.yield(bytes)
  197. }
  198. pollSource.setCancelHandler {
  199. continuation.finish()
  200. }
  201. }
  202. pollSource.resume()
  203. self.readDataStream = stream
  204. }
  205. public func setSettings(receiveRate: BaudRate,
  206. transmitRate: BaudRate,
  207. minimumBytesToRead: Int,
  208. timeout: Int = 0, /* 0 means wait indefinitely */
  209. parityType: ParityType = .none,
  210. sendTwoStopBits: Bool = false, /* 1 stop bit is the default */
  211. dataBitsSize: DataBitsSize = .bits8,
  212. useHardwareFlowControl: Bool = false,
  213. useSoftwareFlowControl: Bool = false,
  214. processOutput: Bool = false) {
  215. guard let fileDescriptor = fileDescriptor else {
  216. return
  217. }
  218. // Set up the control structure
  219. var settings = termios()
  220. // Get options structure for the port
  221. tcgetattr(fileDescriptor, &settings)
  222. // Set baud rates
  223. cfsetispeed(&settings, receiveRate.speedValue)
  224. cfsetospeed(&settings, transmitRate.speedValue)
  225. // Enable parity (even/odd) if needed
  226. settings.c_cflag |= parityType.parityValue
  227. // Set stop bit flag
  228. if sendTwoStopBits {
  229. settings.c_cflag |= tcflag_t(CSTOPB)
  230. } else {
  231. settings.c_cflag &= ~tcflag_t(CSTOPB)
  232. }
  233. // Set data bits size flag
  234. settings.c_cflag &= ~tcflag_t(CSIZE)
  235. settings.c_cflag |= dataBitsSize.flagValue
  236. //Disable input mapping of CR to NL, mapping of NL into CR, and ignoring CR
  237. settings.c_iflag &= ~tcflag_t(ICRNL | INLCR | IGNCR)
  238. // Set hardware flow control flag
  239. #if os(Linux)
  240. if useHardwareFlowControl {
  241. settings.c_cflag |= tcflag_t(CRTSCTS)
  242. } else {
  243. settings.c_cflag &= ~tcflag_t(CRTSCTS)
  244. }
  245. #elseif os(OSX)
  246. if useHardwareFlowControl {
  247. settings.c_cflag |= tcflag_t(CRTS_IFLOW)
  248. settings.c_cflag |= tcflag_t(CCTS_OFLOW)
  249. } else {
  250. settings.c_cflag &= ~tcflag_t(CRTS_IFLOW)
  251. settings.c_cflag &= ~tcflag_t(CCTS_OFLOW)
  252. }
  253. #endif
  254. // Set software flow control flags
  255. let softwareFlowControlFlags = tcflag_t(IXON | IXOFF | IXANY)
  256. if useSoftwareFlowControl {
  257. settings.c_iflag |= softwareFlowControlFlags
  258. } else {
  259. settings.c_iflag &= ~softwareFlowControlFlags
  260. }
  261. // Turn on the receiver of the serial port, and ignore modem control lines
  262. settings.c_cflag |= tcflag_t(CREAD | CLOCAL)
  263. // Turn off canonical mode
  264. settings.c_lflag &= ~tcflag_t(ICANON | ECHO | ECHOE | ISIG)
  265. // Set output processing flag
  266. if processOutput {
  267. settings.c_oflag |= tcflag_t(OPOST)
  268. } else {
  269. settings.c_oflag &= ~tcflag_t(OPOST)
  270. }
  271. //Special characters
  272. //We do this as c_cc is a C-fixed array which is imported as a tuple in Swift.
  273. //To avoid hardcoding the VMIN or VTIME value to access the tuple value, we use the typealias instead
  274. #if os(Linux)
  275. 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)
  276. 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
  277. #elseif os(OSX)
  278. 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)
  279. 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
  280. #endif
  281. specialCharacters.VMIN = cc_t(minimumBytesToRead)
  282. specialCharacters.VTIME = cc_t(timeout)
  283. settings.c_cc = specialCharacters
  284. // Commit settings
  285. tcsetattr(fileDescriptor, TCSANOW, &settings)
  286. }
  287. public func closePort() {
  288. pollSource?.cancel()
  289. pollSource = nil
  290. readDataStream = nil
  291. readBytesStream = nil
  292. readLinesStream = nil
  293. if let fileDescriptor = fileDescriptor {
  294. close(fileDescriptor)
  295. }
  296. fileDescriptor = nil
  297. }
  298. }
  299. // MARK: Receiving
  300. extension SerialPort {
  301. public func readBytes(into buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
  302. guard let fileDescriptor = fileDescriptor else {
  303. throw PortError.mustBeOpen
  304. }
  305. var s: stat = stat()
  306. fstat(fileDescriptor, &s)
  307. if s.st_nlink != 1 {
  308. throw PortError.deviceNotConnected
  309. }
  310. let bytesRead = read(fileDescriptor, buffer, size)
  311. return bytesRead
  312. }
  313. public func readData(ofLength length: Int) throws -> Data {
  314. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: length)
  315. defer {
  316. buffer.deallocate()
  317. }
  318. let bytesRead = try readBytes(into: buffer, size: length)
  319. var data : Data
  320. if bytesRead > 0 {
  321. data = Data(bytes: buffer, count: bytesRead)
  322. } else {
  323. //This is to avoid the case where bytesRead can be negative causing problems allocating the Data buffer
  324. data = Data(bytes: buffer, count: 0)
  325. }
  326. return data
  327. }
  328. public func readString(ofLength length: Int) throws -> String {
  329. var remainingBytesToRead = length
  330. var result = ""
  331. while remainingBytesToRead > 0 {
  332. let data = try readData(ofLength: remainingBytesToRead)
  333. if let string = String(data: data, encoding: String.Encoding.utf8) {
  334. result += string
  335. remainingBytesToRead -= data.count
  336. } else {
  337. return result
  338. }
  339. }
  340. return result
  341. }
  342. public func readUntilChar(_ terminator: CChar) throws -> String {
  343. var data = Data()
  344. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
  345. defer {
  346. buffer.deallocate()
  347. }
  348. while true {
  349. let bytesRead = try readBytes(into: buffer, size: 1)
  350. if bytesRead > 0 {
  351. if ( buffer[0] > 127) {
  352. throw PortError.unableToConvertByteToCharacter
  353. }
  354. let character = CChar(buffer[0])
  355. if character == terminator {
  356. break
  357. } else {
  358. data.append(buffer, count: 1)
  359. }
  360. }
  361. }
  362. if let string = String(data: data, encoding: String.Encoding.utf8) {
  363. return string
  364. } else {
  365. throw PortError.stringsMustBeUTF8
  366. }
  367. }
  368. public func readLine() throws -> String {
  369. let newlineChar = CChar(10) // Newline/Line feed character `\n` is 10
  370. return try readUntilChar(newlineChar)
  371. }
  372. public func readByte() throws -> UInt8 {
  373. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
  374. defer {
  375. buffer.deallocate()
  376. }
  377. while true {
  378. let bytesRead = try readBytes(into: buffer, size: 1)
  379. if bytesRead > 0 {
  380. return buffer[0]
  381. }
  382. }
  383. }
  384. public func readChar() throws -> UnicodeScalar {
  385. let byteRead = try readByte()
  386. let character = UnicodeScalar(byteRead)
  387. return character
  388. }
  389. public func asyncData() throws -> AsyncStream<Data> {
  390. guard
  391. fileDescriptor != nil,
  392. let readDataStream
  393. else {
  394. throw PortError.mustBeOpen
  395. }
  396. return readDataStream
  397. }
  398. public func asyncBytes() throws -> AsyncStream<UInt8> {
  399. guard
  400. fileDescriptor != nil,
  401. let readDataStream
  402. else {
  403. throw PortError.mustBeOpen
  404. }
  405. if let existing = readBytesStream {
  406. return existing
  407. } else {
  408. let new = AsyncStream<UInt8> { continuation in
  409. Task {
  410. for try await data in readDataStream {
  411. for byte in data {
  412. continuation.yield(byte)
  413. }
  414. }
  415. continuation.finish()
  416. }
  417. }
  418. readBytesStream = new
  419. return new
  420. }
  421. }
  422. public func asyncLines() throws -> AsyncStream<String> {
  423. guard
  424. fileDescriptor != nil
  425. else {
  426. throw PortError.mustBeOpen
  427. }
  428. if let existing = readLinesStream {
  429. return existing
  430. } else {
  431. let byteStream = try asyncBytes()
  432. let new = AsyncStream<String> { continuation in
  433. Task {
  434. var accumulator = Data()
  435. for try await byte in byteStream {
  436. accumulator.append(byte)
  437. guard
  438. UnicodeScalar(byte) == "\n".unicodeScalars.first
  439. else { continue }
  440. defer { accumulator = Data() }
  441. guard
  442. let string = String(data: accumulator, encoding: .utf8)
  443. else {
  444. continuation.yield("Error: Non string data. Perhaps you wanted data or bytes output?")
  445. continue
  446. }
  447. continuation.yield(string)
  448. }
  449. continuation.finish()
  450. }
  451. }
  452. readLinesStream = new
  453. return new
  454. }
  455. }
  456. }
  457. // MARK: Transmitting
  458. extension SerialPort {
  459. public func writeBytes(from buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
  460. guard let fileDescriptor = fileDescriptor else {
  461. throw PortError.mustBeOpen
  462. }
  463. let bytesWritten = write(fileDescriptor, buffer, size)
  464. return bytesWritten
  465. }
  466. public func writeData(_ data: Data) throws -> Int {
  467. let size = data.count
  468. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
  469. defer {
  470. buffer.deallocate()
  471. }
  472. data.copyBytes(to: buffer, count: size)
  473. let bytesWritten = try writeBytes(from: buffer, size: size)
  474. return bytesWritten
  475. }
  476. public func writeString(_ string: String) throws -> Int {
  477. guard let data = string.data(using: String.Encoding.utf8) else {
  478. throw PortError.stringsMustBeUTF8
  479. }
  480. return try writeData(data)
  481. }
  482. public func writeChar(_ character: UnicodeScalar) throws -> Int{
  483. let stringEquiv = String(character)
  484. let bytesWritten = try writeString(stringEquiv)
  485. return bytesWritten
  486. }
  487. }