LazyBLE Wrapper (created a library that makes Android BLE super simple) v0.14

Overview

Aside from those who usually develop apps, Bluetooth Low Energy is used for electronic work etc. For those who say "I want to bite a little" (mainly me), Android's BLE API feels a little difficult to use.

I thought it was a difficult reason to do various things, so I developed it with the aim of making the library as simple and easy to use as possible.

... Why did you make such a library?

What kind of library?

All asynchronous processing related to BLE of Android API has been changed to ** synchronous processing. ** ** Originally I should have used promises, but I prioritized simplicity.

So, for example, if you want to communicate with the BBC micro: bit, you can do it with the following code. (Actually, you need to run it in a Thread separate from the UI.)

    private LazyBLEWrapper ble = new LazyBLEWrapper();

    private final String LEDServiceUUID            = "E95DD91D-251D-470A-A062-FA1922DFA9A8";
    private final String LEDTextCharacteristicUUID = "E95D93EE-251D-470A-A062-FA1922DFA9A8";
    private final String ButtonServiceUUID         = "E95D9882-251D-470A-A062-FA1922DFA9A8";
    private final String ButtonACharacteristicUUID = "E95DDA90-251D-470A-A062-FA1922DFA9A8";
    private final String UARTServiceUUID           = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
    private final String UARTTxCharacteristicUUID  = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
    private final String UARTRxCharacteristicUUID  = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
    private final int timeout = 30*1000;

    //scan. It is a prefix match
    BluetoothDevice device = ble.scanDevice(this,"BBC micro:bit", timeout);
    //Connect
    ble.connect(this,device, timeout);

    //Callback when disconnected(Any)
    ble.setDisconnectCallback(new LazyBLEWrapper.DisconnectCallback() {
        @Override
        public void onDisconnect()  {
            Log.e(TAG,"Disconnected!");
        }
    });

    //To LED"Hello World"And write
    ble.writeData("Hello World",LEDServiceUUID,LEDTextCharacteristicUUID, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,timeout);
    //Acquires and displays the button press status
    Integer dat = ble.readDataInt(ButtonServiceUUID,ButtonACharacteristicUUID,BluetoothGattCharacteristic.FORMAT_UINT8,0,timeout);
    Log.d(TAG, dat.toString());

    //Set Notification
    ble.setNotify(ButtonServiceUUID,ButtonACharacteristicUUID,true,timeout);
    //Set Indication
    ble.setIndicate(UARTServiceUUID,UARTTxCharacteristicUUID,true,timeout);
    //Set Callback. This is executed by UIThread.
    ble.setNotificationCallback(new LazyBLEWrapper.NotificationCallback() {
        @Override
        public void onNotification(BluetoothGattCharacteristic characteristic) {
            if(ble.isMatchCharacteristicUUID(characteristic,ButtonACharacteristicUUID)) {
                Integer ButtonA = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
                Log.d(TAG, ButtonA.toString());
            }
            if(ble.isMatchCharacteristicUUID(characteristic,UARTTxCharacteristicUUID)) {
                Log.d(TAG, characteristic.getStringValue(0));
            }
        }
    });

    //UART transmission
    ble.writeData("Hello UART\n",UARTServiceUUID,UARTRxCharacteristicUUID,BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,timeout);
    //Disconnect
    ble.disconnect(timeout);
    //ble.forceDisconnect();

If you want to use deep functions, you can use the standard API in the first place, so narrow down the functions I implemented it simply focusing on ** frequently used functions **.

So the use case is

Only.

Since it is a synchronous method, you can write it in a simple flow. (Though threads are required) Since it is not asynchronous, all methods with latency have a timeout specification.

The process of acquiring detailed location information authority required when using BLE on Android 6.0 or higher is also included as a bonus.

Characteristic operations (setValue, getValue, etc.) at the time of Notification are not included, so It uses standard Android API methods.

Basically, it is designed to return the Android BLE API object as it is, so If you want to do small things, you can use the API in that area.

download

Download here For simplicity, Android 5.0 or later is not supported. The license is a zlib license.

We have confirmed the operation only on Nexus 5X (Android 8.0).

Method list

Terminal settings / permission

