2

Can someone please provide example code on how to use the Bluetooth cn1 library to read and write text data? I tried looking through the project source code (https://github.com/chen-fishbein/bluetoothle-codenameone) for example code, but could not find any that reads/writes text data using the appropriate methods. Those methods also don't have any Java docs.

Nathan
  • 41
  • 3
  • I'm guessing you are interested in file transfer but bluetooth doesn't exactly work that way. Once you connect to a device and form a protocol with it you can exchange data, I'm guessing you will need a serial connection but not a bluetooth expert myself this depends on the device... Notice you have read/write methods in the API and you can detect whether a specific protocol is supported. – Shai Almog Oct 25 '17 at 03:55
  • I actually just need to send text commands and read response from a BLE UART adapter (https://www.adafruit.com/product/2267). The challenge I have is figuring out what parameters to pass for String service, String characteristic? – Nathan Oct 25 '17 at 15:59
  • It's a fork of this plugin which goes into more detail of the various BT options and their values: https://github.com/randdusing/cordova-plugin-bluetoothle – Shai Almog Oct 26 '17 at 03:49

2 Answers2

1

Here's the code I'm using to send:

public void sendMessage(String message) {
    if (Display.getInstance().isSimulator()) {
        System.out.println(message);
    } else {
        // System.out.println("Sending message: " + message);
        String b64WriteString = Base64.encode(message.getBytes());
        try {
            bt.write(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent evt) {

                }
            }, deviceAddress, services.get(0), sendDataCharacteristic, b64WriteString, false);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

And here to receive:

private void registerNotifications() {
    System.out.print("Registering notifications...");
    try {
        bt.subscribe(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                JSONObject dataIncoming = (JSONObject) evt.getSource();
                String base64Value = "";
                try {
                    if (dataIncoming.getString("status").equals("subscribedResult")) {
                        base64Value = dataIncoming.getString("value");
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                String message = new String(Base64.decode(base64Value.getBytes()));
                Display.getInstance().callSerially(new Runnable() {

                    @Override
                    public void run() {
                        messageReceived.set(message);
                    }
                });
            }
        }, deviceAddress, services.get(0), receiveDataCharacteristic);
        System.out.println("Registered");
        System.out.println("Starting communication");
    } catch (IOException e) {
        System.err.println("Unable to register notifications " + e.getMessage());
        e.printStackTrace();
    }
}

I have fields defined for the service and characteristic UUID's. Also, the callSerially might not be needed anymore. I think I recall that the CN1LIB was updated to do that, but I don't remember for certain.

For that device, the service characteristic is "6E400001-B5A3-F393-­E0A9-­E50E24DCCA9E"

The sendCharacteristic is "0x0002"

The receiveCharacteristic is "0x0003"

James H
  • 1,116
  • 1
  • 6
  • 11
  • Having to use base64 encoding for string is something I didn't realize. But the issue I am having seems to be with "Service not found" even though I am using the values from the example for this device (https://github.com/don/cordova-plugin-ble-central/blob/master/examples/bluefruitle/www/js/index.js). Do those also have to be encoded as well? – Nathan Oct 26 '17 at 14:25
  • No encoding on the UUIDs. I'd recommend installing NRF toolbox app. With that, you should be able to see what services the device is advertising. – James H Oct 26 '17 at 18:13
0

After taking the suggestions of James H, and some more trial an error, I finally manage to get data transfer between the Adafruit's Bluefruit LE Friend working consistently, at least on an Android device. Not sure about iOS though, since I haven't tested it. Here are the critical code pieces needed.

First, you need the Service, TX and RX Characteristics UUIDs. These UUIDs were found here. Note, these don't need to be upper case.

public static final String UUID_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
public static final String UUID_RX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
public static final String UUID_TX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";

Next, once you scanned and found the devices, call the connect() method to make the actual connection, and critically call the discover() method. Once the discover() callback gets called, then add the "subscriber" to receive data.

private void connect(String address) {
    bleAddress = address; // set the global BLE address

    if (!connected) {
        // start an infinite progress dialog
        final Dialog ip = new InfiniteProgress().showInifiniteBlocking();

        try {
            bt.connect(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent evt) {
                    ip.dispose();

                    Object obj = evt.getSource();
                    print("Connected to Bluetooth LE device ...\n" + obj, true);
                    // must be called on Android devices in order to load on the UUIDs, otherwise there is an error that service can't be found. Won't do anything on ios though?
                    discover(); 

                    connected = true;
                }

            }, address);
        } catch (IOException ex) {
            ip.dispose();

            String message = "Error connecting to bluetooth device: " + address;
            print(message + "\n" + ex.getMessage(), false);
        }
    } else {
        String message = "BLE device already connected to: " + address;
        print(message, false);
    }
}

private void discover() {
    try {
        bt.discover(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                print("BLE Information Received ...", true);
                addSubscriber();
            }

        }, bleAddress);

    } catch (Exception ex) {
        print(ex.getMessage(), false);
    }

    // if we running on is add the subscriber here since the above bt call
    // does nothing?
    if (Display.getInstance().getPlatformName().equals("ios")) {
        print("Adding subscriber for iOS Device", true);
        addSubscriber();
    }
}

