Bluetooth iBeacon Part 1: Search for the device + Wireless Debugging!
Homan Huang
我是美国公民,SFSU大学毕业。专长是Kotlin, Java, C++程编,精通 Android 手机程式和后台支持软件--SpringBoot。电脑视觉:辨别物体,分辨算法。
Here -> Part 2
Recently, I received my $5 iBeacon, which was looked like a white button on eBay. I ordered three of them to check the function like this kind. The online description about this ibeacon is equiped NRF51882 distance chip, 3.3V. Bluetooth beacon is distance device can tell you how close to it in its broadcast range. Here are the pictures I taken.
It is a tool to help phone display the merchandise in the mall, finding the book in the library, or even tagging your children.
Let's try to search to the device in this part. Our task is:
- Check the Bluetooth status: if it's OFF then turn it ON.
- Search all nearby Bluetooth device and put them on a ListView.
Simple, aren't they? In this project, I need your Android phone stands by instead of emulator. Let's do it.
Open a new project with empty activity, called "Beacon Test". We've nothing change in build.gradle. That's a good beginning.
In activity_main.xml, we need to two buttons: one for turning Bluetooth OFF and one for scanning. Also, we need a ListView to display what the scanner found.
The UI is easy. After you finished it, we need to grab some permissions in AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<application ...
Have you noticed the ACCESS_FINE_LOCATION? Yeah, Bluetooth beacon is a distance device which is required permission for location. After that, please paste the request permission from your other program in MainActivity.java.
static final int REQUEST_ID_MULTIPLE_PERMISSIONS = Your Favor #;
public void requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.INTERNET)
== PackageManager.PERMISSION_GRANTED &&
...BLUETOOTH... &&
...BLUETOOTH_ADMIN... &&
...ACCESS_FINE_LOCATION...) {
ltag("Permission is granted");
} else {
ltag("Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.INTERNET,
...BLUETOOTH,
...BLUETOOTH_ADMIN,
...ACCESS_FINE_LOCATION
},
REQUEST_ID_MULTIPLE_PERMISSIONS);
}
}
}
@Override
public void onRequestPermissionsResult(...) {
...
switch (requestCode) {
case REQUEST_ID_MULTIPLE_PERMISSIONS:
...
break;
}
}
And paste your shortcut for toast and log. Here is mine.
/* Toast shortcut */
public static void msg(Context context, String message) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
/* Log tag and shortcut */
final static String TAG = "MYLOG BLE";
public static void ltag(String message) { Log.i(TAG, message); }
Let's put in some variables.
/*
Variables
*/
private BluetoothAdapter mBluetoothAdapter;
Button bluetoothButton;
boolean bleEnable = false;
//search list and adapter
private boolean mScanning;
private Handler mHandler;
private List<BluetoothDevice> mBles;
//ListView
ListView deviceListView;
private BleDeviceListAdapter bleListAdapter;
/*
End Variables
*/
BluetoothAdapter is your hardware and don't mix up with your ListViewAdapter. Others are for the scanning function. Let's initialize all the variables in onCreate().
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermissions();
// Determine whether BLE is supported on the device.
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH_LE)) {
msg(this, "Bluetooth Low Energy Device Not Supported!");
finish(); //Bye-bye
}
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
bluetoothButton = (Button) findViewById(R.id.bluetoothButton);
checkBluetooth();
//ListView of your scanning function
mHandler = new Handler(); //initial timer
mBles = new ArrayList<>(); //hold search result
deviceListView = (ListView) findViewById(R.id.deviceListView);
}
Everything is following our plan: check device and scan. Let's check bluetooth.
/*
Check bluetooth adapter
*/
private void checkBluetooth() {
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
//ask to turn on bluetooth Hardware
Intent i = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(i);
if (mBluetoothAdapter.isEnabled()) {
msg(this, "Bluetooth turned ON.");
ltag("Bluetooth turned ON.");
bluetoothButton.setText("Bluetooth(ON)\nTurned OFF?");
bleEnable = true;
}
} else {
msg(this, "Bluetooth turned ON!");
bluetoothButton.setText("Bluetooth(ON)\nTurned OFF?");
bleEnable = true;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// User chose not to enable Bluetooth.
if (requestCode == REQUEST_ENABLE_BT &&
resultCode == RESULT_CANCELED) {
finish(); //bye-bye
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
private static final int REQUEST_ENABLE_BT = Your Another Favor;
@Override
protected void onResume() {
super.onResume();
// Ensures Bluetooth is enabled on the device.
if (!mBluetoothAdapter.isEnabled()) {
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
//update
checkBluetooth();
// Initializes list view adapter.
bleListAdapter = new BleDeviceListAdapter(MainActivity.this, mBles);
deviceListView.setAdapter(bleListAdapter);
scanLeDevice(false);
}
/*
End check bluetooth adapter
*/
There're two places required to be checked, beginning and resume. And we have a button to turn off the Bluetooth function.
/*
Turn off bluetooth hardware
*/
public void bluetoothOff(View v) {
if (bleEnable) {
mBluetoothAdapter.disable();
if (mBluetoothAdapter.isEnabled()) {
msg(this, "Error to disable Bluetooth!");
} else {
msg(this, "Bluetooth turned OFF!");
bluetoothButton.setText("Bluetooth(OFF)\nTurned ON?");
bleEnable = false;
}
} else {
checkBluetooth();
}
}
Let's code our scanner. The scanner needs a callback, called BluetoothAdapter.LeScanCallback. Long name and hard to remember, isn't it? It updates your ListView and device list when it founds something.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
bleListAdapter.addDevice(device);
mBles.add(device);
bleListAdapter.notifyDataSetChanged();
}
});
}
};
Here is your scanner:
// Stops scanning after 10 seconds.
private static final long SECOND = 1000;
private static final long SCAN_PERIOD = 10 * SECOND;
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
@Override
protected void onPause() {
super.onPause();
scanLeDevice(false);
bleListAdapter.clear();
}
/*
Find Beacon button
*/
public void findBeacon(View v) {
scanLeDevice(true);
deviceListView.setAdapter(bleListAdapter);
}
Finally, we need the ListViewAdapter, called BleDeviceListAdapter.java. And its custom layout is list_item_device.xml. There're two TextViews in the layout, device name and device address.
Here is the code of adapter.
/**
* Created by Homan on 3/9/2018.
*/
public class BleDeviceListAdapter extends BaseAdapter{
private List<BluetoothDevice> mLeDevices;
private Context context;
public BleDeviceListAdapter(Context context, List<BluetoothDevice> devices) {
super();
this.context = context;
this.mLeDevices = devices;
}
public void addDevice(BluetoothDevice device) {
if(!mLeDevices.contains(device)) {
mLeDevices.add(device);
}
}
public BluetoothDevice getDevice(int position) {
return mLeDevices.get(position);
}
public void clear() {
mLeDevices.clear();
}
@Override
public int getCount() {
return mLeDevices.size();
}
@Override
public Object getItem(int position) {
return mLeDevices.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// General ListView optimization code.
if (convertView == null) {
convertView = LayoutInflater.from(context)
.inflate(R.layout.list_item_device,
parent, false);
}
TextView deviceNameTV = (TextView)
convertView.findViewById(R.id.deviceNameTV);
TextView deviceAddressTV = (TextView)
convertView.findViewById(R.id.deviceAddressTV);
BluetoothDevice device = mLeDevices.get(position);
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 0)
deviceNameTV.setText("Device Name: "+deviceName);
else
deviceNameTV.setText("Device Name: UnKnown");
deviceAddressTV.setText("Address: "+device.getAddress());
return convertView;
}
}
A regular ListViewAdapter, it's not a big deal for us. Here is the method to connect your phone on WiFi without your USB cable.
Let's connect to your phone to your private WiFi network. And you choose Setting->About phone->Status->IP address. For example, I found mine, https://www.dhirubhai.net/redir/invalid-link-page?url=172%2e20%2e10%2e2 .
Now, run the project. Let's check the ON/OFF function.
Working fine. How's about the scanner?
Wow, what a messy! And the duplicate addresses are showed in list. There're too many Bluetooth device around. Let's identify our device and fix the bug in next part.