Android as Bluetooth Low Energy Peripheral (GATT server).



  I demonstrate how to write a simple BLE peripheral application in Android here. I am bad in Android development, The UI would be very ugly, but the code work:

  Currently(5/25/2015), the code could be running in Nexus 6 or Nexus 9 only based on my test. The other phones or tablets DO NOT support to be a BLE peripheral.  So, if you really interested in the issue of Android as BLE Peripheral , please open your wallet or swipe your card, to buy a GOOGLE official device , thank you.


 To add a characteristic as notification is little bit complicated, In here I just add read and write characteristics.
  About the notification, I put code in the last part of this post.

You should add

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

The 2 lines in your AndroidManifest.xml, like this :


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.gaiger.simplebleperipheral"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21" />

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

The kernal code are below , note the AdvertiseCallback is callback of BluetoothLeAdvertiser ::startAdvertising, and BluetoothGattServerCallback is callback function of ALL BluetoothGattCharacteristic.


BLEPeripheral.java: (that is what you want)


package com.gaiger.simplebleperipheral;


import java.util.List;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.util.Log;


public class BLEPeripheral{

 public interface ConnectionCallback {
     void onConnectionStateChange(BluetoothDevice device, int newState);
 }

 
 BluetoothManager mManager;
 BluetoothAdapter mAdapter;
 
 BluetoothLeAdvertiser  mLeAdvertiser;
 
 AdvertiseSettings.Builder settingBuilder;
 AdvertiseData.Builder advBuilder;        
 
 BluetoothGattServer  mGattServer;  
 
 ConnectionCallback mConnectionCallback;
 
 public interface WriteCallback {
     void onWrite(byte[] data);
 }

 WriteCallback mWriteCallback;
 
 public static boolean isEnableBluetooth(){
  return BluetoothAdapter.getDefaultAdapter().isEnabled();
 }
 
 public int init(Context context){
  
  if(null == mManager)
  {
   mManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
  
   if(null == mManager)
    return -1;
  
   if(false == context.getPackageManager().
     hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
    return -2;          
  }      
  
  if(null == mAdapter)
  {
   mAdapter = mManager.getAdapter();
  
   if(false == mAdapter.isMultipleAdvertisementSupported())
    return -3; 
  }
  
  if(null == settingBuilder)
  {
   settingBuilder = new AdvertiseSettings.Builder();
   settingBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
   settingBuilder.setConnectable(true);   
   settingBuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);  
  } 
  
  if(null == advBuilder)
  {
   advBuilder = new AdvertiseData.Builder();                                         
   mAdapter.setName("SimplePeripheral");        
   advBuilder.setIncludeDeviceName(true);
  }
  
  
        if(null == mGattServer)
        {
         mGattServer = mManager.openGattServer(context, mGattServerCallback);
        
         if(null == mGattServer)
          return -4;        
     
         addDeviceInfoService();              
        }
        
  return 0;
 }
 
 public void setConnectionCallback(ConnectionCallback callback)
 {
  mConnectionCallback = callback;
 }
 
 public void close()
 {
  if(null != mLeAdvertiser)
   stopAdvertise();
  
  if(null != mGattServer)
   mGattServer.close();
  mGattServer = null;
  
  if(null != advBuilder)
   advBuilder = null;
  
  if(null != settingBuilder)
        settingBuilder = null;
  
  if(null != mAdapter)
        mAdapter = null;
  
  if(null != mManager)
        mManager = null;   
 }
 

 public static String getAddress(){return BluetoothAdapter.getDefaultAdapter().getAddress();}
 
 private AdvertiseCallback mAdvCallback = new AdvertiseCallback() {
  
  @Override
  public void onStartFailure(int errorCode){
   Log.d("advertise","onStartFailure");
  }
  
  @Override
  public void onStartSuccess(AdvertiseSettings settingsInEffect){
   Log.d("advertise","onStartSuccess");
  };
 };
 
  private final BluetoothGattServerCallback mGattServerCallback 
   = new BluetoothGattServerCallback(){
   
  @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState){
   Log.d("GattServer", "Our gatt server connection state changed, new state ");
         Log.d("GattServer", Integer.toString(newState));      
                  
         if(null != mConnectionCallback && BluetoothGatt.GATT_SUCCESS == status)
          mConnectionCallback.onConnectionStateChange(device, newState);
          
            super.onConnectionStateChange(device, status, newState);
        }
   