Terminal settings and runtime permissions are also provided for simple implementation. It's okay to run these methods in a UI thread. (It will be completed without waiting time.)

v0.13: You no longer keep the Context internally, so you need to pass the context or activity.

//Check if the BLE function of the terminal is enabled. Before communication etc.
boolean isBluetoothFeatureEnabled() 

//Make a request to enable the Bluetooth function of the terminal.
void requestTurnOnBlueTooth(AppCompatActivity activity)

//Check if the terminal supports the BLE function.
boolean isBluetoothFeatureSupported(Context context) 

//Check if you have detailed location permissions.
//Required for BLE scan.
boolean isPermitted(Context context) 

//Request detailed location permissions.
//Originally, it is done by Activity and is not recommended, but for the time being, it can be used when it is troublesome such as when making a prototype.
//Originally, it is necessary to receive the intent and process it, but it works without it, so it is not implemented. Please implement on the Activity side.
void permissionRequest(AppCompatActivity activity)

scan

For the time being, I narrowed it down to the following three types, which I wonder if this is all I need.

v0.13: You no longer keep the Context internally, so you need to pass the context or activity. Also, it has been changed so that the UI thread internally calls BLE-related processing.

//Scan the surrounding peripherals.
//Continue scanning until timed out, ArrayList in order of discovery<BluetoothDevice>Store in and return.
//If not found, returns 0 lists.
ArrayList<BluetoothDevice> scanDevice(Context context, int timeOut) throws IOException 

//Scan the surrounding peripherals.
//Checks the device name with a prefix match and returns the first matching device immediately.
//If not found, raise an exception.
BluetoothDevice scanDevice(Context context, String deviceName, int timeOut) throws IOException 

//Terminal bonding(Pairing)Search from the list of completed peripherals.
//Checks the device name with a prefix match and returns the first matching device immediately.
//If not found, it returns null.
BluetoothDevice scanBondedDevice(String deviceName)

Disconnection

It seems to be troublesome to handle GATT objects (many callbacks), so I held it. Therefore, it is simple and has no return value.

v0.13: You no longer keep the Context internally, so you need to pass the context or activity. Also, it has been changed so that the UI thread internally calls BLE-related processing. The disconnection process is now more polite and there is a waiting time. For disconnection at the end of the application, we recommend force Disconnect without waiting time. Only one GATT connection can be held internally per instance. v0.14: Now throws an exception when trying to connect when connected.

//Connect to Device and perform a service scan. GATT objects are kept internally.
//(To perform cutting processing etc. automatically)
//Raises an exception if the connection fails.
void connect(Context context, BluetoothDevice device, int timeOut) throws IOException 

//Disconnect from Device.
void disconnect(int timeOut)

//Forcibly disconnect from Device(Disconnection process in the old version)
//This can be done in a UI thread
void forceDisconnect()

Basic characteristic reading / writing / notification

If it's just characteristic reading and writing, it has been improved in v0.11 to make it simpler. Search for services, search for characteristics, assign values, etc. in one line.

It's a lot of reading, but I matched it with the Android API. All available formats are supported. On the other hand, for writing, float was omitted. Because it was a style that seemed a little difficult to use The decision is that there are not many opportunities to write floating point numbers.

//**********reading***********

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//The latest data is obtained from the peripheral, interpreted with the specified format / offset, and returned as an integer type.
//Raises an exception when communication fails.
int readDataInt(String ServiceUUID,String CharacteristicUUID,int formatType,int offset,int timeOut) throws IOException 

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//The latest data is obtained from the peripheral, interpreted with the specified format / offset, and returned as a floating point type.
//Raises an exception when communication fails.
float readDataFloat(String ServiceUUID,String CharacteristicUUID,int formatType,int offset,int timeOut) throws IOException 

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//Get the latest data from the peripheral and return it as a byte string.
//Raises an exception when communication fails.
byte[] readDataValue(String ServiceUUID,String CharacteristicUUID,int timeOut) throws IOException 

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//Get the latest data from the peripheral, interpret it at the specified offset and return it as a string.
//Raises an exception when communication fails.
String readDataString(String ServiceUUID,String CharacteristicUUID,int offset,int timeOut) throws IOException 


