141 lines
4.5 KiB
Swift
141 lines
4.5 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
|
|
public nonisolated struct SerialPortDescriptor: Identifiable, Hashable, Sendable {
|
|
public var path: String
|
|
public var name: String?
|
|
public var id: String { path }
|
|
|
|
public var displayName: String {
|
|
guard let name, !name.isEmpty, name != path else { return path }
|
|
return "\(name) — \(path)"
|
|
}
|
|
public init(path: String, name: String? = nil) {
|
|
self.path = path
|
|
self.name = name
|
|
}
|
|
|
|
}
|
|
|
|
public nonisolated enum SerialDataBits: Int, CaseIterable, Identifiable, Sendable {
|
|
case five = 5
|
|
case six = 6
|
|
case seven = 7
|
|
case eight = 8
|
|
public var id: Int { rawValue }
|
|
}
|
|
|
|
public nonisolated enum SerialParity: String, CaseIterable, Identifiable, Sendable {
|
|
case none, even, odd
|
|
public var id: String { rawValue }
|
|
public var displayName: String {
|
|
switch self {
|
|
case .none: String(localized: "None")
|
|
case .even: String(localized: "Even")
|
|
case .odd: String(localized: "Odd")
|
|
}
|
|
}
|
|
}
|
|
|
|
public nonisolated enum SerialStopBits: Int, CaseIterable, Identifiable, Sendable {
|
|
case one = 1
|
|
case two = 2
|
|
public var id: Int { rawValue }
|
|
}
|
|
|
|
public nonisolated enum SerialFlowControl: String, CaseIterable, Identifiable, Sendable {
|
|
case none, software, hardware
|
|
public var id: String { rawValue }
|
|
public var displayName: String {
|
|
switch self {
|
|
case .none: String(localized: "None")
|
|
case .software: String(localized: "XON/XOFF")
|
|
case .hardware: String(localized: "RTS/CTS")
|
|
}
|
|
}
|
|
}
|
|
|
|
public nonisolated enum SerialEnterKey: String, CaseIterable, Identifiable, Sendable {
|
|
case carriageReturn, lineFeed, carriageReturnLineFeed
|
|
public var id: String { rawValue }
|
|
public var displayName: String {
|
|
switch self {
|
|
case .carriageReturn: String(localized: "CR")
|
|
case .lineFeed: String(localized: "LF")
|
|
case .carriageReturnLineFeed: String(localized: "CRLF")
|
|
}
|
|
}
|
|
public var bytes: [UInt8] {
|
|
switch self {
|
|
case .carriageReturn: [0x0D]
|
|
case .lineFeed: [0x0A]
|
|
case .carriageReturnLineFeed: [0x0D, 0x0A]
|
|
}
|
|
}
|
|
}
|
|
|
|
public nonisolated struct SerialConnectionConfiguration: Hashable, Sendable {
|
|
public static let commonBaudRates = [
|
|
300, 600, 1_200, 2_400, 4_800, 9_600, 19_200, 38_400, 57_600,
|
|
115_200, 230_400, 460_800, 921_600, 1_000_000, 2_000_000,
|
|
3_000_000, 4_000_000,
|
|
]
|
|
|
|
public var devicePath = ""
|
|
public var deviceName: String?
|
|
public var baudRate = 9_600
|
|
public var dataBits: SerialDataBits = .eight
|
|
public var parity: SerialParity = .none
|
|
public var stopBits: SerialStopBits = .one
|
|
public var flowControl: SerialFlowControl = .none
|
|
public var enterKey: SerialEnterKey = .carriageReturn
|
|
public var localEcho = false
|
|
public var charset: RemoteCharset = .utf8
|
|
|
|
public var normalizedDevicePath: String {
|
|
devicePath.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
public var isValid: Bool {
|
|
normalizedDevicePath.hasPrefix("/") && (1...4_000_000).contains(baudRate)
|
|
}
|
|
public var summary: String {
|
|
"\(baudRate) \(dataBits.rawValue)-\(parityLetter)-\(stopBits.rawValue)"
|
|
}
|
|
private var parityLetter: String {
|
|
switch parity {
|
|
case .none: "N"
|
|
case .even: "E"
|
|
case .odd: "O"
|
|
}
|
|
}
|
|
|
|
public func transformInteractiveInput(_ data: Data) -> Data {
|
|
guard enterKey != .carriageReturn, data.contains(0x0D) else { return data }
|
|
var result = Data()
|
|
for byte in data {
|
|
if byte == 0x0D { result.append(contentsOf: enterKey.bytes) } else { result.append(byte) }
|
|
}
|
|
return result
|
|
}
|
|
public init(
|
|
devicePath: String = "", deviceName: String? = nil, baudRate: Int = 9_600, dataBits: SerialDataBits = .eight,
|
|
parity: SerialParity = .none, stopBits: SerialStopBits = .one, flowControl: SerialFlowControl = .none,
|
|
enterKey: SerialEnterKey = .carriageReturn, localEcho: Bool = false,
|
|
charset: RemoteCharset = .utf8
|
|
) {
|
|
self.devicePath = devicePath
|
|
self.deviceName = deviceName
|
|
self.baudRate = baudRate
|
|
self.dataBits = dataBits
|
|
self.parity = parity
|
|
self.stopBits = stopBits
|
|
self.flowControl = flowControl
|
|
self.enterKey = enterKey
|
|
self.localEcho = localEcho
|
|
self.charset = charset
|
|
}
|
|
|
|
}
|