I'm trying to detect ibeacon using an android smartphone. I bought ibeacon devices from a company that has provided me android library to make them work (this library is much like the android beacon library for AltBeacon, such as the code that I used). Here is the MainActivity
public class MainActivity extends Activity implements IBeaconConsumer {
private static final String TAG = "BB-EXAPP";
// iBeacon bluetooth scanning parameters
private static final int FOREGROUND_SCAN_PERIOD = 1000;
private static final int FOREGROUND_BETWEEN_SCAN_PERIOD = 1000;
private static final int BACKGROUND_SCAN_PERIOD = 250;
private static final int BACKGROUND_BETWEEN_SCAN_PERIOD = 2000;
// iBeacon Library Stuff
private static final Region blueupRegion = new Region("BlueUp", "acfd065e-c3c0-11e3-9bbe-1a514932ac01", null, null);
private IBeaconManager iBeaconManager = IBeaconManager.getInstanceForApplication(this);
private Intent iBeaconService;
private boolean isMonitoring = false;
private boolean isRanging = false;
// Android BLE Stuff
private BluetoothAdapter mBluetoothAdapter;
private static final int REQUEST_ENABLE_BT = 1;
// UI Stuff
private List<IBeacon> beacons;
private ListView listView;
private IBeaconListAdapter listAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initializes Bluetooth adapter
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// Initializes iBeacon Service
iBeaconService = new Intent(getApplicationContext(), IBeaconService.class);
// Start the iBeacon Service
Log.d(TAG, "Starting service: iBeaconService");
startService(iBeaconService);
// Set desired scan periods
iBeaconManager.setForegroundScanPeriod(FOREGROUND_SCAN_PERIOD);
iBeaconManager.setForegroundBetweenScanPeriod(FOREGROUND_BETWEEN_SCAN_PERIOD);
iBeaconManager.setBackgroundScanPeriod(BACKGROUND_SCAN_PERIOD);
iBeaconManager.setBackgroundBetweenScanPeriod(BACKGROUND_BETWEEN_SCAN_PERIOD);
// Bind the iBeacon Service
iBeaconManager.bind(this);
//
// UI Initialization
//
// Create Empty IBeacons List
beacons = new ArrayList<IBeacon>();
// Create List Adapter
listAdapter = new IBeaconListAdapter(this, beacons);
// Get ListView
listView = (ListView)findViewById(R.id.listView);
// Set ListAdapter
listView.setAdapter(listAdapter);
}
#Override
public void onIBeaconServiceConnect() {
Log.d(TAG, "onIBeaconServiceConnect");
// Set Monitor Notifier
iBeaconManager.setMonitorNotifier(new MonitorNotifier() {
#Override
public void didExitRegion(Region region) {
Log.d(TAG, "didExitRegion: region = " + region.toString());
}
#Override
public void didEnterRegion(Region region) {
Log.d(TAG, "didEnterRegion: region = " + region.toString());
// Set Range Notifier
iBeaconManager.setRangeNotifier(new RangeNotifier() {
#Override
public void didRangeBeaconsInRegion(Collection<IBeacon> iBeacons, Region region) {
// Update UI iBeacons List
beacons = new ArrayList<IBeacon>(iBeacons);
listAdapter.notifyDataSetChanged();
// Log found iBeacons
Log.d(TAG, "didRangeBeaconsInRegion: region = " + region.toString());
if (!iBeacons.isEmpty()) {
int j = 0;
for (IBeacon beacon : iBeacons) {
Log.d(TAG, " [" + j + "] (Major = " + beacon.getMajor() + ", Minor = " + beacon.getMinor() + ", RSSI = " + beacon.getRssi() + ", Accuracy = " + beacon.getAccuracy() + ")");
j++;
}
}
}
});
// Start Ranging
try {
iBeaconManager.startRangingBeaconsInRegion(blueupRegion);
isRanging = true;
Log.d(TAG, "startRangingBeaconsInRegion: region = " + blueupRegion.toString());
} catch (RemoteException e) {
Log.d(TAG, "startRangingBeaconsInRegion [RemoteException]");
e.printStackTrace();
}
}
#Override
public void didDetermineStateForRegion(int state, Region region) {
Log.d(TAG, "didDetermineStateForRegion: state = " + state + ", region = " + region.toString());
}
});
// Start Monitoring
try {
iBeaconManager.startMonitoringBeaconsInRegion(blueupRegion);
isMonitoring = true;
Log.d(TAG, "startMonitoringBeaconsInRegion: region = " + blueupRegion.toString());
} catch (RemoteException e) {
Log.d(TAG, "startMonitoringBeaconsInRegion [RemoteException]");
e.printStackTrace();
}
}
#Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
if (iBeaconManager.isBound(this)) {
iBeaconManager.setBackgroundMode(this, false);
Log.d(TAG, "iBeaconManager.setBackgroundMode = false");
}
}
#Override
public void onStop() {
Log.d(TAG, "onStop");
if (iBeaconManager.isBound(this)) {
iBeaconManager.setBackgroundMode(this, true);
Log.d(TAG, "iBeaconManager.setBackgroundMode = true");
}
super.onStop();
}
#Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
if (isRanging) {
try {
iBeaconManager.stopRangingBeaconsInRegion(blueupRegion);
Log.d(TAG, "stopRangingBeaconsInRegion: region = " + blueupRegion.toString());
} catch (RemoteException e) {
Log.d(TAG, "stopRangingBeaconsInRegion [RemoteException]");
e.printStackTrace();
}
}
if (isMonitoring) {
try {
iBeaconManager.stopMonitoringBeaconsInRegion(blueupRegion);
Log.d(TAG, "stopMonitoringBeaconsInRegion: region = " + blueupRegion.toString());
} catch (RemoteException e) {
Log.d(TAG, "stopMonitoringBeaconsInRegion [RemoteException]");
e.printStackTrace();
}
}
if (iBeaconManager.isBound(this)) {
Log.d(TAG, "Unbinding iBeaconManager");
iBeaconManager.unBind(this);
}
Log.d(TAG, "Stopping service: iBeaconService");
stopService(iBeaconService);
super.onDestroy();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
There's a second activity to make the list:
public class IBeaconListAdapter extends BaseAdapter {
private Activity activity;
private List<IBeacon> beacons;
private static LayoutInflater inflater = null;
public IBeaconListAdapter(Activity _activity, List<IBeacon> _beacons) {
this.activity = _activity;
this.beacons = _beacons;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return beacons.size();
}
#Override
public Object getItem(int position) {
return beacons.get(position);
}
#Override
public long getItemId(int position) {
/*IBeacon beacon = beacons.get(position);
if (beacon != null) {
return beacon.hashCode();
}*/
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (convertView == null) {
view = inflater.inflate(R.layout.beacon_list_row, null);
}
TextView majorTextView = (TextView)view.findViewById(R.id.majorValue);
TextView minorTextView = (TextView)view.findViewById(R.id.minorValue);
TextView rssiTextView = (TextView)view.findViewById(R.id.rssiValue);
TextView accuracyTextView = (TextView)view.findViewById(R.id.accuracyValue);
IBeacon beacon = beacons.get(position);
if (beacon != null) {
DecimalFormat df = new DecimalFormat("#.0");
majorTextView.setText(beacon.getMajor());
minorTextView.setText(beacon.getMinor());
rssiTextView.setText(beacon.getRssi() + " dBm");
accuracyTextView.setText(df.format(beacon.getAccuracy()) + " m");
}
return view;
}
}
When I run the app on the phone, from the LogCat I can see that
it detects the devices, while on the phone I can't see anything but a blank page! I noticed from the LogCat that once the onIBeaconServiceConnect is called, than the app jumps to the startMonitoringBeaconsInRegion and never calls the didEnterRegion method, which includes the didRangeBeaconsInRegion and the code to fill the list.
I couldn't find other answers to a question similar to mine and I really don't know where is my mistake.
A few thoughts:
Make sure your beacons are really transmitting and have the identifiers you think they do. Try downloading an off-the-shelf app like Locate and make sure it sees them.
Your code sets up a region that is looking for a beacon with a UUID of acfd065e-c3c0-11e3-9bbe-1a514932ac01. Are you sure this is exactly right? Try replacing the region definition with Region("BlueUp", null, null, null); so it detects any region regardless of identifier.
If you plan to do significant development, I would strongly recommend you upgrade to the Android Beacon Library 2.0. The old 0.x library version that library is based off of is no longer supported or maintained, and it will be more difficult to find help when problems like this come up.
These are the significant lines in the LogCat.
D/BB-EXAPP(7756): onIBeaconServiceConnect
D/BB-EXAPP(7756): startMonitoringBeaconsInRegion: region = proximityUuid: acfd065e-c3c0-11e3-9bbe-1a514932ac01 major: null minor:null
D/AbsListView(7756): unregisterIRListener() is called
I/IBeaconService(7756): start monitoring received
D/BluetoothAdapter(7756): startLeScan(): null
D/BluetoothAdapter(7756): onClientRegistered() - status=0 clientIf=4
I/IBeaconService(7756): Adjusted scanStopTime to be Sat Jan 17 12:09:58 CET 2015
D/AbsListView(7756): unregisterIRListener() is called
D/BluetoothAdapter(7756): onScanResult() - Device=DA:0D:22:4B:40:17 RSSI=-59
D/Callback(7756): attempting callback via intent: ComponentInfo{com.android.appbeacon/com.blueup.libbeacon.IBeaconIntentProcessor}
D/BluetoothAdapter(7756): onScanResult() - Device=DA:0D:22:4B:40:17 RSSI=-64
I/IBeaconService(7756): iBeacon detected multiple times in scan cycle :acfd065e-c3c0-11e3-9bbe-1a514932ac01 0 0 accuracy: 0.7351236323265571 proximity: 2
D/BluetoothAdapter(7756): stopLeScan()
Related
I want to send BLE data in the image format given below.Advertising Data packet.
I am just sending test data1 in byte format but as shown in image I have to send data 0x22 length of first advertisement data type at 0x11 i have send length of second data type and so on. So how i should set it according to the image.
public class MainActivity extends AppCompatActivity {
private Button mAdvertiseButton,stopAdvertiseButton;
private static final String TAG = "BLEApp";
private BluetoothAdapter mBluetoothAdapter;
public static final int REQUEST_ENABLE_BT = 1;
private String Device_Name="Abc";
private String Device_Id ="0x34Abc";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdvertiseButton=(Button) findViewById(R.id.adv);
stopAdvertiseButton=(Button) findViewById(R.id.stopadv);
mAdvertiseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (savedInstanceState == null)
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
.getAdapter();
// Is Bluetooth supported on this device?
if (mBluetoothAdapter != null) {
// Is Bluetooth turned on?
if (mBluetoothAdapter.isEnabled()) {
// Are Bluetooth Advertisements supported on this device?
if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {
// Everything is supported and enabled, load the method
advertise();
} else {
// Bluetooth Advertisements are not supported.
showErrorText(R.string.bt_ads_not_supported);
}
} else {
// Prompt user to turn on Bluetooth (logic continues in onActivityResult()).
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
} else {
// Bluetooth is not supported.
showErrorText(R.string.bt_not_supported);
}
}
});
stopAdvertiseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// stopAdv();
}
});
}
#RequiresApi(api = Build.VERSION_CODES.O)
private void stopAdv() {
AdvertiseCallback callback= null;
BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
advertiser.stopAdvertising(null);
}
private void advertise() {
BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
AdvertisingSetParameters parameters = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
parameters = (new AdvertisingSetParameters.Builder())
.setLegacyMode(true) // True by default, but set here as a reminder.
.setConnectable(false)
.setInterval(AdvertisingSetParameters.INTERVAL_HIGH)
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM)
.build();
}
byte[] manufacturerData = new byte[] {
// 0x12, 0x34,
// 0x56, 0x66,
0x41,0x4d,0x4f,0x4c
};
String testData = "abcdefghij";
byte[] testData1=testData.getBytes();
ParcelUuid pUuid = new ParcelUuid( UUID.fromString("CDB7950D-73F1-4D4D-8E47-C090502DBD63"));
AdvertiseData data = (new AdvertiseData.Builder())
.addManufacturerData(1, testData1)
.setIncludeDeviceName(true)
.build();
//.addServiceData( pUuid, "Data".getBytes(Charset.forName("UTF-8") ) )
AdvertisingSetCallback callback= null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
callback = new AdvertisingSetCallback() {
#Override
public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {
super.onAdvertisingSetStarted(advertisingSet, txPower, status);
Log.d(TAG, "onAdvertisingSetStarted(): txPower:" + txPower + " , status: "
+ status);
AdvertisingSet currentAdvertisingSet = advertisingSet;
// After onAdvertisingSetStarted callback is called, you can modify the
// advertising data and scan response data:
currentAdvertisingSet.setAdvertisingData(new AdvertiseData.Builder().
addManufacturerData(67, testData1)
.setIncludeDeviceName(true)
.setIncludeTxPowerLevel(true).build());
// Wait for onAdvertisingDataSet callback...
ParcelUuid pUuid = new ParcelUuid(UUID.randomUUID());
currentAdvertisingSet.setScanResponseData(new
AdvertiseData.Builder().addServiceUuid(pUuid).build());
Log.d(TAG,"UUID"+pUuid);
//
// Wait for onScanResponseDataSet callback...
Log.d(TAG, data.toString() + status);
// currentAdvertisingSet.setScanResponseData(new AdvertiseData.Builder());
}
#Override
public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
super.onAdvertisingSetStopped(advertisingSet);
Log.d(TAG, "onAdvertisingSetStopped():");
}
#Override
public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) {
super.onAdvertisingEnabled(advertisingSet, enable, status);
}
#Override
public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
super.onAdvertisingDataSet(advertisingSet, status);
Log.d(TAG, "onAdvertisingDataSet() :status:" + status);
}
#Override
public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {
super.onScanResponseDataSet(advertisingSet, status);
Log.d(TAG, "onScanResponseDataSet(): status:" + status);
}
};
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
advertiser.startAdvertisingSet(parameters, data, null, null, null, callback);
Toast.makeText(MainActivity.this, "Data"+data, Toast.LENGTH_LONG).show();
}
// When done with the advertising:
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// advertiser.stopAdvertisingSet(callback);
// }
//
}
Android provides high-level API for building your BLE advertise message payload, so you only need to add the values of each handles (an handle is a triplet of length-dataType-data) of the message in the image.
For example to add a service (second handle of the message) you don't have to set the length (0x11) and the data type (0x07) but only use addServiceUuid of the AdvertiseData.Builder, the SDK will set the length and type for you. This is valid also for the last handle (the manufacturer data), you have to use the addManufacturerData.
For the first handle (the flags) you can't set with the AdvertiseData.Builder but the AdvertisingSetParameters.Builder(), here. But according this answer the limited discoverable mode is no longer supported
I have an android app to connect to a BLE device and write to it. I can successfully connect, read and write to it. As a part of testing, we are trying different disconnection scenarios.
Sometimes, if BLE device disconnects the connection, I get the connection change as disconnect with status value as 19. Also if there is any bond error, status equals 22. If I programmatically disconnect the connection, this status gives me 0. But none of these states except 0 are specified in android documentation.
Posting a sample BluetoothGattCallback
private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(TAG, "onConnectionStateChange status: "+status+", newState: "+newState);
/*i need to know the possible values for this status variable*/
if(newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else {
gatt.close();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "onServicesDiscovered service discovered");
}
};
Does anyone face this same problem and sorted out the list of statuses. I need to know the possible values for status variable in onConnectionStateChange method
Here is the list of codes i have
Programmatically disconnected - 0
Device went out of range - 8
Disconnected by device - 19
Issue with bond - 22
Device not found - 133(some phone it gives 62)
I have tested disconnect scenario's in 5.0.2, 5.1, 6.0 and 6.0.1. But only found this bond issue code in 6.0.1 android version.
Sorry to bring up an old question, but here is the solution for many of the problems i've had with Bluetooth (BLE) 4.0. Sorry again for the big classes below but be sure they are needed and no method there is irrelevant or unused.
public abstract class AbstractBluetoothBroadcaster extends BroadcastReceiver {
protected static final String LOG_TAG = BluetoothLowEnergy.LOG_TAG;
protected BluetoothLowEnergy bluetoothLowEnergy;
public AbstractBluetoothBroadcaster(BluetoothLowEnergy bluetoothLowEnergy, String action){
super();
this.bluetoothLowEnergy = bluetoothLowEnergy;
IntentFilter intentFilterStateChange = new IntentFilter(action);
intentFilterStateChange.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
this.bluetoothLowEnergy.getActivity().registerReceiver(this, intentFilterStateChange);
}
public void onDestroy(){
this.bluetoothLowEnergy.getActivity().unregisterReceiver(this);
}
}
public class BluetoothBondStateBroadcaster extends AbstractBluetoothBroadcaster {
private BluetoothLowEnergy bluetoothLowEnergy;
private boolean deviceBonded;
public BluetoothBondStateBroadcaster(BluetoothLowEnergy bluetoothLowEnergy) {
super(bluetoothLowEnergy, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
this.bluetoothLowEnergy = bluetoothLowEnergy;
this.deviceBonded = false;
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID())) {
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
switch (state) {
case BluetoothDevice.BOND_NONE:
Log.d(LOG_TAG, " NOT BONDED - dev " + bluetoothDevice.getAddress());
this.deviceBonded = false;
break;
case BluetoothDevice.BOND_BONDING:
Log.d(LOG_TAG, " BONDING ... - dev " + bluetoothDevice.getAddress());
break;
case BluetoothDevice.BOND_BONDED:
Log.d(LOG_TAG, " BONDED - dev " + bluetoothDevice.getAddress());
deviceBonded = true;
bluetoothLowEnergy.onBluetoothBonded();
break;
default:
break;
}
}
}
public void resetDeviceBonded(){
this.deviceBonded = false;
}
public boolean isDeviceBonded() {
return deviceBonded;
}
}
public class BluetoothPairingBroadcaster extends AbstractBluetoothBroadcaster {
private String devicePIN;
public BluetoothPairingBroadcaster(BluetoothLowEnergy bluetoothLowEnergy){
super(bluetoothLowEnergy, BluetoothDevice.ACTION_PAIRING_REQUEST);
this.devicePIN = "";
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int pairingType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID()) &&
!getDevicePIN().isEmpty()) {
if (pairingType == BluetoothDevice.PAIRING_VARIANT_PIN){
bluetoothDevice.setPin(getDevicePIN().getBytes());
Log.d(LOG_TAG," Auto-entering pin - " + getDevicePIN());
bluetoothDevice.createBond();
Log.d(LOG_TAG," pin entered and request sent...");
abortBroadcast();
}
}
}
public void setDevicePIN(String pin){
this.devicePIN = pin;
}
public String getDevicePIN(){
return this.devicePIN ;
}
}
public class BluetoothLowEnergy extends BluetoothGattCallback {
// listener that has the methods that the application (activity)
// will use to send / receive data, or to reflect the system state
// in the UI
public interface BluetoothListener {
/**
* Triggered when the scanning has started successfully
*/
void onBluetoothStartScan();
/**
* Triggered when the scanning stops
* #param scanResults results of the scanning
*/
void onBluetoothStopScan(Collection<BluetoothDevice> scanResults);
/**
* Triggered when the device is ready to send/receive data
*/
void onBluetoothConnectionReady();
/**
* Triggered when a bluetooth msg is received
* #param msg message received
*/
void onBluetoothReceiveMsg(String msg);
/**
* Triggered whenever data is send
* #param success true means data was sent fine to the remote device, false otherwise
*/
void onBluetoothSend(String data, boolean success);
/**
* Triggered if no bluetooth is connected, and we need a connection
* to send / receive / discover services
*/
void onBluetoothNotConnected();
}
// custom exceptions
public class BluetoothNotEnabledException extends Exception { }
public class BluetoothLowEnergyNotSupported extends Exception { }
public class BluetoothDeviceNotFound extends Exception { }
// service and characteristic uuids that are going to be used to
// send / receive data between central and peripheral GATTs
private static final String SERVICE_UUID = "FFE0-";
private static final String CHARACTERISTIC_UUID = "FFE1-";
// timeout for bluetooth scan (in ms)
public static final int SCAN_TIMEOUT = 5000;
// BLE LOG TAG
public static final String LOG_TAG = "BLUETOOTH_BLE";
// model
private boolean bluetoothScanning;
private boolean bluetoothConnected;
private Map<String, BluetoothDevice> bluetoothScanResults;
// gui
private Activity activity;
// bluetooth
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bluetoothLeScanner;
private ScanCallback bluetoothScanCallback;
private BluetoothGatt bluetoothGatt;
private BluetoothGattCharacteristic characteristic;
public BluetoothLowEnergy(Activity activity, BluetoothListener bluetoothListener){
this.activity = activity;
this.bluetoothListener = bluetoothListener;
// this keeps track of the scanning and connection states
this.bluetoothScanning = this.bluetoothConnected = false;
// keeps track of the scanning results
this.bluetoothScanResults = new HashMap<>();
// set bluetooth pairing request and bonded callback
// these broadcasters will be responsible to detect and validate
// the bonded state of your device
this.pairingRequestBroadcaster = new BluetoothPairingBroadcaster(this);
this.bondedBroadcaster = new BluetoothBondStateBroadcaster(this);
// set the scan callback methods that will add results to
// this.bluetoothScanResults map
this.bluetoothScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
addScanResult(result);
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result: results) {
addScanResult(result);
}
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(LOG_TAG, "Scan Failed with code " + errorCode);
}
private void addScanResult(ScanResult result) {
BluetoothDevice device = result.getDevice();
String deviceAddress = device.getAddress();
bluetoothScanResults.put(deviceAddress, device);
Log.d(LOG_TAG, "Found device " + deviceAddress);
}
};
// Use this to determine whether BLE is supported on the device.
if (!this.activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
throw new BluetoothLowEnergyNotSupported();
}
}
/**
* This method should be called when the activity is destroyed
*/
public void onDestroy(){
this.bondedBroadcaster.onDestroy();
this.pairingRequestBroadcaster.onDestroy();
this.disconnect();
}
/**
* This method is called when we finish pairing/bonding to the device
*/
public void onBluetoothBonded(){
// if we have the services already discovered, then we can
// send/receive data, to do so we call the bluetooth listener below
if (servicesDiscovered){
this.bluetoothListener.onBluetoothConnectionReady();
// if we know we have a connection established, then we can
// discover services
} else if (bluetoothConnected){
bluetoothGatt.discoverServices();
}
}
/**
* This method is called whenever a connection is established or a disconnection happens
*/
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
BluetoothDevice bluetoothDevice = gatt.getDevice();
// if these conditions == true, then we have a disconnect
if ( status == BluetoothGatt.GATT_FAILURE ||
status != BluetoothGatt.GATT_SUCCESS ||
newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Disconnected from %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
this.disconnect();
// if these conditions == true, then we have a successful connection
} else if (newState == BluetoothProfile.STATE_CONNECTED) {
bluetoothConnected = true;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Connected to %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
// this sleep is here to avoid TONS of problems in BLE, that occur whenever we start
// service discovery immediately after the connection is established
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
gatt.discoverServices();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
// BEGIN - find the service and characteristic that we want (defined as a static attribute
// of the BluetoothLowEnergy class)
Log.d(LOG_TAG, "Discovering services ...");
BluetoothGattService service = null;
for (BluetoothGattService serv: gatt.getServices()){
Log.d(LOG_TAG, "Found service " + serv.getUuid().toString());
if (serv.getUuid().toString().toUpperCase().contains(SERVICE_UUID)){
service = serv;
Log.d(LOG_TAG, "---> Selected service " + serv.getUuid().toString());
break;
}
}
if (service == null){
return;
}
for (BluetoothGattCharacteristic charac: service.getCharacteristics()){
Log.d(LOG_TAG, "Found characteristic " + charac.getUuid().toString());
if (charac.getUuid().toString().toUpperCase().contains(CHARACTERISTIC_UUID)){
this.characteristic = charac;
Log.d(LOG_TAG, "---> Selected characteristic " + charac.getUuid().toString());
break;
}
}
if (this.characteristic == null){
return;
}
Log.d(LOG_TAG, "Setting write and notification to the characteristic ...");
bluetoothAdapter.cancelDiscovery();
// END - find the service and characteristic
// set that we want to write to the selected characteristic and be notified if
// it changes (the remote GATT peripheral sends data to the Android's GATT Center)
this.characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
gatt.setCharacteristicNotification(this.characteristic, true);
// we finished service discovery
this.servicesDiscovered = true;
// if we have paired/bonded then we are ready to send/receive data
if (pairingRequestBroadcaster.getDevicePIN().isEmpty() || bondedBroadcaster.isDeviceBonded()) {
this.bluetoothListener.onBluetoothConnectionReady();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicRead(gatt, charac, status);
restartDisconnectTimeout();
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
try {
String characValue = new String(charac.getValue(), CHARSET)
.replaceAll(DATA_FILTER_REGEX,"");
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Read - %s",
characValue
));
if (charac.getUuid().equals(this.characteristic.getUuid())) {
this.bluetoothListener.onBluetoothReceiveMsg(characValue);
}
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Read - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicWrite(gatt, charac, status);
restartDisconnectTimeout();
try {
String characValue = new String(charac.getValue(), CHARSET);
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Write - SUCCESS - %s",
characValue
));
bluetoothListener.onBluetoothSend( characValue, (status == BluetoothGatt.GATT_SUCCESS) );
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Write - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic charac) {
super.onCharacteristicChanged(gatt, charac);
Log.d(LOG_TAG,"Characteristic Changed");
onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS);
}
/**
* Remove pairing/bonding of the device
* #param device Device to remove bonding
*/
public static void removeBond(BluetoothDevice device){
try {
if (device == null){
throw new Exception();
}
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
method.invoke(device, (Object[]) null);
Log.d(LOG_TAG, "removeBond() called");
Thread.sleep(600);
Log.d(LOG_TAG, "removeBond() - finished method");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Clears the GATT services cache, so that new services can be discovered
* #param bluetoothGatt GATT Client to clear service's discovery cache
*/
public static void refresh(BluetoothGatt bluetoothGatt){
try {
Method method = bluetoothGatt.getClass().getMethod("refresh", (Class[]) null);
method.invoke(bluetoothGatt, (Object[]) null);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Connect to the GATT Peripheral device
* #param uuid GATT Peripheral address / mac / uuid to connect to
* #param pin PIN to authenticate and pair to the device
*/
public void connect(String uuid, String pin) throws BluetoothNotEnabledException, BluetoothDeviceNotFound {
checkBluetooth();
// do not connect twice
if (this.isConnected()){
return;
}
// get device
BluetoothDevice device = this.bluetoothScanResults.get(uuid);
if (device == null){
throw new BluetoothDeviceNotFound();
}
this.deviceUUID = uuid;
pairingRequestBroadcaster.setDevicePIN(pin);
removeBond(device);
// create connection to the bluetooth device
bluetoothGatt = device.connectGatt(activity, false, this);
refresh(bluetoothGatt);
}
/**
* Disconnect from BLE device. This method should be called whenever we want to
* close the APP, or the BLE connection.
*/
public void disconnect() {
Log.d(LOG_TAG, "disconnect() - executed");
if (bluetoothGatt != null) {
if (characteristic != null) {
bluetoothGatt.setCharacteristicNotification(characteristic, false);
}
//remove device authorization/ bond/ pairing
removeBond(bluetoothGatt);
// disconnect now
bluetoothGatt.disconnect();
bluetoothGatt.close();
Log.d(LOG_TAG, "disconnect() - bluetoothGatt disconnect happened");
}
bluetoothGatt = null;
characteristic = null;
bluetoothConnected = false;
servicesDiscovered = false;
// set device as not bonded anymore
bondedBroadcaster.resetDeviceBonded();
}
/**
* bluetooth nearby devices scan is on
* #return true if scanning is on, false otherwise
*/
public boolean isScanning(){
return (this.bluetoothScanning);
}
/**
* Check bluetooth system state (on or off)
* #return true if system is on, false otherwise
*/
public boolean isEnabled(){
try {
checkBluetooth();
return bluetoothAdapter.isEnabled();
} catch (BluetoothNotEnabledException e) {
return false;
}
}
/**
* Check bluetooth connection
* #return true if connected, false otherwise
*/
public boolean isConnected(){
return (this.bluetoothConnected);
}
/**
* Start bluetooth scan for nearby devices
* #param filters Scan filters that define what devices to scan for
*/
public void startScan(List<ScanFilter> filters)
throws BluetoothNotEnabledException{
checkBluetooth();
// dont run two scans simultaneously
if (isScanning()) {
return;
}
// disconnect previously connected devices
if (isConnected()) {
this.disconnect();
return;
}
// setup bluetooth scanning settings
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
// start scanning
this.bluetoothScanning = true;
this.bluetoothScanResults.clear();
this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
// Stops scanning after a pre-defined scan period.
Handler bluetoothHandler = new Handler();
bluetoothHandler.postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
}, SCAN_TIMEOUT);
// start scan with default scan callback
this.bluetoothLeScanner.startScan(filters, settings, bluetoothScanCallback);
// we have started successfully the BLE scanning
bluetoothListener.onBluetoothStartScan();
}
/**
* Stop bluetooth scan for nearby devices
*/
public void stopScan(){
if (!bluetoothScanning) {
return;
}
// set app scan state to false
bluetoothScanning = false;
if (bluetoothLeScanner != null) {
bluetoothLeScanner.stopScan(bluetoothScanCallback);
bluetoothLeScanner = null;
}
// we have stopped BLE scanning, call the user's callback
bluetoothListener.onBluetoothStopScan(bluetoothScanResults.values());
}
/**
* Send a message via bluetooth
* #param msg message to send
*/
public void send(String msg) {
if (!bluetoothConnected || characteristic == null){
bluetoothListener.onBluetoothNotConnected();
return;
}
try {
msg = msg.replaceAll(DATA_FILTER_REGEX, "") + TERMINATION_CHAR;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Sending message: %s",
msg));
characteristic.setValue(msg.getBytes(CHARSET));
bluetoothGatt.writeCharacteristic(characteristic);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG,
"BluetoothLowEnergy.send: Failed to convert message string to byte array");
}
}
public String getDeviceUUID(){
return deviceUUID;
}
public Activity getActivity(){
return activity;
}
/**
* Check if bluetooth is enabled and working properly
*/
private void checkBluetooth() throws BluetoothNotEnabledException{
if (bluetoothAdapter == null) {
final BluetoothManager bluetoothManager =
(BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null){
throw new BluetoothNotEnabledException();
}
bluetoothAdapter = bluetoothManager.getAdapter();
}
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
throw new BluetoothNotEnabledException();
}
}
}
The key methods and functions to avoid problems used above are:
Thread.sleep(600)
removeBond(device)
refresh(gatt)
gatt.disconnect()
gatt.close()
In my case I got this response from bluetooth stack because the device was already bonded with my phone. I removed it from my settings and the error 22 vanished.
in aosp (android source code). you can find any error in bluetooth source code, and know the meaning of status code.
the file path is system/bt/stack/include/gatt_api.h
Here's the link: https://android.googlesource.com/platform/system/bt/+/ea7ab70a711e642653dd5922b83aa04a53af9e0e/stack/include/gatt_api.h but it all display by hex.
for example:
hex
Decimal
reason
0x08
8
connection timeout
0x13
19
connection terminate by peer user
0x16
22
connectionterminated by local host
0x22
34
connection fail for LMP response tout
0x85
133
gatt_error
I have an App that Monitors room noise levels, I initially got the Code from Github, in the original code, the programmer was monitoring noise levels from Main Activity and displaying the results in textviews, but I want to monitor using a service, I have implemented everything and its working but the textviews seem to be lagging behind, lets say I make a bit of noise and the noise level reach 5, it sticks at 5 even when there is no noise in the room, but in the original app, it was so sensitive that it would go back to 0 or another value depending on the noise levels, I do not know where I have gone wrong but below is my code:
Main Activity
public class StartingPoint extends Activity {
private String volumeBars;
private String volumeLevel;
private TextView volumeBarView;
private TextView volumeLevelView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(getBaseContext(), "Loading...", Toast.LENGTH_LONG).show();
setContentView(R.layout.activity_starting_point);
//starting Service
startService(new Intent(this, VolumeListerner.class));
volumeBarView = (TextView) findViewById(R.id.volumeBars);
volumeLevelView = (TextView) findViewById(R.id.volumeLevel);
}
#Override
public void onResume() {
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter("UI_UPDATER"));
super.onResume();
// Sound based code
}
#Override
public void onPause() {
super.onPause();
}
public void updateTextView() {
volumeBarView.setText(volumeBars);
volumeLevelView.setText(volumeLevel);
return;
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
volumeBars = intent.getStringExtra("VolumeBars");
volumeLevel = intent.getStringExtra("volumeLevel");
Log.d("receiver", "Got message: " + volumeBars + " : " + volumeLevel);
updateTextView();
}
};
Service:
public class VolumeListerner extends Service {
private static String volumeVisual = "";
private static int volumeToSend;
private Handler handler;
private SoundMeter mSensor;
/** interface for clients that bind */
IBinder mBinder;
/** indicates whether onRebind should be used */
boolean mAllowRebind;
/** The service is starting, due to a call to startService() */
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
soundLevelCheck();
return super.onStartCommand(intent, flags, startId);
}
private void soundLevelCheck()
{
mSensor = new SoundMeter();
try {
mSensor.start();
Toast.makeText(getBaseContext(), "Sound sensor initiated.", Toast.LENGTH_SHORT).show();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
handler = new Handler();
final Runnable r = new Runnable() {
public void run() {
// Get the volume from 0 to 255 in 'int'
double volume = 10 * mSensor.getTheAmplitude() / 32768;
volumeToSend = (int) volume;
volumeVisual = "";
for( int i=0; i<volumeToSend; i++){
volumeVisual += "|";
updateUI();
}
handler.postDelayed(this, 250); // amount of delay between every cycle of volume level detection + sending the data out
}
};
// Is this line necessary? --- YES IT IS, or else the loop never runs
// this tells Java to run "r"
handler.postDelayed(r, 250);
}
private void updateUI()
{
Intent intent = new Intent( "UI_UPDATER" );
intent.putExtra("VolumeBars", "Volume Bars: " + String.valueOf(volumeVisual));
intent.putExtra("volumeLevel","Volume Levels: " + String.valueOf(volumeToSend));
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
I recommmand you to use an enhanced event bus with emphasis on Android support. you have a choice between :
1- Otto
2- Event Bus
I have 2 phones with Android 5.0.2, they both installed the latest Radius Beacon's App: Locate Beacon, meanwhile, I turned on 2 IBeacon sender, and can see the RSSI keep changing in both phone with the App.
But when I tried to write some sample code to simulate above situation, I found the ble scan callback always stop get called after called 2 or 3 times, I initially suspect the 'Locate Beacon' may use different way, so I tried with 2 kinds of API, one is for old 4.4, and another is the new way introduced in android 5, but both the same behavior(but all running on android 5).
the 4.4 one:
public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter;
private static final String LOG_TAG = "BleCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
calledTimes++;
runOnUiThread(new Runnable() {
#Override
public void run() {
calledTimesTextView.setText(Integer.toString(calledTimes));
}
});
Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
.getAdapter();
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}}
And the 5.0.2:
public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mLescanner;
private ScanCallback mLeScanCallback;
private static final String LOG_TAG = "BleFingerprintCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
this.mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
.getAdapter();
this.mLescanner = this.mBluetoothAdapter.getBluetoothLeScanner();
ScanSettings bleScanSettings = new ScanSettings.Builder().setScanMode(
ScanSettings.SCAN_MODE_LOW_LATENCY).build();
this.mLeScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
calledTimes++;
runOnUiThread(new Runnable() {
#Override
public void run() {
calledTimesTextView.setText(Integer
.toString(calledTimes));
}
});
Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
}
#Override
public void onScanFailed(int errorCode) {
}
};
this.mLescanner.startScan(null, bleScanSettings, this.mLeScanCallback);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}}
They are very simple and just show a counter in UI, proved finally always stopped at 2 or 3.
I've played this ble advertising receiving before on a SamSung note 2 with android 4.4 device, it works perfectly, the callback get called every second.
then anyone can help? why Radius' Locate Beacon works well here?
Different Android devices behave differently when scanning for connectable BLE advertisements. On some devices (e.g. the old Nexus 4), the scanning APIs only get one callback per scan for transmitters sending a connectable advertisement, whereas they get a scan callback for every advertisement for non-connectable advertisements. Newer devices (e.g. the Nexus 5 and most built after 2015) provide a scan callback every single advertisement regardless of whether it is connectable.
The Locate app you mention uses the open source Android Beacon Library to detect beacons. It is built on top of the same scanning APIs you show in your question, but it gets around this problem by defining a scan period (1.1 seconds by default in the foreground) and stopping and restarting a scan at this interval. Stopping and restarting the scan causes Android to send a new callback.
A few other notes here:
This issue of getting multiple scan callbacks for connectable devices applies to both the 4.x and 5.x scanning APIs.
It is unclear whether the difference in delivering scan callbacks for connectable advertisements on different devices is due to Android firmware differences or bluetooth hardware chipset differences.
There doesn't seem to be a way to detect if a device requires a scan restart to get additional callbacks for connectable advertisements, so if you are targeting a wide variety of devices, you need to plan to stop and restart scanning.
Using Android's raw scanning APIs is a great way to understand how BLE beacons work. But there are lots of complexities with working with BLE beacons (this is just one example) which is why using a SDK like the Android Beacon Library is a good choice to keep you from pulling your hair out.
Full disclosure: I am the author of the Locate app in the lead developer on the Android Beacon Library open source project.
David - Are you sure that scan callback gets called for every non-connectable advertisement. I have a Xiaomi Redmi 3 and another Nexus 5 phone running Android 6.0. I have a BLE sensor that at every 1 minute interval sends the data. These phones appearing as central BLE device should receive and process the data from the sensor. I can see from an Over the Air (OTA) BLE capture device that it the sensor is sending data every 1 minute. However both phones seems to process data for few minutes at 1 minute interval but after that stop processing for 4 - 6 minutes and then start processing agenter code hereain.
Time interval of phone processing on looks like this
1 min, 2 min, 3 min, 8min, 9min, 10min, 11 min
So after processing 3 packets at 1 minute interval, either phone will stop processing for 4 -6 minutes.
Here is code that does the processing.
public class BluetoothDataReader {
private final Context context;
public BluetoothDataReader(Context context) {
this.context = context;
}
public void startReading() {
BluetoothAdapter btAdapter = getBluetoothAdapter();
if (btAdapter == null) return;
BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
scanner.startScan(Collections.<ScanFilter>emptyList(), settings, new ScanRecordReader());
}
public void uploadScanBytes(SensorDataUploader sensorDataUploader, int count) {
BluetoothAdapter btAdapter = getBluetoothAdapter();
if (btAdapter == null) return;
BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build();
// scanner.startScan(Arrays.asList(new ScanFilter.Builder().setDeviceAddress("26:50:26:50:26:50").build()), settings, new LimitedScanRecordReader(sensorDataUploader, count, scanner));
scanner.startScan(Collections.<ScanFilter>emptyList(), settings, new LimitedScanRecordReader(sensorDataUploader, count, scanner));
}
#Nullable
private BluetoothAdapter getBluetoothAdapter() {
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
if(btAdapter == null){
Log.i(BluetoothDataReader.class.getName(), "No bluetooth adapter available");
return null;
}
if(!btAdapter.isEnabled()){
Log.i(BluetoothDataReader.class.getName(), "Enable bluetooth adapter");
Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
context.startActivity(enableBluetooth);
}
return btAdapter;
}
private class LimitedScanRecordReader extends ScanCallback {
private final int limit;
private final BluetoothLeScanner scanner;
private int scanRecordRead = 0;
private final SensorDataUploader sensorDataUploader;
private LimitedScanRecordReader( SensorDataUploader sensorDataUploader, int limit, BluetoothLeScanner scanner) {
this.limit = limit;
this.scanner = scanner;
this.sensorDataUploader = sensorDataUploader;
}
#Override
public void onScanResult(int callbackType, ScanResult result) {
// if(scanRecordRead++ < limit) {
// if(result.getDevice().getAddress().equals("A0:E6:F8:01:02:03")) {
// if(result.getDevice().getAddress().equals("C0:97:27:2B:74:D5")) {
if(result.getDevice().getAddress().equals("A0:E6:F8:01:02:03")) {
long timestamp = System.currentTimeMillis() -
SystemClock.elapsedRealtime() +
result.getTimestampNanos() / 1000000;
byte[] rawBytes = result.getScanRecord().getBytes();
Log.i(DataTransferService.class.getName(), "Raw bytes: " + byteArrayToHex(rawBytes));
sensorDataUploader.upload(timestamp, rawBytes);
}
// }else {
// scanner.stopScan(this);
// }
}
public String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02x", b & 0xff));
return sb.toString();
}
public void onScanFailed(int errorCode) {
Log.i(DataTransferService.class.getName(), "Error code is:" + errorCode);
}
public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult> results) {
Log.i(DataTransferService.class.getName(), "Batch scan results");
}
}
private class ScanRecordReader extends ScanCallback {
#Override
public void onScanResult(int callbackType, ScanResult result) {
byte []rawBytes = result.getScanRecord().getBytes();
Log.i(DataTransferService.class.getName(), "Raw bytes: " + byteArrayToHex(rawBytes ));
// Map<ParcelUuid, byte[]> serviceData = result.getScanRecord().getServiceData();
// for(ParcelUuid uuid : serviceData.keySet()) {
// Log.i(DataTransferService.class.getName(), uuid.toString() + ":" + byteArrayToHex(serviceData.get(uuid)));
// }
// Log.i(DataTransferService.class.getName(),result.toString());
}
public String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02x", b & 0xff));
return sb.toString();
}
public void onScanFailed(int errorCode) {
Log.i(DataTransferService.class.getName(), "Error code is:" + errorCode);
}
public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult> results) {
Log.i(DataTransferService.class.getName(), "Batch scan results");
}
}
}
I am using the proximity-reference-android sample application provided by Radius Network to detect an iBeacon. I have an iPad configured as an iBeacon and have added around 3 beacon regions in the proximity kit. The problem I am facing right now I am unable to fetch the Beacon name ,and the additional url which I had in the proximity kit in Android.
I need to basically show up the url associated with beacon region in that proximity kit in Android App just like how the iOS application does.
While Debugging I had checked that even after the beacon is detected in the application,the didEnterRegion doesn't get called.I need to basically save the details of that particular beacon in the database once it is detected.
Neither is the application calling didExitRegion.
Posting the below code,please let me know what I am doing wrong in this.
public class AndroidProximityReferenceApplication extends Application implements
BootstrapNotifier {
private static final String TAG = "AndroidProximityReferenceApplication";
private RegionBootstrap regionBootstrap;
private BackgroundPowerSaver backgroundPowerSaver;
private boolean haveDetectedIBeaconsSinceBoot = false;
public void onCreate() {
super.onCreate();
Log.d(TAG,
"setting up background monitoring for iBeacons and power saving");
// wake up the app when an iBeacon is seen
Region region = new Region(
"com.radiusnetworks.androidproximityreference.backgroundRegion",
"2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6", null, null);
regionBootstrap = new RegionBootstrap(this, region);
// simply constructing this class and holding a reference to it in your
// custom Application
// class will automatically cause the iBeaconLibrary to save battery
// whenever the application
// is not visible. This reduces bluetooth power usage by about 60%
backgroundPowerSaver = new BackgroundPowerSaver(this);
}
#Override
public void didDetermineStateForRegion(int arg0, Region arg1) {
// This method is not used in this example
}
#Override
public void didEnterRegion(Region arg0) {
// In this example, this class sends a notification to the user whenever
// an iBeacon
// matching a Region (defined above) are first seen.
Log.d(TAG, "did enter region.");
if (!haveDetectedIBeaconsSinceBoot) {
Log.d(TAG, "auto launching MainActivity");
// The very first time since boot that we detect an iBeacon, we
// launch the
// MainActivity
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Important: make sure to add android:launchMode="singleInstance"
// in the manifest
// to keep multiple copies of this activity from getting created if
// the user has
// already manually launched the app.
this.startActivity(intent);
haveDetectedIBeaconsSinceBoot = true;
} else {
// If we have already seen iBeacons and launched the MainActivity
// before, we simply
// send a notification to the user on subsequent detections.
Log.d(TAG, "Sending notification.");
ParseObject beacon = new ParseObject("Beacon");
beacon.put("beacon_name", arg0.getClass().getName());
beacon.put("beacon_id", arg0.getUniqueId());
beacon.put("device_type", "Android");
beacon.put("device_UUID", android.os.Build.MODEL);
beacon.put("beacon_status", "ENTRY");
beacon.saveInBackground();
sendNotification();
}
}
#Override
public void didExitRegion(Region arg0) {
Log.d(TAG, "exited region");
ParseObject beacon = new ParseObject("Beacon");
beacon.put("beacon_name", arg0.getClass().getName());
beacon.put("beacon_id", arg0.getUniqueId());
beacon.put("device_type", "Android");
beacon.put("device_UUID", android.os.Build.MODEL);
beacon.put("beacon_status", "ENTRY");
beacon.saveInBackground();
}
private void sendNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(
this).setContentTitle("Proximity Reference Application")
.setContentText("An iBeacon is nearby.")
.setSmallIcon(R.drawable.ic_launcher);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntent(new Intent(this, MainActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(resultPendingIntent);
NotificationManager notificationManager = (NotificationManager) this
.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, builder.build());
}
}
The code below is of the mainActivity class
public class MainActivity extends Activity implements IBeaconConsumer,
RangeNotifier, IBeaconDataNotifier {
public static final String TAG = "MainActivity";
IBeaconManager iBeaconManager;
Map<String, TableRow> rowMap = new HashMap<String, TableRow>();
#Override
protected void onCreate(Bundle savedInstanceState) {
Parse.initialize(this, "test123",
"test345");
IBeaconManager.LOG_DEBUG = true;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iBeaconManager = IBeaconManager.getInstanceForApplication(this
.getApplicationContext());
iBeaconManager.bind(this);
}
#Override
public void onIBeaconServiceConnect() {
Region region = new Region("MainActivityRanging", null, null, null);
try {
iBeaconManager.startRangingBeaconsInRegion(region);
iBeaconManager.setRangeNotifier(this);
} catch (RemoteException e) {
e.printStackTrace();
}
}
#Override
public void onDestroy() {
super.onDestroy();
iBeaconManager.unBind(this);
}
#Override
public void didRangeBeaconsInRegion(Collection<IBeacon> iBeacons,
Region region) {
for (IBeacon iBeacon : iBeacons) {
iBeacon.requestData(this);
Log.d(TAG, "I see an iBeacon: " + iBeacon.getProximityUuid() + ","
+ iBeacon.getMajor() + "," + iBeacon.getMinor());
String displayString = iBeacon.getProximityUuid() + " "
+ iBeacon.getMajor() + " " + iBeacon.getMinor() + "\n";
displayTableRow(iBeacon, displayString, false);
}
}
#Override
public void iBeaconDataUpdate(IBeacon iBeacon, IBeaconData iBeaconData,
DataProviderException e) {
if (e != null) {
Log.d(TAG, "data fetch error:" + e);
}
if (iBeaconData != null) {
Log.d(TAG,
"I have an iBeacon with data: uuid="
+ iBeacon.getProximityUuid() + " major="
+ iBeacon.getMajor() + " minor="
+ iBeacon.getMinor() + " welcomeMessage="
+ iBeaconData.get("welcomeMessage"));
String displayString = iBeacon.getProximityUuid() + " "
+ iBeacon.getMajor() + " " + iBeacon.getMinor() + "\n"
+ "Welcome message:" + iBeaconData.get("welcomeMessage");
displayTableRow(iBeacon, displayString, true);
}
}
private void displayTableRow(final IBeacon iBeacon,
final String displayString, final boolean updateIfExists) {
runOnUiThread(new Runnable() {
#Override
public void run() {
TableLayout table = (TableLayout) findViewById(R.id.beacon_table);
String key = iBeacon.getProximity() + "-" + iBeacon.getMajor()
+ "-" + iBeacon.getMinor();
TableRow tr = (TableRow) rowMap.get(key);
if (tr == null) {
tr = new TableRow(MainActivity.this);
tr.setLayoutParams(new TableRow.LayoutParams(
TableRow.LayoutParams.WRAP_CONTENT,
TableRow.LayoutParams.WRAP_CONTENT));
rowMap.put(key, tr);
table.addView(tr);
} else {
if (updateIfExists == false) {
return;
}
}
tr.removeAllViews();
TextView textView = new TextView(MainActivity.this);
textView.setText(displayString);
tr.addView(textView);
}
});
}
}
Any help would be appreciated.Thanks :)
When using Proximity Kit for Android, there are two separate sets of APIs available. One set uses a ProximityKitManager, and it is intended for simpler use cases where you can pre-configure all of your iBeacon identifiers and associated data server-side, and let the ProximityKitManager handle setting up ranging and monitoring in a global Application class.
The second set of APIs use the IBeaconManager and provide more fine-grained control. But because the ProximityKitManager uses the IBeaconManager under the hood, you should not use both at the same time, because you can easily break the automatic configuration done by ProximityKitManager. I suspect that is what is causing your problem, because the code uses the ProximityKitManager in the Application class and IBeaconManager in the Activity class. See here for more info.
If you need to track beacons regardless of the identifiers set up in ProximityKit, but still want to access the data configured for certain iBeacons in ProximityKit, you should not use the ProximityKitManager and instead just use the IBeaconManager. There is a reference application that shows how to access ProximityKit data using this API available here.