Example of Bluetooth BLE Development Guide

I have done a function about Bluetooth low energy consumption in a company before, and I have time to record it today.
Bluetooth Low Energy (Bluetooth Low Energy) is added from Android 4.3 (API level 18), and the power consumption is relatively low compared to traditional ones. Let's first understand a wave of theory~

write picture description here

Key terms and concepts:


  • Generic Attribute Profile (GATT)
    A GATT profile is a generic specification for sending and receiving short segments of data called "attributes" over a BLE link. All current low energy application profiles are based on GATT.
    The Bluetooth SIG defines a number of profiles for low energy devices. A profile is a specification of how a device works in a specific application. Note that a device can implement multiple profiles. For example, a device might contain a heart rate monitor and a battery level detector.
  • Attribute Protocol (ATT)
    GATT builds on the Attribute Protocol (ATT). This is also known as GATT/ATT. ATT is optimized to run on BLE devices. For this, it uses as few bytes as possible. Each attribute is uniquely identified by a Universally Unique Identifier (UUID), which is a standardized 128-bit format of a string ID used to uniquely identify information. Attributes transmitted by ATT are formatted as characteristics and services.
  • Characteristic
    A feature contains a single value and 0-n descriptors that describe the value of the feature. A trait can be thought of as a type, similar to a class.
  • Descriptor
    descriptors are defining properties that describe the value of a feature. For example, descriptors can specify a human-readable description, an acceptable range of eigenvalues, or eigenvalue-specific units of measure.
  • Service
    A service is a set of characteristics. For example, you can use a service called "Heart Rate Monitor" which includes features like "Heart Rate Measurement".

  • Bluetooth permissions

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

If you want to declare that your app only works with BLE-enabled devices, include the following in your app's manifest:

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

However, if you want your app to work with devices that don't support BLE, you should include this element in your app's manifest, but set required="false". Then at runtime you can use PackageManager.hasSystemFeature() to determine BLE availability:

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

1. Get the Bluetooth Adapter BluetoothAdapter

private BluetoothAdapter mBluetoothAdapter;
...
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

2. Turn on Bluetooth

//确保蓝牙在设备上可用并且已启用。 如果不,
//显示一个对话框,请求用户启用蓝牙的权限。
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

Note: The REQUEST_ENABLE_BT constant passed to startActivityForResult(android.content.Intent, int) is a locally defined integer that the system passes back to you in onActivityResult(int, int, android.content) (it must be greater than 0). Intent) implementation as the requestCode parameter.

3. Scan for Bluetooth devices

To find BLE devices, use the startLeScan() method. This method takes BluetoothAdapter.LeScanCallback as a parameter. You must implement this callback because this is how scan results are returned. Since scanning is power-intensive, you should follow these guidelines:

Once the desired device is found, stop scanning.
Never scan loops and set a time limit on the scan. A previously available device may have moved out of range and continues to scan for battery power.
Note: 6.0 needs to add ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION these two permission scans to return results!

/**
 * Activity for scanning and displaying available BLE devices.
 */
public class DeviceScanActivity extends ListActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler mHandler;

    // Stops scanning after 10 seconds.
    private static final long SCAN_PERIOD = 10000;
    ...
    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);
        }
        ...
    }
...
}

If you only want to scan for a specific type of peripheral, you can instead call startLeScan(UUID[], BluetoothAdapter.LeScanCallback), providing an array of UUID objects to specify the GATT services supported by the application.

The following is an implementation of BluetoothAdapter.LeScanCallback, which is the interface used to pass BLE scan results:

private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
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() {
               mLeDeviceListAdapter.addDevice(device);
               mLeDeviceListAdapter.notifyDataSetChanged();
           }
       });
   }
};

NOTE: You can only scan for Bluetooth LE devices or scan for classic Bluetooth devices as described in Bluetooth. You can't scan for Bluetooth LE and legacy devices at the same time.

4. Connect to the GATT server

The first step in interacting with a BLE device is to connect to it - more specifically, to a GATT server on the device. To connect to a GATT server on a BLE device, use the connectGatt() method. The method takes three parameters: a Context object, autoConnect (a boolean value indicating whether to automatically connect when a BLE device becomes available), and a reference to the BluetoothGattCallback:

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

It connects to the GATT server hosted by the BLE device and returns a BluetoothGatt instance, which you can then use to perform GATT client operations. The caller (Android app) is the GATT client. BluetoothGattCallback is used to communicate results to the client, such as connection status and any further GATT client operations.
After the connection is successful, the various states of ble will be returned in the mGattCallback object, for example:

