@bricks-soft/capacitor-thermal-printer 0.0.0-stage → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BricksSoftCapacitorThermalPrinter.podspec +17 -0
- package/LICENSE +201 -0
- package/Package.swift +13 -0
- package/README.md +80 -2
- package/android/build.gradle +40 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/brickssoft/thermalprinter/ThermalPrinterPlugin.java +463 -0
- package/dist/esm/definitions.d.ts +73 -0
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/web.d.ts +13 -0
- package/dist/esm/web.js +33 -0
- package/dist/plugin.cjs.js +65 -0
- package/dist/plugin.js +68 -0
- package/ios/Sources/ThermalPrinter/EscPosBarcode.swift +121 -0
- package/ios/Sources/ThermalPrinter/EscPosBitmap.swift +178 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetooth.swift +341 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetoothConnection.swift +298 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetoothRules.swift +78 -0
- package/ios/Sources/ThermalPrinter/EscPosCharsetEncoding.swift +115 -0
- package/ios/Sources/ThermalPrinter/EscPosCoreBluetoothLink.swift +287 -0
- package/ios/Sources/ThermalPrinter/EscPosDeviceConnection.swift +47 -0
- package/ios/Sources/ThermalPrinter/EscPosJavaCompat.swift +213 -0
- package/ios/Sources/ThermalPrinter/EscPosKnownPrinterStore.swift +44 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinter.swift +55 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinterCommands.swift +367 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinterSize.swift +53 -0
- package/ios/Sources/ThermalPrinter/EscPosQRCode.swift +70 -0
- package/ios/Sources/ThermalPrinter/EscPosQueue.swift +30 -0
- package/ios/Sources/ThermalPrinter/EscPosTcpConnection.swift +265 -0
- package/ios/Sources/ThermalPrinter/PrinterTextParser.swift +622 -0
- package/ios/Sources/ThermalPrinter/ThermalPrinterPlugin.swift +85 -0
- package/ios/Sources/ThermalPrinter/ThermalPrinterService.swift +380 -0
- package/package.json +61 -4
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import CoreBluetooth
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
enum EscPosBluetoothRules {
|
|
5
|
+
static let printerServices: [CBUUID] = [
|
|
6
|
+
CBUUID(string: "18F0"),
|
|
7
|
+
CBUUID(string: "E7810A71-73AE-499D-8C15-FAA9AEF0C3F2"),
|
|
8
|
+
CBUUID(string: "49535343-FE7D-4AE5-8FA9-9FAFD205E455"),
|
|
9
|
+
CBUUID(string: "FF00"),
|
|
10
|
+
CBUUID(string: "FFE0"),
|
|
11
|
+
CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
static let printerCharacteristics: [CBUUID] = [
|
|
15
|
+
CBUUID(string: "2AF1"),
|
|
16
|
+
CBUUID(string: "BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F"),
|
|
17
|
+
CBUUID(string: "49535343-8841-43F4-A8D4-ECBE34729BB3"),
|
|
18
|
+
CBUUID(string: "FF02"),
|
|
19
|
+
CBUUID(string: "FFE1"),
|
|
20
|
+
CBUUID(string: "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
static let standardServices: [CBUUID] = [
|
|
24
|
+
CBUUID(string: "1800"),
|
|
25
|
+
CBUUID(string: "1801"),
|
|
26
|
+
CBUUID(string: "180A"),
|
|
27
|
+
CBUUID(string: "180F"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
static let printerNameFragments = ["print", "thermal", "receipt"]
|
|
31
|
+
static let printerNamePrefixes = ["mpt", "mtp", "rpp", "xp-", "pt-", "zj-", "pos", "goojprt", "peripage"]
|
|
32
|
+
|
|
33
|
+
static func isPrinterCandidate(name: String?, advertisedServices: [CBUUID], connectable: Bool?) -> Bool {
|
|
34
|
+
if connectable == false {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
if advertisedServices.contains(where: { printerServices.contains($0) }) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
guard let name = name?.lowercased(), !name.isEmpty else {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
return printerNameFragments.contains { name.contains($0) } || printerNamePrefixes.contains { name.hasPrefix($0) }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static func writeType(for properties: CBCharacteristicProperties) -> CBCharacteristicWriteType? {
|
|
47
|
+
if properties.contains(.writeWithoutResponse) {
|
|
48
|
+
return .withoutResponse
|
|
49
|
+
}
|
|
50
|
+
if properties.contains(.write) {
|
|
51
|
+
return .withResponse
|
|
52
|
+
}
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static func selectCharacteristic(in services: [CBService]) -> (CBCharacteristic, CBCharacteristicWriteType)? {
|
|
57
|
+
let characteristics = services.flatMap { $0.characteristics ?? [] }
|
|
58
|
+
for uuid in printerCharacteristics {
|
|
59
|
+
if let match = characteristics.first(where: { $0.uuid == uuid }), let type = writeType(for: match.properties) {
|
|
60
|
+
return (match, type)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for uuid in printerServices {
|
|
64
|
+
for service in services where service.uuid == uuid {
|
|
65
|
+
for characteristic in service.characteristics ?? [] {
|
|
66
|
+
if let type = writeType(for: characteristic.properties) {
|
|
67
|
+
return (characteristic, type)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let vendorWritable = services
|
|
73
|
+
.filter { !standardServices.contains($0.uuid) }
|
|
74
|
+
.flatMap { $0.characteristics ?? [] }
|
|
75
|
+
.compactMap { characteristic in writeType(for: characteristic.properties).map { (characteristic, $0) } }
|
|
76
|
+
return vendorWritable.count == 1 ? vendorWritable[0] : nil
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
struct EscPosCharsetEncoding: Equatable {
|
|
4
|
+
let name: String
|
|
5
|
+
let command: [UInt8]
|
|
6
|
+
|
|
7
|
+
init(name: String, escPosCharsetId: Int) {
|
|
8
|
+
self.name = name
|
|
9
|
+
self.command = [0x1B, 0x74, UInt8(truncatingIfNeeded: escPosCharsetId)]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func encode(_ text: JavaString) throws -> [UInt8] {
|
|
13
|
+
let encoding = try EscPosCharsetEncoding.stringEncoding(for: name)
|
|
14
|
+
if let data = text.string.data(using: encoding, allowLossyConversion: false), !EscPosCharsetEncoding.hasLoneSurrogate(text) {
|
|
15
|
+
return EscPosCharsetEncoding.dropByteOrderMark([UInt8](data), encoding: encoding)
|
|
16
|
+
}
|
|
17
|
+
var bytes: [UInt8] = []
|
|
18
|
+
var index = 0
|
|
19
|
+
let units = text.units
|
|
20
|
+
while index < units.count {
|
|
21
|
+
let unit = units[index]
|
|
22
|
+
var codePointUnits = [unit]
|
|
23
|
+
if UTF16.isLeadSurrogate(unit), index + 1 < units.count, UTF16.isTrailSurrogate(units[index + 1]) {
|
|
24
|
+
codePointUnits.append(units[index + 1])
|
|
25
|
+
}
|
|
26
|
+
index += codePointUnits.count
|
|
27
|
+
let isLoneSurrogate = codePointUnits.count == 1 && (UTF16.isLeadSurrogate(unit) || UTF16.isTrailSurrogate(unit))
|
|
28
|
+
if !isLoneSurrogate,
|
|
29
|
+
let data = String(decoding: codePointUnits, as: UTF16.self).data(using: encoding, allowLossyConversion: false) {
|
|
30
|
+
bytes.append(contentsOf: EscPosCharsetEncoding.dropByteOrderMark([UInt8](data), encoding: encoding))
|
|
31
|
+
} else {
|
|
32
|
+
bytes.append(contentsOf: EscPosCharsetEncoding.replacement(for: encoding))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return bytes
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static func stringEncoding(for charsetName: String) throws -> String.Encoding {
|
|
39
|
+
for candidate in candidates(for: charsetName) {
|
|
40
|
+
let cfEncoding = CFStringConvertIANACharSetNameToEncoding(candidate as CFString)
|
|
41
|
+
if cfEncoding != kCFStringEncodingInvalidId {
|
|
42
|
+
return String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(cfEncoding))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throw EscPosError.encoding(charsetName)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private static func candidates(for charsetName: String) -> [String] {
|
|
49
|
+
let trimmed = charsetName.trimmingCharacters(in: .whitespaces)
|
|
50
|
+
guard !trimmed.isEmpty else { return [] }
|
|
51
|
+
let lower = trimmed.lowercased()
|
|
52
|
+
var names = [trimmed]
|
|
53
|
+
if let digits = suffixDigits(lower, prefixes: ["cp", "ibm", "ibm-", "cp-"]) {
|
|
54
|
+
if digits.count == 4, digits.hasPrefix("12") {
|
|
55
|
+
names.append("windows-\(digits)")
|
|
56
|
+
} else {
|
|
57
|
+
names.append("IBM\(digits)")
|
|
58
|
+
names.append("cp\(digits)")
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if let digits = suffixDigits(lower, prefixes: ["windows-", "windows"]) {
|
|
62
|
+
names.append("windows-\(digits)")
|
|
63
|
+
}
|
|
64
|
+
if let digits = suffixDigits(lower, prefixes: ["iso8859_", "iso8859-", "iso-8859_", "iso_8859-", "iso_8859_"]) {
|
|
65
|
+
names.append("ISO-8859-\(digits)")
|
|
66
|
+
}
|
|
67
|
+
if lower == "utf8" { names.append("UTF-8") }
|
|
68
|
+
if lower == "ascii" || lower == "us-ascii" { names.append("US-ASCII") }
|
|
69
|
+
return names
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private static func suffixDigits(_ name: String, prefixes: [String]) -> String? {
|
|
73
|
+
for prefix in prefixes where name.hasPrefix(prefix) {
|
|
74
|
+
let rest = String(name.dropFirst(prefix.count))
|
|
75
|
+
if !rest.isEmpty, rest.allSatisfy({ $0.isASCII && $0.isNumber }) {
|
|
76
|
+
return rest
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private static func hasLoneSurrogate(_ text: JavaString) -> Bool {
|
|
83
|
+
var index = 0
|
|
84
|
+
let units = text.units
|
|
85
|
+
while index < units.count {
|
|
86
|
+
let unit = units[index]
|
|
87
|
+
if UTF16.isLeadSurrogate(unit) {
|
|
88
|
+
if index + 1 < units.count, UTF16.isTrailSurrogate(units[index + 1]) {
|
|
89
|
+
index += 2
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
if UTF16.isTrailSurrogate(unit) {
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
index += 1
|
|
98
|
+
}
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private static func dropByteOrderMark(_ bytes: [UInt8], encoding: String.Encoding) -> [UInt8] {
|
|
103
|
+
if encoding == .utf8, bytes.starts(with: [0xEF, 0xBB, 0xBF]) {
|
|
104
|
+
return Array(bytes.dropFirst(3))
|
|
105
|
+
}
|
|
106
|
+
return bytes
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static func replacement(for encoding: String.Encoding) -> [UInt8] {
|
|
110
|
+
if let data = "?".data(using: encoding, allowLossyConversion: false) {
|
|
111
|
+
return dropByteOrderMark([UInt8](data), encoding: encoding)
|
|
112
|
+
}
|
|
113
|
+
return [0x3F]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import CoreBluetooth
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
protocol EscPosPeripheralDriver: AnyObject {
|
|
5
|
+
var isAvailable: Bool { get }
|
|
6
|
+
var isConnected: Bool { get }
|
|
7
|
+
var canSendWriteWithoutResponse: Bool { get }
|
|
8
|
+
var services: [CBService] { get }
|
|
9
|
+
func attach(_ delegate: CBPeripheralDelegate)
|
|
10
|
+
func maximumWriteValueLength(for type: CBCharacteristicWriteType) -> Int
|
|
11
|
+
func connect()
|
|
12
|
+
func cancelConnection()
|
|
13
|
+
func discoverServices()
|
|
14
|
+
func discoverCharacteristics(for service: CBService)
|
|
15
|
+
func write(_ data: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
final class EscPosCBPeripheralDriver: EscPosPeripheralDriver {
|
|
19
|
+
let peripheral: CBPeripheral
|
|
20
|
+
private unowned let central: EscPosBluetoothCentral
|
|
21
|
+
|
|
22
|
+
init(peripheral: CBPeripheral, central: EscPosBluetoothCentral) {
|
|
23
|
+
self.peripheral = peripheral
|
|
24
|
+
self.central = central
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var isAvailable: Bool { central.isPoweredOn }
|
|
28
|
+
|
|
29
|
+
var isConnected: Bool { peripheral.state == .connected }
|
|
30
|
+
|
|
31
|
+
var canSendWriteWithoutResponse: Bool { peripheral.canSendWriteWithoutResponse }
|
|
32
|
+
|
|
33
|
+
var services: [CBService] { peripheral.services ?? [] }
|
|
34
|
+
|
|
35
|
+
func attach(_ delegate: CBPeripheralDelegate) {
|
|
36
|
+
peripheral.delegate = delegate
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func maximumWriteValueLength(for type: CBCharacteristicWriteType) -> Int {
|
|
40
|
+
peripheral.maximumWriteValueLength(for: type)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func connect() {
|
|
44
|
+
central.startConnecting(peripheral)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func cancelConnection() {
|
|
48
|
+
central.cancelConnection(peripheral)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func discoverServices() {
|
|
52
|
+
peripheral.discoverServices(nil)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func discoverCharacteristics(for service: CBService) {
|
|
56
|
+
peripheral.discoverCharacteristics(nil, for: service)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func write(_ data: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType) {
|
|
60
|
+
peripheral.writeValue(data, for: characteristic, type: type)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
final class EscPosCoreBluetoothLink: NSObject, EscPosBluetoothLink, CBPeripheralDelegate {
|
|
65
|
+
weak var events: EscPosBluetoothLinkEvents?
|
|
66
|
+
let driver: EscPosPeripheralDriver
|
|
67
|
+
private var session: Int?
|
|
68
|
+
private var pendingConnect: Int?
|
|
69
|
+
private var closing: Int?
|
|
70
|
+
private var queuedConnect: Int?
|
|
71
|
+
private var pendingDiscovery: Int?
|
|
72
|
+
private var pendingWrites: [Int] = []
|
|
73
|
+
private var characteristic: CBCharacteristic?
|
|
74
|
+
private var pendingCharacteristicDiscoveries = 0
|
|
75
|
+
|
|
76
|
+
init(driver: EscPosPeripheralDriver) {
|
|
77
|
+
self.driver = driver
|
|
78
|
+
super.init()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var isCancelling: Bool { closing != nil }
|
|
82
|
+
|
|
83
|
+
var isConnected: Bool { closing == nil && driver.isConnected }
|
|
84
|
+
|
|
85
|
+
var canSendWriteWithoutResponse: Bool { driver.canSendWriteWithoutResponse }
|
|
86
|
+
|
|
87
|
+
func maximumWriteValueLength(for type: CBCharacteristicWriteType) -> Int {
|
|
88
|
+
driver.maximumWriteValueLength(for: type)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func connect(attempt: Int) {
|
|
92
|
+
resetSession()
|
|
93
|
+
if closing != nil {
|
|
94
|
+
queuedConnect = attempt
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
startConnecting(attempt)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func adopt(attempt: Int) {
|
|
101
|
+
resetSession()
|
|
102
|
+
if closing != nil {
|
|
103
|
+
queuedConnect = attempt
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
session = attempt
|
|
107
|
+
driver.attach(self)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func cancel() {
|
|
111
|
+
let open = session ?? pendingConnect
|
|
112
|
+
queuedConnect = nil
|
|
113
|
+
resetSession()
|
|
114
|
+
pendingConnect = nil
|
|
115
|
+
guard let open = open else { return }
|
|
116
|
+
closing = open
|
|
117
|
+
driver.cancelConnection()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func discoverPrinterCharacteristic(attempt: Int) {
|
|
121
|
+
guard session == attempt else { return }
|
|
122
|
+
characteristic = nil
|
|
123
|
+
pendingCharacteristicDiscoveries = 0
|
|
124
|
+
pendingDiscovery = attempt
|
|
125
|
+
driver.discoverServices()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func write(_ data: Data, type: CBCharacteristicWriteType, token: Int) {
|
|
129
|
+
guard session != nil, let characteristic = characteristic else {
|
|
130
|
+
if type == .withResponse {
|
|
131
|
+
events?.linkDidWrite(token: token, error: EscPosError.connection("Unable to send data to device."))
|
|
132
|
+
}
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
if type == .withResponse {
|
|
136
|
+
pendingWrites.append(token)
|
|
137
|
+
}
|
|
138
|
+
driver.write(data, for: characteristic, type: type)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
func handleConnected() {
|
|
142
|
+
if closing != nil {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
guard let attempt = pendingConnect else { return }
|
|
146
|
+
pendingConnect = nil
|
|
147
|
+
session = attempt
|
|
148
|
+
events?.linkDidConnect(attempt: attempt)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
func handleFailedToConnect(_ error: Error?) {
|
|
152
|
+
if let ended = closing {
|
|
153
|
+
finishCancellation { $0.linkDidFailToConnect(attempt: ended, error: error) }
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
guard let attempt = pendingConnect else { return }
|
|
157
|
+
pendingConnect = nil
|
|
158
|
+
events?.linkDidFailToConnect(attempt: attempt, error: error)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func handleDisconnected(_ error: Error?) {
|
|
162
|
+
if let ended = closing {
|
|
163
|
+
finishCancellation { $0.linkDidDisconnect(attempt: ended, error: error) }
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
guard let live = session else { return }
|
|
167
|
+
resetSession()
|
|
168
|
+
events?.linkDidDisconnect(attempt: live, error: error)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func handleUnavailable() {
|
|
172
|
+
let cancelled = closing
|
|
173
|
+
let live = session
|
|
174
|
+
let pending = pendingConnect
|
|
175
|
+
let queued = queuedConnect
|
|
176
|
+
resetSession()
|
|
177
|
+
closing = nil
|
|
178
|
+
pendingConnect = nil
|
|
179
|
+
queuedConnect = nil
|
|
180
|
+
guard let events = events else { return }
|
|
181
|
+
if let cancelled = cancelled {
|
|
182
|
+
events.linkDidDisconnect(attempt: cancelled, error: nil)
|
|
183
|
+
}
|
|
184
|
+
if let live = live {
|
|
185
|
+
events.linkDidDisconnect(attempt: live, error: nil)
|
|
186
|
+
}
|
|
187
|
+
if let pending = pending {
|
|
188
|
+
events.linkDidFailToConnect(attempt: pending, error: nil)
|
|
189
|
+
}
|
|
190
|
+
if let queued = queued {
|
|
191
|
+
events.linkDidFailToConnect(attempt: queued, error: nil)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func handleServicesDiscovered(error: Error?) {
|
|
196
|
+
guard session != nil, let attempt = pendingDiscovery else { return }
|
|
197
|
+
if let error = error {
|
|
198
|
+
pendingDiscovery = nil
|
|
199
|
+
events?.linkDidDiscover(attempt: attempt, writeType: nil, error: error)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
let services = driver.services
|
|
203
|
+
if services.isEmpty {
|
|
204
|
+
finishDiscovery()
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
pendingCharacteristicDiscoveries = services.count
|
|
208
|
+
for service in services {
|
|
209
|
+
driver.discoverCharacteristics(for: service)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
func handleCharacteristicsDiscovered() {
|
|
214
|
+
guard session != nil, pendingDiscovery != nil else { return }
|
|
215
|
+
pendingCharacteristicDiscoveries -= 1
|
|
216
|
+
if pendingCharacteristicDiscoveries == 0 {
|
|
217
|
+
finishDiscovery()
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
func handleWriteResponse(error: Error?) {
|
|
222
|
+
guard session != nil, !pendingWrites.isEmpty else { return }
|
|
223
|
+
events?.linkDidWrite(token: pendingWrites.removeFirst(), error: error)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func handleReadyToSend() {
|
|
227
|
+
guard let attempt = session else { return }
|
|
228
|
+
events?.linkIsReadyToSend(attempt: attempt)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
|
232
|
+
handleServicesDiscovered(error: error)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
|
236
|
+
handleCharacteristicsDiscovered()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
240
|
+
handleWriteResponse(error: error)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
|
|
244
|
+
handleReadyToSend()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private func startConnecting(_ attempt: Int) {
|
|
248
|
+
guard driver.isAvailable else {
|
|
249
|
+
events?.linkDidFailToConnect(attempt: attempt, error: nil)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
pendingConnect = attempt
|
|
253
|
+
driver.attach(self)
|
|
254
|
+
driver.connect()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private func finishCancellation(_ report: (EscPosBluetoothLinkEvents) -> Void) {
|
|
258
|
+
closing = nil
|
|
259
|
+
let next = queuedConnect
|
|
260
|
+
queuedConnect = nil
|
|
261
|
+
if let events = events {
|
|
262
|
+
report(events)
|
|
263
|
+
}
|
|
264
|
+
if let next = next {
|
|
265
|
+
startConnecting(next)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private func finishDiscovery() {
|
|
270
|
+
guard let attempt = pendingDiscovery else { return }
|
|
271
|
+
pendingDiscovery = nil
|
|
272
|
+
if let (found, type) = EscPosBluetoothRules.selectCharacteristic(in: driver.services) {
|
|
273
|
+
characteristic = found
|
|
274
|
+
events?.linkDidDiscover(attempt: attempt, writeType: type, error: nil)
|
|
275
|
+
} else {
|
|
276
|
+
events?.linkDidDiscover(attempt: attempt, writeType: nil, error: nil)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private func resetSession() {
|
|
281
|
+
session = nil
|
|
282
|
+
pendingDiscovery = nil
|
|
283
|
+
pendingWrites = []
|
|
284
|
+
characteristic = nil
|
|
285
|
+
pendingCharacteristicDiscoveries = 0
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
class EscPosDeviceConnection: NSObject {
|
|
4
|
+
private(set) var data: [UInt8] = []
|
|
5
|
+
var pacesSends = true
|
|
6
|
+
|
|
7
|
+
var isConnected: Bool { false }
|
|
8
|
+
|
|
9
|
+
@discardableResult
|
|
10
|
+
func connect() throws -> EscPosDeviceConnection { self }
|
|
11
|
+
|
|
12
|
+
@discardableResult
|
|
13
|
+
func disconnect() -> EscPosDeviceConnection {
|
|
14
|
+
data = []
|
|
15
|
+
return self
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func transmit(_ bytes: [UInt8]) throws {}
|
|
19
|
+
|
|
20
|
+
func write(_ bytes: [UInt8]) {
|
|
21
|
+
data.append(contentsOf: bytes)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func send(_ addWaitingTime: Int = 0) throws {
|
|
25
|
+
guard isConnected else {
|
|
26
|
+
data = []
|
|
27
|
+
throw EscPosError.connection("Unable to send data to device.")
|
|
28
|
+
}
|
|
29
|
+
let payload = data
|
|
30
|
+
do {
|
|
31
|
+
try transmit(payload)
|
|
32
|
+
} catch {
|
|
33
|
+
data = []
|
|
34
|
+
disconnect()
|
|
35
|
+
throw error
|
|
36
|
+
}
|
|
37
|
+
let waitingTime = addWaitingTime + payload.count / 16
|
|
38
|
+
data = []
|
|
39
|
+
if pacesSends && waitingTime > 0 {
|
|
40
|
+
Thread.sleep(forTimeInterval: Double(waitingTime) / 1000.0)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func clearBuffer() {
|
|
45
|
+
data = []
|
|
46
|
+
}
|
|
47
|
+
}
|