Cordova Bluetooth Plugin for the Kundalini Piano Mirror

<this is a draft article, and is not complete>

Introduction

This post provides some implementation notes on the Cordova Plugin I wrote to interface with the Kundalini Piano Mirror. Complete source code for the plugin is available here:

https://github.com/BenjaminPritchard/cordova-plugin-kundalini-piano-bt

The Piano Mirror is a Raspberri-PI based device I created which interfaces to a digital piano via MIDI. The point of the device is to perform transpositions which help pianists develop their technique.

Another component of the Piano Mirror is a Tablet-Optimized Cordova-based app which displays scores transposed various ways for use by pianists as they work with the Piano Mirror.

The point of this Cordova plugin is so that the Tablet-Score viewer App can send commands to the embedded system via Bluetooth, despite the fact that most of the app is implemented in a webview.

Note on Bluetooth

The Kundalini Piano Mirror is still in development, and currently only supports an interface using “classic” bluetooth via RFCOMM. One implication of this is that IOS cannot interface to this device.

Interfacing between Native Java Code and Javascript in a Webview

Before we dive into how to make this all work using a Cordova plugin, it first helps to understand how to do it without Cordova.

(The reason I say this is because the Cordova build system is an abstraction OVER TOP of the native build system of Android (and IOS, et. al.). Additionally, the CordovaWebView is basically just a standardized way to interface Native Code (i.e. in either ObjectiveC or Java) to JavaScript. So to really ally “get” what Cordova is all about, it helps to think through what it is doing under the hood.)

Executing Javascript in a Webview from Native code is pretty simple, and is accomplished using the Webview’s evaluateJavascript() function.

However, we just have to remember to explicitly enable JavaScript because by default the Webview won’t execute it at all:

myWebView.getSettings();
webSettings.setJavaScriptEnabled(true); 

Once this is accomplished, arbitary JS code can be executed from Java with a call similar to the following:

String javaScriptText = "alert('a');";
webView.evaluateJavascript(javaScriptText)

Conversely, it is also sometimes necessary to execute native java code from Javascript within a Webview. Doing this is also pretty easy, and all we have to do is to use the Webview’s addJavascriptInterface() method.

class JsObject {
    @JavascriptInterface
    public String toString() { return "injectedObject"; }
 }
 webView.addJavascriptInterface(new JsObject(), "injectedObject");

which can then be called from Javascript like so:

alert(injectedObject.toString())

Enter Cordova

So as I mentioned before, Cordova sort of consists of several parts: a a cross-platform build system, an enhanced Webview that deals with threading and marshalling between Javascript and Native Code, and some “standard plugins” (i.e. libraries) which expose native-type functionality (such as the accelerometer or the camera) to javascript code.

So for right now, let’s look at how Cordova handles calling native code from Javascript.

The first question is, what code is the javascript going to call? To answer this, we just have to remember that a Cordova Android “plug-in” is just a Java Class that extends the Cordova-supplied base class called CordovaPlugin:

public class PianoMirrorBTProtocolCordova extends CordovaPlugin {

    public static Context thisContext;
    private PianoMirrorBTProtocol PianoMirror;

    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        thisContext = cordova.getActivity().getApplicationContext();
        PianoMirror = new PianoMirrorBTProtocol(thisContext);
    }

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {

        if (action.equals("connect")) {
            PianoMirror.connect();
            callbackContext.success();
            return true;
        }

        if (action.equals("setMode")) {
            PianoMirror.sendSendModeChangeCommand(Integer.parseInt(args.get(0).toString()));
            callbackContext.success();
            return true;
        }

        return false;
    }

As you can see, the only thing we have to over ride is the execute() method, which is like a central dispatch routine that gets called from our Javascript.

The question now is why is Cordova possibly structured like this? In other words, why does Cordova force us to pass in strings representing the methods to call, and their parameters. (Just think how much more elegant the Google-supplied native way to do this is with addJavascriptInterface())

The answer to this is the cross platform nature of Cordova. Remember, the whole idea is that the same Javascript code can be used on multiple platforms, with only additional plugin modules to be supplied for any subsequent platforms. (As I said, in part two of this article, I will show how to create

Setting up a Cordova Project

To create a new Cordova project, just find a nice place on your drive and execute the commands:

cordova create BluetoothPianoMirrorInterface
cordova cordova platform add android

Note: if you get an error message when invoking Cordova that says something like: “wmic is not recognized as an internal or external command“, make sure that the directory C:\Windows\System32\wbem is in your path. I just figured this out through trial and error; I have no idea why Cordova is doing anything with Windows Management Instrumentation, and I don’t care. So just add that directory to your path and move on:

path=%PATH%;c:\windows\system32\wbem

Adding the Native Java Code

When developing a Cordova Plugin to deal with external hardware, it makes a lot of sense to develop

Dealing with Android Permissions

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION">

To work with Bluetooth connected devices on Android, several permissions must be declared in the AndroidManifest.xml file:

The first permission (BLUETOOTH_ADMIN) is really only needed to initiate device discovery (i.e. if you are going to call startDiscovery(), which in our example we don’t do.) However — just for the sake of completeness — I will include it here, because in more advanced usage-scenarios, device discovery is probably necessary.

The second permission (BLUETOOTH) is the main bluetooth permission, and is required to initiate any bluetooth communication at all.

Finally, ACCESS_FINE_LOCATION is required because Bluetooth can potentially return the user’s current location.

One more step that is required when setting up a build using Cordova’s CLI to perform platform-independent builds is to list the permissions in Cordova’s top-level configuration file, which is called config.xml:

<config-file target="AndroidManifest.xml" parent="/*">
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</config-file>

Finally, one additional consideration is the fact that recent versions of Android (6.0 and above) introduced a new permissions model, whereby certain permissions must be explicity granted by the user at run time.

Of the permissions we need, only ACCESS_FINE_LOCATION requires such treatment…. which when using Cordova, ends up requiring code like this:

if !(cordova.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) cordova.requestPermission(this, 0, Manifest.permission.ACCESS_FINE_LOCATION)

somewhere in the execute() method in the cordova plugin class.

Appendix 1: Understanding the JAVA Bluetooth Code

Take a look at

Conclusion

Next Steps

  • Learn more about the Kundalini Piano Mirror
  • Download the Source code for this project on GitHub