macOS串口编程指南:使用ORSSerial库实现高效通信
目录
本文凌顺实验室(lingshunlab.com)详细介绍了如何在macOS应用中利用ORSSerial库进行串口通信编程。主要内容包括:
- macOS串口通信基础
- ORSSerial库的安装和配置
- 串口设备枚举和列表展示
- 串口打开和关闭操作
- 十六进制数据的发送技巧
- 异步数据接收和解析方法
- 串口连接状态监控
本教程采用Swift语言,展示了串口通信的核心功能实现,包括单例模式的串口管理、事件驱动的数据处理,以及用户友好的错误处理。
无论您是开发串口调试工具、硬件控制软件,还是需要与串口设备交互的应用,本文都为您提供了全面的macOS串口编程解决方案。掌握这些技能,将帮助您在macOS平台上轻松实现各种串口通信需求。
#macOS开发 #串口通信 #ORSSerial #Swift编程 #硬件交互 #异步通信 #串口调试
新建项目
新建一个项目,这里名称为「easySerial」
添加APP访问串口的权限
要让App可以访问到串口我们需要添加串口的权限,不然的话是无法打开任何的串口。
打开的方法需要在项目中以项目名称命名的文件,例如例子中打开如下图的easySerial文件,添加一个Key 为「com.apple.security.device.serial」的字段,并且Key的Type为「Bool」,Value为「Yes」
添加ORSSerial库
在顶部菜单栏中yi「File」->「Add Package Dependencies...」打开Package的管理窗口。
在Package的管理窗口中添加ORSSerial库
在打开的「Add Package Collection」对话框中,输入ORSSerial库的github地址:
https://github.com/armadsen/ORSSerialPort
编写代码
列出所有串口的代码
例如在本例子,在项目的根目录会新建一个「AppDelegate」的swift文件,代码如下:
// welcom to www.lingshunlab.com
import SwiftUI
import ORSSerial
final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, NSMenuDelegate {
let serial = SerialPortManager.shared
func applicationDidFinishLaunching(_ aNotification: Notification) {
print(serial.listSerialPorts()) // print serial prots in debug window
}
}
class SerialPortManager {
static let shared = SerialPortManager()
let serialPortManger = ORSSerialPortManager.shared()
@Published var serialPorts: [ORSSerialPort] = []
@objc dynamic var serialPort: ORSSerialPort? {
didSet {
oldValue?.close()
oldValue?.delegate = nil
serialPort?.delegate = self
}
}
func listSerialPorts() -> [ORSSerialPort]{
self.serialPorts = serialPortManger.availablePorts // list serial prots
return self.serialPorts
}
}
并且在「easySerialApp」文件中添加调用AppDelegate的代码:
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
点击运行App,可以在调试窗口中看到当前系统中有多少个串口,如下图显示:
打开指定串口
通过listSerialPorts()
方法我们得知所有可用串口的名称,现在我们只需要查找到符合名称的对应串口即可指定打开。
在SerialPortManager类中添加这个函数,实现打开指定串口的功能:
func openSerialPort(portName: String, baudRate: Int) {
self.serialPort = self.serialPorts.filter{ $0.path.contains(portName)}.first
self.serialPort?.baudRate = NSNumber(value: baudRate)
if let port = self.serialPort {
port.open()
if port.isOpen {
print("The serial port has been opened")
} else {
print("the serial port fail to open")
}
}
}
使用方法如下:
serial.openSerialPort(portName: "usbserial-14120", baudRate: 9600)
读取串口数据
打开串口后,就需要进行读写操作了。ORSSerial库提供了非常丰富的API和功能,可以非常简单地实现异步读取串口数据,只需要在SerialPortManager类中添加以下代码:
func serialPort(_ serialPort: ORSSerialPort, didReceive data: Data) {
print(" ⬅️ 接收 到的数据原始内容: \(data as NSData)")
}
即可实现当串口有数据返回的时候,会立即响应。
发送串口数据
在这里演示一个比较复杂的数据转换,输入String字符串组转换为十六进制数据组发送给串口的方法:
func sendHexData(_ hexString: String) {
guard let serialPort = self.serialPort else {
print("Serial port is not open")
return
}
// 将16进制字符串转换为Data
let data = hexStringToData(hexString)
// 发送数据
let dataString = data.map { String(format: "%02X", $0) }.joined(separator: " ")
print("➡️ 发送 的十六进制数据: \(dataString)")
serialPort.send(data)
}
private func hexStringToData(_ hexString: String) -> Data {
var data = Data()
var temp = ""
for char in hexString {
temp.append(char)
if temp.count == 2 {
if let byte = UInt8(temp, radix: 16) {
data.append(byte)
}
temp = ""
}
}
return data
}
使用方法如下,例如输入“57AB00010003”,实际上会转换成“57 AB 00 01 00 03”的十六进制数据组:
serial.sendHexData("57AB00010003")
关闭串口数据
使用一下简单的例子,可以实现关闭当前打开的串口:
serial.serialPort?.close()
添加串口连接,断开事件
有时候我们还需要知道串口的突然断开和突然接上做一些处理,例如即插即用的应用,则我们需要用到串口的通知事件。
在SerialPortManager类添加以下代码,即可实现:
override init() {
super.init()
setupNotifications()
updateAvailablePorts()
}
private func setupNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(serialPortsWereConnected(_:)),
name: .ORSSerialPortsWereConnected,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(serialPortsWereDisconnected(_:)),
name: .ORSSerialPortsWereDisconnected,
object: nil)
}
@objc private func serialPortsWereConnected(_ notification: Notification) {
if let connectedPorts = notification.userInfo?[ORSConnectedSerialPortsKey] as? [ORSSerialPort] {
print("新的串口被连接:")
for port in connectedPorts {
print("- \(port.name)")
}
updateAvailablePorts()
}
}
@objc private func serialPortsWereDisconnected(_ notification: Notification) {
if let disconnectedPorts = notification.userInfo?[ORSDisconnectedSerialPortsKey] as? [ORSSerialPort] {
print("串口被断开连接:")
for port in disconnectedPorts {
print("- \(port.name)")
}
updateAvailablePorts()
}
}
private func updateAvailablePorts() {
serialPorts = serialPortManger.availablePorts
print("可用串口更新:")
for port in serialPorts {
print("- \(port.name)")
}
}
至此,我们已经实现了在MacOS系统开发的APP中使用串口的大多数应用场景。
完整的代码
// welcom to www.lingshunlab.com
import SwiftUI
import ORSSerial
final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate, NSMenuDelegate {
let serial = SerialPortManager.shared
func applicationDidFinishLaunching(_ aNotification: Notification) {
print(serial.listSerialPorts())
serial.openSerialPort(portName: "usbserial-14120", baudRate: 9600)
serial.sendHexData("57AB00010003")
blockMainThreadFor2Seconds()
serial.serialPort?.close()
}
func blockMainThreadFor2Seconds() {
let expirationDate = Date(timeIntervalSinceNow: 2)
while Date() < expirationDate {
RunLoop.current.run(mode: .default, before: expirationDate)
}
}
func calculateChecksum(data: [UInt8]) -> UInt8 {
return UInt8(data.reduce(0, { (sum, element) in sum + Int(element) }) & 0xFF)
}
}
class SerialPortManager: NSObject, ORSSerialPortDelegate {
static let shared = SerialPortManager()
let serialPortManger = ORSSerialPortManager.shared()
@Published var serialPorts: [ORSSerialPort] = []
@objc dynamic var serialPort: ORSSerialPort? {
didSet {
oldValue?.close()
oldValue?.delegate = nil
serialPort?.delegate = self
}
}
override init() {
super.init()
setupNotifications()
updateAvailablePorts()
}
private func setupNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(serialPortsWereConnected(_:)),
name: .ORSSerialPortsWereConnected,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(serialPortsWereDisconnected(_:)),
name: .ORSSerialPortsWereDisconnected,
object: nil)
}
@objc private func serialPortsWereConnected(_ notification: Notification) {
if let connectedPorts = notification.userInfo?[ORSConnectedSerialPortsKey] as? [ORSSerialPort] {
print("新的串口被连接:")
for port in connectedPorts {
print("- \(port.name)")
}
updateAvailablePorts()
}
}
@objc private func serialPortsWereDisconnected(_ notification: Notification) {
if let disconnectedPorts = notification.userInfo?[ORSDisconnectedSerialPortsKey] as? [ORSSerialPort] {
print("串口被断开连接:")
for port in disconnectedPorts {
print("- \(port.name)")
}
updateAvailablePorts()
}
}
private func updateAvailablePorts() {
serialPorts = serialPortManger.availablePorts
print("可用串口更新:")
for port in serialPorts {
print("- \(port.name)")
}
}
func listSerialPorts() -> [ORSSerialPort]{
self.serialPorts = serialPortManger.availablePorts
return self.serialPorts
}
func openSerialPort(portName: String, baudRate: Int) {
self.serialPort = self.serialPorts.filter{ $0.path.contains(portName)}.first
self.serialPort?.baudRate = NSNumber(value: baudRate)
if let port = self.serialPort {
port.open()
if port.isOpen {
print("The serial port has been opened")
} else {
print("the serial port fail to open")
}
}
}
func sendHexData(_ hexString: String) {
guard let serialPort = self.serialPort else {
print("Serial port is not open")
return
}
// 将16进制字符串转换为Data
let data = hexStringToData(hexString)
// 发送数据
let dataString = data.map { String(format: "%02X", $0) }.joined(separator: " ")
print("➡️ 发送 的十六进制数据: \(dataString)")
serialPort.send(data)
}
private func hexStringToData(_ hexString: String) -> Data {
var data = Data()
var temp = ""
for char in hexString {
temp.append(char)
if temp.count == 2 {
if let byte = UInt8(temp, radix: 16) {
data.append(byte)
}
temp = ""
}
}
return data
}
func serialPort(_ serialPort: ORSSerialPort, didEncounterError error: Error) {
print(error)
}
func serialPortWasOpened(_ serialPort: ORSSerialPort) {
print("串口已打开")
}
func serialPortWasClosed(_ serialPort: ORSSerialPort) {
print("串口已关闭")
}
func serialPortWasRemovedFromSystem(_ serialPort: ORSSerialPort) {
self.serialPort = nil
}
func serialPort(_ serialPort: ORSSerialPort, didReceive data: Data) {
print(" ⬅️ 接收 到的数据原始内容: \(data as NSData)")
// 将接收到的数据转换为十六进制字符串
let dataString = data.map { String(format: "%02X", $0) }.joined(separator: " ")
print(" ⬅️ 接收 到的十六进制数据: \(dataString)")
// 将接收到的数据转换为字符串
if let string = String(data: data, encoding: .utf8) {
print("转换后的字符串: \(string)")
} else {
print("无法将数据转换为字符串")
}
}
}
运行代码,在特定的串口通信机制下,实现如下图的所有功能: