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 not support to be a BLE peripheral. So, if you really interested in the Android
as peripheral issue, please open your wallet and buy a GOOGLE official device, thank you.
How to add a characteristic as notification is little bit complicated, In here I just add read write ones.
About the notification, I put code in the lat 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.awind.presentsenseperipheral"
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.awind.presentsenseperipheral;
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.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.util.Log;
public class BLEPeripheral{
Context mContext;
BluetoothManager mManager;
BluetoothAdapter mAdapter;
BluetoothLeAdvertiser mLeAdvertiser;
BluetoothGattServer mGattServer;
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;
mContext = context;
return 0;
}
public void close()
{
}
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));
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);
}
@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(BluetoothGattServer gattServer)
{
if(null == gattServer)
return;
//
// device info
//
final String SERVICE_DEVICE_INFORMATION = "0000180a-0000-1000-8000-00805f9b34fb";
final String SOFTWARE_REVISION_STRING = "00002A28-0000-1000-8000-00805f9b34fb";
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.1").getBytes());
deviceInfoService.addCharacteristic(softwareVerCharacteristic);
gattServer.addService(deviceInfoService);
}
public void startAdvertise()
{
if(null == mAdapter)
return;
if (null == mLeAdvertiser)
mLeAdvertiser = mAdapter.getBluetoothLeAdvertiser();
if(null == mLeAdvertiser)
return;
AdvertiseSettings.Builder settingBuilder;
settingBuilder = new AdvertiseSettings.Builder();
settingBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
settingBuilder.setConnectable(true);
settingBuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
AdvertiseData.Builder advBuilder;
advBuilder = new AdvertiseData.Builder();
mAdapter.setName("PeripheralAndroid"); //8 characters works, 9+ fails
advBuilder.setIncludeDeviceName(true);
mGattServer = mManager.openGattServer(mContext, mGattServerCallback);
addDeviceInfoService(mGattServer);
final String SERVICE_A = "0000fff0-0000-1000-8000-00805f9b34fb";
final String CHAR_READ_1 = "00fff1-0000-1000-8000-00805f9b34fb";
final String CHAR_READ_2 = "00fff2-0000-1000-8000-00805f9b34fb";
final String CHAR_WRITE = "00fff3-0000-1000-8000-00805f9b34fb";
BluetoothGattCharacteristic read1Characteristic = new BluetoothGattCharacteristic(
UUID.fromString(CHAR_READ_1),
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_READ
);
read1Characteristic.setValue(new String("this is read 1").getBytes());
BluetoothGattCharacteristic read2Characteristic = new BluetoothGattCharacteristic(
UUID.fromString(CHAR_READ_2),
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_READ
);
read2Characteristic.setValue(new String("this is read 2").getBytes());
BluetoothGattCharacteristic writeCharacteristic = new BluetoothGattCharacteristic(
UUID.fromString(CHAR_WRITE),
BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE
);
/
BluetoothGattService AService = new BluetoothGattService(
UUID.fromString(SERVICE_A),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
AService.addCharacteristic(read1Characteristic);
AService.addCharacteristic(read2Characteristic);
AService.addCharacteristic(writeCharacteristic);
// Add notify characteristic here !!!
mGattServer.addService(AService);
mLeAdvertiser.startAdvertising(settingBuilder.build(),
advBuilder.build(), mAdvCallback);
}
public void stopAdvertise()
{
if(null != mLeAdvertiser)
mLeAdvertiser.stopAdvertising(mAdvCallback);
mLeAdvertiser = null;
}
}
MainActivity.java : (UI part)
package com.awind.presentsenseperipheral;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
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_text);
textView.setText("advertising");
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();
}
}
@Override
public void onResume(){
super.onResume();
int sts;
sts = blePeri.init(this);
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_text);
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/status_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="15dp"
android:layout_marginTop="25dp"
android:text="Disable"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/mac_text"
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_alignLeft="@+id/status_text"
android:layout_below="@+id/mac_text"
android:layout_marginTop="34dp"
android:text="Advertise" />
</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 .
About notification characteristic:
Replace the line :
// Add nodify characteristic here !!!
As :
final String CHAR_NOTIFY = "00fffB-0000-1000-8000-00805f9b34fb";
final BluetoothGattCharacteristic notifyCharacteristic = new BluetoothGattCharacteristic(
UUID.fromString(CHAR_NOTIFY),
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ
);
notifyCharacteristic.setValue(new String("0"));
presentSenseService.addCharacteristic(notifyCharacteristic);
final Handler handler = new Handler();
Thread thread = new Thread() {
int i = 0;
@Override
public void run() {
try {
while(true) {
sleep(1500);
handler.post(this);
List<BluetoothDevice> connectedDevices
= mManager.getConnectedDevices(BluetoothProfile.GATT);
if(null != connectedDevices)
{
notifyCharacteristic.setValue(String.valueOf(i).getBytes());
mGattServer.notifyCharacteristicChanged(connectedDevices.get(0),
notifyCharacteristic, false);
}
i++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
That is, create a thread , that updates value and send a signal to BluetoothGattServer to note the value has been change.