@ruhiverse/thermal-printer-plugin 1.0.8 → 1.0.11
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.
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
3
3
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
4
4
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
5
|
-
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
|
5
|
+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
|
6
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
7
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
6
8
|
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
|
7
9
|
</manifest>
|
|
@@ -4,6 +4,8 @@ import android.Manifest;
|
|
|
4
4
|
import android.app.Activity;
|
|
5
5
|
import android.app.AlertDialog;
|
|
6
6
|
import android.app.PendingIntent;
|
|
7
|
+
import android.bluetooth.BluetoothAdapter;
|
|
8
|
+
import android.bluetooth.BluetoothDevice;
|
|
7
9
|
import android.content.BroadcastReceiver;
|
|
8
10
|
import android.content.Context;
|
|
9
11
|
import android.content.Intent;
|
|
@@ -35,6 +37,8 @@ import com.ruhiverse.thermalprinter.AsyncBluetoothEscPosPrint;
|
|
|
35
37
|
import com.ruhiverse.thermalprinter.AsyncEscPosPrint;
|
|
36
38
|
import com.ruhiverse.thermalprinter.AsyncEscPosPrinter;
|
|
37
39
|
import com.ruhiverse.thermalprinter.AsyncUsbEscPosPrint;
|
|
40
|
+
import java.util.HashSet;
|
|
41
|
+
import java.util.Set;
|
|
38
42
|
|
|
39
43
|
@CapacitorPlugin(
|
|
40
44
|
name = "ThermalPrinter",
|
|
@@ -60,6 +64,8 @@ public class ThermalPrinterPlugin extends Plugin {
|
|
|
60
64
|
public static final int PERMISSION_BLUETOOTH_CONNECT = 3;
|
|
61
65
|
public static final int PERMISSION_BLUETOOTH_SCAN = 4;
|
|
62
66
|
private static final String ACTION_USB_PERMISSION = "com.ruhiverse.thermalprinter.USB_PERMISSION";
|
|
67
|
+
|
|
68
|
+
|
|
63
69
|
|
|
64
70
|
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
|
|
65
71
|
public void onReceive(Context context, Intent intent) {
|
|
@@ -263,34 +269,84 @@ public class ThermalPrinterPlugin extends Plugin {
|
|
|
263
269
|
this.checkBluetoothPermissions(
|
|
264
270
|
() -> {
|
|
265
271
|
try {
|
|
266
|
-
BluetoothConnection[] bluetoothPrinters = (new BluetoothPrintersConnections()).getList();
|
|
267
272
|
JSArray printers = new JSArray();
|
|
273
|
+
Set<String> addedAddresses = new HashSet<>();
|
|
268
274
|
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
// Method 1: Get ALL paired Bluetooth devices directly from BluetoothAdapter
|
|
276
|
+
// This is more reliable than BluetoothPrintersConnections which filters by device class
|
|
277
|
+
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
278
|
+
|
|
279
|
+
if (bluetoothAdapter == null) {
|
|
280
|
+
Log.e(TAG, "Bluetooth not supported on this device");
|
|
281
|
+
JSObject ret = new JSObject();
|
|
282
|
+
ret.put("printers", printers);
|
|
283
|
+
call.resolve(ret);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!bluetoothAdapter.isEnabled()) {
|
|
288
|
+
Log.e(TAG, "Bluetooth is not enabled");
|
|
289
|
+
call.reject("Bluetooth is not enabled. Please turn on Bluetooth.");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
|
|
294
|
+
Log.d(TAG, "Found " + (bondedDevices != null ? bondedDevices.size() : 0) + " paired Bluetooth devices");
|
|
295
|
+
|
|
296
|
+
if (bondedDevices != null) {
|
|
297
|
+
for (BluetoothDevice device : bondedDevices) {
|
|
271
298
|
try {
|
|
272
|
-
|
|
273
|
-
String
|
|
274
|
-
String address = printer.getDevice().getAddress();
|
|
299
|
+
String name = device.getName();
|
|
300
|
+
String address = device.getAddress();
|
|
275
301
|
|
|
276
|
-
// Handle null/empty names
|
|
277
302
|
if (name == null || name.isEmpty()) {
|
|
278
303
|
name = "Unknown Device";
|
|
279
304
|
}
|
|
305
|
+
if (address == null) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
280
308
|
|
|
309
|
+
JSObject printerObj = new JSObject();
|
|
281
310
|
printerObj.put("name", name);
|
|
282
|
-
printerObj.put("address", address
|
|
311
|
+
printerObj.put("address", address);
|
|
283
312
|
printers.put(printerObj);
|
|
313
|
+
addedAddresses.add(address);
|
|
284
314
|
|
|
285
|
-
Log.d(TAG, "
|
|
315
|
+
Log.d(TAG, "Paired device: " + name + " (" + address + ")");
|
|
286
316
|
} catch (Exception e) {
|
|
287
|
-
Log.e(TAG, "Error processing
|
|
317
|
+
Log.e(TAG, "Error processing paired device: " + e.getMessage());
|
|
288
318
|
}
|
|
289
319
|
}
|
|
290
|
-
} else {
|
|
291
|
-
Log.d(TAG, "No paired Bluetooth printers found. Make sure printers are paired in Android Settings.");
|
|
292
320
|
}
|
|
293
321
|
|
|
322
|
+
// Method 2: Also try the ESCPOS library (may find additional devices)
|
|
323
|
+
try {
|
|
324
|
+
BluetoothConnection[] escposPrinters = (new BluetoothPrintersConnections()).getList();
|
|
325
|
+
if (escposPrinters != null) {
|
|
326
|
+
for (BluetoothConnection printer : escposPrinters) {
|
|
327
|
+
String address = printer.getDevice().getAddress();
|
|
328
|
+
if (address != null && !addedAddresses.contains(address)) {
|
|
329
|
+
String name = printer.getDevice().getName();
|
|
330
|
+
if (name == null || name.isEmpty()) {
|
|
331
|
+
name = "Unknown Device";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
JSObject printerObj = new JSObject();
|
|
335
|
+
printerObj.put("name", name);
|
|
336
|
+
printerObj.put("address", address);
|
|
337
|
+
printers.put(printerObj);
|
|
338
|
+
addedAddresses.add(address);
|
|
339
|
+
|
|
340
|
+
Log.d(TAG, "ESCPOS printer: " + name + " (" + address + ")");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} catch (Exception e) {
|
|
345
|
+
Log.d(TAG, "ESCPOS library scan: " + e.getMessage());
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
Log.d(TAG, "Total printers found: " + printers.length());
|
|
349
|
+
|
|
294
350
|
JSObject ret = new JSObject();
|
|
295
351
|
ret.put("printers", printers);
|
|
296
352
|
call.resolve(ret);
|
|
@@ -286,26 +286,32 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
|
|
|
286
286
|
print("ThermalPrinter: listBluetoothPrinters called")
|
|
287
287
|
var printers: [[String: String]] = []
|
|
288
288
|
|
|
289
|
-
//
|
|
289
|
+
// Method 1: Get MFi/ExternalAccessory printers (classic Bluetooth printers)
|
|
290
|
+
// Most thermal printers use classic Bluetooth, which on iOS requires
|
|
291
|
+
// ExternalAccessory framework + MFi certification
|
|
290
292
|
let manager = EAAccessoryManager.shared()
|
|
291
|
-
let
|
|
293
|
+
let allAccessories = manager.connectedAccessories
|
|
292
294
|
|
|
293
|
-
print("ThermalPrinter: Found \(
|
|
295
|
+
print("ThermalPrinter: Found \(allAccessories.count) total ExternalAccessory devices")
|
|
294
296
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
297
|
+
// Return ALL connected accessories (not just those matching printer protocols)
|
|
298
|
+
// because many printers use custom protocol strings
|
|
299
|
+
for accessory in allAccessories {
|
|
300
|
+
let name = accessory.name
|
|
301
|
+
let address = accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber
|
|
302
|
+
let protocols = accessory.protocolStrings.joined(separator: ", ")
|
|
303
|
+
|
|
304
|
+
print("ThermalPrinter: Accessory: \(name), protocols: [\(protocols)]")
|
|
305
|
+
|
|
306
|
+
printers.append([
|
|
307
|
+
"name": name,
|
|
308
|
+
"address": address,
|
|
309
|
+
])
|
|
304
310
|
}
|
|
305
|
-
printers.append(contentsOf: mfiPrinters)
|
|
306
|
-
print("ThermalPrinter: Found \(mfiPrinters.count) MFi printers")
|
|
307
311
|
|
|
308
|
-
|
|
312
|
+
print("ThermalPrinter: Found \(printers.count) ExternalAccessory printers")
|
|
313
|
+
|
|
314
|
+
// Method 2: Also scan for BLE printers (some thermal printers support BLE)
|
|
309
315
|
discoveredPeripherals.removeAll()
|
|
310
316
|
listPrintersCall = call
|
|
311
317
|
|
|
@@ -323,69 +329,59 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
|
|
|
323
329
|
|
|
324
330
|
print("ThermalPrinter: Bluetooth state: \(centralManager.state.rawValue)")
|
|
325
331
|
|
|
326
|
-
// Check Bluetooth state
|
|
327
332
|
switch centralManager.state {
|
|
328
333
|
case .poweredOn:
|
|
329
|
-
|
|
330
|
-
print("ThermalPrinter: Bluetooth powered on, starting scan")
|
|
334
|
+
print("ThermalPrinter: Bluetooth powered on, starting BLE scan")
|
|
331
335
|
startBLEScan(for: call, existingPrinters: printers)
|
|
332
336
|
case .poweredOff:
|
|
333
|
-
print("ThermalPrinter: Bluetooth powered off
|
|
337
|
+
print("ThermalPrinter: Bluetooth powered off")
|
|
338
|
+
// Still return any MFi printers we found
|
|
334
339
|
call.resolve(["printers": printers])
|
|
340
|
+
listPrintersCall = nil
|
|
335
341
|
case .unauthorized:
|
|
336
342
|
print("ThermalPrinter: Bluetooth unauthorized")
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
call.reject("Bluetooth is not supported on this device.")
|
|
343
|
+
// Still return any MFi printers, but warn about BLE
|
|
344
|
+
call.resolve(["printers": printers])
|
|
345
|
+
listPrintersCall = nil
|
|
341
346
|
case .resetting, .unknown:
|
|
342
347
|
print("ThermalPrinter: Bluetooth state unknown/resetting, waiting for update")
|
|
343
|
-
// Wait for state update
|
|
344
|
-
// The state will be updated via centralManagerDidUpdateState
|
|
345
348
|
break
|
|
346
|
-
|
|
347
|
-
print("ThermalPrinter: Unknown Bluetooth state, returning \(printers.count) printers")
|
|
349
|
+
default:
|
|
348
350
|
call.resolve(["printers": printers])
|
|
351
|
+
listPrintersCall = nil
|
|
349
352
|
}
|
|
350
353
|
}
|
|
351
354
|
|
|
352
355
|
private func startBLEScan(for call: CAPPluginCall, existingPrinters: [[String: String]]) {
|
|
353
|
-
guard let centralManager = centralManager else {
|
|
354
|
-
call.resolve(["printers": existingPrinters])
|
|
355
|
-
return
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
guard centralManager.state == .poweredOn else {
|
|
356
|
+
guard let centralManager = centralManager, centralManager.state == .poweredOn else {
|
|
359
357
|
call.resolve(["printers": existingPrinters])
|
|
358
|
+
listPrintersCall = nil
|
|
360
359
|
return
|
|
361
360
|
}
|
|
362
361
|
|
|
363
|
-
// Clear previous discoveries
|
|
364
362
|
discoveredPeripherals.removeAll()
|
|
365
363
|
|
|
366
|
-
// Scan for all peripherals
|
|
367
364
|
print("ThermalPrinter: Starting BLE scan...")
|
|
368
365
|
centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
|
|
369
366
|
|
|
370
|
-
//
|
|
371
|
-
DispatchQueue.main.asyncAfter(deadline: .now() +
|
|
367
|
+
// Scan for 4 seconds (shorter — BLE devices respond quickly)
|
|
368
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
|
|
372
369
|
centralManager.stopScan()
|
|
373
370
|
print("ThermalPrinter: Stopped BLE scan. Found \(self.discoveredPeripherals.count) peripherals")
|
|
374
371
|
|
|
375
372
|
var printers = existingPrinters
|
|
376
373
|
|
|
377
|
-
// Add discovered BLE printers - return all devices, even without names
|
|
378
374
|
let blePrinters: [[String: String]] = self.discoveredPeripherals.compactMap { peripheral in
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
375
|
+
guard let name = peripheral.name, !name.isEmpty else {
|
|
376
|
+
return nil
|
|
377
|
+
}
|
|
382
378
|
return [
|
|
383
379
|
"name": name,
|
|
384
380
|
"address": peripheral.identifier.uuidString,
|
|
385
381
|
]
|
|
386
382
|
}
|
|
387
383
|
|
|
388
|
-
print("ThermalPrinter: Returning \(blePrinters.count) BLE printers")
|
|
384
|
+
print("ThermalPrinter: Returning \(blePrinters.count) BLE printers + \(existingPrinters.count) MFi printers")
|
|
389
385
|
printers.append(contentsOf: blePrinters)
|
|
390
386
|
call.resolve(["printers": printers])
|
|
391
387
|
self.listPrintersCall = nil
|
|
@@ -399,36 +395,25 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
|
|
|
399
395
|
|
|
400
396
|
// If we're waiting to list printers, start scanning now
|
|
401
397
|
if let call = listPrintersCall {
|
|
398
|
+
// Get MFi printers
|
|
399
|
+
var mfiPrinters: [[String: String]] = []
|
|
400
|
+
let manager = EAAccessoryManager.shared()
|
|
401
|
+
for accessory in manager.connectedAccessories {
|
|
402
|
+
mfiPrinters.append([
|
|
403
|
+
"name": accessory.name,
|
|
404
|
+
"address": accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber,
|
|
405
|
+
])
|
|
406
|
+
}
|
|
407
|
+
|
|
402
408
|
switch central.state {
|
|
403
409
|
case .poweredOn:
|
|
404
|
-
print("ThermalPrinter: Bluetooth now powered on, starting scan")
|
|
405
|
-
// Get existing MFi printers first
|
|
406
|
-
let manager = EAAccessoryManager.shared()
|
|
407
|
-
let accessories = manager.connectedAccessories
|
|
408
|
-
let mfiPrinters: [[String: String]] = accessories.compactMap { accessory in
|
|
409
|
-
guard accessory.protocolStrings.contains(where: { supportedPrinterProtocols.contains($0) }) else {
|
|
410
|
-
return nil
|
|
411
|
-
}
|
|
412
|
-
return [
|
|
413
|
-
"name": accessory.name,
|
|
414
|
-
"address": accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber,
|
|
415
|
-
]
|
|
416
|
-
}
|
|
410
|
+
print("ThermalPrinter: Bluetooth now powered on, starting BLE scan")
|
|
417
411
|
startBLEScan(for: call, existingPrinters: mfiPrinters)
|
|
418
|
-
case .poweredOff:
|
|
419
|
-
print("ThermalPrinter: Bluetooth
|
|
420
|
-
call.resolve(["printers":
|
|
421
|
-
listPrintersCall = nil
|
|
422
|
-
case .unauthorized:
|
|
423
|
-
print("ThermalPrinter: Bluetooth unauthorized")
|
|
424
|
-
call.reject("Bluetooth access denied. Please enable Bluetooth permissions in Settings.")
|
|
425
|
-
listPrintersCall = nil
|
|
426
|
-
case .unsupported:
|
|
427
|
-
print("ThermalPrinter: Bluetooth unsupported")
|
|
428
|
-
call.reject("Bluetooth is not supported on this device.")
|
|
412
|
+
case .poweredOff, .unauthorized, .unsupported:
|
|
413
|
+
print("ThermalPrinter: Bluetooth not available (state: \(central.state.rawValue))")
|
|
414
|
+
call.resolve(["printers": mfiPrinters])
|
|
429
415
|
listPrintersCall = nil
|
|
430
416
|
default:
|
|
431
|
-
print("ThermalPrinter: Bluetooth state: \(central.state.rawValue)")
|
|
432
417
|
break
|
|
433
418
|
}
|
|
434
419
|
}
|