//**********writing***********

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//Set the specified character string as the content to the peripheral with the specified writing method.
//Raises an exception when communication fails.
void writeData(String string,String ServiceUUID,String CharacteristicUUID,int writeType,int timeOut) throws IOException 

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//Set the specified byte string as the content to the peripheral with the specified writing method.
//Raises an exception when communication fails.
void writeData(byte[] value,String ServiceUUID,String CharacteristicUUID,int writeType,int timeOut) throws IOException 

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//Set the specified integer as the content to the peripheral with the specified writing method.
//Raises an exception when communication fails.
void writeData(int num,int formatType,int offset,String ServiceUUID,String CharacteristicUUID,int writeType,int timeOut) throws IOException 

The formatType is as follows.

BluetoothGattCharacteristic.FORMAT_FLOAT  //(32-bit float)
BluetoothGattCharacteristic.FORMAT_SFLOAT //(16-bit float)
BluetoothGattCharacteristic.FORMAT_SINT16
BluetoothGattCharacteristic.FORMAT_SINT32
BluetoothGattCharacteristic.FORMAT_SINT8
BluetoothGattCharacteristic.FORMAT_UINT16
BluetoothGattCharacteristic.FORMAT_UINT32
BluetoothGattCharacteristic.FORMAT_UINT8

The writeType is as follows.

BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
BluetoothGattCharacteristic.WRITE_TYPE_SIGNED

Notification / Indication

Since the notification settings are also standard, we have made it possible to write them in abbreviated form. Since setNotify is the setting on the peripheral side and setCallback is the setting on the Android side, both are required.

However, only one callback can be registered with setCallback, and the characteristic is internally created. Since it is a form of checking and branching, setCallback only needs to be done once at the beginning of the application.

From v0.12, Indication is supported.

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//Enables / disables the notification setting on the peripheral side for that characteristic.
//Raises an exception when communication fails.
void setNotify(String ServiceUUID,String CharacteristicUUID,boolean enable,int timeOut) throws IOException 

//Get the service characteristic from the specified service UUID string, characteristic UUID string,
//Enable / disable the Indication setting on the peripheral side for that characteristic.
//Raises an exception when communication fails.
void setIndicate(String ServiceUUID,String CharacteristicUUID,boolean enable,int timeOut) throws IOException

Callback

v0.14: Please set null to cancel the callback.

//Set the callback when a notification is received from the peripheral.
void setNotificationCallback(final NotificationCallback callback)