  @Override
        public void onServiceAdded(int status, BluetoothGattService service) {
            Log.d("GattServer", "Our gatt server service was added.");
            super.onServiceAdded(status, service);
        }

        @Override
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
            Log.d("GattServer", "Our gatt characteristic was read.");
            super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, 
              characteristic.getValue());
        }

        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            Log.d("GattServer", "We have received a write request for one of our hosted characteristics");
            //Log.d("GattServer", "data = "+ value.toString()); 
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
            
            if(null != mWriteCallback)
             mWriteCallback.onWrite(value);
            
        }

        @Override
        public void onNotificationSent(BluetoothDevice device, int status)
        {
         Log.d("GattServer", "onNotificationSent");          
         super.onNotificationSent(device, status);                  
        }
        
        @Override
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
            Log.d("GattServer", "Our gatt server descriptor was read.");
            super.onDescriptorReadRequest(device, requestId, offset, descriptor);
            
        }

        @Override
        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            Log.d("GattServer", "Our gatt server descriptor was written.");
            super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
        }

        @Override
        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
            Log.d("GattServer", "Our gatt server on execute write.");
            super.onExecuteWrite(device, requestId, execute);
        }
   
 };
 
 private void addDeviceInfoService()
 {  
  if(null == mGattServer)
   return;  
  
  final String SERVICE_DEVICE_INFORMATION = "0000180a-0000-1000-8000-00805f9b34fb";
        final String SOFTWARE_REVISION_STRING = "00002A28-0000-1000-8000-00805f9b34fb";
        
        
  BluetoothGattService previousService =
           mGattServer.getService( UUID.fromString(SERVICE_DEVICE_INFORMATION));
                 
     if(null != previousService)        
         mGattServer.removeService(previousService);
      
        
        BluetoothGattCharacteristic softwareVerCharacteristic = new BluetoothGattCharacteristic(
          UUID.fromString(SOFTWARE_REVISION_STRING), 
          BluetoothGattCharacteristic.PROPERTY_READ,
          BluetoothGattCharacteristic.PERMISSION_READ
          );
        
        BluetoothGattService deviceInfoService = new BluetoothGattService(
          UUID.fromString(SERVICE_DEVICE_INFORMATION), 
          BluetoothGattService.SERVICE_TYPE_PRIMARY);
        
        
        softwareVerCharacteristic.setValue(new String("0.0.0").getBytes());
        
        deviceInfoService.addCharacteristic(softwareVerCharacteristic);
        mGattServer.addService(deviceInfoService);
 }
 
 
 
 
 public void setService(String read1Data, String read2Data, WriteCallback
   writeCallBack)
 {  
         
  if(null == mGattServer)
   return ;
  
  stopAdvertise();
  
        final String  SERVICE_A = "0000fff0-0000-1000-8000-00805f9b34fb";
        final String  CHAR_READ1 = "0000fff1-0000-1000-8000-00805f9b34fb";
        final String  CHAR_READ2 = "0000fff2-0000-1000-8000-00805f9b34fb";
        final String  CHAR_WRITE = "0000fff3-0000-1000-8000-00805f9b34fb";       
        final String  CHAR_NOTIFY = "0000fff4-0000-1000-8000-00805f9b34fb";       
        
        
        
        BluetoothGattService previousService =
          mGattServer.getService( UUID.fromString(SERVICE_A));
                
     if(null != previousService)        
         mGattServer.removeService(previousService);
        
               
        
        BluetoothGattCharacteristic read1Characteristic = new BluetoothGattCharacteristic(
          UUID.fromString(CHAR_READ1), 
          BluetoothGattCharacteristic.PROPERTY_READ,
          BluetoothGattCharacteristic.PERMISSION_READ
          );                 
        
        BluetoothGattCharacteristic read2Characteristic = new BluetoothGattCharacteristic(
          UUID.fromString(CHAR_READ2), 
          BluetoothGattCharacteristic.PROPERTY_READ,
          BluetoothGattCharacteristic.PERMISSION_READ
          );
                
        BluetoothGattCharacteristic writeCharacteristic = new BluetoothGattCharacteristic(
             UUID.fromString(CHAR_WRITE), 
             BluetoothGattCharacteristic.PROPERTY_WRITE,
             BluetoothGattCharacteristic.PERMISSION_WRITE
             );
        
       
       
                               
        read1Characteristic.setValue(read1Data.getBytes());
        read2Characteristic.setValue(read2Data.getBytes());
        mWriteCallback = writeCallBack;
       
        
        BluetoothGattService AService = new BluetoothGattService(
          UUID.fromString(SERVICE_A), 
          BluetoothGattService.SERVICE_TYPE_PRIMARY);
                         
        
        AService.addCharacteristic(read1Characteristic);
        AService.addCharacteristic(read2Characteristic);
        AService.addCharacteristic(writeCharacteristic); 
        
        

        final BluetoothGattCharacteristic notifyCharacteristic = new BluetoothGattCharacteristic(
             UUID.fromString(CHAR_NOTIFY), 
             BluetoothGattCharacteristic.PROPERTY_NOTIFY,
             BluetoothGattCharacteristic.PERMISSION_READ
             );
           
        
        notifyCharacteristic.setValue(new String("0"));
        AService.addCharacteristic(notifyCharacteristic);  
        
        final Handler handler = new Handler();
        
        Thread thread = new Thread() {
         int i = 0;         
         
            @Override
            public void run() {             
               
                    while(true) {
                     
                        try {
       sleep(1500);
      } catch (InterruptedException e) {}
                        
                        handler.post(this);
                        
                        List<BluetoothDevice> connectedDevices 
                         = mManager.getConnectedDevices(BluetoothProfile.GATT);
                        
                        if(null != connectedDevices)
                        {
                         notifyCharacteristic.setValue(String.valueOf(i).getBytes());
                                                 
                         if(0 != connectedDevices.size())
                          mGattServer.notifyCharacteristicChanged(connectedDevices.get(0),
                            notifyCharacteristic, false);
                        }                       
                        i++;
                    }               
            }
        };

        thread.start();
           
        
        mGattServer.addService(AService);
 }
  
 
 public void startAdvertise(String scanRespenseName)
 {  
  mAdapter.setName(scanRespenseName);        
        advBuilder.setIncludeDeviceName(true);
        
        startAdvertise();
 }
 
 public void startAdvertise()
 {
  if(null == mAdapter)
   return;
  
  if (null == mLeAdvertiser) 
   mLeAdvertiser = mAdapter.getBluetoothLeAdvertiser();
        
  if(null == mLeAdvertiser)
   return;
                     
         mLeAdvertiser.startAdvertising(settingBuilder.build(), 
           advBuilder.build(), mAdvCallback);         
 }
 
 public void stopAdvertise()
 {
  if(null != mLeAdvertiser)
   mLeAdvertiser.stopAdvertising(mAdvCallback);
  
  mLeAdvertiser = null;  
 }
  
}


