SwiftSerial.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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.pollSource = pollSource
  204. self.readDataStream = stream
  205. }
  206. public func setSettings(receiveRate: BaudRate,
  207. transmitRate: BaudRate,
  208. minimumBytesToRead: Int,
  209. timeout: Int = 0, /* 0 means wait indefinitely */
  210. parityType: ParityType = .none,
  211. sendTwoStopBits: Bool = false, /* 1 stop bit is the default */
  212. dataBitsSize: DataBitsSize = .bits8,
  213. useHardwareFlowControl: Bool = false,
  214. useSoftwareFlowControl: Bool = false,
  215. processOutput: Bool = false) {
  216. guard let fileDescriptor = fileDescriptor else {
  217. return
  218. }
  219. // Set up the control structure
  220. var settings = termios()
  221. // Get options structure for the port
  222. tcgetattr(fileDescriptor, &settings)
  223. // Set baud rates
  224. cfsetispeed(&settings, receiveRate.speedValue)
  225. cfsetospeed(&settings, transmitRate.speedValue)
  226. // Enable parity (even/odd) if needed
  227. settings.c_cflag |= parityType.parityValue
  228. // Set stop bit flag
  229. if sendTwoStopBits {
  230. settings.c_cflag |= tcflag_t(CSTOPB)
  231. } else {
  232. settings.c_cflag &= ~tcflag_t(CSTOPB)
  233. }
  234. // Set data bits size flag
  235. settings.c_cflag &= ~tcflag_t(CSIZE)
  236. settings.c_cflag |= dataBitsSize.flagValue
  237. //Disable input mapping of CR to NL, mapping of NL into CR, and ignoring CR
  238. settings.c_iflag &= ~tcflag_t(ICRNL | INLCR | IGNCR)
  239. // Set hardware flow control flag
  240. #if os(Linux)
  241. if useHardwareFlowControl {
  242. settings.c_cflag |= tcflag_t(CRTSCTS)
  243. } else {
  244. settings.c_cflag &= ~tcflag_t(CRTSCTS)
  245. }
  246. #elseif os(OSX)
  247. if useHardwareFlowControl {
  248. settings.c_cflag |= tcflag_t(CRTS_IFLOW)
  249. settings.c_cflag |= tcflag_t(CCTS_OFLOW)
  250. } else {
  251. settings.c_cflag &= ~tcflag_t(CRTS_IFLOW)
  252. settings.c_cflag &= ~tcflag_t(CCTS_OFLOW)
  253. }
  254. #endif
  255. // Set software flow control flags
  256. let softwareFlowControlFlags = tcflag_t(IXON | IXOFF | IXANY)
  257. if useSoftwareFlowControl {
  258. settings.c_iflag |= softwareFlowControlFlags
  259. } else {
  260. settings.c_iflag &= ~softwareFlowControlFlags
  261. }
  262. // Turn on the receiver of the serial port, and ignore modem control lines
  263. settings.c_cflag |= tcflag_t(CREAD | CLOCAL)
  264. // Turn off canonical mode
  265. settings.c_lflag &= ~tcflag_t(ICANON | ECHO | ECHOE | ISIG)
  266. // Set output processing flag
  267. if processOutput {
  268. settings.c_oflag |= tcflag_t(OPOST)
  269. } else {
  270. settings.c_oflag &= ~tcflag_t(OPOST)
  271. }
  272. //Special characters
  273. //We do this as c_cc is a C-fixed array which is imported as a tuple in Swift.
  274. //To avoid hardcoding the VMIN or VTIME value to access the tuple value, we use the typealias instead
  275. #if os(Linux)
  276. 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)
  277. 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
  278. #elseif os(OSX)
  279. 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)
  280. 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
  281. #endif
  282. specialCharacters.VMIN = cc_t(minimumBytesToRead)
  283. specialCharacters.VTIME = cc_t(timeout)
  284. settings.c_cc = specialCharacters
  285. // Commit settings
  286. tcsetattr(fileDescriptor, TCSANOW, &settings)
  287. }
  288. public func closePort() {
  289. pollSource?.cancel()
  290. pollSource = nil
  291. readDataStream = nil
  292. readBytesStream = nil
  293. readLinesStream = nil
  294. if let fileDescriptor = fileDescriptor {
  295. close(fileDescriptor)
  296. }
  297. fileDescriptor = nil
  298. }
  299. }
  300. // MARK: Receiving
  301. extension SerialPort {
  302. public func readBytes(into buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
  303. guard let fileDescriptor = fileDescriptor else {
  304. throw PortError.mustBeOpen
  305. }
  306. var s: stat = stat()
  307. fstat(fileDescriptor, &s)
  308. if s.st_nlink != 1 {
  309. throw PortError.deviceNotConnected
  310. }
  311. let bytesRead = read(fileDescriptor, buffer, size)
  312. return bytesRead
  313. }
  314. public func readData(ofLength length: Int) throws -> Data {
  315. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: length)
  316. defer {
  317. buffer.deallocate()
  318. }
  319. let bytesRead = try readBytes(into: buffer, size: length)
  320. var data : Data
  321. if bytesRead > 0 {
  322. data = Data(bytes: buffer, count: bytesRead)
  323. } else {
  324. //This is to avoid the case where bytesRead can be negative causing problems allocating the Data buffer
  325. data = Data(bytes: buffer, count: 0)
  326. }
  327. return data
  328. }
  329. public func readString(ofLength length: Int) throws -> String {
  330. var remainingBytesToRead = length
  331. var result = ""
  332. while remainingBytesToRead > 0 {
  333. let data = try readData(ofLength: remainingBytesToRead)
  334. if let string = String(data: data, encoding: String.Encoding.utf8) {
  335. result += string
  336. remainingBytesToRead -= data.count
  337. } else {
  338. return result
  339. }
  340. }
  341. return result
  342. }
  343. public func readUntilChar(_ terminator: CChar) throws -> String {
  344. var data = Data()
  345. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
  346. defer {
  347. buffer.deallocate()
  348. }
  349. while true {
  350. let bytesRead = try readBytes(into: buffer, size: 1)
  351. if bytesRead > 0 {
  352. if ( buffer[0] > 127) {
  353. throw PortError.unableToConvertByteToCharacter
  354. }
  355. let character = CChar(buffer[0])
  356. if character == terminator {
  357. break
  358. } else {
  359. data.append(buffer, count: 1)
  360. }
  361. }
  362. }
  363. if let string = String(data: data, encoding: String.Encoding.utf8) {
  364. return string
  365. } else {
  366. throw PortError.stringsMustBeUTF8
  367. }
  368. }
  369. public func readLine() throws -> String {
  370. let newlineChar = CChar(10) // Newline/Line feed character `\n` is 10
  371. return try readUntilChar(newlineChar)
  372. }
  373. public func readByte() throws -> UInt8 {
  374. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
  375. defer {
  376. buffer.deallocate()
  377. }
  378. while true {
  379. let bytesRead = try readBytes(into: buffer, size: 1)
  380. if bytesRead > 0 {
  381. return buffer[0]
  382. }
  383. }
  384. }
  385. public func readChar() throws -> UnicodeScalar {
  386. let byteRead = try readByte()
  387. let character = UnicodeScalar(byteRead)
  388. return character
  389. }
  390. public func asyncData() throws -> AsyncStream<Data> {
  391. guard
  392. fileDescriptor != nil,
  393. let readDataStream
  394. else {
  395. throw PortError.mustBeOpen
  396. }
  397. return readDataStream
  398. }
  399. public func asyncBytes() throws -> AsyncStream<UInt8> {
  400. guard
  401. fileDescriptor != nil,
  402. let readDataStream
  403. else {
  404. throw PortError.mustBeOpen
  405. }
  406. if let existing = readBytesStream {
  407. return existing
  408. } else {
  409. let new = AsyncStream<UInt8> { continuation in
  410. Task {
  411. for try await data in readDataStream {
  412. for byte in data {
  413. continuation.yield(byte)
  414. }
  415. }
  416. continuation.finish()
  417. }
  418. }
  419. readBytesStream = new
  420. return new
  421. }
  422. }
  423. public func asyncLines() throws -> AsyncStream<String> {
  424. guard
  425. fileDescriptor != nil
  426. else {
  427. throw PortError.mustBeOpen
  428. }
  429. if let existing = readLinesStream {
  430. return existing
  431. } else {
  432. let byteStream = try asyncBytes()
  433. let new = AsyncStream<String> { continuation in
  434. Task {
  435. var accumulator = Data()
  436. for try await byte in byteStream {
  437. accumulator.append(byte)
  438. guard
  439. UnicodeScalar(byte) == "\n".unicodeScalars.first
  440. else { continue }
  441. defer { accumulator = Data() }
  442. guard
  443. let string = String(data: accumulator, encoding: .utf8)
  444. else {
  445. continuation.yield("Error: Non string data. Perhaps you wanted data or bytes output?")
  446. continue
  447. }
  448. continuation.yield(string)
  449. }
  450. continuation.finish()
  451. }
  452. }
  453. readLinesStream = new
  454. return new
  455. }
  456. }
  457. }
  458. // MARK: Transmitting
  459. extension SerialPort {
  460. public func writeBytes(from buffer: UnsafeMutablePointer<UInt8>, size: Int) throws -> Int {
  461. guard let fileDescriptor = fileDescriptor else {
  462. throw PortError.mustBeOpen
  463. }
  464. let bytesWritten = write(fileDescriptor, buffer, size)
  465. return bytesWritten
  466. }
  467. public func writeData(_ data: Data) throws -> Int {
  468. let size = data.count
  469. let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
  470. defer {
  471. buffer.deallocate()
  472. }
  473. data.copyBytes(to: buffer, count: size)
  474. let bytesWritten = try writeBytes(from: buffer, size: size)
  475. return bytesWritten
  476. }
  477. public func writeString(_ string: String) throws -> Int {
  478. guard let data = string.data(using: String.Encoding.utf8) else {
  479. throw PortError.stringsMustBeUTF8
  480. }
  481. return try writeData(data)
  482. }
  483. public func writeChar(_ character: UnicodeScalar) throws -> Int{
  484. let stringEquiv = String(character)
  485. let bytesWritten = try writeString(stringEquiv)
  486. return bytesWritten
  487. }
  488. }