//Callback when disconnected
void setDisconnectCallback(final DisconnectCallback callback){

//Check if the characteristic UUID and string UUID are located
//Ture if they match, false otherwise
//It can be used to identify the characteristic sent by Notification or Indication.
boolean isMatchCharacteristicUUID(BluetoothGattCharacteristic characteristic,String CharacteristicUUID)

//Callback interface for notifications
interface NotificationCallback {
    void onNotification(BluetoothGattCharacteristic characteristic);
}

//Callback interface on disconnect
interface DisconnectCallback {
    void onDisconnect();
}

Applied characteristic reading / writing / notification

This is the characteristic operation provided by v0.10. You can get the raw characteristic, so please use this if you want to perform detailed operations.

If you just read and write, grab the service, then the characteristic, It's a little complicated because you need at least 4 lines in the order of operation and issuance.

v0.13: Now runs internally in the UI thread. Enhanced null checking.

//Get the service from the UUID string using the searched service information.
//If not found, it returns null.
BluetoothGattService getService(String serviceUUID) 

//Use the searched service information to get the characteristic from the UUID string.
//BluetoothGattService acquired by getService is required to determine the characteristic with the same UUID.
//If not found, it returns null.
BluetoothGattCharacteristic getCharacteristicInService(BluetoothGattService service, String CharacteristicUUID) 

//Reflect the contents of the current characteristic in the peripheral.
//Raises an exception when communication fails.
void writeCharacteristic(BluetoothGattCharacteristic characteristic, int timeOut) throws IOException 

//Gets the latest characteristic content from the peripheral and returns it.
//(At this time, the original characteristic is probably unchanged.)
//Raises an exception when communication fails.
BluetoothGattCharacteristic readCharacteristic(BluetoothGattCharacteristic characteristic, int timeOut) throws IOException 

Descriptor operation

Descriptor operation. Used internally for Notification settings. Note that I haven't tested much.

v0.13: Now runs internally in the UI thread. Enhanced null checking.

//Write the descriptor to the peripheral.
//Raises an exception when communication fails.
void writeDescriptor(BluetoothGattDescriptor descriptor, int timeOut) throws IOException 

//Read descriptors from peripherals.
//Raises an exception when communication fails.
BluetoothGattDescriptor readDescriptor(BluetoothGattDescriptor descriptor, int timeOut) throws IOException 

//Enables / disables the notification setting on the peripheral side of a specific characteristic.
//Raises an exception when communication fails.
void setNotification(BluetoothGattCharacteristic characteristic,boolean enable,int timeOut) throws IOException

//Enables / disables the Indication setting on the peripheral side of a specific characteristic.
//Raises an exception when communication fails.
void setIndication(BluetoothGattCharacteristic characteristic,boolean enable,int timeOut) throws IOException

Other

//Pass the Bluetooth Gatt that is held inside. If it is null, it is disconnected.
//Please note that it will be invalidated without permission when communication is disconnected.
BluetoothGatt getGatt() 

//Enables or disables very verbose logs.
//Enabled by default(I think it's often used for prototyping)
//By passing false, only fatal logs will be output.
void setDebug(boolean f)

//Check if there is a method running
//boolean getLockState()

//Forced opening
//void forceUnlock()

Android side API

It's not a library method, but it's a favorite guy.

//Ask the OS to bond with the specified device.
//What kind of bonding is done depends on the design of the peripheral.
device.createBond();

//Set the value characteristically.
//Use with writeCharacteristic.
characteristic.setValue("Hello World");

//Extracts the value from the characteristic in the specified format.
//Use with readCharacteristic.
characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);

Finally

It was created by Android beginners and java beginners for more beginners, I'm a beginner for a few minutes, so I think there are various strange things.

If you find a more useful library or something strange here, please let us know in the comments.

References