There is a callback interface WriteCallback to hold tthe data which the BLE has written.

MainActivity.java : (UI part)


package com.gaiger.simplebleperipheral;

import java.io.UnsupportedEncodingException;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;


public class MainActivity extends Activity {

 private BLEPeripheral blePeri; 
 private CheckBox  adverstiseCheckBox;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        adverstiseCheckBox = (CheckBox) findViewById(R.id.advertise_checkBox);
        
        blePeri = new BLEPeripheral();                        
        
        adverstiseCheckBox.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
             
             if(true == adverstiseCheckBox.isChecked())
             {              
            
              TextView textView;
              textView = (TextView)findViewById(R.id.status_textView);
              textView.setText("advertising");              
              
              blePeri.setService(new String("GAIGER"),
                new String("AndroidBLE"),
                mWrittenCallback
                );    
              
              blePeri.startAdvertise();
             }
             else
             {
              TextView textView;
              textView = (TextView)findViewById(R.id.status_text);
              textView.setText("disable");
              blePeri.stopAdvertise();              
             }
            }
        });
        
        adverstiseCheckBox.setEnabled(false);
        
     if(false == BLEPeripheral.isEnableBluetooth())
     {
      
      Intent intentBtEnabled = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 
            // The REQUEST_ENABLE_BT constant passed to startActivityForResult() is a locally defined integer (which must be greater than 0), that the system passes back to you in your onActivityResult() 
            // implementation as the requestCode parameter. 
            int REQUEST_ENABLE_BT = 1;
            startActivityForResult(intentBtEnabled, REQUEST_ENABLE_BT);
            
            Toast.makeText(this, "Please enable bluetooth and execute the application agagin.",
     Toast.LENGTH_LONG).show();
     }          
     
    }

    
    byte[]  writtenByte;
    BLEPeripheral.WriteCallback mWrittenCallback = new BLEPeripheral.WriteCallback()
    {
      @Override
  public        
      void onWrite(byte[] data)
      {
       writtenByte = data.clone();
      
       Thread timer = new Thread(){
              public void run() {
                  
                  runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                     TextView textView;
                     textView = (TextView)findViewById(R.id.written_textView);
                  try {
          textView.setText(new String(writtenByte, "UTF-8"));
         } catch (UnsupportedEncodingException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
         }
                   }
                      });
              }
              
          };
          
          timer.start();
       
       
        }            
          
   };
    
   
 
    
    Runnable mCleanTextRunnable = new Runnable() {
        public void run() {
         TextView textView;
       textView = (TextView)findViewById(R.id.connected_textView);
       textView.setText("no connection");             
        }
    };
    
    Handler mConnectTextHandler = new Handler() {  
        
        @Override                  
        public void handleMessage(Message msg) {  
            super.handleMessage(msg);
            String data = (String)msg.obj;  
            
            
            switch (msg.what) {
            
            case 0:
             data += new String(" disconnected");
             this.postDelayed(mCleanTextRunnable, 3000);
             break;
             
            case 2: 
             data += new String(" connected");                                                          
                break;                  
            default:  
                break;  
            }
            
            TextView textView;
      textView = (TextView)findViewById(R.id.connected_textView);
      textView.setText(data);              
        }  
  
    }; 
    
    
    @Override
    public void onResume(){
     
        super.onResume();
                       
        int sts;
        sts = blePeri.init(this);
       
//        blePeri.mConnectionCallback = new BLEPeripheral.ConnectionCallback (){
//         @Override
//      public void onConnectionStateChange(BluetoothDevice device, int newState){
//       Log.d("main","onConnectionStateChange");
//      }
//        };
//       
        blePeri.setConnectionCallback( new BLEPeripheral.ConnectionCallback (){
             @Override
          public void onConnectionStateChange(BluetoothDevice device, int newState){           
                                          
              Message msg = new Message();    
              
              msg.what = newState;                      
              msg.obj = new String( device.getName() +" "+ device.getAddress() );
              
              mConnectTextHandler.sendMessage(msg);                                       
          }
            }
           
         );
        
         
        
        if(0  > sts)
     {
      if(-1 == sts)
       Toast.makeText(this, "this device is without bluetooth module",
         Toast.LENGTH_LONG).show();
      
      if(-2 == sts)
       Toast.makeText(this, "this device do not support Bluetooth low energy", 
         Toast.LENGTH_LONG).show();
      
      if(-3 == sts)
       Toast.makeText(this, "this device do not support to be a BLE peripheral, " +
         "please buy nexus 6 or 9 then try again", 
         Toast.LENGTH_LONG).show();
      
      finish();
     }   
        
        TextView textView;
     textView = (TextView)findViewById(R.id.mac_textView);
     
     textView.setText(BLEPeripheral.getAddress());
     
     
     adverstiseCheckBox.setEnabled(true);            
          
     
    }
    
    
    
    @Override
    protected void onStop() {
        super.onStop();      
    }

        
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

