筆者藍牙低功耗(BLE)掃描(Scan)程式完成,測試是可以順利掃描藍牙低功耗的設備,便開始編寫 Gatt 連接程式。首先掃描藍牙低功耗的設備後,選擇連接的 Device Address,便連接 Device 設備,監聽連接回檔,讀取 Device 的 Service UUID,取得 Service 後通過特徵的 UUID 獲取所要的特徵 Characteristic,每個特徵都含有一個或多個對 Value 的描述 Descriptor。通過操作特徵可以讀取和寫入資料。
實現藍牙低功耗(BLE)Gatt 程式 |
- 操作系統:Windows 7 64-bit 版本
- 開發環境:Android Studio 4.0.1 版本
- 測試手機:Samsung Galaxy M33 5G
- 測試手機系統版本:Android 12(Snow Cone / 2021)
- 原程式:C:\Development\Development_Android\Android_Project\DIY-Android-013-33a BLE_Connect Bugworkshop 20230122
- 程式:C:\Development\Development_Android\Android_Project\DIY-Android-013-33a BLE_Connect Bugworkshop 20230122
藍牙低功耗(BLE)掃描編程步驟:
- 開啟藍牙啟用權
- 在 Layout 配置檔(activity_ble_01)裡面放一個 TextView 和Button。
- 在 BLE01SetupActivity 內設定 BluetoothManager 和 MyArrayAdapter。
- 掃描 BLE 設備並放入 MyArrayAdapter 並顯示在 Device Address。
- 選擇 Device Address,連接 Device。
- 讀取 Service UUID 並放入 ServiceArrayAdapter 並顯示 UUID。
activity_ble_03.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".BLE03GattActivity">
<Button android:id="@+id/btn_scan" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/text_hello" android:layout_alignParentStart="true" android:layout_marginStart="10dp" android:layout_marginTop="10dp" android:text="Scan" />
<TextView android:id="@+id/text_hello" android:layout_width="391dp" android:layout_height="37dp" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_marginStart="12dp" android:layout_marginTop="13dp" android:text="Hello" android:textSize="20sp" />
<ListView android:id="@+id/device_list" android:layout_width="match_parent" android:layout_height="336dp" android:layout_below="@+id/text_hello" android:layout_alignParentStart="true" android:layout_marginStart="0dp" android:layout_marginTop="65dp" />
<TextView android:id="@+id/text_UUID" android:layout_width="403dp" android:layout_height="42dp" android:layout_below="@+id/device_list" android:layout_alignParentStart="true" android:layout_centerHorizontal="true" android:layout_marginStart="0dp" android:layout_marginTop="5dp" android:text="TextView" />
<ListView android:id="@+id/services_list" android:layout_width="wrap_content" android:layout_height="209dp" android:layout_below="@+id/text_UUID" android:layout_alignParentStart="true" android:layout_alignParentEnd="true" android:layout_marginStart="3dp" android:layout_marginTop="1dp" android:layout_marginEnd="0dp" />
</RelativeLayout> |
BLE03GattActivity.java:
package bugworkshop.bleconnection;
import android.Manifest; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar;
import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast;
import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map;
public class BLE03GattActivity extends AppCompatActivity {
private static final int REQUEST_ENABLE_BT = 10; private static final int PERMISSION_REQUEST_COARSE_LOCATION = 20; private BluetoothAdapter mBluetoothAdapter; private Handler mHandler; private boolean mScanning;
private ListView mDeviceList; private MyArrayAdapter myArrayAdapter;
private Map<String, BluetoothDevice> devices = new LinkedHashMap<>();
private static final long SCAN_PERIOD = 10000;
private TextView mtxt_hello; private TextView mtxt_item; private Button mbtn_scan; private TextView mtxt_UUID;
// Gatt public static BluetoothGatt mBluetoothGatt; private BluetoothDevice mDevice; private boolean mConnected; private ListView mServicesList; private List<BluetoothGattService> mServices = new ArrayList<>(); public final static String ACTION_DATA_AVAILABLE = "DeviceActivity.ACTION_DATA_AVAILABLE"; private boolean lockCharacteristicRead = false; private ServiceArrayAdapter serviceArrayAdapter;
// Start Here @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ble_03);
myArrayAdapter = new MyArrayAdapter(this, android.R.layout.simple_list_item_1);
mDeviceList = (ListView) findViewById(R.id.device_list); mtxt_hello = (TextView) findViewById(R.id.text_hello); mbtn_scan = (Button) findViewById(R.id.btn_scan);
mtxt_UUID = (TextView) findViewById(R.id.text_UUID);
serviceArrayAdapter = new ServiceArrayAdapter(this, android.R.layout.simple_list_item_1); mServicesList = (ListView) findViewById(R.id.services_list); mServicesList.setAdapter(serviceArrayAdapter);
mbtn_scan.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scanLeDevice(true); } });
mDeviceList.setAdapter(myArrayAdapter); mDeviceList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String deviceAddress = myArrayAdapter.getItem(position);
mDevice = mBluetoothAdapter.getRemoteDevice(deviceAddress); mtxt_hello.setText("Selected Address= "+deviceAddress); connect(); } });
mHandler = new Handler();
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
if (!isBluetoothEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); return; }
checkLocationPermission(); scanLeDevice(true); }
// onActivityResult @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data);
if (REQUEST_ENABLE_BT == requestCode) { if (!isBluetoothEnabled()){ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } else { checkLocationPermission(); } } }
// Check Location premission private void checkLocationPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs location access"); builder.setMessage("Please grant location access so this app can perform BLE scanning"); builder.setPositiveButton(android.R.string.ok, null); builder.setOnDismissListener(new DialogInterface.OnDismissListener() { @SuppressLint("NewApi") @Override public void onDismiss(DialogInterface dialog) { requestPermissions(new String[]{ Manifest.permission.ACCESS_FINE_LOCATION }, PERMISSION_REQUEST_COARSE_LOCATION); } }); builder.show(); } } }
// Bluetooth private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { Log.d(TAG, "MainActivity.LeScanCallback().Found device= " + device.getAddress() + ", RSSI " + rssi);
devices.put(device.getAddress(), device); myArrayAdapter.notifyDataSetChanged();
} }); } };
private boolean isBluetoothEnabled() { return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled(); }
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; devices.clear(); myArrayAdapter.notifyDataSetChanged(); mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } }
// Adapter private class MyArrayAdapter extends ArrayAdapter<String> { MyArrayAdapter(Context context, int resource) { super(context, resource); }
@Override public int getCount() { return devices.size(); }
@Nullable @Override public String getItem(int position) { BluetoothDevice devicesa[] = new BluetoothDevice[0]; devicesa = devices.values().toArray(devicesa); BluetoothDevice device = devicesa[position];
return device.getAddress(); } }
// Gatt Connection private void connect() { mBluetoothGatt = mDevice.connectGatt(this, false, mGattCallback); }
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) { mConnected = true; mServices.clear(); mHandler.post(new Runnable() { @Override public void run() { serviceArrayAdapter.notifyDataSetChanged(); } });
gatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { mConnected = false; }
mHandler.post(new Runnable() { @Override public void run() { } }); }
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) { mServices = gatt.getServices(); mHandler.post(new Runnable() { @Override public void run() { serviceArrayAdapter.notifyDataSetChanged(); } });
} else {
} }
// Read Characteristic Value @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); }
@Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); if (mBluetoothAdapter == null || mBluetoothGatt == null) { return; } lockCharacteristicRead = true; mBluetoothGatt.readCharacteristic(characteristic); String record = characteristic.getStringValue(0);
byte[] a = characteristic.getValue(); a = characteristic.getValue(); } } };
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); 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)); } sendBroadcast(intent); }
private class ServiceArrayAdapter extends ArrayAdapter<String> { ServiceArrayAdapter(Context context, int resource) { super(context, resource); }
@Override public int getCount() { return mServices.size(); }
@Nullable @Override public String getItem(int position) { return mServices.get(position).getUuid().toString(); } } } |
沒有留言:
張貼留言