private void addSubscriber() {
    try {
        bt.subscribe(new ActionListener() {
            StringBuilder sb = new StringBuilder();

            @Override
            public void actionPerformed(ActionEvent evt) {
                JSONObject dataIncoming = (JSONObject) evt.getSource();
                String base64Value = "";
                try {
                    if (dataIncoming.getString("status").equals("subscribedResult")) {
                        base64Value = dataIncoming.getString("value");
                    }
                } catch (JSONException e) {
                    console.setText("Error reading data: " + e.getMessage());
                }

                String message = new String(Base64.decode(base64Value.getBytes()));
                sb.append(message);

                if (message.endsWith("\r\n")) {
                    processData(sb.toString());
                    sb = new StringBuilder();
                }
            }

        }, bleAddress, UUID_SERVICE, UUID_RX);

        String message = console.getText() + "\nSubcriber added ...";
        console.setText(message);
    } catch (IOException ex) {
        String message = "Error Subscribing: " + ex.getMessage();
        console.setText(message);
    }
}

So this sets up the connection, discovers the services, and finally adds the subscriber method which receives the data, and critically uses a buffer to collect the received data until the CRLF characters are received.

However, another major issue I ran into was the default 23 byte send limit (maybe an Android only issue?) of the BLE specification. If you tried sending more than this, the connection just gets dropped with no meaningful error message being returned. To get around this, I used the technique suggested here, which entails splitting data into chunks of 20 byte arrays. Since we sending regular ASCII text, then 20 characters should be 20 bytes, so I just split the text into Strings 20 characters long. Not the most efficient by it works and it easier to debug.

private void sendText(final String data) {
    try {
        String b64String = Base64.encode(data.getBytes());

        bt.write(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                if(data.endsWith("\r\n")) {
                    print("Data sent ...", true);
                }
            }

        }, bleAddress, UUID_SERVICE, UUID_TX, b64String, false);
    } catch (IOException ex) {
        String message = "Error sending: " + data + "\n"
                + UUID_SERVICE + "\n"
                + UUID_TX + "\n"
                + ex.getMessage();
        print(message, false);
    }
}

private void splitAndSend(String text) {
    text += "\r\n";

    // first split data in chunk size of 20 chracters
    ArrayList<String> sl = new ArrayList<>();

    char[] data = text.toCharArray();       
    int len = data.length;
    int chunkSize = 20;

    for (int i=0; i < len; i+= chunkSize) {
        sl.add(new String(data, i, Math.min(chunkSize,len - i)));
    }

    // now send chunks amd wait 100 ms to prevent any erros
    for(String word: sl) {
        sendText(word);
        try {
            Thread.sleep(100);
        } catch (InterruptedException ex) {}
    }
}

The complete source code with GUI stuff can be found here, but this is definitely a work in progress.

Nathan
  • 41
  • 3
  • A couple of other things I’ve found: you definitely want to disconnect and then close the BT connection before exiting the app. Also, Android seems to take 15 seconds to realize a disconnect has happened (especially if the remote device disconnects). If you try to connect again during that time, the whole BLE stack can get jammed up. I put a 20 second delay in before attempting to reconnect to avoid this. Oh, and I always use Connect and not Reconnect as the latter did not work well on Android for me. – James H Nov 02 '17 at 03:50