@nitra/zebra 8.1.0 → 8.2.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.
|
@@ -23,9 +23,6 @@ import ExternalAccessory
|
|
|
23
23
|
/// Невелика затримка між черговими завданнями, щоб дати прошивці час стабілізуватися
|
|
24
24
|
private let cooldownInterval: TimeInterval = 1.0
|
|
25
25
|
|
|
26
|
-
/// Перемикач debug-логування
|
|
27
|
-
private static let debugLogging = true
|
|
28
|
-
|
|
29
26
|
/// Один раз реєструємося для нотифікацій EA — потрібно для коректного доступу до аксесуарів при холодному запуску (без Xcode).
|
|
30
27
|
private static var didRegisterForAccessoryNotifications = false
|
|
31
28
|
private static let registerLock = NSLock()
|
|
@@ -35,13 +32,7 @@ import ExternalAccessory
|
|
|
35
32
|
guard !Zebra.didRegisterForAccessoryNotifications else { return }
|
|
36
33
|
EAAccessoryManager.shared().registerForLocalNotifications()
|
|
37
34
|
Zebra.didRegisterForAccessoryNotifications = true
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/// Функція логування з префіксом [Zebra]
|
|
42
|
-
private func log(_ items: Any...) {
|
|
43
|
-
guard Zebra.debugLogging else { return }
|
|
44
|
-
print("[Zebra]", items.map { String(describing: $0) }.joined(separator: " "))
|
|
35
|
+
logDebug("EAAccessoryManager.registerForLocalNotifications() called")
|
|
45
36
|
}
|
|
46
37
|
|
|
47
38
|
/// Представляє інформацію про підключений аксесуар, яку використовує плагін
|
|
@@ -103,12 +94,10 @@ import ExternalAccessory
|
|
|
103
94
|
private let timeout: TimeInterval
|
|
104
95
|
private var totalWritten = 0
|
|
105
96
|
private var completion: ((Result<PrintResult, ZebraError>) -> Void)?
|
|
106
|
-
private weak var owner: Zebra?
|
|
107
97
|
private var startTime = Date()
|
|
108
98
|
private let session: EASession
|
|
109
99
|
|
|
110
|
-
init(owner: Zebra, session: EASession, output: OutputStream, data: [UInt8], address: String, timeout: TimeInterval = 5.0, completion: @escaping (Result<PrintResult, ZebraError>) -> Void) {
|
|
111
|
-
self.owner = owner
|
|
100
|
+
init(owner _: Zebra, session: EASession, output: OutputStream, data: [UInt8], address: String, timeout: TimeInterval = 5.0, completion: @escaping (Result<PrintResult, ZebraError>) -> Void) {
|
|
112
101
|
self.session = session
|
|
113
102
|
self.output = output
|
|
114
103
|
self.data = data
|
|
@@ -130,7 +119,7 @@ import ExternalAccessory
|
|
|
130
119
|
output.delegate = self
|
|
131
120
|
output.schedule(in: runLoop, forMode: mode)
|
|
132
121
|
output.open()
|
|
133
|
-
|
|
122
|
+
logDebug("StreamWriter started, bytes= \(self.data.count)")
|
|
134
123
|
startTime = Date()
|
|
135
124
|
}
|
|
136
125
|
|
|
@@ -141,7 +130,7 @@ import ExternalAccessory
|
|
|
141
130
|
while Date() < deadline {
|
|
142
131
|
RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.1))
|
|
143
132
|
}
|
|
144
|
-
|
|
133
|
+
logDebug("Flush done, closing stream")
|
|
145
134
|
close(with: .success(PrintResult(address: address)))
|
|
146
135
|
}
|
|
147
136
|
|
|
@@ -172,26 +161,26 @@ import ExternalAccessory
|
|
|
172
161
|
completion(result)
|
|
173
162
|
}
|
|
174
163
|
completion = nil
|
|
175
|
-
|
|
164
|
+
logDebug("StreamWriter finished with \(String(describing: result))")
|
|
176
165
|
}
|
|
177
166
|
|
|
178
167
|
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
|
179
168
|
// Події input-потоку ігноруємо (потрібен лише для коректного життєвого циклу сесії)
|
|
180
169
|
guard aStream === output else { return }
|
|
181
170
|
if Date().timeIntervalSince(startTime) > timeout {
|
|
182
|
-
|
|
171
|
+
logDebug("StreamWriter timeout")
|
|
183
172
|
close(with: .failure(.writeFailed("Таймаут запису")))
|
|
184
173
|
return
|
|
185
174
|
}
|
|
186
175
|
switch eventCode {
|
|
187
176
|
case .openCompleted:
|
|
188
177
|
// Потік відкрито
|
|
189
|
-
|
|
178
|
+
logDebug("Stream open completed")
|
|
190
179
|
case .hasSpaceAvailable:
|
|
191
180
|
// Є простір для запису, пишемо дані порціями
|
|
192
181
|
if totalWritten >= data.count {
|
|
193
182
|
// Усі байти записані — даємо час на флаш буфера перед закриттям (iOS часто не встигає відправити на пристрій)
|
|
194
|
-
|
|
183
|
+
logDebug("All bytes written, flushing before close...")
|
|
195
184
|
flushThenClose()
|
|
196
185
|
return
|
|
197
186
|
}
|
|
@@ -200,7 +189,7 @@ import ExternalAccessory
|
|
|
200
189
|
let wrote = output.write(Array(data[totalWritten..<(totalWritten + chunkSize)]), maxLength: chunkSize)
|
|
201
190
|
if wrote <= 0 {
|
|
202
191
|
let reason = output.streamError?.localizedDescription ?? "Невідома помилка потоку"
|
|
203
|
-
|
|
192
|
+
logDebug("StreamWriter write failed: \(reason)")
|
|
204
193
|
close(with: .failure(.writeFailed(reason)))
|
|
205
194
|
return
|
|
206
195
|
}
|
|
@@ -208,11 +197,11 @@ import ExternalAccessory
|
|
|
208
197
|
case .errorOccurred:
|
|
209
198
|
// Сталася помилка потоку
|
|
210
199
|
let reason = output.streamError?.localizedDescription ?? "Невідома помилка потоку"
|
|
211
|
-
|
|
200
|
+
logDebug("Stream error: \(reason)")
|
|
212
201
|
close(with: .failure(.writeFailed(reason)))
|
|
213
202
|
case .endEncountered:
|
|
214
203
|
// Потік закінчився, перевіряємо чи всі дані записані
|
|
215
|
-
|
|
204
|
+
logDebug("Stream end encountered, bytes= \(self.totalWritten)")
|
|
216
205
|
if totalWritten >= data.count {
|
|
217
206
|
close(with: .success(PrintResult(address: address)))
|
|
218
207
|
} else {
|
|
@@ -239,7 +228,18 @@ import ExternalAccessory
|
|
|
239
228
|
/// Повертає список підключених EA аксесуарів у вигляді DeviceInfo, вибираючи серійний номер або ім'я як адресу
|
|
240
229
|
public func getConnectedAccessories() -> [DeviceInfo] {
|
|
241
230
|
ensureRegisteredForAccessoryNotifications()
|
|
242
|
-
|
|
231
|
+
// Якщо аксесуарів немає, чекаємо 0.6 с і повторюємо спробу до 3 разів (щоб дати системі час підхопити нове підключення)
|
|
232
|
+
let maxAttempts = 3
|
|
233
|
+
let retryDelay: TimeInterval = 0.6
|
|
234
|
+
var accessories = EAAccessoryManager.shared().connectedAccessories
|
|
235
|
+
for attempt in 1...maxAttempts {
|
|
236
|
+
if !accessories.isEmpty { break }
|
|
237
|
+
if attempt < maxAttempts {
|
|
238
|
+
logDebug("connectedAccessories attempt: \(attempt)")
|
|
239
|
+
Thread.sleep(forTimeInterval: retryDelay)
|
|
240
|
+
accessories = EAAccessoryManager.shared().connectedAccessories
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
243
|
return accessories.compactMap { accessory in
|
|
244
244
|
let address = accessory.serialNumber.isEmpty ? accessory.name : accessory.serialNumber
|
|
245
245
|
if address.isEmpty {
|
|
@@ -252,14 +252,14 @@ import ExternalAccessory
|
|
|
252
252
|
/// Виводить діагностичну інформацію щодо підключених аксесуарів та протоколів у Info.plist
|
|
253
253
|
public func diagnoseEnvironment() {
|
|
254
254
|
let accessories = EAAccessoryManager.shared().connectedAccessories
|
|
255
|
-
|
|
255
|
+
logDebug("Connected accessories count: \(accessories.count)")
|
|
256
256
|
for a in accessories {
|
|
257
|
-
|
|
257
|
+
logDebug("Accessory: name=\(a.name) serial=\(a.serialNumber) protocols=\(a.protocolStrings)")
|
|
258
258
|
}
|
|
259
259
|
if let supported = Bundle.main.object(forInfoDictionaryKey: "UISupportedExternalAccessoryProtocols") as? [String] {
|
|
260
|
-
|
|
260
|
+
logDebug("Info.plist UISupportedExternalAccessoryProtocols: \(supported)")
|
|
261
261
|
} else {
|
|
262
|
-
|
|
262
|
+
logDebug("Info.plist UISupportedExternalAccessoryProtocols: MISSING or not an array")
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
|
|
@@ -270,10 +270,10 @@ import ExternalAccessory
|
|
|
270
270
|
for proto in protocolsToTry {
|
|
271
271
|
for attempt in 1...retries {
|
|
272
272
|
if let session = EASession(accessory: accessory, forProtocol: proto) {
|
|
273
|
-
|
|
273
|
+
logDebug("EASession open success on attempt \(attempt) protocol: \(proto)")
|
|
274
274
|
return (session, proto)
|
|
275
275
|
} else {
|
|
276
|
-
|
|
276
|
+
logDebug("EASession open failed attempt \(attempt) protocol: \(proto)")
|
|
277
277
|
Thread.sleep(forTimeInterval: delay)
|
|
278
278
|
}
|
|
279
279
|
}
|
|
@@ -299,18 +299,25 @@ import ExternalAccessory
|
|
|
299
299
|
payload += "\r\n"
|
|
300
300
|
}
|
|
301
301
|
let zplBytes = Array(payload.utf8)
|
|
302
|
-
|
|
302
|
+
logDebug("printZpl called with address=\(trimmedAddress) zplLength=\(zplBytes.count)")
|
|
303
303
|
|
|
304
304
|
DispatchQueue.main.async { [weak self] in
|
|
305
305
|
guard let self = self else { return }
|
|
306
306
|
self.ensureRegisteredForAccessoryNotifications()
|
|
307
307
|
let accessories = EAAccessoryManager.shared().connectedAccessories
|
|
308
|
-
|
|
308
|
+
logDebug("EA connectedAccessories count= \(accessories.count)")
|
|
309
309
|
|
|
310
310
|
// Шукаємо аксесуар, що відповідає адресі (serialNumber або name)
|
|
311
311
|
let availableList = self.getConnectedAccessories()
|
|
312
|
+
|
|
313
|
+
if availableList.isEmpty {
|
|
314
|
+
logDebug("No accessories found")
|
|
315
|
+
completion(.failure(.accessoryNotFound(requestedAddress: trimmedAddress, availableAccessories: availableList)))
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
|
|
312
319
|
guard let accessory = accessories.first(where: { self.accessoryMatches($0, address: trimmedAddress) }) else {
|
|
313
|
-
|
|
320
|
+
logDebug("No accessory matched address: \(trimmedAddress)")
|
|
314
321
|
// У failure передаємо не лише адресу, а й список доступних аксесуарів (назви та адреси) для показу в помилці
|
|
315
322
|
completion(.failure(.accessoryNotFound(requestedAddress: trimmedAddress, availableAccessories: availableList)))
|
|
316
323
|
return
|
|
@@ -318,30 +325,30 @@ import ExternalAccessory
|
|
|
318
325
|
|
|
319
326
|
// Обираємо протокол для з’єднання
|
|
320
327
|
let protocolString = self.preferredProtocol(for: accessory)
|
|
321
|
-
|
|
322
|
-
|
|
328
|
+
logDebug("Chosen accessory: \(accessory.name) serial=\(accessory.serialNumber)")
|
|
329
|
+
logDebug("Selected protocol: \(protocolString)")
|
|
323
330
|
if let supported = Bundle.main.object(forInfoDictionaryKey: "UISupportedExternalAccessoryProtocols") as? [String] {
|
|
324
331
|
if !supported.contains(protocolString) {
|
|
325
|
-
|
|
332
|
+
logDebug("WARNING: Selected protocol not present in Info.plist UISupportedExternalAccessoryProtocols")
|
|
326
333
|
}
|
|
327
334
|
} else {
|
|
328
|
-
|
|
335
|
+
logDebug("WARNING: UISupportedExternalAccessoryProtocols missing in Info.plist")
|
|
329
336
|
}
|
|
330
337
|
|
|
331
|
-
|
|
338
|
+
logDebug("Attempting to open EASession with retries and fallback protocols...")
|
|
332
339
|
Thread.sleep(forTimeInterval: 0.1)
|
|
333
340
|
let fallback = accessory.protocolStrings
|
|
334
341
|
// Відкриваємо сесію, пробуємо ретраї і fallback протоколи
|
|
335
342
|
guard let result = self.openSessionWithRetries(accessory: accessory, preferredProtocol: protocolString, fallbackProtocols: fallback) else {
|
|
336
|
-
|
|
343
|
+
logDebug("EASession failed after retries and fallback")
|
|
337
344
|
completion(.failure(.sessionFailed("EASession повернув nil")))
|
|
338
345
|
return
|
|
339
346
|
}
|
|
340
347
|
let session = result.session
|
|
341
348
|
let protocolUsed = result.protocolUsed
|
|
342
|
-
|
|
349
|
+
logDebug("Using protocol for session: \(protocolUsed)")
|
|
343
350
|
guard let outputStream = session.outputStream else {
|
|
344
|
-
|
|
351
|
+
logDebug("Session opened but outputStream is nil")
|
|
345
352
|
completion(.failure(.sessionFailed("Відсутній outputStream")))
|
|
346
353
|
return
|
|
347
354
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
/// Спільна функція дебаг-логування для модуля Zebra.
|
|
4
|
+
/// У Release не виконується й не обчислює повідомлення (zero cost).
|
|
5
|
+
#if DEBUG
|
|
6
|
+
func logDebug(_ message: @autoclosure @escaping () -> String) {
|
|
7
|
+
Logger(subsystem: "com.nitra.zebra", category: "Zebra").debug("\(message(), privacy: .public)")
|
|
8
|
+
}
|
|
9
|
+
#else
|
|
10
|
+
func logDebug(_ message: @autoclosure @escaping () -> String) {}
|
|
11
|
+
#endif
|
|
@@ -36,9 +36,8 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
36
36
|
return
|
|
37
37
|
}
|
|
38
38
|
Task {
|
|
39
|
-
// Зберігаємо адресу в UserDefaults
|
|
40
39
|
await implementation.setPrinterAddress(address)
|
|
41
|
-
|
|
40
|
+
logDebug("setPrinterAddress: \(address)")
|
|
42
41
|
await MainActor.run {
|
|
43
42
|
call.resolve(["address": address])
|
|
44
43
|
}
|
|
@@ -49,13 +48,10 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
49
48
|
/// - Parameter call: Виклик без параметрів
|
|
50
49
|
@objc func getPairedDevices(_ call: CAPPluginCall) {
|
|
51
50
|
Task {
|
|
52
|
-
// Отримуємо список підключених аксесуарів
|
|
53
51
|
let devices = await implementation.getConnectedAccessories()
|
|
54
|
-
|
|
55
|
-
// Перетворюємо масив DeviceInfo в масив словників
|
|
52
|
+
logDebug("getPairedDevices: \(devices.count) device(s)")
|
|
56
53
|
let list = devices.map { ["address": $0.address, "name": $0.name] }
|
|
57
54
|
await MainActor.run {
|
|
58
|
-
// Повертаємо результат
|
|
59
55
|
call.resolve(["devices": list])
|
|
60
56
|
}
|
|
61
57
|
}
|
|
@@ -71,13 +67,15 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
71
67
|
return
|
|
72
68
|
}
|
|
73
69
|
Task {
|
|
70
|
+
logDebug("print address=\(address) zplLength=\(zpl.utf8.count)")
|
|
74
71
|
do {
|
|
75
72
|
_ = try await implementation.printZpl(address: address, zpl: zpl)
|
|
73
|
+
logDebug("print success")
|
|
76
74
|
await MainActor.run {
|
|
77
|
-
// Успішний друк - повертаємо лише success
|
|
78
75
|
call.resolve(["success": true])
|
|
79
76
|
}
|
|
80
77
|
} catch let error as Zebra.ZebraError {
|
|
78
|
+
logDebug("print failed: \(error.localizedDescription)")
|
|
81
79
|
// Помилка друку - визначаємо код помилки
|
|
82
80
|
await MainActor.run {
|
|
83
81
|
let code: String
|
|
@@ -110,8 +108,8 @@ public class ZebraPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
110
108
|
call.reject(error.localizedDescription, code, error)
|
|
111
109
|
}
|
|
112
110
|
} catch {
|
|
111
|
+
logDebug("print failed (unknown): \(error.localizedDescription)")
|
|
113
112
|
await MainActor.run {
|
|
114
|
-
// Повертаємо помилку з UNKNOWN
|
|
115
113
|
call.reject(error.localizedDescription, "UNKNOWN", error)
|
|
116
114
|
}
|
|
117
115
|
}
|