I am making an android app that uses BLE. I am trying to write data to a characteristic on button click but unable to do that.
I have a service which is having two characteristic and out of those two characteristic I am writing data on one on button click.
Ble class where I have defined writeDatatoCharacteristic
/* set new value for particular characteristic */
public void writeDataToCharacteristic(final BluetoothGattCharacteristic ch, final byte[] value) {
if (mBluetoothAdapter == null || mBluetoothGatt == null || ch == null) {
return;
}
// first set it locally....
ch.setValue(value);
// ... and then "commit" changes to the peripheral
mBluetoothGatt.writeCharacteristic(ch);
}
xml file for button
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/imageButtonPlay"
android:src="#drawable/play"
android:background="#android:color/transparent"
android:layout_alignTop="#+id/ivRestart"
android:layout_centerHorizontal="true"
android:onClick="onClickWrite"/>
Main Activity where I am calling the write data to characteristic function
public class Tens_modes extends AppCompatActivity {
private final String LOGTAG = "BLETEST";
private final String TARGET = "CC2650 SensorTag";
private BleWrapper mBleWrapper = null;
private mSensorState mState;
private String gattList = "";
private TextView mTv;
private enum mSensorState {IDLE, ACC_ENABLE, ACC_READ};
public final static UUID
UUID_ACC_SERV = fromString("FFE0"),
UUID_ACC_DATA = fromString("f000aa11-0451-4000-b000-000000000000"),
UUID_ACC_CONF = fromString("FFE9"), // 0: disable, 1: enable
UUID_ACC_PERI = fromString("f000aa13-0451-4000-b000-000000000000"); // Period in tens of milliseconds
private ImageButton play;
private ImageButton pause;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tens_modes);
play = (ImageButton)findViewById(R.id.imageButtonPlay);
pause = (ImageButton)findViewById(R.id.imageButtonPause);
play.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
play.setVisibility(View.INVISIBLE);
pause.setVisibility(View.VISIBLE);
pause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
pause.setVisibility(View.INVISIBLE);
play.setVisibility(View.VISIBLE);
//Ble stuff
mBleWrapper = new BleWrapper(this, new BleWrapperUiCallbacks.Null()
{
#Override
public void uiDeviceFound(final BluetoothDevice device, final int rssi, final byte[] record)
{
Log.d(LOGTAG, "uiDeviceFound: " + device.getName() + ", " + rssi + ", " + record.toString());
if (device.getName().equals(TARGET))
{
if (!mBleWrapper.connect(device.getAddress()))
{
Log.d(LOGTAG, "uiDeviceFound: Problem connecting to remote device.");
}
}
//stopScan();
}
#Override
public void uiDeviceConnected(BluetoothGatt gatt, BluetoothDevice device)
{
Log.d(LOGTAG, "uiDeviceConnected: State = " + mBleWrapper.getAdapter().getState());
}
#Override
public void uiDeviceDisconnected(BluetoothGatt gatt, BluetoothDevice device) {
Log.d(LOGTAG, "uiDeviceDisconnected: State = " + mBleWrapper.getAdapter().getState());
}
#Override
public void uiAvailableServices(BluetoothGatt gatt, BluetoothDevice device, List<BluetoothGattService> services)
{
BluetoothGattCharacteristic c;
BluetoothGattDescriptor d;
for (BluetoothGattService service : services)
{
String serviceName = BleNamesResolver.resolveUuid(service.getUuid().toString());
Log.e(LOGTAG, serviceName);
gattList += serviceName + "\n";
mBleWrapper.getCharacteristicsForService(service);
}
// enable services
Log.e(LOGTAG, "uiAvailableServices: Enabling services");
c = gatt.getService(UUID_ACC_SERV).getCharacteristic(UUID_ACC_CONF);
mBleWrapper.writeDataToCharacteristic(c, new byte[] {0300000000AA});
//mState = mSensorState.ACC_ENABLE;
// set notification on characteristic
//Log.d(LOGTAG, "uiAvailableServices: Setting notification");
//c = gatt.getService(UUID_IRT_SERV).getCharacteristic(UUID_IRT_DATA);
//mBleWrapper.setNotificationForCharacteristic(c, true);
// enable notification on descriptor
//d = c.getDescriptor(UUID_CCC_DESC);
//d.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//gatt.writeDescriptor(d);
}
#Override
public void uiCharacteristicForService( BluetoothGatt gatt,
BluetoothDevice device,
BluetoothGattService service,
List<BluetoothGattCharacteristic> chars)
{
super.uiCharacteristicForService(gatt, device, service, chars);
for (BluetoothGattCharacteristic c : chars)
{
String charName = BleNamesResolver.resolveCharacteristicName(c.getUuid().toString());
Log.d(LOGTAG, charName);
gattList += "Characteristic: " + charName + "\n";
}
}
#Override
public void uiSuccessfulWrite( BluetoothGatt gatt,
BluetoothDevice device,
BluetoothGattService service,
BluetoothGattCharacteristic ch,
String description)
{
BluetoothGattCharacteristic c;
super.uiSuccessfulWrite(gatt, device, service, ch, description);
Log.d(LOGTAG, "uiSuccessfulWrite");
switch (mState)
{
case ACC_ENABLE:
Log.d(LOGTAG, "uiSuccessfulWrite: Reading acc");
c = gatt.getService(UUID_ACC_SERV).getCharacteristic(UUID_ACC_DATA);
mBleWrapper.requestCharacteristicValue(c);
mState = mSensorState.ACC_READ;
break;
case ACC_READ:
Log.d(LOGTAG, "uiSuccessfulWrite: state = ACC_READ");
break;
default:
break;
}
}
#Override
public void uiFailedWrite( BluetoothGatt gatt,
BluetoothDevice device,
BluetoothGattService service,
BluetoothGattCharacteristic ch,
String description)
{
super.uiFailedWrite(gatt, device, service, ch, description);
Log.d(LOGTAG, "uiFailedWrite");
}
#Override
public void uiNewValueForCharacteristic(BluetoothGatt gatt,
BluetoothDevice device,
BluetoothGattService service,
BluetoothGattCharacteristic ch,
String strValue,
int intValue,
byte[] rawValue,
String timestamp)
{
super.uiNewValueForCharacteristic(gatt, device, service, ch, strValue, intValue, rawValue, timestamp);
Log.d(LOGTAG, "uiNewValueForCharacteristic");
for (byte b:rawValue)
{
Log.d(LOGTAG, "Val: " + b);
}
}
#Override
public void uiGotNotification( BluetoothGatt gatt,
BluetoothDevice device,
BluetoothGattService service,
BluetoothGattCharacteristic characteristic)
{
super.uiGotNotification(gatt, device, service, characteristic);
String ch = BleNamesResolver.resolveCharacteristicName(characteristic.getUuid().toString());
Log.d(LOGTAG, "uiGotNotification: " + ch);
}
});
}
public void onClickWrite(View v){
if(mBleWrapper != null) {
// enable services
Log.e(LOGTAG, "uiAvailableServices: Enabling services");
byte[] value = new byte[1];
value[0] = (byte)21;
mBleWrapper.writeDataToCharacteristic(UUID_ACC_CONF, new byte[] {0300000000AA});
}
}
I want to write 0300000000AA as a value to characteristic but don't know how to do that. Searching for it from last 4,5 hours but didn;t get anything useful
Please if anyone can help me with this and tell me what I am doing wrong
Thanks
This should do it for you. (well be a start, I'm not sure about encoding)
byte[] temp;
//This will at least compile, however I don't know if that is what you want, strings in java are unicode, not ascii. you might have to convert to your desired encoding
temp = "0300000000AA".getBytes();
mBleWrapper.writeDataToCharacteristic(UUID_ACC_CONF, temp );
Related
I'm using agora to make voice calls in my android application , i have set up the code as per the documentation but whenever i try to join a call it crashes and says that rtcEngine is null even though it is initialized , any help would be appreciated , Thank you
Code
public class AudioCallActivity extends AppCompatActivity {
// An integer that identifies the local user.
private int uid = 0;
// Track the status of your connection
private boolean isJoined = false;
// Agora engine instance
private RtcEngine agoraEngine;
// UI elements
private TextView infoText;
private Button joinLeaveButton;
private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS = { Manifest.permission.RECORD_AUDIO};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_call);
// If all the permissions are granted, initialize the RtcEngine object and join a channel.
if (!checkSelfPermission()) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, PERMISSION_REQ_ID);
}
setupVoiceSDKEngine();
// Set up access to the UI elements
joinLeaveButton = findViewById(R.id.joinLeaveButton);
infoText = findViewById(R.id.infoText);
}
private boolean checkSelfPermission() {
return ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[0]) == PackageManager.PERMISSION_GRANTED;
}
void showMessage(String message) {
runOnUiThread(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
}
private void setupVoiceSDKEngine() {
try {
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getBaseContext();
config.mAppId = getString(R.string.app_id);
config.mEventHandler = mRtcEventHandler;
agoraEngine = RtcEngine.create(config);
} catch (Exception e) {
throw new RuntimeException("Check the error.");
}
}
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
#Override
// Listen for the remote user joining the channel.
public void onUserJoined(int uid, int elapsed) {
runOnUiThread(()->infoText.setText("Remote user joined: " + uid));
}
#Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
// Successfully joined a channel
isJoined = true;
showMessage("Joined Channel " + channel);
runOnUiThread(()->infoText.setText("Waiting for a remote user to join"));
}
#Override
public void onUserOffline(int uid, int reason) {
// Listen for remote users leaving the channel
showMessage("Remote user offline " + uid + " " + reason);
if (isJoined) runOnUiThread(()->infoText.setText("Waiting for a remote user to join"));
}
#Override
public void onLeaveChannel(RtcStats stats) {
// Listen for the local user leaving the channel
runOnUiThread(()->infoText.setText("Press the button to join a channel"));
isJoined = false;
}
};
private void joinChannel() {
ChannelMediaOptions options = new ChannelMediaOptions();
options.autoSubscribeAudio = true;
// Set both clients as the BROADCASTER.
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// Set the channel profile as BROADCASTING.
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Join the channel with a temp token.
// You need to specify the user ID yourself, and ensure that it is unique in the channel.
agoraEngine.joinChannel(getString(R.string.agora_token), "ChannelOne", uid, options);
}
public void joinLeaveChannel(View view) {
try {
if (isJoined) {
agoraEngine.leaveChannel();
joinLeaveButton.setText("Join");
} else {
joinChannel();
joinLeaveButton.setText("Leave");
}
}catch (Exception e){
Log.d("TAG","Error is " + e.getMessage());
}
}
#Override
protected void onDestroy() {
agoraEngine.leaveChannel();
// Destroy the engine in a sub-thread to avoid congestion
new Thread(() -> {
RtcEngine.destroy();
agoraEngine = null;
}).start();
onDestroy();
}
}
Error Thrown
Attempt to invoke virtual method 'int io.agora.rtc2.RtcEngine.joinChannel(java.lang.String, java.lang.String, int, io.agora.rtc2.ChannelMediaOptions)' on a null object reference
I am trying to write some custom data to a BLE device with a custom service on it. I have followed this tutorial: https://www.youtube.com/watch?v=vUbFB1Qypg8&feature=emb_logo which is from android.
I can see my custom service with data on it. Also via another application (NRF connect) I can write data to this custom service. Therefore, I know its possible to write data to the service.
The issue I have is other examples of how to write data to the custom service use BluetoothGatt whereas, this code seems to use BluetoothLeService instead. This is an issue as the documentation does not seem to have a read API for the BluetoothLeService. So I can not use something like mBluetoothGatt.readCharacteristic(mReadCharacteristic);
How can I go about creating a function to write a value (integer) to the characterisitic.
My UUID for the service is: "f3641400-00b0-4240-ba50-05ca45bf8abc"
My UUID for the characteristic is: "f3641401-00b0-4240-ba50-05ca45bf8abc"
For reference my code is below:
public class DeviceControlActivity extends Activity {
private final static String TAG =
DeviceControlActivity.class.getSimpleName();
public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME";
public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS";
private TextView mConnectionState;
private TextView mDataField;
private EditText Command_string;
private BluetoothGatt mGatt;
private Button btn_gatt_connect;
private Button btn_live,btn_command;
private String mDeviceName;
private String mDeviceAddress;
private String User_data;
private ExpandableListView mGattServicesList;
private BluetoothLeService mBluetoothLeService;
private ArrayList<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristics =
new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
private boolean mConnected = false;
private BluetoothGattCharacteristic mNotifyCharacteristic;
private final String LIST_NAME = "NAME";
private final String LIST_UUID = "UUID";
private String Switch_case ="Connect";
private boolean Write_ble_command = true;
// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
if (!mBluetoothLeService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
// Automatically connects to the device upon successful start-up initialization.
mBluetoothLeService.connect(mDeviceAddress);
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
mBluetoothLeService = null;
}
};
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a result of read
// or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
mConnected = true;
updateConnectionState(R.string.connected);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
mConnected = false;
updateConnectionState(R.string.disconnected);
invalidateOptionsMenu();
clearUI();
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
// Show all the supported services and characteristics on the user interface.
displayGattServices(mBluetoothLeService.getSupportedGattServices());
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
}
}
};
// If a given GATT characteristic is selected, check for supported features. This sample
// demonstrates 'Read' and 'Notify' features. See
// http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
// list of supported characteristic features.
private final ExpandableListView.OnChildClickListener servicesListClickListner =
new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id) {
if (mGattCharacteristics != null) {
final BluetoothGattCharacteristic characteristic =
mGattCharacteristics.get(groupPosition).get(childPosition);
final int charaProp = characteristic.getProperties();
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
// If there is an active notification on a characteristic, clear
// it first so it doesn't update the data field on the user interface.
if (mNotifyCharacteristic != null) {
mBluetoothLeService.setCharacteristicNotification(
mNotifyCharacteristic, false);
mNotifyCharacteristic = null;
}
mBluetoothLeService.readCharacteristic(characteristic);
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
mNotifyCharacteristic = characteristic;
mBluetoothLeService.setCharacteristicNotification(
characteristic, true);
}
return true;
}
return false;
}
};
private void Gatt_Connect()
{
switch(Switch_case) {
case "Connect":
btn_gatt_connect.setText("Disconnect");//Presently connected allow to disconnect
mBluetoothLeService.connect(mDeviceAddress);
Switch_case = "Disconnect";
break;
case "Disconnect":
btn_gatt_connect.setText("Connect");//Presently disconnected allow to connect
mBluetoothLeService.disconnect();
Switch_case = "Connect";
break;
}
}
private void Live_data(){
//Ensure you are disconnected
btn_gatt_connect.setText("Connect");//Presently disconnected allow to connect
mBluetoothLeService.disconnect();//Might only run if connected
Switch_case = "Connect";
Intent myIntent = new Intent(DeviceControlActivity.this, MainActivity.class);
Bundle bundle = new Bundle();
bundle.putString("User_data", User_data);//Pass through the user's email address to the main activity for display
myIntent.putExtras(bundle);
DeviceControlActivity.this.startActivity(myIntent);//Run the main activity}
}
private void clearUI() {
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
mDataField.setText(R.string.no_data);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gatt_services_characteristics);
//Get the extras passed in
Bundle bundle = getIntent().getExtras();
mDeviceName = bundle.getString("Device_Name");
mDeviceAddress = bundle.getString("Device_Address");
User_data = bundle.getString("User_data");
// Sets up UI references.
((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
Command_string = (EditText)findViewById(R.id.Command_string);
btn_gatt_connect = (Button)findViewById(R.id.btn_gatt_connect);
btn_live = (Button) findViewById(R.id.btn_live);
btn_command = (Button) findViewById(R.id.btn_command);
btn_gatt_connect.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Gatt_Connect();
}
});
btn_gatt_connect.setText("Disconnect");//Presently disconnected allow to connect
btn_live.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Live_data();
}
});
btn_command.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Send_Command();//Sends the command in the text box to the nordic board
}
});
mGattServicesList = (ExpandableListView) findViewById(R.id.gatt_services_list);
mGattServicesList.setOnChildClickListener(servicesListClickListner);
mConnectionState = (TextView) findViewById(R.id.connection_state);
mDataField = (TextView) findViewById(R.id.data_value);
//setTitle(mDeviceAddress);//Change the title to Scanner as the device is no longer being read
Intent gattServiceIntent = new Intent(DeviceControlActivity.this, BluetoothLeService.class);
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
#Override
protected void onResume() {
super.onResume();
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
if (mBluetoothLeService != null) {
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
Log.d(TAG, "Connect request result=" + result);
}
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(mGattUpdateReceiver);
}
#Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
mBluetoothLeService = null;
}
protected void Send_Command()
{
Write_ble_command = true;//Write command issued on display gatt service
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.gatt_services, menu);
if (mConnected) {
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(true);
} else {
menu.findItem(R.id.menu_connect).setVisible(true);
menu.findItem(R.id.menu_disconnect).setVisible(false);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_connect:
mBluetoothLeService.connect(mDeviceAddress);
return true;
case R.id.menu_disconnect:
mBluetoothLeService.disconnect();
return true;
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private void updateConnectionState(final int resourceId) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mConnectionState.setText(resourceId);
}
});
}
private void displayData(String data) {
if (data != null) {
mDataField.setText(data);
}
}
// Demonstrates how to iterate through the supported GATT Services/Characteristics.
// In this sample, we populate the data structure that is bound to the ExpandableListView
// on the UI.
private void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
String uuid = null;
String unknownServiceString = getResources().getString(R.string.unknown_service);
String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();
ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
= new ArrayList<ArrayList<HashMap<String, String>>>();
mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
HashMap<String, String> currentServiceData = new HashMap<String, String>();
uuid = gattService.getUuid().toString();
currentServiceData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);
ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
new ArrayList<HashMap<String, String>>();
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
ArrayList<BluetoothGattCharacteristic> charas =
new ArrayList<BluetoothGattCharacteristic>();
// Loops through available Characteristics.
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
charas.add(gattCharacteristic);
HashMap<String, String> currentCharaData = new HashMap<String, String>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
mGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
this,
gattServiceData,
android.R.layout.simple_expandable_list_item_2,
new String[] {LIST_NAME, LIST_UUID},
new int[] { android.R.id.text1, android.R.id.text2 },
gattCharacteristicData,
android.R.layout.simple_expandable_list_item_2,
new String[] {LIST_NAME, LIST_UUID},
new int[] { android.R.id.text1, android.R.id.text2 }
);
mGattServicesList.setAdapter(gattServiceAdapter);
}
private static IntentFilter makeGattUpdateIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
return intentFilter;
}
}
I fixed my issue by creating a function inside of the Bluetoothle service. This is where the bluetooth gatt connection is. The function is like so.
public void writeCharacteristic(int Data) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
byte[] value = intToByteArray(Data);
BluetoothGattService mCustomService = mBluetoothGatt.getService(UUID.fromString("f3641400-00b0-4240-ba50-05ca45bf8abc"));
if(mCustomService == null){
Log.w(TAG, "Custom BLE Service not found");
return;
}
/*get the read characteristic from the service*/
BluetoothGattCharacteristic characteristic = mCustomService.getCharacteristic(UUID.fromString("f3641401-00b0-4240-ba50-05ca45bf8abc"));
characteristic.setValue(value);
mBluetoothGatt.writeCharacteristic(characteristic);
}
It just needs a data (Int value passing into it). Then this is received by my Bluetooth board and, can see its data on a terminal being sent correctly.
I'm in the process of implementing in app billing for Android and have got to the point where I can retrieve a list of products from the store. And can activate the Google purchase dialog via calling the launchBillingFlow() method. The documentation indicates that once this has been called, the onPurchasesUpdated is then called with the result. However this isn't happening for me.
The logging confirms that the purchase is requested (from within my method: startPurchaseFlow()). My onPurchasesUpdated() is also called when the activity first runs and provides a OK result (0) to confirm connection set up.
But why isn't it being called after launchBillingFlow()?
Class that holds purchase mechanics:
public class BillingManager implements PurchasesUpdatedListener {
private final BillingClient mBillingClient; // Billing client used to interface with Google Play
private final Store mActivity; // Referenced in constructor
// Structure to hold the details of SKUs returned from querying store
private static final HashMap<String, List<String>> SKUS;
static
{
SKUS = new HashMap<>();
SKUS.put(BillingClient.SkuType.INAPP, Arrays.asList("com.identifier.unlock")); // Strings for in app permanent products
}
public List<String> getSkus(#BillingClient.SkuType String type) {
return SKUS.get(type);
}
// Constructor
public BillingManager(Store activity) {
mActivity = activity;
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build(); // Initialise billing client and set listener
mBillingClient.startConnection(new BillingClientStateListener() { // Start connection via billing client
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) { // Actions to complete when connection is set up
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i("dev", "onBillingSetupFinished() response: " + billingResponse);
mActivity.getProducts();
} else {
Log.w("dev", "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() { // Called when the connection is disconnected
Log.w("dev", "onBillingServiceDisconnected()");
}
});
}
// Receives callbacks on updates regarding future purchases
#Override
public void onPurchasesUpdated(#BillingClient.BillingResponse int responseCode,
List<Purchase> purchases) {
Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
if (responseCode == 0 && !purchases.isEmpty()) {
String purchaseToken;
for (Purchase element : purchases) {
purchaseToken = element.getPurchaseToken();
mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
}
}
}
// Used to query store and get details of products args include products to query including type and list of SKUs and a listener for response
public void querySkuDetailsAsync(#BillingClient.SkuType final String itemType,
final List<String> skuList, final SkuDetailsResponseListener listener) {
// Create a SkuDetailsParams instance containing args
SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
.setSkusList(skuList).setType(itemType).build();
//Query the billing client using the SkuDetailsParams object as an arg
mBillingClient.querySkuDetailsAsync(skuDetailsParams,
new SkuDetailsResponseListener() {
// Override the response to use the listener provided originally in args
#Override
public void onSkuDetailsResponse(int responseCode,
List<SkuDetails> skuDetailsList) {
listener.onSkuDetailsResponse(responseCode, skuDetailsList);
}
});
}
// Start purchase flow with retry option
public void startPurchaseFlow(final String skuId, final String billingType) {
Log.i("dev", "Starting purchaseflow...");
// Specify a runnable to start when connection to Billing client is established
Runnable executeOnConnectedService = new Runnable() {
#Override
public void run() {
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setType(billingType)
.setSku(skuId)
.build();
mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
Log.i("dev", "Just called launchBillingFlow..." + skuId);
}
};
// If Billing client was disconnected, we retry 1 time
// and if success, execute the query
startServiceConnectionIfNeeded(executeOnConnectedService);
}
// Starts connection with reconnect try
private void startServiceConnectionIfNeeded(final Runnable executeOnSuccess) {
if (mBillingClient.isReady()) {
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
} else {
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
} else {
Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()");
}
});
}
}
} // End of class
Class that implements interface and initiates request for purchases and displays product information:
public class Store extends AppCompatActivity {
SharedPreferences prefs; // used to access and update the pro value
BillingManager billingManager; // Used to process purchases
// Following are used to store local details about unlock product from the play store
String productSku = "Loading"; // Holds SKU details
String productBillingType = "Loading";
String productTitle = "Loading"; // Will be used to display product title in the store activity
String productPrice = "Loading"; // Used to display product price
String productDescription = "Loading"; // Used to display the product description
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_store);
// Set up toolbar
Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(myToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
// Create billing manager instance
billingManager = new BillingManager(this);
// Set up the shared preferences variable
prefs = this.getSharedPreferences(
"com.identifier", Context.MODE_PRIVATE); // Initiate the preferences
// set up buttons
final Button btnBuy = findViewById(R.id.btnBuy);
btnBuy.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
billingManager.startPurchaseFlow(/*productSku*/ "android.test.purchased", productBillingType); // Amended for TEST
}
});
final Button btnPro = findViewById(R.id.btnPro);
btnPro.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
getProducts();
}
});
getProducts();
updateDisplay();
} // End of onCreate
// Used to unlock the app
public void unlock() {
Log.d("dev", "in unlock(), about to set to true");
prefs.edit().putBoolean("pro", true).apply();
MainActivity.pro = true;
}
// Go back if back/home pressed
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
// Used to request details of products from the store from this class
public void getProducts() {
List<String> inAppSkus = billingManager.getSkus(BillingClient.SkuType.INAPP); // Create local list of Skus for query
billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, inAppSkus, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
for (SkuDetails details : skuDetailsList) {
productSku = details.getSku();
productTitle = details.getTitle();
productDescription = details.getDescription();
productPrice = details.getPrice();
productBillingType = details.getType();
}
updateDisplay();
}
}
});
}
// Helper method to update the display with strings
private void updateDisplay() {
final TextView titleText = findViewById(R.id.txtTitle);
final TextView descriptionText = findViewById(R.id.txtDescription);
final TextView priceText = findViewById(R.id.txtPrice);
titleText.setText(productTitle);
descriptionText.setText(productDescription);
priceText.setText(productPrice);
}
}
Ok, so this (replacing the onPurchasesUpdated method above) is now working/responding as expected. Why, I don't know, but it is.
#Override
public void onPurchasesUpdated(#BillingClient.BillingResponse int responseCode,
List<Purchase> purchases) {
if (responseCode == BillingClient.BillingResponse.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
Log.i("dev", "successful purchase...");
String purchasedSku = purchase.getSku();
Log.i("dev", "Purchased SKU: " + purchasedSku);
String purchaseToken = purchase.getPurchaseToken();
mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
mActivity.unlock();
}
} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Log.d(TAG, "onPurchasesUpdated() response: User cancelled" + responseCode);
} else {
// Handle any other error codes.
}
}
I've got a problem with this tutorial from the Book "Getting Started with Bluetooth Low Energy".
I only want to scan for BLE devices and post the results via logcat. But anytime I do start the scan, the app shut down and give me a NullPointerException (See Logcat below). For this example I'm using the BleWrapper Class, but I also try to set up BLE Scan without it in another app. But it is always the same error... NullPointerException...
I also tried out 2 different demo apps, import them into my Android studio(v1.1) and they perform well. But if I try to write the same code in its own activity it will crash with this error.
I guess it's something wrong with the callback, or the scanning-method, maybe I forget some reference?
Here is my main activity:
package com.example.oliver.blebuch;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
private BleWrapper mBleWrapper = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("tag", "Test");
mBleWrapper = new BleWrapper(this, new BleWrapperUiCallbacks.Null()
{
#Override
public void uiDeviceFound(final BluetoothDevice device,
final int rssi,
final byte[] scanRecord)
{
Log.d("tag", "uiDeviceFound: "+device.getName()+", "+rssi+", "+scanRecord.toString());
}
});
if(mBleWrapper.checkBleHardwareAvailable() == false)
{
Toast.makeText(this,"No BLE-compatible hardware detected",
Toast.LENGTH_SHORT).show();
finish();
}
}
protected void onResume(){
super.onResume();
//check for Bluetooth enabled on each resume
if (mBleWrapper.isBtEnabled() == false)
{
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
finish();
}
}
#Override
protected void onPause(){
super.onPause();
mBleWrapper.disconnect();
mBleWrapper.close();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId())
{
case R.id.action_scan:
mBleWrapper.startScanning();
break;
case R.id.action_stop:
mBleWrapper.stopScanning();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
}
Logcat:
03-26 10:28:49.986 26288-26288/com.example.oliver.blebuch D/OpenGLRenderer﹕ Enabling debug mode 0
03-26 10:28:53.236 26288-26288/com.example.oliver.blebuch W/dalvikvm﹕ method Landroid/support/v7/internal/widget/ListViewCompat;.lookForSelectablePosition incorrectly overrides package-private method with same name in Landroid/widget/ListView;
03-26 10:28:53.266 26288-26288/com.example.oliver.blebuch D/AbsListView﹕ Get MotionRecognitionManager
03-26 10:28:54.366 26288-26288/com.example.oliver.blebuch D/AndroidRuntime﹕ Shutting down VM
03-26 10:28:54.366 26288-26288/com.example.oliver.blebuch W/dalvikvm﹕ threadid=1: thread exiting with uncaught exception (group=0x41783da0)
03-26 10:28:54.376 26288-26288/com.example.oliver.blebuch E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.example.oliver.blebuch, PID: 26288
java.lang.NullPointerException
at com.example.oliver.blebuch.BleWrapper.startScanning(BleWrapper.java:79)
at com.example.oliver.blebuch.MainActivity.onOptionsItemSelected(MainActivity.java:78)
at android.app.Activity.onMenuItemSelected(Activity.java:2708)
at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:350)
at android.support.v7.app.ActionBarActivity.onMenuItemSelected(ActionBarActivity.java:155)
at android.support.v7.app.ActionBarActivityDelegate$1.onMenuItemSelected(ActionBarActivityDelegate.java:74)
at android.support.v7.app.ActionBarActivityDelegateBase.onMenuItemSelected(ActionBarActivityDelegateBase.java:556)
at android.support.v7.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:802)
at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:153)
at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949)
at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939)
at android.support.v7.internal.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:187)
at android.widget.AdapterView.performItemClick(AdapterView.java:308)
at android.widget.AbsListView.performItemClick(AbsListView.java:1495)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3453)
at android.widget.AbsListView$3.run(AbsListView.java:4816)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5479)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
at dalvik.system.NativeStart.main(Native Method)
BLE Wrapper Class:
package com.example.oliver.blebuch;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.util.Log;
public class BleWrapper {
/* defines (in milliseconds) how often RSSI should be updated */
private static final int RSSI_UPDATE_TIME_INTERVAL = 1500; // 1.5 seconds
/* callback object through which we are returning results to the caller */
private BleWrapperUiCallbacks mUiCallback = null;
/* define NULL object for UI callbacks */
private static final BleWrapperUiCallbacks NULL_CALLBACK = new BleWrapperUiCallbacks.Null();
/* creates BleWrapper object, set its parent activity and callback object */
public BleWrapper(Activity parent, BleWrapperUiCallbacks callback) {
this.mParent = parent;
mUiCallback = callback;
if(mUiCallback == null) mUiCallback = NULL_CALLBACK;
}
public BluetoothManager getManager() { return mBluetoothManager; }
public BluetoothAdapter getAdapter() { return mBluetoothAdapter; }
public BluetoothDevice getDevice() { return mBluetoothDevice; }
public BluetoothGatt getGatt() { return mBluetoothGatt; }
public BluetoothGattService getCachedService() { return mBluetoothSelectedService; }
public List<BluetoothGattService> getCachedServices() { return mBluetoothGattServices; }
public boolean isConnected() { return mConnected; }
/* run test and check if this device has BT and BLE hardware available */
public boolean checkBleHardwareAvailable() {
// First check general Bluetooth Hardware:
// get BluetoothManager...
final BluetoothManager manager = (BluetoothManager) mParent.getSystemService(Context.BLUETOOTH_SERVICE);
if(manager == null) return false;
// .. and then get adapter from manager
final BluetoothAdapter adapter = manager.getAdapter();
if(adapter == null) return false;
// and then check if BT LE is also available
boolean hasBle = mParent.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
return hasBle;
}
/* before any action check if BT is turned ON and enabled for us
* call this in onResume to be always sure that BT is ON when Your
* application is put into the foreground */
public boolean isBtEnabled() {
final BluetoothManager manager = (BluetoothManager) mParent.getSystemService(Context.BLUETOOTH_SERVICE);
if(manager == null) return false;
final BluetoothAdapter adapter = manager.getAdapter();
if(adapter == null) return false;
return adapter.isEnabled();
}
/* start scanning for BT LE devices around */
public void startScanning() {
mBluetoothAdapter.startLeScan(mDeviceFoundCallback);
}
/* stops current scanning */
public void stopScanning() {
mBluetoothAdapter.stopLeScan(mDeviceFoundCallback);
}
/* initialize BLE and get BT Manager & Adapter */
public boolean initialize() {
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) mParent.getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
return false;
}
}
if(mBluetoothAdapter == null) mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
return false;
}
return true;
}
/* connect to the device with specified address */
public boolean connect(final String deviceAddress) {
if (mBluetoothAdapter == null || deviceAddress == null) return false;
mDeviceAddress = deviceAddress;
// check if we need to connect from scratch or just reconnect to previous device
if(mBluetoothGatt != null && mBluetoothGatt.getDevice().getAddress().equals(deviceAddress)) {
// just reconnect
return mBluetoothGatt.connect();
}
else {
// connect from scratch
// get BluetoothDevice object for specified address
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
if (mBluetoothDevice == null) {
// we got wrong address - that device is not available!
return false;
}
// connect with remote device
mBluetoothGatt = mBluetoothDevice.connectGatt(mParent, false, mBleCallback);
}
return true;
}
/* disconnect the device. It is still possible to reconnect to it later with this Gatt client */
public void disconnect() {
if(mBluetoothGatt != null) mBluetoothGatt.disconnect();
mUiCallback.uiDeviceDisconnected(mBluetoothGatt, mBluetoothDevice);
}
/* close GATT client completely */
public void close() {
if(mBluetoothGatt != null) mBluetoothGatt.close();
mBluetoothGatt = null;
}
/* request new RSSi value for the connection*/
public void readPeriodicalyRssiValue(final boolean repeat) {
mTimerEnabled = repeat;
// check if we should stop checking RSSI value
if(mConnected == false || mBluetoothGatt == null || mTimerEnabled == false) {
mTimerEnabled = false;
return;
}
mTimerHandler.postDelayed(new Runnable() {
#Override
public void run() {
if(mBluetoothGatt == null ||
mBluetoothAdapter == null ||
mConnected == false)
{
mTimerEnabled = false;
return;
}
// request RSSI value
mBluetoothGatt.readRemoteRssi();
// add call it once more in the future
readPeriodicalyRssiValue(mTimerEnabled);
}
}, RSSI_UPDATE_TIME_INTERVAL);
}
/* starts monitoring RSSI value */
public void startMonitoringRssiValue() {
readPeriodicalyRssiValue(true);
}
/* stops monitoring of RSSI value */
public void stopMonitoringRssiValue() {
readPeriodicalyRssiValue(false);
}
/* request to discover all services available on the remote devices
* results are delivered through callback object */
public void startServicesDiscovery() {
if(mBluetoothGatt != null) mBluetoothGatt.discoverServices();
}
/* gets services and calls UI callback to handle them
* before calling getServices() make sure service discovery is finished! */
public void getSupportedServices() {
if(mBluetoothGattServices != null && mBluetoothGattServices.size() > 0) mBluetoothGattServices.clear();
// keep reference to all services in local array:
if(mBluetoothGatt != null) mBluetoothGattServices = mBluetoothGatt.getServices();
mUiCallback.uiAvailableServices(mBluetoothGatt, mBluetoothDevice, mBluetoothGattServices);
}
/* get all characteristic for particular service and pass them to the UI callback */
public void getCharacteristicsForService(final BluetoothGattService service) {
if(service == null) return;
List<BluetoothGattCharacteristic> chars = null;
chars = service.getCharacteristics();
mUiCallback.uiCharacteristicForService(mBluetoothGatt, mBluetoothDevice, service, chars);
// keep reference to the last selected service
mBluetoothSelectedService = service;
}
/* request to fetch newest value stored on the remote device for particular characteristic */
public void requestCharacteristicValue(BluetoothGattCharacteristic ch) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) return;
mBluetoothGatt.readCharacteristic(ch);
// new value available will be notified in Callback Object
}
/* get characteristic's value (and parse it for some types of characteristics)
* before calling this You should always update the value by calling requestCharacteristicValue() */
public void getCharacteristicValue(BluetoothGattCharacteristic ch) {
if (mBluetoothAdapter == null || mBluetoothGatt == null || ch == null) return;
byte[] rawValue = ch.getValue();
String strValue = null;
int intValue = 0;
// lets read and do real parsing of some characteristic to get meaningful value from it
UUID uuid = ch.getUuid();
if(uuid.equals(BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT)) { // heart rate
// follow https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
// first check format used by the device - it is specified in bit 0 and tells us if we should ask for index 1 (and uint8) or index 2 (and uint16)
int index = ((rawValue[0] & 0x01) == 1) ? 2 : 1;
// also we need to define format
int format = (index == 1) ? BluetoothGattCharacteristic.FORMAT_UINT8 : BluetoothGattCharacteristic.FORMAT_UINT16;
// now we have everything, get the value
intValue = ch.getIntValue(format, index);
strValue = intValue + " bpm"; // it is always in bpm units
}
else if (uuid.equals(BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT) || // manufacturer name string
uuid.equals(BleDefinedUUIDs.Characteristic.MODEL_NUMBER_STRING) || // model number string)
uuid.equals(BleDefinedUUIDs.Characteristic.FIRMWARE_REVISION_STRING)) // firmware revision string
{
// follow https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.manufacturer_name_string.xml etc.
// string value are usually simple utf8s string at index 0
strValue = ch.getStringValue(0);
}
else if(uuid.equals(BleDefinedUUIDs.Characteristic.APPEARANCE)) { // appearance
// follow: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml
intValue = ((int)rawValue[1]) << 8;
intValue += rawValue[0];
strValue = BleNamesResolver.resolveAppearance(intValue);
}
else if(uuid.equals(BleDefinedUUIDs.Characteristic.BODY_SENSOR_LOCATION)) { // body sensor location
// follow: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.body_sensor_location.xml
intValue = rawValue[0];
strValue = BleNamesResolver.resolveHeartRateSensorLocation(intValue);
}
else if(uuid.equals(BleDefinedUUIDs.Characteristic.BATTERY_LEVEL)) { // battery level
// follow: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.battery_level.xml
intValue = rawValue[0];
strValue = "" + intValue + "% battery level";
}
else {
// not known type of characteristic, so we need to handle this in "general" way
// get first four bytes and transform it to integer
intValue = 0;
if(rawValue.length > 0) intValue = (int)rawValue[0];
if(rawValue.length > 1) intValue = intValue + ((int)rawValue[1] << 8);
if(rawValue.length > 2) intValue = intValue + ((int)rawValue[2] << 8);
if(rawValue.length > 3) intValue = intValue + ((int)rawValue[3] << 8);
if (rawValue.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(rawValue.length);
for(byte byteChar : rawValue) {
stringBuilder.append(String.format("%c", byteChar));
}
strValue = stringBuilder.toString();
}
}
String timestamp = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS").format(new Date());
mUiCallback.uiNewValueForCharacteristic(mBluetoothGatt,
mBluetoothDevice,
mBluetoothSelectedService,
ch,
strValue,
intValue,
rawValue,
timestamp);
}
/* reads and return what what FORMAT is indicated by characteristic's properties
* seems that value makes no sense in most cases */
public int getValueFormat(BluetoothGattCharacteristic ch) {
int properties = ch.getProperties();
if((BluetoothGattCharacteristic.FORMAT_FLOAT & properties) != 0) return BluetoothGattCharacteristic.FORMAT_FLOAT;
if((BluetoothGattCharacteristic.FORMAT_SFLOAT & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SFLOAT;
if((BluetoothGattCharacteristic.FORMAT_SINT16 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SINT16;
if((BluetoothGattCharacteristic.FORMAT_SINT32 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SINT32;
if((BluetoothGattCharacteristic.FORMAT_SINT8 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SINT8;
if((BluetoothGattCharacteristic.FORMAT_UINT16 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_UINT16;
if((BluetoothGattCharacteristic.FORMAT_UINT32 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_UINT32;
if((BluetoothGattCharacteristic.FORMAT_UINT8 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_UINT8;
return 0;
}
/* set new value for particular characteristic */
public void writeDataToCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite) {
if (mBluetoothAdapter == null || mBluetoothGatt == null || ch == null) return;
// first set it locally....
ch.setValue(dataToWrite);
// ... and then "commit" changes to the peripheral
mBluetoothGatt.writeCharacteristic(ch);
}
/* enables/disables notification for characteristic */
public void setNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) return;
boolean success = mBluetoothGatt.setCharacteristicNotification(ch, enabled);
if(!success) {
Log.e("------", "Seting proper notification status for characteristic failed!");
}
// This is also sometimes required (e.g. for heart rate monitors) to enable notifications/indications
// see: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
BluetoothGattDescriptor descriptor = ch.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if(descriptor != null) {
byte[] val = enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
descriptor.setValue(val);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
/* defines callback for scanning results */
private BluetoothAdapter.LeScanCallback mDeviceFoundCallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
mUiCallback.uiDeviceFound(device, rssi, scanRecord);
}
};
/* callbacks called for any action on particular Ble Device */
private final BluetoothGattCallback mBleCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
mConnected = true;
mUiCallback.uiDeviceConnected(mBluetoothGatt, mBluetoothDevice);
// now we can start talking with the device, e.g.
mBluetoothGatt.readRemoteRssi();
// response will be delivered to callback object!
// in our case we would also like automatically to call for services discovery
startServicesDiscovery();
// and we also want to get RSSI value to be updated periodically
startMonitoringRssiValue();
}
else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
mConnected = false;
mUiCallback.uiDeviceDisconnected(mBluetoothGatt, mBluetoothDevice);
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// now, when services discovery is finished, we can call getServices() for Gatt
getSupportedServices();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status)
{
// we got response regarding our request to fetch characteristic value
if (status == BluetoothGatt.GATT_SUCCESS) {
// and it success, so we can get the value
getCharacteristicValue(characteristic);
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic)
{
// characteristic's value was updated due to enabled notification, lets get this value
// the value itself will be reported to the UI inside getCharacteristicValue
getCharacteristicValue(characteristic);
// also, notify UI that notification are enabled for particular characteristic
mUiCallback.uiGotNotification(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, characteristic);
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
String deviceName = gatt.getDevice().getName();
String serviceName = BleNamesResolver.resolveServiceName(characteristic.getService().getUuid().toString().toLowerCase(Locale.getDefault()));
String charName = BleNamesResolver.resolveCharacteristicName(characteristic.getUuid().toString().toLowerCase(Locale.getDefault()));
String description = "Device: " + deviceName + " Service: " + serviceName + " Characteristic: " + charName;
// we got response regarding our request to write new value to the characteristic
// let see if it failed or not
if(status == BluetoothGatt.GATT_SUCCESS) {
mUiCallback.uiSuccessfulWrite(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, characteristic, description);
}
else {
mUiCallback.uiFailedWrite(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, characteristic, description + " STATUS = " + status);
}
};
#Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
if(status == BluetoothGatt.GATT_SUCCESS) {
// we got new value of RSSI of the connection, pass it to the UI
mUiCallback.uiNewRssiAvailable(mBluetoothGatt, mBluetoothDevice, rssi);
}
};
};
private Activity mParent = null;
private boolean mConnected = false;
private String mDeviceAddress = "";
private BluetoothManager mBluetoothManager = null;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothDevice mBluetoothDevice = null;
private BluetoothGatt mBluetoothGatt = null;
private BluetoothGattService mBluetoothSelectedService = null;
private List<BluetoothGattService> mBluetoothGattServices = null;
private Handler mTimerHandler = new Handler();
private boolean mTimerEnabled = false;
}
replace your startScanning method of BleWrapper class to below code and it should work.
public void startScanning() {
if(mBluetoothAdapter == null)
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.startLeScan(mDeviceFoundCallback);
}
mBluetoothAdapter is null and therefore the startLeScan method call fails
Hope this helps :)
This might sound really basic but I'm a beginner at Android BLE Development.
So far I am able to Create my Nexus 9 device as a peripheral device and Moto G as Central. Plus i'm connecting the devices successfully. But i cant figure out that when i send a characteristic from central device where will it be received from peripheral? The Advertise call back only return if advertisement is started successfully of not(Which in my case is successful)
Here is my peripheral Code
btleGattCallback = new BluetoothGattCallback() {
#Override
// Result of a characteristic read operation
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
#Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
// this will get called when a device connects or disconnects
}
#Override
// New services discovered
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w("doscover", "onServicesDiscovered received: " + status);
}
}
};
BeaconParser bp = new BeaconParser().setBeaconLayout("m:2-3=0,i:4-19,p:20-20");
mBeaconTransmitter = new BeaconTransmitter(this, bp );
// Transmit a beacon with Identifiers 2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6 1 2
Long li = new Long(-0l);
ArrayList<Long> l = new ArrayList<Long>();
l.add(li);
Beacon beacon = new Beacon.Builder()
.setId1("2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6")
.setId2("1")
.setId3("2")
.setManufacturer(0x00ff) // Choose a number of 0x00ff or less as some devices cannot detect beacons with a manufacturer code > 0x00ff
.setTxPower(-59)
.setDataFields(l)
.build();
mBeaconTransmitter.startAdvertising(beacon);
And the Central Code ofcorse
btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE) ;
madapter = btManager.getAdapter();
if (madapter != null && !madapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent,1);
}
mHandler = new Handler();
btleGattCallback = new BluetoothGattCallback() {
#Override
// Result of a characteristic read operation
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
#Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
Log.w("doscover", "Connected " + status);
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.w("discover", "onServicesDiscovered received: " + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w("doscover", "onServicesDiscovered received: " + status);
}
}
};
devicefound = new ArrayList<BluetoothDevice>();
devices = new ArrayAdapter<String>( this , R.layout.device_name);
ListView pairedListView = (ListView) findViewById(R.id.textView3);
pairedListView.setAdapter(devices);
mleScanCallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
devicefound.add(device);
//UUID = device.getAddress().toString();
//Log.e("Search", "" + device.getName().toString() );
//Toast`.makeText(context,device.getName().toString(), 1 ).show();
Log.e("Search", "" + device.toString());
Log.e("Search", "" + String.valueOf(rssi) );
devices.add("Nex" + device.getName() );
//mBluetoothGatt = device.connectGatt(getActivity(), true, btleGattCallback);
}
};
pairedListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter, View arg1,
int position, long id) {
dv = devicefound.get(position);
mBluetoothGatt = dv.connectGatt(getApplicationContext() , true, btleGattCallback);
mBluetoothGatt.connect();
dv.createBond();
//BluetoothDevice dd = madapter.getRemoteDevice(devicefound.get(position).getAddress());
if(i == false)
{
Toast.makeText(getApplicationContext(), "Service found. Connected to " + dv.getName() , 1).show();
}
else
{
Toast.makeText(getApplicationContext(), "Already Connected " + dv.getName() , 1).show();
}
i = mBluetoothGatt.connect();
mBluetoothGatt.beginReliableWrite();
boolean b = mBluetoothGatt.discoverServices();
Log.e("Discovery Started", "" + b );
Log.e("get ItemATPosition", "" + adapter.getItemAtPosition(position));
//BluetoothGattService Service = mBluetoothGatt.getService( );
// = Service.getCharacteristic(k);
BluetoothGattCharacteristic characteristic= new BluetoothGattCharacteristic(k,2,1); //Service
characteristic.setValue( "This is a Charects ");
byte[] value = {(byte)91,(byte)92,(byte)93};
characteristic.setValue(value);
boolean st = mBluetoothGatt.writeCharacteristic(characteristic);
//Toast.makeText(getActivity(), t1.getText() + " " + st, 1).show();
boolean b1 = mBluetoothGatt.executeReliableWrite();
//Toast.makeText(getActivity(), t1.getText() + " " + b1, 1).show();
Intent in = new Intent("ACTION_BOND_STATE_CHANGED.");
sendBroadcast(in);
}
});
scanLeDevice(true);
I'm using the writeCharacteristic command of the connected Gatt from central but dont know how to receive from Peripheral end
Any sort of Help will be appreciated.
peripheral mode may support good on IOS, I suggest you use light blue for test purpose, from here:https://itunes.apple.com/us/app/lightblue-bluetooth-low-energy/id557428110?mt=8
and as I know Lollipop 5 installed on nexus 6 and nexus 9 can support peripheral mode, nexus 5 do not support it.
Moto G support peripheral mode?