// A service that interacts with the BLE device via the Android BLE API.
public class BluetoothLeService extends Service {
    private final static String TAG = BluetoothLeService.class.getSimpleName();

    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private String mBluetoothDeviceAddress;
    private BluetoothGatt mBluetoothGatt;
    private int mConnectionState = STATE_DISCONNECTED;

    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.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    public final static String EXTRA_DATA =
            "com.example.bluetooth.le.EXTRA_DATA";

    public final static UUID UUID_HEART_RATE_MEASUREMENT =
            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);

    // BLE API定义的各种回调方法。
    private final 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;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }

        @Override
        //发现新服务(一般来说,发现服务后才可以遍历可用的特征和描述之类的信息)
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        // 特征读取操作的结果
        public void onCharacteristicRead(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
     ...
    };
...
}

send broadcast

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}

private void broadcastUpdate(final String action,
                             final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is carried out as per profile specifications.
    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
        int flag = characteristic.getProperties();
        int format = -1;
        if ((flag & 0x01) != 0) {
            format = BluetoothGattCharacteristic.FORMAT_UINT16;
            Log.d(TAG, "Heart rate format UINT16.");
        } else {
            format = BluetoothGattCharacteristic.FORMAT_UINT8;
            Log.d(TAG, "Heart rate format UINT8.");
        }
        final int heartRate = characteristic.getIntValue(format, 1);
        Log.d(TAG, String.format("Received heart rate: %d", heartRate));
        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    } else {
        // For all other profiles, writes the data formatted in HEX.
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
                    stringBuilder.toString());
        }
    }
    sendBroadcast(intent);
}

receive broadcast

// 处理由服务发起的各种事件。
// ACTION_GATT_CONNECTED:连接到GATT服务器。
// ACTION_GATT_DISCONNECTED:与GATT服务器断开连接。
// ACTION_GATT_SERVICES_DISCOVERED:发现GATT服务。
// ACTION_DATA_AVAILABLE:收到来自设备的数据。 这可能是一个
//读取或通知操作的结果。
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            mConnected = true;
            updateConnectionState(R.string.connected);
            invalidateOptionsMenu();
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
            mConnected = false;
            updateConnectionState(R.string.disconnected);
            invalidateOptionsMenu();
            clearUI();
        } else if (BluetoothLeService.
                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
            // Show all the supported services and characteristics on the
            // user interface.
            displayGattServices(mBluetoothLeService.getSupportedGattServices());
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
        }
    }
};

Read BLE properties

public class DeviceControlActivity extends Activity {
    ...
    /**
     * 遍历服务和特征
     *
     * @param gattServices 通过调用mBluetoothGatt.getServices();
     */
    private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        String uuid = null;
        String unknownServiceString = getResources().
                getString(R.string.unknown_service);
        String unknownCharaString = getResources().
                getString(R.string.unknown_characteristic);
        ArrayList<HashMap<String, String>> gattServiceData =
                new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics =
                new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            HashMap<String, String> currentServiceData =
                    new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.
                            lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();
           // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic :
                    gattCharacteristics) {
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData =
                        new HashMap<String, String>();
                uuid = gattCharacteristic.getUuid().toString();
                currentCharaData.put(
                        LIST_NAME, SampleGattAttributes.lookup(uuid,
                                unknownCharaString));
                currentCharaData.put(LIST_UUID, uuid);
                gattCharacteristicGroupData.add(currentCharaData);
            }
            mGattCharacteristics.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
         }
    ...
    }
...
}

Receiving GATT notifications
It is common for BLE applications to request notification when certain characteristics change on the device. When the setCharacteristicNotification() method is set, the onCharacteristicChanged() method in mGattCallback will be called back! This code shows how to set a notification for a characteristic using the setCharacteristicNotification() method:

private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

Once notifications are enabled for a characteristic, the onCharacteristicChanged() callback is fired if the characteristic changes on the remote device:

@Override
// Characteristic notification
public void onCharacteristicChanged(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}

Disconnect the client

public void disconnect() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.disconnect();
}

Closing the client app
Once your app finishes using the BLE device, it should call close() so the system can release resources appropriately:

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

OK, a simple BLE usage example is complete here. The next article is Bluetooth advanced, and will introduce some pits encountered in actual development and how to fill them.

write picture description here

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325566134&siteId=291194637