@rdlabo/capacitor-brotherprint 8.1.1 → 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.
- package/LICENSE +21 -0
- package/README.md +60 -249
- package/android/src/main/java/jp/rdlabo/capacitor/plugin/brotherprint/BluetoothPrinterFilter.kt +12 -0
- package/android/src/main/java/jp/rdlabo/capacitor/plugin/brotherprint/BrotherPrint.kt +120 -60
- package/dist/docs.json +3 -3
- package/dist/esm/definitions.d.ts +2 -2
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/interfaces.d.ts +6 -0
- package/dist/esm/interfaces.js.map +1 -1
- package/docs/events.md +58 -0
- package/docs/installation.md +133 -0
- package/docs/print.md +36 -0
- package/docs/search.md +106 -0
- package/docs/usage.md +76 -0
- package/ios/Sources/BrotherPrintPlugin/BrotherPrintPlugin.swift +34 -20
- package/ios/Sources/BrotherPrintPlugin/Info.plist +3 -1
- package/ios/Sources/BrotherPrintPlugin/PrintImageDecoder.swift +9 -0
- package/ios/Tests/BrotherPrintPluginTests/BrotherPrintPluginTests.swift +31 -10
- package/package.json +7 -4
|
@@ -23,6 +23,7 @@ import com.brother.sdk.lmprinter.PrintError
|
|
|
23
23
|
import com.brother.sdk.lmprinter.PrinterDriverGenerator
|
|
24
24
|
import com.brother.sdk.lmprinter.PrinterModel
|
|
25
25
|
import com.brother.sdk.lmprinter.PrinterSearcher
|
|
26
|
+
import com.brother.sdk.lmprinter.PrinterSearchError
|
|
26
27
|
import com.brother.sdk.lmprinter.PrinterSearcher.cancelNetworkSearch
|
|
27
28
|
import com.brother.sdk.lmprinter.setting.PrintSettings
|
|
28
29
|
import com.brother.sdk.lmprinter.setting.QLPrintSettings
|
|
@@ -45,8 +46,6 @@ import jp.rdlabo.capacitor.plugin.brotherprint.models.BrotherPrintSettings
|
|
|
45
46
|
Permission(
|
|
46
47
|
alias = "bluetooth",
|
|
47
48
|
strings = [
|
|
48
|
-
Manifest.permission.BLUETOOTH,
|
|
49
|
-
Manifest.permission.BLUETOOTH_ADMIN,
|
|
50
49
|
Manifest.permission.BLUETOOTH_CONNECT,
|
|
51
50
|
Manifest.permission.BLUETOOTH_SCAN,
|
|
52
51
|
],
|
|
@@ -69,17 +68,36 @@ class BrotherPrint : Plugin() {
|
|
|
69
68
|
"Unable to do call operation, user denied permission request"
|
|
70
69
|
|
|
71
70
|
private var storeCall: PluginCall? = null
|
|
71
|
+
private var usbReceiverRegistered = false
|
|
72
72
|
|
|
73
73
|
@PluginMethod
|
|
74
74
|
fun printImage(call: PluginCall) {
|
|
75
75
|
val encodedImage = call.getString("encodedImage", "")
|
|
76
76
|
if (encodedImage == "") {
|
|
77
|
+
notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
|
|
78
|
+
JSObject().put("code", 0).put("message", "Error - Image data is not found.")
|
|
79
|
+
)
|
|
77
80
|
call.reject("Error - Image data is not found.")
|
|
78
81
|
return
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
val decodedString =
|
|
84
|
+
val decodedString = try {
|
|
85
|
+
Base64.decode(encodedImage, Base64.DEFAULT)
|
|
86
|
+
} catch (error: IllegalArgumentException) {
|
|
87
|
+
notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
|
|
88
|
+
JSObject().put("code", 0).put("message", "Error - Invalid Base64 image data")
|
|
89
|
+
)
|
|
90
|
+
call.reject("Error - Invalid Base64 image data")
|
|
91
|
+
return
|
|
92
|
+
}
|
|
82
93
|
val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
|
|
94
|
+
if (decodedByte == null) {
|
|
95
|
+
notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
|
|
96
|
+
JSObject().put("code", 0).put("message", "Error - Create decodedByte From ImageData is failed.")
|
|
97
|
+
)
|
|
98
|
+
call.reject("Error - Create decodedByte From ImageData is failed.")
|
|
99
|
+
return
|
|
100
|
+
}
|
|
83
101
|
|
|
84
102
|
val port: String? = call.getString("port", "wifi")
|
|
85
103
|
val channelInfo: String? = call.getString("channelInfo", "")
|
|
@@ -100,6 +118,9 @@ class BrotherPrint : Plugin() {
|
|
|
100
118
|
settings = BrotherPrintSettings().modelTDSettings(call, settings)
|
|
101
119
|
settings.workPath = bridge.context.cacheDir.path;
|
|
102
120
|
} else {
|
|
121
|
+
notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
|
|
122
|
+
JSObject().put("code", 0).put("message", "Error - modelName:$modelName is not supported")
|
|
123
|
+
)
|
|
103
124
|
call.reject("Error - modelName:$modelName is not supported")
|
|
104
125
|
return;
|
|
105
126
|
}
|
|
@@ -113,6 +134,9 @@ class BrotherPrint : Plugin() {
|
|
|
113
134
|
channelInfo, bridge.context, getBluetoothAdapter(bridge.context)
|
|
114
135
|
)
|
|
115
136
|
else -> {
|
|
137
|
+
notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
|
|
138
|
+
JSObject().put("code", 0).put("message", "Error - port:$port is not supported")
|
|
139
|
+
)
|
|
116
140
|
call.reject("Error - port:$port is not supported")
|
|
117
141
|
return@Thread
|
|
118
142
|
}
|
|
@@ -161,6 +185,10 @@ class BrotherPrint : Plugin() {
|
|
|
161
185
|
|
|
162
186
|
@PluginMethod
|
|
163
187
|
fun isChannelAvailable(call: PluginCall) {
|
|
188
|
+
if (call.getString("port") in listOf("bluetooth", "bluetoothLowEnergy") && !isBluetoothPermissionGranted()) {
|
|
189
|
+
call.resolve(JSObject().put("result", false))
|
|
190
|
+
return
|
|
191
|
+
}
|
|
164
192
|
val port: String? = call.getString("port", "wifi")
|
|
165
193
|
val channelInfo: String? = call.getString("channelInfo", "")
|
|
166
194
|
|
|
@@ -200,29 +228,22 @@ class BrotherPrint : Plugin() {
|
|
|
200
228
|
}
|
|
201
229
|
}
|
|
202
230
|
|
|
231
|
+
@Synchronized
|
|
203
232
|
private fun searchUsbPrinter(call: PluginCall) {
|
|
233
|
+
if (this.storeCall != null) {
|
|
234
|
+
call.reject("Error - USB permission request is already pending")
|
|
235
|
+
return
|
|
236
|
+
}
|
|
204
237
|
if (!this.requestUsbPermission(call)) {
|
|
205
|
-
this.storeCall = call;
|
|
206
238
|
return
|
|
207
239
|
}
|
|
208
|
-
this.storeCall = null
|
|
209
240
|
|
|
210
241
|
Thread {
|
|
211
242
|
val result = PrinterSearcher.startUSBSearch(bridge.context)
|
|
212
243
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.NotPermitted -> {
|
|
217
|
-
// TODO: has error
|
|
218
|
-
}
|
|
219
|
-
com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.Canceled,
|
|
220
|
-
com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.InterfaceInactive,
|
|
221
|
-
com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.InterfaceUnsupported,
|
|
222
|
-
com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.AlreadySearching,
|
|
223
|
-
com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.UnknownError -> {
|
|
224
|
-
}
|
|
225
|
-
null -> {}
|
|
244
|
+
if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
|
|
245
|
+
call.reject("Error - startUSBSearch: " + result.error.code.toString())
|
|
246
|
+
return@Thread
|
|
226
247
|
}
|
|
227
248
|
|
|
228
249
|
for (channel in result.channels){
|
|
@@ -231,8 +252,8 @@ class BrotherPrint : Plugin() {
|
|
|
231
252
|
this.chanelToPrinter("usb", channel)
|
|
232
253
|
);
|
|
233
254
|
}
|
|
255
|
+
call.resolve();
|
|
234
256
|
}.start()
|
|
235
|
-
call.resolve();
|
|
236
257
|
}
|
|
237
258
|
|
|
238
259
|
private fun searchWiFiPrinter(call: PluginCall) {
|
|
@@ -243,7 +264,7 @@ class BrotherPrint : Plugin() {
|
|
|
243
264
|
}
|
|
244
265
|
val intDuration: Int = call.getInt("searchDuration") ?: 15 ;
|
|
245
266
|
val option = NetworkSearchOption(intDuration.toDouble(), false);
|
|
246
|
-
PrinterSearcher.startNetworkSearch(bridge.context, option){ channel ->
|
|
267
|
+
val result = PrinterSearcher.startNetworkSearch(bridge.context, option){ channel ->
|
|
247
268
|
run {
|
|
248
269
|
Log.d("brother", this.chanelToPrinter("wifi", channel).toString())
|
|
249
270
|
this.notifyListeners(
|
|
@@ -253,8 +274,12 @@ class BrotherPrint : Plugin() {
|
|
|
253
274
|
}
|
|
254
275
|
}
|
|
255
276
|
this.cancelRoutineWiFi = null
|
|
277
|
+
if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
|
|
278
|
+
call.reject("Error - startNetworkSearch: " + result.error.code.toString())
|
|
279
|
+
return@Thread
|
|
280
|
+
}
|
|
281
|
+
call.resolve();
|
|
256
282
|
}.start()
|
|
257
|
-
call.resolve();
|
|
258
283
|
}
|
|
259
284
|
|
|
260
285
|
private fun checkBLEChannel(call: PluginCall) {
|
|
@@ -263,12 +288,21 @@ class BrotherPrint : Plugin() {
|
|
|
263
288
|
} else {
|
|
264
289
|
Log.d("brother", "checkBLEChannel")
|
|
265
290
|
Thread {
|
|
266
|
-
|
|
291
|
+
val result = PrinterSearcher.startBluetoothSearch(bridge.context)
|
|
292
|
+
if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
|
|
293
|
+
call.reject("Error - startBluetoothSearch: " + result.error.code.toString())
|
|
294
|
+
return@Thread
|
|
295
|
+
}
|
|
296
|
+
for (channel in result.channels){
|
|
297
|
+
if (!matchesBluetoothPrinterFilter(call.getBoolean("bluetoothPrintersOnly", false) == true) {
|
|
298
|
+
val device = getBluetoothAdapter(bridge.context)?.getRemoteDevice(channel.channelInfo)
|
|
299
|
+
device?.bluetoothClass?.deviceClass
|
|
300
|
+
}) continue
|
|
267
301
|
Log.d("brother", this.chanelToPrinter("bluetooth", channel).toString())
|
|
268
302
|
this.notifyListeners(BrotherPrintEvent.onPrinterAvailable.webEventName, this.chanelToPrinter("bluetooth", channel));
|
|
269
303
|
}
|
|
304
|
+
call.resolve();
|
|
270
305
|
}.start()
|
|
271
|
-
call.resolve();
|
|
272
306
|
}
|
|
273
307
|
}
|
|
274
308
|
|
|
@@ -276,18 +310,18 @@ class BrotherPrint : Plugin() {
|
|
|
276
310
|
if (!isBluetoothPermissionGranted()) {
|
|
277
311
|
requestPermissionForAlias("bluetooth", call, "permissionCallback");
|
|
278
312
|
return;
|
|
279
|
-
} else if (!isLocationPermissionGranted()) {
|
|
280
|
-
requestPermissionForAlias("location", call, "
|
|
313
|
+
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && !isLocationPermissionGranted()) {
|
|
314
|
+
requestPermissionForAlias("location", call, "locationPermissionCallback");
|
|
281
315
|
return;
|
|
282
316
|
} else {
|
|
283
317
|
Log.d("brother", "searchBLEPrinter")
|
|
284
318
|
Thread {
|
|
285
319
|
this.cancelRoutineBluetooth = {
|
|
286
|
-
|
|
320
|
+
PrinterSearcher.cancelBLESearch()
|
|
287
321
|
}
|
|
288
322
|
val intDuration: Int = call.getInt("searchDuration") ?: 15 ;
|
|
289
323
|
val option = BLESearchOption(intDuration.toDouble())
|
|
290
|
-
PrinterSearcher.startBLESearch(bridge.context, option){ channel ->
|
|
324
|
+
val result = PrinterSearcher.startBLESearch(bridge.context, option){ channel ->
|
|
291
325
|
run {
|
|
292
326
|
Log.d("brother", this.chanelToPrinter("bluetoothLowEnergy", channel).toString())
|
|
293
327
|
this.notifyListeners(
|
|
@@ -297,15 +331,19 @@ class BrotherPrint : Plugin() {
|
|
|
297
331
|
}
|
|
298
332
|
}
|
|
299
333
|
this.cancelRoutineBluetooth = null;
|
|
334
|
+
if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
|
|
335
|
+
call.reject("Error - startBLESearch: " + result.error.code.toString())
|
|
336
|
+
return@Thread
|
|
337
|
+
}
|
|
338
|
+
call.resolve();
|
|
300
339
|
}.start()
|
|
301
|
-
call.resolve();
|
|
302
340
|
}
|
|
303
341
|
}
|
|
304
342
|
|
|
305
343
|
private fun chanelToPrinter(port: String, channel: Channel): JSObject? {
|
|
306
344
|
Log.d("brother", channel.toString());
|
|
307
345
|
val modelName = channel.extraInfo[Channel.ExtraInfoKey.ModelName] ?: ""
|
|
308
|
-
val serialNumber = channel.extraInfo[Channel.ExtraInfoKey.
|
|
346
|
+
val serialNumber = channel.extraInfo[Channel.ExtraInfoKey.SerialNumber] ?: ""
|
|
309
347
|
val macAddress = channel.extraInfo[Channel.ExtraInfoKey.MACAddress] ?: ""
|
|
310
348
|
val nodeName = channel.extraInfo[Channel.ExtraInfoKey.NodeName] ?: ""
|
|
311
349
|
val location = channel.extraInfo[Channel.ExtraInfoKey.Location] ?: ""
|
|
@@ -346,49 +384,37 @@ class BrotherPrint : Plugin() {
|
|
|
346
384
|
call.reject(PERMISSION_DENIED_ERROR)
|
|
347
385
|
return
|
|
348
386
|
}
|
|
387
|
+
when (call.methodName) {
|
|
388
|
+
"search" -> this.search(call)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
@PermissionCallback
|
|
393
|
+
private fun locationPermissionCallback(call: PluginCall) {
|
|
349
394
|
if (!isLocationPermissionGranted()) {
|
|
350
395
|
Log.d("brother", "!isLocationPermissionGranted()")
|
|
351
396
|
call.reject(PERMISSION_DENIED_ERROR)
|
|
352
397
|
return
|
|
353
398
|
}
|
|
354
|
-
|
|
355
|
-
"search" -> this.search(call)
|
|
356
|
-
}
|
|
399
|
+
this.search(call)
|
|
357
400
|
}
|
|
358
401
|
|
|
359
|
-
/**
|
|
360
|
-
* TODO: This is not called for in spite of registration.
|
|
361
|
-
* Therefore, it is now necessary for the user to run it again after permission is granted.
|
|
362
|
-
*/
|
|
363
402
|
private val usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
|
364
403
|
override fun onReceive(context: Context?, intent: Intent?) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
storeCall
|
|
404
|
+
synchronized(this@BrotherPrint) {
|
|
405
|
+
if (intent?.action != ActionUSBPermission) return
|
|
406
|
+
val call = storeCall ?: return
|
|
407
|
+
storeCall = null
|
|
408
|
+
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
|
409
|
+
searchUsbPrinter(call)
|
|
410
|
+
} else {
|
|
411
|
+
call.reject("Error - usbReceiver can't current receiver");
|
|
412
|
+
}
|
|
369
413
|
}
|
|
370
414
|
}
|
|
371
415
|
}
|
|
372
416
|
|
|
373
417
|
private fun requestUsbPermission(call: PluginCall): Boolean {
|
|
374
|
-
val permissionIntent = PendingIntent.getBroadcast(
|
|
375
|
-
bridge.context, 0, Intent(ActionUSBPermission), PendingIntent.FLAG_IMMUTABLE
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
379
|
-
bridge.context.registerReceiver(
|
|
380
|
-
usbReceiver, IntentFilter(ActionUSBPermission),
|
|
381
|
-
Context.RECEIVER_NOT_EXPORTED
|
|
382
|
-
)
|
|
383
|
-
} else {
|
|
384
|
-
ContextCompat.registerReceiver(
|
|
385
|
-
bridge.context,
|
|
386
|
-
usbReceiver,
|
|
387
|
-
IntentFilter(ActionUSBPermission),
|
|
388
|
-
ContextCompat.RECEIVER_NOT_EXPORTED
|
|
389
|
-
)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
418
|
var connectDevice: UsbDevice? = null
|
|
393
419
|
val usbManager = bridge.context.getSystemService(Context.USB_SERVICE) as UsbManager
|
|
394
420
|
for (device in usbManager.deviceList.values) {
|
|
@@ -399,14 +425,48 @@ class BrotherPrint : Plugin() {
|
|
|
399
425
|
call.reject("Error - connection failed: device not found")
|
|
400
426
|
return false
|
|
401
427
|
}
|
|
428
|
+
if (usbManager.hasPermission(connectDevice)) return true
|
|
429
|
+
|
|
430
|
+
val permissionIntent = PendingIntent.getBroadcast(
|
|
431
|
+
bridge.context, 0, Intent(ActionUSBPermission), PendingIntent.FLAG_IMMUTABLE
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if (!usbReceiverRegistered) {
|
|
435
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
436
|
+
bridge.context.registerReceiver(
|
|
437
|
+
usbReceiver, IntentFilter(ActionUSBPermission),
|
|
438
|
+
Context.RECEIVER_NOT_EXPORTED
|
|
439
|
+
)
|
|
440
|
+
} else {
|
|
441
|
+
ContextCompat.registerReceiver(
|
|
442
|
+
bridge.context,
|
|
443
|
+
usbReceiver,
|
|
444
|
+
IntentFilter(ActionUSBPermission),
|
|
445
|
+
ContextCompat.RECEIVER_NOT_EXPORTED
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
usbReceiverRegistered = true
|
|
449
|
+
}
|
|
402
450
|
|
|
451
|
+
storeCall = call
|
|
403
452
|
usbManager.requestPermission(connectDevice, permissionIntent)
|
|
453
|
+
return false
|
|
454
|
+
}
|
|
404
455
|
|
|
405
|
-
|
|
456
|
+
@Synchronized
|
|
457
|
+
override fun handleOnDestroy() {
|
|
458
|
+
val call = storeCall
|
|
459
|
+
storeCall = null
|
|
460
|
+
call?.reject("Error - plugin destroyed while waiting for USB permission")
|
|
461
|
+
if (usbReceiverRegistered) {
|
|
462
|
+
bridge.context.unregisterReceiver(usbReceiver)
|
|
463
|
+
usbReceiverRegistered = false
|
|
464
|
+
}
|
|
465
|
+
super.handleOnDestroy()
|
|
406
466
|
}
|
|
407
467
|
|
|
408
468
|
private fun isBluetoothPermissionGranted(): Boolean {
|
|
409
|
-
return getPermissionState("bluetooth") == PermissionState.GRANTED
|
|
469
|
+
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || getPermissionState("bluetooth") == PermissionState.GRANTED
|
|
410
470
|
}
|
|
411
471
|
|
|
412
472
|
private fun isLocationPermissionGranted(): Boolean {
|
package/dist/docs.json
CHANGED
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"parameters": [],
|
|
67
67
|
"returns": "Promise<void>",
|
|
68
68
|
"tags": [],
|
|
69
|
-
"docs": "
|
|
69
|
+
"docs": "Stop an active search before its timeout, including when leaving the screen.",
|
|
70
70
|
"complexTypes": [],
|
|
71
71
|
"slug": "cancelsearchwifiprinter"
|
|
72
72
|
},
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"parameters": [],
|
|
77
77
|
"returns": "Promise<void>",
|
|
78
78
|
"tags": [],
|
|
79
|
-
"docs": "
|
|
79
|
+
"docs": "Stop an active search before its timeout, including when leaving the screen.",
|
|
80
80
|
"complexTypes": [],
|
|
81
81
|
"slug": "cancelsearchbluetoothprinter"
|
|
82
82
|
},
|
|
@@ -929,7 +929,7 @@
|
|
|
929
929
|
"docs": "",
|
|
930
930
|
"types": [
|
|
931
931
|
{
|
|
932
|
-
"text": "{\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n}",
|
|
932
|
+
"text": "{\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n /**\n * Android Bluetooth Classic only. Include only devices whose Bluetooth class\n * reports a printer. Defaults to false; ignored for other ports and on iOS.\n * This does not identify Brother devices. Devices with an unknown class are excluded when true.\n */\n bluetoothPrintersOnly?: boolean;\n}",
|
|
933
933
|
"complexTypes": [
|
|
934
934
|
"BRLMPrinterPort"
|
|
935
935
|
]
|
|
@@ -13,11 +13,11 @@ export interface BrotherPrintPlugin {
|
|
|
13
13
|
*/
|
|
14
14
|
isChannelAvailable(option: BRLMChannelResult): Promise<isChannelAvailableResult>;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Stop an active search before its timeout, including when leaving the screen.
|
|
17
17
|
*/
|
|
18
18
|
cancelSearchWiFiPrinter(): Promise<void>;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Stop an active search before its timeout, including when leaving the screen.
|
|
21
21
|
*/
|
|
22
22
|
cancelSearchBluetoothPrinter(): Promise<void>;
|
|
23
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\nimport type { BrotherPrintEventsEnum } from './events.enum';\nimport type {\n BRLMChannelResult,\n BRLMPrintOptions,\n BRLMSearchOption,\n ErrorInfo,\n isChannelAvailableResult,\n} from './interfaces';\n\nexport interface BrotherPrintPlugin {\n printImage(options: BRLMPrintOptions): Promise<void>;\n\n /**\n * Search for printers. If not found, it will return an empty array.(not error)\n */\n search(option: BRLMSearchOption): Promise<void>;\n\n /**\n * If you have saved the last connected BRLMChannelResult,\n * you can use it to verify whether it is currently usable.\n */\n isChannelAvailable(option: BRLMChannelResult): Promise<isChannelAvailableResult>;\n\n /**\n *
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\nimport type { BrotherPrintEventsEnum } from './events.enum';\nimport type {\n BRLMChannelResult,\n BRLMPrintOptions,\n BRLMSearchOption,\n ErrorInfo,\n isChannelAvailableResult,\n} from './interfaces';\n\nexport interface BrotherPrintPlugin {\n printImage(options: BRLMPrintOptions): Promise<void>;\n\n /**\n * Search for printers. If not found, it will return an empty array.(not error)\n */\n search(option: BRLMSearchOption): Promise<void>;\n\n /**\n * If you have saved the last connected BRLMChannelResult,\n * you can use it to verify whether it is currently usable.\n */\n isChannelAvailable(option: BRLMChannelResult): Promise<isChannelAvailableResult>;\n\n /**\n * Stop an active search before its timeout, including when leaving the screen.\n */\n cancelSearchWiFiPrinter(): Promise<void>;\n\n /**\n * Stop an active search before its timeout, including when leaving the screen.\n */\n cancelSearchBluetoothPrinter(): Promise<void>;\n\n /**\n * Find the printer that can connected to the device.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrinterAvailable,\n listenerFunc: (printers: BRLMChannelResult) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Success Print Event\n */\n addListener(eventName: BrotherPrintEventsEnum.onPrint, listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n /**\n * Failed to connect to the printer.\n * ex: Bluetooth is off, Printer is off, etc.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrintFailedCommunication,\n listenerFunc: (info: ErrorInfo) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Failed to print.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrintError,\n listenerFunc: (info: ErrorInfo) => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
|
package/dist/esm/interfaces.d.ts
CHANGED
|
@@ -124,6 +124,12 @@ export type BRLMSearchOption = {
|
|
|
124
124
|
* use only port is 'wifi' or 'bluetoothLowEnergy'.
|
|
125
125
|
*/
|
|
126
126
|
searchDuration: number;
|
|
127
|
+
/**
|
|
128
|
+
* Android Bluetooth Classic only. Include only devices whose Bluetooth class
|
|
129
|
+
* reports a printer. Defaults to false; ignored for other ports and on iOS.
|
|
130
|
+
* This does not identify Brother devices. Devices with an unknown class are excluded when true.
|
|
131
|
+
*/
|
|
132
|
+
bluetoothPrintersOnly?: boolean;
|
|
127
133
|
};
|
|
128
134
|
export type ErrorInfo = {
|
|
129
135
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n BRLMPrinterLabelName,\n BRLMPrinterModelName,\n BRLMPrinterAutoCutType,\n BRLMPrinterCompressMode,\n BRLMPrinterHalftone,\n BRLMPrinterHalftoneThresholdType,\n BRLMPrinterHorizontalAlignment,\n BRLMPrinterNumberOfCopies,\n BRLMPrinterPrintQuality,\n BRLMPrinterImageRotation,\n BRLMPrinterScaleMode,\n BRLMPrinterScaleValueType,\n BRLMPrinterVerticalAlignment,\n BRLMPrinterCustomPaperType,\n BRLMPrinterCustomPaperUnit,\n BRLMPrinterPort,\n} from './brother-printer.enum';\n\nexport type BRLMChannelResult = {\n port: BRLMPrinterPort;\n modelName: string;\n serialNumber: string;\n macAddress: string;\n nodeName: string;\n location: string;\n\n /**\n * This need to connect to the printer.\n * wifi: IP Address\n * bluetooth: macAddress\n * bluetoothLowEnergy: modelName for bluetoothLowEnergy\n */\n channelInfo: string;\n};\n\nexport type BRLMPrintOptions = {\n encodedImage: string;\n\n /**\n * Should use enum BRLMPrinterModelName\n */\n modelName: BRLMPrinterModelName;\n} & Partial<BRLMChannelResult> &\n (BRLMPrinterQLModelSettings | BRLMPrinterTDModelSettings);\n\nexport type isChannelAvailableResult = {\n result: boolean;\n};\n\nexport type BRLMPrinterTDModelSettings = {\n /**\n * Should use enum BRKMPrinterCustomPaperType\n */\n paperType: BRLMPrinterCustomPaperType;\n\n /**\n * The width of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeWidth: number;\n\n /**\n * The length of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeLength: number;\n\n /**\n * It is the difference between a sticker and a mount.\n * For example, the RD-U04J1 is `1.0, 2.0, 1.0, 2.0`\n */\n marginTop: number;\n marginRight: number;\n marginBottom: number;\n marginLeft: number;\n\n /**\n * The spacing between seals. For example, the RD-U04J1 is 0.2.\n */\n gapLength: number;\n\n paperMarkPosition: number;\n paperMarkLength: number;\n\n /**\n * Should use enum BRKMPrinterCustomPaperUnit.\n * For example, the RD-U04J1 is mm.\n */\n paperUnit: BRLMPrinterCustomPaperUnit;\n};\n\nexport type BRLMPrinterQLModelSettings = {\n /**\n * Should use enum BRLMPrinterLabelName\n */\n labelName: BRLMPrinterLabelName;\n} & BRLMPrinterSettings;\n\n/**\n * These are optional. If these are not set, default values are assigned by the printer.\n */\nexport type BRLMPrinterSettings = {\n /**\n * The number of copies you print.\n */\n numberOfCopies?: BRLMPrinterNumberOfCopies;\n\n /**\n * Whether the auto-cut is enabled or not. If true, your printer cut the paper each page.\n */\n autoCut?: BRLMPrinterAutoCutType;\n\n /**\n * A scale mode that specifies how your data is scaled in a print area of your printer.\n */\n scaleMode?: BRLMPrinterScaleMode;\n\n /**\n * A scale value. This is effective when ScaleMode is ScaleValue.\n */\n scaleValue?: BRLMPrinterScaleValueType;\n\n /**\n * A way to rasterize your data.\n */\n halftone?: BRLMPrinterHalftone;\n\n /**\n * A threshold value. This is effective when the Halftone is Threshold.\n */\n halftoneThreshold?: BRLMPrinterHalftoneThresholdType;\n\n /**\n * An image rotation that specifies the angle in which your data is placed in the print area. Rotation direction is clockwise.\n */\n imageRotation?: BRLMPrinterImageRotation;\n\n /**\n * A vertical alignment that specifies how your data is placed in the printable area.\n */\n verticalAlignment?: BRLMPrinterVerticalAlignment;\n\n /**\n * A horizontal alignment that specifies how your data is placed in the printable area.\n */\n horizontalAlignment?: BRLMPrinterHorizontalAlignment;\n\n /**\n * A compress mode that specifies how to compress your data.\n * note: This is ios only.\n */\n compressMode?: BRLMPrinterCompressMode;\n\n /**\n * A priority that is print speed or print quality. Whether or not this has an effect is depend on your printer.\n */\n printQuality?: BRLMPrinterPrintQuality;\n};\n\nexport type BRLMSearchOption = {\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n};\n\nexport type ErrorInfo = {\n message: string;\n code: number;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n BRLMPrinterLabelName,\n BRLMPrinterModelName,\n BRLMPrinterAutoCutType,\n BRLMPrinterCompressMode,\n BRLMPrinterHalftone,\n BRLMPrinterHalftoneThresholdType,\n BRLMPrinterHorizontalAlignment,\n BRLMPrinterNumberOfCopies,\n BRLMPrinterPrintQuality,\n BRLMPrinterImageRotation,\n BRLMPrinterScaleMode,\n BRLMPrinterScaleValueType,\n BRLMPrinterVerticalAlignment,\n BRLMPrinterCustomPaperType,\n BRLMPrinterCustomPaperUnit,\n BRLMPrinterPort,\n} from './brother-printer.enum';\n\nexport type BRLMChannelResult = {\n port: BRLMPrinterPort;\n modelName: string;\n serialNumber: string;\n macAddress: string;\n nodeName: string;\n location: string;\n\n /**\n * This need to connect to the printer.\n * wifi: IP Address\n * bluetooth: macAddress\n * bluetoothLowEnergy: modelName for bluetoothLowEnergy\n */\n channelInfo: string;\n};\n\nexport type BRLMPrintOptions = {\n encodedImage: string;\n\n /**\n * Should use enum BRLMPrinterModelName\n */\n modelName: BRLMPrinterModelName;\n} & Partial<BRLMChannelResult> &\n (BRLMPrinterQLModelSettings | BRLMPrinterTDModelSettings);\n\nexport type isChannelAvailableResult = {\n result: boolean;\n};\n\nexport type BRLMPrinterTDModelSettings = {\n /**\n * Should use enum BRKMPrinterCustomPaperType\n */\n paperType: BRLMPrinterCustomPaperType;\n\n /**\n * The width of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeWidth: number;\n\n /**\n * The length of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeLength: number;\n\n /**\n * It is the difference between a sticker and a mount.\n * For example, the RD-U04J1 is `1.0, 2.0, 1.0, 2.0`\n */\n marginTop: number;\n marginRight: number;\n marginBottom: number;\n marginLeft: number;\n\n /**\n * The spacing between seals. For example, the RD-U04J1 is 0.2.\n */\n gapLength: number;\n\n paperMarkPosition: number;\n paperMarkLength: number;\n\n /**\n * Should use enum BRKMPrinterCustomPaperUnit.\n * For example, the RD-U04J1 is mm.\n */\n paperUnit: BRLMPrinterCustomPaperUnit;\n};\n\nexport type BRLMPrinterQLModelSettings = {\n /**\n * Should use enum BRLMPrinterLabelName\n */\n labelName: BRLMPrinterLabelName;\n} & BRLMPrinterSettings;\n\n/**\n * These are optional. If these are not set, default values are assigned by the printer.\n */\nexport type BRLMPrinterSettings = {\n /**\n * The number of copies you print.\n */\n numberOfCopies?: BRLMPrinterNumberOfCopies;\n\n /**\n * Whether the auto-cut is enabled or not. If true, your printer cut the paper each page.\n */\n autoCut?: BRLMPrinterAutoCutType;\n\n /**\n * A scale mode that specifies how your data is scaled in a print area of your printer.\n */\n scaleMode?: BRLMPrinterScaleMode;\n\n /**\n * A scale value. This is effective when ScaleMode is ScaleValue.\n */\n scaleValue?: BRLMPrinterScaleValueType;\n\n /**\n * A way to rasterize your data.\n */\n halftone?: BRLMPrinterHalftone;\n\n /**\n * A threshold value. This is effective when the Halftone is Threshold.\n */\n halftoneThreshold?: BRLMPrinterHalftoneThresholdType;\n\n /**\n * An image rotation that specifies the angle in which your data is placed in the print area. Rotation direction is clockwise.\n */\n imageRotation?: BRLMPrinterImageRotation;\n\n /**\n * A vertical alignment that specifies how your data is placed in the printable area.\n */\n verticalAlignment?: BRLMPrinterVerticalAlignment;\n\n /**\n * A horizontal alignment that specifies how your data is placed in the printable area.\n */\n horizontalAlignment?: BRLMPrinterHorizontalAlignment;\n\n /**\n * A compress mode that specifies how to compress your data.\n * note: This is ios only.\n */\n compressMode?: BRLMPrinterCompressMode;\n\n /**\n * A priority that is print speed or print quality. Whether or not this has an effect is depend on your printer.\n */\n printQuality?: BRLMPrinterPrintQuality;\n};\n\nexport type BRLMSearchOption = {\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n /**\n * Android Bluetooth Classic only. Include only devices whose Bluetooth class\n * reports a printer. Defaults to false; ignored for other ports and on iOS.\n * This does not identify Brother devices. Devices with an unknown class are excluded when true.\n */\n bluetoothPrintersOnly?: boolean;\n};\n\nexport type ErrorInfo = {\n message: string;\n code: number;\n};\n"]}
|
package/docs/events.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Events
|
|
2
|
+
|
|
3
|
+
Listen for discovered printers and print results. Register listeners before [Search](/docs/search) and [Print](/docs/print) so the first events are not missed.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import type { PluginListenerHandle } from '@capacitor/core';
|
|
7
|
+
import { BrotherPrint, BrotherPrintEventsEnum } from '@rdlabo/capacitor-brotherprint';
|
|
8
|
+
|
|
9
|
+
const handles: PluginListenerHandle[] = [];
|
|
10
|
+
|
|
11
|
+
const registerPrintListeners = async () => {
|
|
12
|
+
handles.push(
|
|
13
|
+
await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrinterAvailable, (printer) => {
|
|
14
|
+
console.log('printer', printer.channelInfo);
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
17
|
+
handles.push(
|
|
18
|
+
await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrint, () => {
|
|
19
|
+
console.log('onPrint');
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
handles.push(
|
|
23
|
+
await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrintFailedCommunication, (info) => {
|
|
24
|
+
console.log('onPrintFailedCommunication', info);
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
handles.push(
|
|
28
|
+
await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrintError, (info) => {
|
|
29
|
+
console.log('onPrintError', info);
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const removePrintListeners = async () => {
|
|
35
|
+
await Promise.all(handles.map((handle) => handle.remove()));
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| Event | When it fires |
|
|
40
|
+
| ---------------------------- | ------------------------------------ |
|
|
41
|
+
| `onPrinterAvailable` | A printer that can connect was found |
|
|
42
|
+
| `onPrint` | Print succeeded |
|
|
43
|
+
| `onPrintFailedCommunication` | The printer could not be reached |
|
|
44
|
+
| `onPrintError` | Print failed |
|
|
45
|
+
|
|
46
|
+
When `printImage` rejects an invalid image, unsupported model or port, or a print-settings creation failure, it also emits `onPrintError` with `code: 0` and an explanatory `message`. This code denotes a plugin validation error, not an SDK error. Failure to open the printer channel emits `onPrintFailedCommunication` instead.
|
|
47
|
+
|
|
48
|
+
See the demo for a complete page:
|
|
49
|
+
|
|
50
|
+
https://github.com/rdlabo-dev/capacitor-brotherprint/blob/v8.1.1/demo/src/app/home/home.page.ts
|
|
51
|
+
|
|
52
|
+
<!-- !::addListener.BrotherPrintEventsEnum:: -->
|
|
53
|
+
|
|
54
|
+
<!-- !::BrotherPrintEventsEnum:: -->
|
|
55
|
+
|
|
56
|
+
<!-- !::PluginListenerHandle:: -->
|
|
57
|
+
|
|
58
|
+
<!-- !::ErrorInfo:: -->
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm install @rdlabo/capacitor-brotherprint
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The published plugin package declares an SPM dependency on a **local** Brother kit at your app root:
|
|
10
|
+
|
|
11
|
+
`ios/LocalPackages/BRLMPrinterKit`
|
|
12
|
+
|
|
13
|
+
That path is relative from `node_modules/@rdlabo/capacitor-brotherprint` (`../../../ios/LocalPackages/BRLMPrinterKit`). Place the Brother iOS SDK under your Capacitor app’s `ios` tree as shown below, then run `npx cap sync`. This plugin requires **iOS 15** and **Swift Package Manager** only (no CocoaPods / Podfile steps).
|
|
14
|
+
|
|
15
|
+
This plugin does not redistribute the Brother SDK. Download it from Brother’s official pages for your platform.
|
|
16
|
+
|
|
17
|
+
## Initialize the Brother SDK
|
|
18
|
+
|
|
19
|
+
### Android configuration
|
|
20
|
+
|
|
21
|
+
1. Place the following files in the android folder of your Capacitor project:
|
|
22
|
+
|
|
23
|
+
- `android/BrotherPrintLibrary/BrotherPrintLibrary.aar`
|
|
24
|
+
- `android/BrotherPrintLibrary/build.gradle`
|
|
25
|
+
|
|
26
|
+
Download the Android SDK from Brother: https://support.brother.co.jp/j/s/es/dev/ja/mobilesdk/android/index.html?c=jp&lang=ja&navi=offall&comple=on&redirect=on#ver4
|
|
27
|
+
|
|
28
|
+
2. In `android/BrotherPrintLibrary/build.gradle`, include:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
configurations.maybeCreate("default")
|
|
32
|
+
artifacts.add("default", file('BrotherPrintLibrary.aar'))
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Open `android/settings.gradle` and add:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
include ':BrotherPrintLibrary'
|
|
39
|
+
project(':BrotherPrintLibrary').projectDir = new File('./BrotherPrintLibrary/')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### iOS configuration
|
|
43
|
+
|
|
44
|
+
1. Under your Capacitor app (not inside `node_modules`), place:
|
|
45
|
+
|
|
46
|
+
- `ios/LocalPackages/BRLMPrinterKit/Sources/BRLMPrinterKit.xcframework`
|
|
47
|
+
- `ios/LocalPackages/BRLMPrinterKit/Package.swift`
|
|
48
|
+
|
|
49
|
+
Download the iOS SDK from Brother: https://support.brother.com/g/s/es/dev/en/mobilesdk/ios/index.html
|
|
50
|
+
|
|
51
|
+
2. Create `ios/LocalPackages/BRLMPrinterKit/Package.swift` for that local binary package (minimum iOS 15 to match the plugin):
|
|
52
|
+
|
|
53
|
+
```swift
|
|
54
|
+
// swift-tools-version: 5.9
|
|
55
|
+
import PackageDescription
|
|
56
|
+
|
|
57
|
+
let package = Package(
|
|
58
|
+
name: "BRLMPrinterKit",
|
|
59
|
+
platforms: [
|
|
60
|
+
.iOS(.v15)
|
|
61
|
+
],
|
|
62
|
+
products: [
|
|
63
|
+
.library(name: "BRLMPrinterKit", targets: ["BRLMPrinterKit"])
|
|
64
|
+
],
|
|
65
|
+
targets: [
|
|
66
|
+
.binaryTarget(
|
|
67
|
+
name: "BRLMPrinterKit",
|
|
68
|
+
path: "Sources/BRLMPrinterKit.xcframework"
|
|
69
|
+
)
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
3. After the SDK files are in place, run `npx cap sync` so the app’s iOS project picks up the plugin and the local package path.
|
|
75
|
+
|
|
76
|
+
## Permission configuration
|
|
77
|
+
|
|
78
|
+
### Android configuration
|
|
79
|
+
|
|
80
|
+
Update `AndroidManifest.xml` to include the following permissions:
|
|
81
|
+
|
|
82
|
+
```diff
|
|
83
|
+
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
84
|
+
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
85
|
+
+ xmlns:tools="http://schemas.android.com/tools">
|
|
86
|
+
...
|
|
87
|
+
+ <!-- For Bluetooth -->
|
|
88
|
+
+ <uses-permission android:name="android.permission.BLUETOOTH" />
|
|
89
|
+
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
90
|
+
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
91
|
+
|
|
92
|
+
+ <!-- For Bluetooth Low Energy, Android 11 and earlier-->
|
|
93
|
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
94
|
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
95
|
+
|
|
96
|
+
+ <!-- For Bluetooth Low Energy, Android 12 and later -->
|
|
97
|
+
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
|
98
|
+
+ android:usesPermissionFlags="neverForLocation"
|
|
99
|
+
+ tools:targetApi="s" />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
More information: https://support.brother.co.jp/j/s/support/html/mobilesdk/guide/getting-started/getting-started-android.html
|
|
103
|
+
|
|
104
|
+
### iOS configuration
|
|
105
|
+
|
|
106
|
+
Update `Info.plist` to include the following keys. `UISupportedExternalAccessoryProtocols` must be an **array of strings**.
|
|
107
|
+
|
|
108
|
+
```diff
|
|
109
|
+
+ <key>NSBluetoothAlwaysUsageDescription</key>
|
|
110
|
+
+ <string>【Why use Bluetooth for your app.】</string>
|
|
111
|
+
+ <key>NSBluetoothPeripheralUsageDescription</key>
|
|
112
|
+
+ <string>【Why use Bluetooth for your app.】</string>
|
|
113
|
+
+ <key>NSBonjourServices</key>
|
|
114
|
+
+ <array>
|
|
115
|
+
+ <string>_pdl-datastream._tcp</string>
|
|
116
|
+
+ <string>_printer._tcp</string>
|
|
117
|
+
+ <string>_ipp._tcp</string>
|
|
118
|
+
+ </array>
|
|
119
|
+
+ <key>NSLocalNetworkUsageDescription</key>
|
|
120
|
+
+ <string>【Why use WiFi for your app.】</string>
|
|
121
|
+
+ <key>UISupportedExternalAccessoryProtocols</key>
|
|
122
|
+
+ <array>
|
|
123
|
+
+ <string>com.brother.ptcbp</string>
|
|
124
|
+
+ </array>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### Bluetooth plist types (verified September 9, 2026)
|
|
128
|
+
|
|
129
|
+
`UISupportedExternalAccessoryProtocols` must be an **array of strings**, even when `com.brother.ptcbp` is the only protocol. Earlier versions of this repository's example incorrectly used a single `<string>`, which caused Bluetooth discovery to crash with `-[__NSCFString count]: unrecognized selector`. Use the `<array>` shown above. See [Apple's type definition](https://developer.apple.com/documentation/bundleresources/information-property-list/uisupportedexternalaccessoryprotocols).
|
|
130
|
+
|
|
131
|
+
The `NSBluetoothAlwaysUsageDescription` and `NSBluetoothPeripheralUsageDescription` values are **strings**, not arrays. The examples for these keys in [Brother's official iOS setup guide](https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/getting-started/getting-started-ios.html) are correct as of September 9, 2026; they were not the cause of this crash. Brother's guide separately instructs adding `com.brother.ptcbp` as an item under `UISupportedExternalAccessoryProtocols`. It requires `NSBluetoothPeripheralUsageDescription` additionally only for deployment targets earlier than iOS 13.
|
|
132
|
+
|
|
133
|
+
More information: https://support.brother.co.jp/j/s/support/html/mobilesdk/guide/getting-started/getting-started-ios.html
|