activity_main.xml: (layout, very ugly)


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.awind.presentsenseperipheral.MainActivity" >

    <TextView
        android:id="@+id/mac_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/status_text"
        android:layout_marginLeft="18dp"
        android:layout_toRightOf="@+id/status_text"
        android:text="00:11:22:AA:BB:CC"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <CheckBox
        android:id="@+id/advertise_checkBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/mac_text"
        android:layout_marginLeft="34dp"
        android:layout_marginTop="41dp"
        android:text="Advertise" />

    <TextView
        android:id="@+id/status_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/advertise_checkBox"
        android:layout_alignParentTop="true"
        android:layout_marginTop="124dp"
        android:text="Disable"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/connected_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/mac_text"
        android:layout_centerVertical="true"
        android:text="no connection"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/written_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/mac_text"
        android:layout_alignParentBottom="true"
        android:text="WrittenText"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>


    I do not like to write too much explanation in here, One said: if you could implement, you understand it; if you could not, you know about nothing of it .

    Notice the part of notifyCharacteristic ,I create a thread, which updates value and send a signal to BluetoothGattServer, to inform the BLE central the value has changed.

  There is a bug in written text update: once WriteCallback::onWrite been called, the runOnUiThread for updating  R.id.written_textView should be executed once. But in my code, the update would not work. That is very minior for the purpose of demonstration BLE on Android, So, please ignore it.

你可能感兴趣的:(android)