Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Bluetooth Support for iOS, extend support for Android #15

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 45 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,40 @@

Uses the Brother Print SDK for Android...

More info can be found here, including a list of compatible printers: http://www.brother.com/product/dev/mobile/android/index.htm
More info can be found here, including a list of compatible printers: [http://www.brother.com/product/dev/mobile/android/index.htm](http://www.brother.com/product/dev/mobile/android/index.htm)

Already bundled is the following version: v3.0.4 (5/18/2016) which is in the `src/android/libs` dir. By downloading this you agree to the Brother SDK License terms which are included in the README under the libs dir.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What version is bundled for IOS?

Also, do we know if v3.0.4 is the current bundled version for Android? I'm guessing that's out of date...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did see that the Android SDK was updated not too long ago... but the iOS SDK hasn't been changed since May I think?

The current SDK version for iOS is 3.1.1

The current SDK version for Android is 3.0.7

I have not updated these binaries though. At least, I don't think I did.


## Installing

In your Cordova project, run the following command to install the plugin.

```
```shell
cordova plugin add https://github.com/gordol/cordova-brother-label-printer
```

And then read [usage](#usage) below.

## TODO

- [ ] Add the ability to change the label of the printers
- [ ] Revisit the USB interface Printing to allow for Printer and Label Selection

## Help and Support

Please do not email me for support or help with this plugin, use the issue tracker link above, so everyone will benefit from community questions and involvement, and I don't have to answer the same questions over and over for many individuals.

This is a Cordova plugin, firstly. You should be familiar with the Cordova plugin system before you try to use this plugin. Fortunately, it's pretty straight forward and easy to understand.

You can [read more about Android plugin development for Cordova here](https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html). Knowledge of all of these internals is not necessary, but it doesn't hurt to be familiar either.
You can [read more about Android plugin development for Cordova here](https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html). Knowledge of all of these internals is not necessary, but it doesn't hurt to be familiar either.

Read here to [learn how to use Cordova Plugins](https://cordova.apache.org/docs/en/latest/guide/cli/index.html#add-plugins).

If you still have questions, please use the [issue tracker](https://github.com/3screens/cordova-brother-label-printer/issues). Please look at existing issues, and if your question is not answered yet, feel free to open a new issue and I'm happy to assist.

## Target mobile printers

## Target mobile printers:
```
```plaintext
PocketJet PJ-722, PJ-723, PJ-762, PJ-763, PJ-763MFi, PJ-773
PocketJet PJ-622, PJ-623, PJ-662, PJ-663
PocketJet PJ-520, PJ-522, PJ-523, PJ-560, PJ-562, PJ-563
Expand All @@ -49,49 +54,64 @@ __Tested models:__ `QL-720NW`, `QL-820NWB`

(if you have tried this with other models, please update this list and send a pull request)

## Supported interfaces (by this plugin)

## Supported interfaces (by this plugin):

* Wi-Fi (Infrastructure mode)
* Bluetooth (Android only, at the moment, iOS needs more work. See [PR10](https://github.com/gordol/cordova-brother-label-printer/pull/10)
* USB

- Wi-Fi (Infrastructure mode)
- Bluetooth
- USB

## Usage

See here for JS interfaces to the plugin: `www/printer.js`

There are six available methods...
In general all failure callbacks should expect to receive an `Error` object back. If the `Error` object was spawned from the native side of the plugin it should have two fields that can be checked and compared, `.code`, and `.namespace`. Otherwise, the Error will just contain a message.

| TODO: document, and finalize error messages and error codes.

* [findNetworkPrinters(success, failure)](#findnetworkprinters)
* [findBluetoothPrinters(success, failure)](#findbluetoothprinters)
* [findPrinters(success, failure)](#findprinters)
* [setPrinter(printer, success, failure)](#setprinter)
* [printViaSDK(data, success)](#printviasdk)
* [sendUSBConfig(data, success)](#sendusbconfig)
There are seven available methods...

- [findNetworkPrinters(success, failure)](#findnetworkprinters)
- [pairBluetoothPrinters(success)](#pairBluetoothPrinters)
- [findBluetoothPrinters(success, failure)](#findbluetoothprinters)
- [findPrinters(success, failure)](#findprinters)
- [setPrinter(printer, success, failure)](#setprinter)
- [printViaSDK(data, success, failure)](#printviasdk)
- [sendUSBConfig(data, success, failure)](#sendusbconfig)

### findNetworkPrinters

Upon success, [`findNetworkPrinters`](#findNetworkPrinters) will provide a list of printers that were discovered on the network (likely using WiFi). It is not considered an error for no printers to be found, and in this case the list will just be empty.

```typescript
function findNetworkPrinters(success: (printers: Printer[]) => void, failure: (reason: string) => void): void
function findNetworkPrinters(success: (printers: Printer[]) => void, failure: (reason: Error) => void): void
```

### pairBluetoothPrinters

(Primarily used for iOS). This function behaves differently on iOS than it does on Android. In either case the function returns immediately as there's no guaranteed way to determine if the user has paired a printer or not.

When invoked on iOS, it will spawn a pairing list that will be pre-filtered to only show devices that match the Brother Label Printer's naming.

When invoked on Android, this will take the user to the device's Bluetooth Settings page, so that the user can attempt to pair the printer manually.

```typescript
function pairBluetoothPrinters(success: () => void)
```

### findBluetoothPrinters

Upon success, [`findBluetoothPrinters`](#findBluetoothPrinters) will provide a list of printers that were discovered that have already been paired via Bluetooth. It is not considered an error for no printers to be found, and in this case the list will just be empty.

```typescript
function findBluetoothPrinters(success: (printers: Printer[]) => void, failure: (reason: string) => void): void
function findBluetoothPrinters(success: (printers: Printer[]) => void, failure: (reason: Error) => void): void
```

### findPrinters

[`findPrinters`](#findPrinters) is a convenience function that will perform the actions of both [`findNetworkPrinters`](#findNetworkPrinters) and [`findBluetoothPrinters`](#findBluetoothPrinters), and combine the the results into a single continuous list.

```typescript
function findPrinters(success: (printers: Printer[]) => void, failure: (reason: string) => void): void
function findPrinters(success: (printers: Printer[]) => void, failure: (reason: Error) => void): void
```

### setPrinter
Expand All @@ -100,7 +120,7 @@ must be called before [`printViaSDK`](#printViaSDK). It takes a single object th
will be invoked. Otherwise, the error callback will be invoked with a string for an error message.

```typescript
function setPrinter(printer: Printer, success: () => void, failure: (reason: string) => void): void
function setPrinter(printer: Printer, success: () => void, failure: (reason: Error) => void): void
```

### printViaSDK
Expand All @@ -110,19 +130,18 @@ takes one parameter, which is a base64 encoded bitmap image. The result should b
__Clarification__:
> A bitmap image in this case can be any image with an encoding that is supported by the platform.


```typescript
function printViaSDK(data: string, success: () => void): void
function printViaSDK(data: string, success: () => void, failure: (reason: Error) => void): void
```

### sendUSBConfig

calls the Brother SDK's `printFile` method. The expected input is a string containing raw print commands, which is written to a temporary file in the app cache directory, and is then sent to the `printFile` method and deleted afterwards. You will need a device that supports USB-OTG and a USB-OTG cable. On first run the app will request USB permissions, and it should be saved after that for subsequent prints. As-is, this method is used to send raw commands in PCL (Printer Control Language) to the printer... For example, to configure the network settings of the printer, etc... You will need to reach out to Brother for documentation of the PCL commands. You can probably find them by searching for "[Brother Printer Command Reference](https://duckduckgo.com/?q=Brother+Printer+Command+Reference)" and appending your model number. This method could be extended easily to accept other types of file input, so you could, for example, print JPG images, etc... See here for a simple way to generate a PJL file to reconfigure the network: https://github.com/gordol/PJL-Generator


```typescript
function sendUSBConfig(data: string, success: () => void): void
function sendUSBConfig(data: string, success: () => void, failure: (reason: Error) => void): void
```

### Interface Reference

```typescript
Expand Down
48 changes: 41 additions & 7 deletions src/android/BrotherPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
Expand All @@ -36,6 +37,7 @@
import android.graphics.BitmapFactory;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.telecom.Call;
import android.util.Base64;
import android.util.Log;
Expand All @@ -55,8 +57,8 @@

public class BrotherPrinter extends CordovaPlugin {
private static PrinterInfo.Model[] supportedModels = {
PrinterInfo.Model.QL_720NW,
PrinterInfo.Model.QL_820NWB,
PrinterInfo.Model.QL_720NW,
PrinterInfo.Model.QL_820NWB,
};

private MsgHandle mHandle;
Expand Down Expand Up @@ -93,6 +95,11 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
@Override
public boolean execute (String action, JSONArray args, CallbackContext callbackContext) throws JSONException {

if ("pairBluetoothPrinters".equals(action)) {
pairBluetoothPrinters(callbackContext);
return true;
}

if ("findNetworkPrinters".equals(action)) {
findNetworkPrinters(callbackContext);
return true;
Expand Down Expand Up @@ -290,6 +297,20 @@ private void sendDiscoveredPrinters(final CallbackContext callbackctx, List<Disc
callbackctx.sendPluginResult(result);
}

private static final int OPEN_BLUETOOTH_SETTINGS_REQUEST = 0x2345;
private void pairBluetoothPrinters(final CallbackContext callbackctx) {
cordova.getThreadPool().execute(new Runnable() {
@Override
public void run() {
final Intent openPairSection = new Intent();
openPairSection.setAction(Settings.ACTION_BLUETOOTH_SETTINGS);
cordova.startActivityForResult(BrotherPrinter.this, openPairSection, OPEN_BLUETOOTH_SETTINGS_REQUEST);

callbackctx.success();
}
});
}

Copy link
Owner

@gordol gordol Sep 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use BluetoothManager.getAdapter() to get BluetoothAdapter along with BluetoothDevice. Listen for: ACTION_DISCOVERY_STARTED, ACTION_DISCOVERY_FINISHED, and ACTION_FOUND. Then call BluetoothAdapter.startDiscovery().

Then when you find a brother device via ACTION_FOUND, simply call: BluetoothAdapter.getRemoteDevice() to get a BluetoothDevice. Then on the device returned from getRemoteDevice(), issue a createBond() call: BluetoothDevice.createBond().

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively... this may provide some insight too, though probably overkill for what we need here https://github.com/tanelih/phonegap-bluetooth-plugin this is an android-only plugin for old phonegap, but the android bits should be usable.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can make this better on android with a new ticket though... if you want to stick to IOS here. lemme know...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably use BluetoothManager instead of BluetoothAdapter without much issue. It seems like the code would operate roughly the same without it.

As far as the .startDiscovery() call, that would seem to imply you'd like to do the pairing within the plugin itself instead of externally. Is this what you're intending? The only thing that really stopped me from doing that before is that I don't know of any UI we can display to the user that's similar to iOS's. As such, we'd either have to create that UI, or forward the options through the plugin. I didn't like either option really, because the first forces a UI we design on the developer, and the second means that there's different behavior between the plugins.

Unless you're saying you just want to find the first printer you can find and try to bond with it immediately. The only problem with that is when there are multiple printers and you only want to pair with a specific one.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'd like to do the pairing within the plugin itself

ideally, yes... the plugin should encapsulate all of the steps to provision a printer. that's why it's possible to send USB commands. for example, to set the printer's network settings. i'd like to support the same sort of functionality for bluetooth printing too, if possible.

however, that would mean that it should be done similarly on the IOS side too... and that may be problematic.

i think CoreBluetooth should do what we need, on the IOS side, to get a list of printers and enumerate the services, pair, etc? here's a nice wrapper for it: https://github.com/LGBluetooth/LGBluetooth but i'm not 100% sure if it's possible to pair with "legacy" bluetooth devices programmatically on IOS. are these printers using BLE? if so, wonderful, if not... damnit!

The only thing that really stopped me from doing that before is that I don't know of any UI we can display to the user

well, we don't need a UI, necessarily. this is just a cordova plugin. it's exposing an api to app developers to do with as they please. we can provide an example implementation with a UI for test or example purposes, but that's ultimately a burden that falls on the end user of the plugin, to implement as needed, for their specific purposes.

ideally we just spit back a list of compatible printers, and then the implementor can show that list to a user and let them pick the one they want, or their app code can pick one automatically based on whatever criteria they want. that's outside the scope of this plugin, i think. there should be no UI here, just API.

this sort of workflow is already there for network printing. you ask for a list of printers, then pick one. ideally we can do this on both android and ios for bluetooth printing as well. but, i do understand if it's not possible on IOS. i'm sure there are "ways" such as using private frameworks, and for enterprise usage, that's fine, but you can't use private frameworks if you're pushing to the app store. for my particular use cases, app store compatibility is irrelevant, but i don't know about everyone else.

that being said...

PrivateFrameworks.BluetoothManager has the following methods: scanForConnectableDevices() and connectDevice().

maybe we can add a config.xml flag to include those methods into the ios side of the plugin, optionally, depending whether they want to fully automate, with the caveat that it's only for enterprise or private use, not for deploying to the app store.

thoughts?

private void findNetworkPrinters(final CallbackContext callbackctx) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
Expand Down Expand Up @@ -342,7 +363,7 @@ private void setPrinter(JSONArray args, final CallbackContext callbackctx) {
callbackctx.sendPluginResult(result);
} catch (JSONException e) {
e.printStackTrace();
PluginResult result = new PluginResult(PluginResult.Status.ERROR, "An error occurred while trying to set the printer.");
PluginResult result = pluginErrorResult("setPrinter", 2, "an error occurred while trying to set the printer: " + e.getLocalizedMessage());
callbackctx.sendPluginResult(result);
}
}
Expand All @@ -365,15 +386,15 @@ private void printViaSDK(final JSONArray args, final CallbackContext callbackctx

String port = sharedPreferences.getString("port", "");
if ("".equals(port)) {
PluginResult result = new PluginResult(PluginResult.Status.ERROR, "No printer has been set.");
PluginResult result = pluginErrorResult("printViaSDK", 4, "no printer has been set.");
callbackctx.sendPluginResult(result);
return;
}

if (PrinterInfo.Port.BLUETOOTH.toString().equals(port)) {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
PluginResult result = new PluginResult(PluginResult.Status.ERROR, "This device does not have a bluetooth adapter.");
PluginResult result = pluginErrorResult("printViaSDK", 3, "this device does not have a bluetooth adapter.");
callbackctx.sendPluginResult(result);
return;
}
Expand All @@ -394,13 +415,13 @@ private void printViaSDK(final JSONArray args, final CallbackContext callbackctx
bitmap = bmpFromBase64(encodedImg);
} catch (JSONException e) {
e.printStackTrace();
PluginResult result = new PluginResult(PluginResult.Status.ERROR, "An error occurred while trying to retrieve the image passed in.");
PluginResult result = pluginErrorResult("printViaSDK", 1, "an error occurred while trying to retrieve the image passed in: " + e.getLocalizedMessage());
callbackctx.sendPluginResult(result);
return;
}

if (bitmap == null) {
PluginResult result = new PluginResult(PluginResult.Status.ERROR, "The passed in data did not seem to be a decodable image. Please ensure it is a base64 encoded string of a supported Android format");
PluginResult result = pluginErrorResult("printViaSDK", 1, "the passed in data did not seemd to be a decoable image. Please ensure it is a base64 encoded stirng of a supported Android format.");
callbackctx.sendPluginResult(result);
return;
}
Expand Down Expand Up @@ -514,4 +535,17 @@ public void onReceive(Context context, Intent intent) {
});
}

private PluginResult pluginErrorResult(String namespace, int code, String message) {
JSONObject result = new JSONObject();
try {
result.put("namespace", namespace);
result.put("code", code);
result.put("message", message);
} catch (JSONException e) {
Log.d(TAG, "unable to encode error as json object: ", e);
}

return new PluginResult(PluginResult.Status.ERROR, result);
}

}
19 changes: 16 additions & 3 deletions src/android/MsgHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public void handleMessage(Message msg) {
break;
}

final PluginResult cancelResult = new PluginResult(PluginResult.Status.ERROR, "Cancelled");
final PluginResult cancelResult = pluginErrorResult("printCancelled", 1, "cancelled.");
mCallback.sendPluginResult(cancelResult);
mCallback = null;
break;
Expand All @@ -222,7 +222,7 @@ public void handleMessage(Message msg) {
break;
}

final PluginResult wrongOSResult = new PluginResult(PluginResult.Status.ERROR, "Android OS is not supported");
final PluginResult wrongOSResult = pluginErrorResult("wrongOS", 1, "Android OS is not supported");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean the android version is not supported? obviously android operating system is supported, haha

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this error is that a specific version of the Android OS is not supported. It's an error that can be returned from the Library itself, so I just made it forward the error in this case.

mCallback.sendPluginResult(wrongOSResult);
mCallback = null;
break;
Expand All @@ -231,12 +231,25 @@ public void handleMessage(Message msg) {
break;
}

final PluginResult noUSBResult = new PluginResult(PluginResult.Status.ERROR, "USB device is not found");
final PluginResult noUSBResult = pluginErrorResult("noUSB", 1, "USB device is not found.");
mCallback.sendPluginResult(noUSBResult);
mCallback = null;
break;
default:
break;
}
}

private PluginResult pluginErrorResult(String namespace, int code, String message) {
JSONObject result = new JSONObject();
try {
result.put("namespace", namespace);
result.put("code", code);
result.put("message", message);
} catch (JSONException e) {
Log.d(TAG, "unable to encode error as json object: ", e);
}

return new PluginResult(PluginResult.Status.ERROR, result);
}
}
1 change: 1 addition & 0 deletions src/ios/BrotherPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@interface BrotherPrinter : CDVPlugin<BRPtouchNetworkDelegate>
@property (retain, atomic) NSOperationQueue *operationQueue;

-(void)pairBluetoothPrinters:(CDVInvokedUrlCommand*)command;
-(void)findNetworkPrinters:(CDVInvokedUrlCommand*)command;
-(void)findBluetoothPrinters:(CDVInvokedUrlCommand*)command;
-(void)findPrinters:(CDVInvokedUrlCommand*)command;
Expand Down
Loading