I have referred to the following sites very much.

  • BRILLIANTSERVICE TECHNICAL BLOG: Develop a Bluetooth SMART device with mbed (5) < / li>
  • Using BLE with Android 5.0 ~ (Central edition) --vaguely
  • Use getSystemService in a non-Activity class --yife's diary
  • Correct usage of Handler class (inter-thread communication on Android)-Chikutaku
  • Operate peripherals with BLE --yamataka's notebook
  • Try to make a GATT communication app on Android on @Qiita
  • [Java] Create a simple callback function using interface
  • How to throw Java exception Memo --Hishidama

    The various pains I experienced during development were already written two years ago. #kyobashidex that Android BLE talked hard on kyobashi.dex

    And here. 5 TIPS FOR BLUETOOTH LOW ENERGY (BLE) ON ANDROID

    Sample activity

    We have confirmed the operation with Nexus 5X (Android 8.0) and BBC micro: bit. Please refer to this site for the preparation on the micro: bit side. Try BLE communication with Micro: bit using only a browser using the Web Bluetooth API.

    AndroidManifest.xml

    
        <uses-permission android:name="android.permission.BLUETOOTH" />
       <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
        <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
        <uses-feature android:name="android.hardware.location.gps" />
    

    MainActivity.java

    
    package jp.ne.sakura.sabowl.gpsnmeajp.bletest;
    import android.bluetooth.BluetoothDevice;
    import android.bluetooth.BluetoothGattCharacteristic;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.util.ArrayList;
    import java.util.UUID;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        ...
    
        final String LEDServiceUUID = "E95DD91D-251D-470A-A062-FA1922DFA9A8";
        final String LEDTextCharacteristicUUID = "E95D93EE-251D-470A-A062-FA1922DFA9A8";
        final String ButtonServiceUUID = "E95D9882-251D-470A-A062-FA1922DFA9A8";
        final String ButtonACharacteristicUUID = "E95DDA90-251D-470A-A062-FA1922DFA9A8";
    
        private LazyBLEWrapper ble = new LazyBLEWrapper(this);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ...
    
            ble.setDebug(true);
            if(!ble.isBluetoothFeatureSupported(this))
            {
                Toast.makeText(this, "It is a terminal that cannot use Bluetooth", Toast.LENGTH_LONG).show();
                finish();
            }
            if(!ble.isPermitted(this)){
               ble.permissionRequest(this);
            }
    
        }
    
        void connect()
        {
            try {
                //Device detection.
    
                //ArrayList<BluetoothDevice> result = ble.scanDevice(this,5000); //Scan all devices
                //BluetoothDevice device = ble.scanDevice(this,"BBC micro:bit", 5000); //Name scan with prefix match
                BluetoothDevice device = ble.scanBondedDevice("BBC micro:bit"); //Search from the bonded list.(No detailed position authority required)
                //device.createBond();//bonding
    
                //Connect
                ble.connect(this,device, 30000);
                ble.setDisconnectCallback(new LazyBLEWrapper.DisconnectCallback() {
                    @Override
                    public void onDisconnect()  {
                        Log.e(TAG,"Disconnected!");
                    }
                });
    
                //LED writing
                ble.writeData("Hello",LEDServiceUUID,LEDTextCharacteristicUUID, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,30000);
                //Get button
                Integer dat = ble.readDataInt(ButtonServiceUUID,ButtonACharacteristicUUID,BluetoothGattCharacteristic.FORMAT_UINT8,0,30000);
                Log.d(TAG, dat.toString());
    
                //Button notification settings
                ble.setNotify(ButtonServiceUUID,ButtonACharacteristicUUID,true,30000);
                //UART reception settings
                ble.setIndicate(UARTServiceUUID,UARTTxCharacteristicUUID,true,30000);
                ble.setNotificationCallback(new LazyBLEWrapper.NotificationCallback() {
                    @Override
                    public void onNotification(BluetoothGattCharacteristic characteristic) {
                        if(ble.isMatchCharacteristicUUID(characteristic,ButtonACharacteristicUUID)) {
                            Integer ButtonA = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
                            Log.d(TAG, ButtonA.toString());
                            mText.setText(ButtonA.toString());
                        }
                        if(ble.isMatchCharacteristicUUID(characteristic,UARTTxCharacteristicUUID)) {
                            Log.d(TAG, characteristic.getStringValue(0));
                        }
                    }
                });
                //UART transmission
                ble.writeData("Hello UART\n",UARTServiceUUID,UARTRxCharacteristicUUID,BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,30000);
    
            }catch (Exception e){
                Log.e(TAG,"connect",e);
                ble.forceDisconnect();
            }
        }
    
        void disconnect(){
            try {
    //            ble.setNotify(ButtonServiceUUID,ButtonACharacteristicUUID,false,30000);
    //            ble.setDisconnectCallback(null);
    //            ble.setNotificationCallback(null);
                ble.disconnect(30*1000);
    //            finish();
            }catch (Exception e){
                Log.e(TAG,"disconnect",e);
                ble.forceDisconnect();
            }
        }
    
        public void onClick(View v) {
            Button btn = (Button)v;
            Log.i(TAG,"onClick");
    
            switch( btn.getId() ){
                //When the button is pressed
                case R.id.buttonConnect:
                    //BLE processing
                    if(ble.isBluetoothFeatureEnabled())
                    {
                        //Do not run on the main thread as it will lock(Will not come back for the rest of my life))
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                connect();
                            }
                        }).start();
                    }else{
                        ble.requestTurnOnBlueTooth(this);
                    }
                    mText.setText("Wow!");
    
                    break;
                case R.id.buttonDisconnect:
                    //Do not run on the main thread as it will lock(Will not come back for the rest of my life))
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            disconnect();
                        }
                    }).start();
                    break;
    
                default:
                    break;
            }
        }
    

    Recommended Posts

    LazyBLE Wrapper (created a library that makes Android BLE super simple) v0.14
    Created a library that makes it easy to handle Android Shared Prefences
    Created a library that makes it easy to handle Android Shared Prefences
    LazyBLE Wrapper (created a library that makes Android BLE super simple) v0.14
    I made a library for displaying tutorials on Android.
    Find a Java library for Bayesian networks that might work