2023年2月7日 星期二

Android Studio - 實現藍牙低功耗(BLE)Gatt 程式(一百零一)

Android Studio - 實現藍牙低功耗(BLE)Gatt 程式(一百零一):

筆者藍牙低功耗(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();

        }

    }

}


2023年 2月 7日(Tue)天氣報告
氣溫:44.0°F / 7.0°C @ 07:00
風速:每小時 21公里
降雨機會:95%
相對濕度:百分之 89%
天氣:雨

沒有留言:

張貼留言