I read almost every single one thread about BLE on the stackoverflow, nordicsemi devzone, online tutorials etc but still can't solve problem of connecting 10> BLE devices to android in parallel communication. I know that theoretically android limit is 7 so when I write 10 I mean 10 in minimum amount of time.
Setup is standard. Service, GATT for every device, broadcast listeners, leScanCallbacks etc.
Problem occurs when few devices are communicating with android and it occurs on different communication steps.
Cases are like this:
One device is connected and communicating, second also, third one is trying to connect but it keeps failing. After successful connection of teh third one, two new devices are trying to connect but keep failing.
Problem also occurs when few devices are communicating but are on different steps. One is on discovery, second on reading battery level and the third one can't connect until those two finish and disconnect/close.
Sometimes response doesn't come and after programatically set timeout requests retry nothing happens etc
I could write xy failed cases but there wouldn't be pattern which could point out obvious error and that is the main problem. Only thing certain is that some kind of collision exists and "block" response from device to my broadcast receiver but that collision is very odd since android device supports 7 connections at the time and sometimes i don't get response even when <7 devices are communicating simultaneously.
When I set communication to go one after another(when previous disconnects start next) everything goes smoothly but that takes time and time is the main reason I am trying to sync max number of devices asynchronously.
My question is does multiple communicating through bluetooth could work and if it can how to achieve it on android? I saw example of iOS handling it through multiple threads but I don't have the idea how to achieve in android with broadcast receiver so I put communication steps in different threads but it changed nothing.
This is an example of how I use BLE threads:
From the main java I start it:
uartBluetoothLaser = new UartBluetooth(this, mHandler);
uartBluetoothRF = new UartBluetooth(this, mHandler);
btAdapter = bluetoothManager.getAdapter();
btAdapter.startLeScan(scanCallback);
uartBluetoothLaser.connect(mainActivity,btAdapter,bluetoothDevice);
And this is the thread code:
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.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import java.util.ArrayList;
import java.util.UUID;
import com.rsradar.app.MainActivity;
import com.rsradar.app.model.Constants;
public class UartBluetooth {
private final static String TAG = UartBluetooth.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private BluetoothDevice device;
private MainActivity mainActivity;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.nordicsemi.nrfUART.ACTION_GATT_CONNECTED";
public final static String ACTION_DATA_AVAILABLE =
"com.nordicsemi.nrfUART.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.nordicsemi.nrfUART.EXTRA_DATA";
public final static String DEVICE_DOES_NOT_SUPPORT_UART =
"com.nordicsemi.nrfUART.DEVICE_DOES_NOT_SUPPORT_UART";
public static final UUID TX_POWER_UUID = UUID.fromString("00001804-0000-1000-8000-00805f9b34fb");
public static final UUID TX_POWER_LEVEL_UUID = UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb");
public static final UUID CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
public static final UUID FIRMWARE_REVISON_UUID = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
public static final UUID DIS_UUID = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb");
public static final UUID RX_SERVICE_UUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
public static final UUID RX_CHAR_UUID = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
public static final UUID TX_CHAR_UUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
// Debugging
private static final boolean D = true;
// Name for the SDP record when creating server socket
private static final String NAME = "BluetoothMulti";
// Member fields
private final BluetoothAdapter mAdapter;
private final Handler mHandler;
private int mState;
private ArrayList<String> mDeviceAddresses;
private ArrayList<BluetoothSocket> mSockets;
/**
* A bluetooth piconet can support up to 7 connections. This array holds 7 unique UUIDs.
* When attempting to make a connection, the UUID on the client must match one that the server
* is listening for. When accepting incoming connections server listens for all 7 UUIDs.
* When trying to form an outgoing connection, the client tries each UUID one at a time.
*/
private ArrayList<UUID> mUuids;
// Constants that indicate the current connection state
public static final int STATE_NONE = 0; // we're doing nothing
public static final int STATE_LISTEN = 1; // now listening for incoming connections
//public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
//public static final int STATE_CONNECTED = 3; // now connected to a remote device
public UartBluetooth(Context context, Handler handler) {
mAdapter = BluetoothAdapter.getDefaultAdapter();
mState = STATE_NONE;
mHandler = handler;
mDeviceAddresses = new ArrayList<String>();
mSockets = new ArrayList<BluetoothSocket>();
mUuids = new ArrayList<UUID>();
// 7 randomly-generated UUIDs. These must match on both server and client.
mUuids.add(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
mUuids.add(UUID.fromString("2d64189d-5a2c-4511-a074-77f199fd0834"));
}
/**
* Set the current state of the chat connection
* #param state An integer defining the current connection state
*/
private synchronized void setState(int state) {
if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
mState = state;
// Give the new state to the Handler so the UI Activity can update
mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
}
/**
* Return the current connection state. */
public synchronized int getState() {
return mState;
}
/**
* Start the chat service. Specifically start AcceptThread to begin a
* session in listening (server) mode. Called by the Activity onResume() */
public synchronized void start() {
if (D) Log.d(TAG, "start");
// Cancel any thread currently running a connection
//if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
setState(STATE_LISTEN);
}
/**
* Start the ConnectThread to initiate a connection to a remote device.
* #param device The BluetoothDevice to connect
*/
public synchronized void connect(MainActivity mainActivity, BluetoothAdapter bluetoothAdapter, BluetoothDevice device) {
this.mBluetoothAdapter = bluetoothAdapter;
this.mainActivity = mainActivity;
this.device = device;
Log.w(TAG, "test on try connect");
if (mBluetoothAdapter == null || device.getAddress() == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return;
}
Log.w(TAG, "run is running");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothGatt = device.connectGatt(mainActivity, true, mGattCallback);
}
mConnectionState = STATE_CONNECTING;
}
/**
* Stop all threads
*/
public synchronized void stop() {
if (D) Log.d(TAG, "stop");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if(mBluetoothGatt != null){
mBluetoothGatt.disconnect();
}
}
try {
this.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
setState(STATE_NONE);
}
/**
* This thread runs while listening for incoming connections. It behaves
* like a server-side client. It runs until a connection is accepted
* (or until cancelled).
*/
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
// TODO: handler
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = Constants.ACTION_GATT_LASER_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
// TODO: handler
mainActivity.btListener.listenerSet(false,device.getName());
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG, "mBluetoothGatt = " + mBluetoothGatt );
// TODO: handler
mainActivity.btListener.listenerSet(true,device.getName());
enableTXNotification();
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// TODO: handler
System.out.println(" read ok");
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
//System.out.println("RUN");
// TODO: handler
byte[] buffer = characteristic.getValue();
int deviceType = 0;
if (device.getName().equals(Constants.DEVICE_LASER)){
deviceType = 1;
} else if (device.getName().equals(Constants.DEVICE_RF)){
deviceType = 2;
}
mHandler.obtainMessage(Constants.MESSAGE_READ, deviceType, -1, buffer).sendToTarget();
//System.out.println(" change ok");
}
};
public void enableTXNotification()
{
/*
if (mBluetoothGatt == null) {
showMessage("mBluetoothGatt null" + mBluetoothGatt);
broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
return;
}
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
BluetoothGattService RxService = mBluetoothGatt.getService(RX_SERVICE_UUID);
if (RxService == null) {
return;
}
BluetoothGattCharacteristic TxChar = RxService.getCharacteristic(TX_CHAR_UUID);
if (TxChar == null) {
return;
}
mBluetoothGatt.setCharacteristicNotification(TxChar,true);
BluetoothGattDescriptor descriptor = TxChar.getDescriptor(CCCD);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothGatt.readCharacteristic(characteristic);
}
}
}
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've developed and android chatting app, through the gRPC protocol, problem is, after I send the first message to the server, the server propagates the message to the available clients but then it runs in to this Exception: "io.grpc.StatusRuntimeException: CANCELLED: cancelled before receiving half close"
I've scoured the web for a hint at what could be wrong or a fix but I've got pretty much nothing useful.
This is my server class function that deals with the clients messages
public StreamObserver<Messaging.Message> sendReceiveMessage(StreamObserver<Messaging.Message> responseObserver) {
// return super.sendReceiveMessage(responseObserver);
//While you are on the inside of the chat the bilateral stream will be a continue one
observers.add(responseObserver);
return new StreamObserver<Messaging.Message>() {
#Override
public void onNext(Messaging.Message message) {
//receiving data from client
System.out.println(String.format("Got a message from: '%s' : '%s'", message.getMessageOwnerId(), message.getMessage()));
observers.stream().forEach(o -> {
o.onNext(message);
});
}
#Override
public void onError(Throwable throwable) {
observers.remove(responseObserver);
}
#Override
public void onCompleted() {
observers.remove(responseObserver);
}
};
}
This is the protobuff for the messages
service MessagingService{
rpc GetMessagingHistory(MessageHistoryRequest) returns (stream Message);
rpc SendReceiveMessage(stream Message) returns (stream Message);
}
message MessageHistoryRequest{
int32 chat_id = 1;
int32 last_message_id = 2;
}
message Message{
int32 message_id = 1;
int32 chat_id = 2;
int32 message_owner_id = 3;
string message = 4;
}
and lastly the client side code:
private int CHAT_ID;
private ImageView profilePicture;
private TextView name;
private Button sendButton;
private EditText messageEditText;
private RecyclerView chat;
private MessageAdapter msgAdapter;
private Vector<Messaging.Message> messages = new Vector<>();
private ManagedChannel channel;
private MessagingServiceGrpc.MessagingServiceStub stub;
private StreamObserver<Messaging.Message> toServer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
initViews();
}
private void createStub(){
channel = ManagedChannelBuilder.forAddress("10.0.2.2",8080)
.usePlaintext()
.build();
stub = MessagingServiceGrpc.newStub(channel);
toServer = stub.sendReceiveMessage(new StreamObserver<Messaging.Message>() {
#Override
public void onNext(Messaging.Message value) {
msgAdapter.receiveMessage(value);
Log.e("MYERROR",value.getMessageOwnerId() + " : " + value.getMessage() );
msgAdapter.notifyDataSetChanged();
}
#Override
public void onError(Throwable t) {
//nothing
t.printStackTrace();
}
#Override
public void onCompleted() {
Log.e("Completed:","Why");
}
});
}
private void initViews(){
profilePicture = findViewById(R.id.profilePictureChatMenu);
name = findViewById(R.id.chatNameTextView);
Intent intent = getIntent();
CHAT_ID = intent.getIntExtra("ID",-1);
name.setText(intent.getStringExtra("NAME"));
sendButton = findViewById(R.id.sendCommentButton);
messageEditText = findViewById(R.id.writingBarEditText);
chat = findViewById(R.id.chat);
chat.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
msgAdapter = new MessageAdapter(getApplicationContext(),messages);
chat.setAdapter(msgAdapter);
createStub();
sendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Messaging.Message sentMessage = Messaging.Message.newBuilder()
.setChatId(CHAT_ID)
.setMessage(messageEditText.getText().toString())
.setMessageOwnerId(ApplicationController.getAccount().getId())
.build();
toServer.onNext(sentMessage);
messageEditText.setText("");
msgAdapter.receiveMessage(sentMessage);
msgAdapter.notifyDataSetChanged();
}
});
}
The message is just informing the server the client cancelled. There's nothing more for the server to do other than clean up resources.
The cancellation may have been triggered because of an I/O failure. The TCP connection may have been dead and sending a message on the connection exposed the fact the TCP connection died (TCP can only reliably detect breakages by performing a write).
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 );
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 :)
I am trying to create a service which should log accelerometer values and timestamp on disk as fast as possible.
It works fine as long as the Activity is present, but the problem is as soon as I quit the Activity which this service is coming with, the service stops. I put a toast in onCreate, onStartCommand and onDestroy, for first two, it works normally but it never shows anything on onDestroy so I am clue less what is the cause. I also put breakpoints in Android Studio on onDestroy but it does not fire too.
Here is the complete code, please let me know what you think can be the problem:
package com.embedonix.mobilehealth.services.accelerometerlog;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Environment;
import android.os.IBinder;
import android.widget.Toast;
import com.embedonix.mobilehealth.AppConstants;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AccelerometerLogService extends Service {
private boolean mIsServiceStarted = false;
private Context mContext = null;
private SensorManager mSensorManager = null;
private Sensor mSensor;
private File mLogFile = null;
private FileOutputStream mFileStream = null;
private AccelerometerLogService mReference = null;
private Float[] mValues = null;
private long mTimeStamp = 0;
private ExecutorService mExecutor = null;
/**
* Default empty constructor needed by Android OS
*/
public AccelerometerLogService() {
super();
}
/**
* Constructor which takes context as argument
*
* #param context
*/
public AccelerometerLogService(Context context) {
super();
if (context != null)
mContext = context;
else
mContext = getBaseContext();
}
#Override
public void onCreate() {
super.onCreate();
Toast.makeText(getBaseContext(), "Service onCreate", Toast.LENGTH_SHORT).show();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (isServiceStarted() == false) {
mContext = getBaseContext();
mReference = this;
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mValues = new Float[]{0f, 0f, 0f};
mTimeStamp = 0;
mExecutor = Executors.newSingleThreadExecutor();
setupFolderAndFile();
startLogging();
}
//set started to true
mIsServiceStarted = true;
Toast.makeText(mContext, "Service onStartCommand", Toast.LENGTH_SHORT).show();
return Service.START_STICKY;
}
private void setupFolderAndFile() {
mLogFile = new File(Environment.getExternalStorageDirectory().toString()
+ "/" + AppConstants.APP_LOG_FOLDER_NAME + "/test.txt");
try {
mFileStream = new FileOutputStream(mLogFile, true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private void startLogging() {
mExecutor.execute(new Runnable() {
#Override
public void run() {
mSensorManager.registerListener(
new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
mTimeStamp = System.currentTimeMillis();
mValues[0] = sensorEvent.values[0];
mValues[1] = sensorEvent.values[1];
mValues[2] = sensorEvent.values[2];
String formatted = String.valueOf(mTimeStamp)
+ "\t" + String.valueOf(mValues[0])
+ "\t" + String.valueOf(mValues[1])
+ "\t" + String.valueOf(mValues[2])
+ "\r\n";
try {
mFileStream.write(formatted.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
}, mSensor, SensorManager.SENSOR_DELAY_FASTEST
);
}
});
}
#Override
public void onDestroy() {
super.onDestroy();
//Flush and close file stream
if (mFileStream != null) {
try {
mFileStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
mFileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Toast.makeText(mContext, "Service onDestroy", Toast.LENGTH_LONG).show();
mIsServiceStarted = false;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Indicates if service is already started or not
*
* #return
*/
public boolean isServiceStarted() {
return mIsServiceStarted;
}
}
UPDATE 1
I found out that this happens on Nexus 7 tablet. The same code works fine on my phone, MotoG. Both devices have Android 4.4.2 installed....what can be the reason?
I think the problem exists into the logic to log accelerometer values and timestamp using services. An android service run on the main UI thread. If your service needs to do work in the background, it needs to be launched in a separate thread (like AsyncTask does) explicitly.
Running on the main thread you run the risk to interrupt UI responsiveness and in my opinion this is the root of your problems. As a result it may run ok on some devices and on some others no.
My advice is inside your service to run an AsyncTask as a background worker for logging accelerometer values.
Inside you class AccelerometerLogService you implement an AsyncTask like that :
public class AccelerometerLogService extends Service {
..........................
/**
* #author
* Private class which logs accelerometer values and timestamp.
*/
private class AsyncTaskRunner extends AsyncTask<String, String, String> {
private String resp;
#Override
protected String doInBackground(String... params) {
publishProgress("running..."); // Calls onProgressUpdate()
try {
setupFolderAndFile(); // Here you are doing the job
startLogging();
} catch (Exception e) {
e.printStackTrace();
resp = e.getMessage();
}
return resp;
}
/*
* #see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
#Override
protected void onPostExecute(String result) {
// execution of result of Long time consuming operation
finalResult.setText(result);
}
/*
* #see android.os.AsyncTask#onPreExecute()
*/
#Override
protected void onPreExecute() {
// Things to be done before execution of long running operation. For
// example showing ProgessDialog
}
/*
* #see android.os.AsyncTask#onProgressUpdate(Progress[])
*/
#Override
protected void onProgressUpdate(String... text) {
// Things to be done while execution of long running operation is in
// progress. For example updating ProgessDialog
}
}}
and you call the AsyncTaskRunner inside onStartCommand like :
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (isServiceStarted() == false) {
mContext = getBaseContext();
mReference = this;
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mValues = new Float[]{0f, 0f, 0f};
mTimeStamp = 0;
mExecutor = Executors.newSingleThreadExecutor();
_runner = new AsyncTaskRunner(); // Start backgroud thread here
_runner.execute();
}
//set started to true
mIsServiceStarted = true;
Toast.makeText(mContext, "Service onStartCommand", Toast.LENGTH_SHORT).show();
return Service.START_STICKY;
}
runner is a private variable initialized like that :
public class AccelerometerLogService extends Service {
private boolean mIsServiceStarted = false;
.............
AsyncTaskRunner _runner = null;
.............
This mainly describes my solution but it demands a little more work from you. Hope it's more clear now.
Try change START_STICKY -> START_REDELIVER_INTENT. Although the documentation states that the method onDestroy() is called before the destruction of the service, but in practice, this method is not always called. Personally faced with this and most likely this is due to the implementation of the service (in accordance with the recommendations of Google)
None of the above are the answer. Unfortunately this is a known problem with Nexus 7 and propably Nexus 5 products of Google.
Here is the link to the bugreport (although I think its an intentional feautur!!!)
https://code.google.com/p/android/issues/detail?id=63793