เชื่อมต่อกับเซิร์ฟเวอร์ GATT

ขั้นตอนแรกในการโต้ตอบกับอุปกรณ์ BLE คือการเชื่อมต่อกับอุปกรณ์ กล่าวโดยละเอียดคือการเชื่อมต่อกับเซิร์ฟเวอร์ GATT ในอุปกรณ์ หากต้องการเชื่อมต่อกับเซิร์ฟเวอร์ GATT ในอุปกรณ์ BLE ให้ใช้วิธีconnectGatt() เมธอดนี้ใช้พารามิเตอร์ 3 ตัว ได้แก่ ออบเจ็กต์ Context, autoConnect (บูลีน ที่ระบุว่าจะเชื่อมต่อกับอุปกรณ์ BLE โดยอัตโนมัติทันทีที่ พร้อมใช้งานหรือไม่) และการอ้างอิงถึง BluetoothGattCallback

var bluetoothGatt: BluetoothGatt? = null
// ...
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)

ซึ่งจะเชื่อมต่อกับเซิร์ฟเวอร์ GATT ที่โฮสต์โดยอุปกรณ์ BLE และส่งคืนอินสแตนซ์ BluetoothGatt ซึ่งคุณสามารถใช้เพื่อดำเนินการไคลเอ็นต์ GATT ได้ ผู้โทร (แอป Android) คือไคลเอ็นต์ GATT โดย BluetoothGattCallback จะใช้เพื่อแสดงผลลัพธ์แก่ไคลเอ็นต์ เช่น สถานะการเชื่อมต่อ รวมถึงการดำเนินการอื่นๆ ของไคลเอ็นต์ GATT

ตั้งค่าบริการที่มีผลผูกพัน

ในตัวอย่างต่อไปนี้ แอป BLE จะมีกิจกรรม (DeviceControlActivity) เพื่อเชื่อมต่อกับอุปกรณ์บลูทูธ แสดงข้อมูลอุปกรณ์ และแสดงบริการและลักษณะ GATT ที่อุปกรณ์รองรับ กิจกรรมนี้จะสื่อสารกับ Service ที่ชื่อ BluetoothLeService ตามข้อมูลที่ผู้ใช้ป้อน ซึ่งจะโต้ตอบกับอุปกรณ์ BLE ผ่าน BLE API การสื่อสารจะดำเนินการโดยใช้บริการที่เชื่อมโยงซึ่งช่วยให้กิจกรรมเชื่อมต่อกับ BluetoothLeService และเรียกใช้ฟังก์ชันเพื่อเชื่อมต่อกับอุปกรณ์ได้ BluetoothLeService ต้องมีการติดตั้งใช้งาน Binder ที่ให้สิทธิ์เข้าถึง บริการสำหรับกิจกรรม

class BluetoothLeService : Service() {

    private val binder = LocalBinder()

    override fun onBind(intent: Intent): IBinder? {
        return binder
    }

    inner class LocalBinder : Binder() {
        fun getService(): BluetoothLeService {
            return this@BluetoothLeService
        }
    }
}

กิจกรรมสามารถเริ่มบริการได้โดยใช้ bindService() ส่ง Intent เพื่อเริ่ม บริการ การติดตั้งใช้งาน ServiceConnection เพื่อฟังเหตุการณ์การเชื่อมต่อและการยกเลิกการเชื่อมต่อ และแฟล็ก เพื่อระบุตัวเลือกการเชื่อมต่อเพิ่มเติม

class DeviceControlActivity : AppCompatActivity() {

    private var bluetoothService: BluetoothLeService? = null
    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                // call functions on service to check connection and connect to devices
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.gatt_services_characteristics)

        val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
        bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
}

ตั้งค่า BluetoothAdapter

เมื่อผูกบริการแล้ว บริการจะต้องเข้าถึงBluetoothAdapter โดยควร ตรวจสอบว่าอะแดปเตอร์พร้อมใช้งานในอุปกรณ์ อ่านข้อมูลเพิ่มเติมเกี่ยวกับBluetoothAdapter ได้ที่ตั้งค่า บลูทูธ ตัวอย่างต่อไปนี้จะรวมโค้ดการตั้งค่านี้ไว้ในฟังก์ชัน initialize() ซึ่งแสดงผลค่า Boolean ที่บ่งบอกถึงความสำเร็จ

private const val TAG = "BluetoothLeService"

class BluetoothLeService : Service() {

    private var bluetoothAdapter: BluetoothAdapter? = null

    fun initialize(): Boolean {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        if (bluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.")
            return false
        }
        return true
    }

    // ...
}

กิจกรรมจะเรียกใช้ฟังก์ชันนี้ภายในServiceConnectionการติดตั้งใช้งาน การจัดการค่าที่ส่งคืนเป็นเท็จจากฟังก์ชัน initialize() ขึ้นอยู่กับแอปพลิเคชันของคุณ คุณอาจแสดงข้อความแสดงข้อผิดพลาดแก่ผู้ใช้เพื่อระบุว่า อุปกรณ์ปัจจุบันไม่รองรับการทำงานของบลูทูธ หรือปิดใช้ฟีเจอร์ใดๆ ที่ต้องใช้บลูทูธในการทำงาน ในตัวอย่างต่อไปนี้ finish() จะเรียกใช้ในกิจกรรม เพื่อส่งผู้ใช้กลับไปยังหน้าจอก่อนหน้า

class DeviceControlActivity : AppCompatActivity() {

    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                if (!bluetooth.initialize()) {
                    Log.e(TAG, "Unable to initialize Bluetooth")
                    finish()
                }
                // perform device connection
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    // ...
}

เชื่อมต่อกับอุปกรณ์

เมื่อเริ่มต้นอินสแตนซ์ BluetoothLeService แล้ว อินสแตนซ์จะเชื่อมต่อกับอุปกรณ์ BLE ได้ กิจกรรมต้องส่งที่อยู่อุปกรณ์ไปยังบริการเพื่อให้บริการเริ่มการเชื่อมต่อได้ โดยบริการจะโทรหา getRemoteDevice() ในวันที่ BluetoothAdapter เพื่อเข้าถึงอุปกรณ์ หากอะแดปเตอร์ไม่พบ อุปกรณ์ที่มีที่อยู่นั้น getRemoteDevice() จะส่ง IllegalArgumentException

fun connect(address: String): Boolean {
    bluetoothAdapter?.let { adapter ->
        try {
            val device = adapter.getRemoteDevice(address)
        } catch (exception: IllegalArgumentException) {
            Log.w(TAG, "Device not found with provided address.")
            return false
        }
        // connect to the GATT server on the device
        return true
    } ?: run {
        Log.w(TAG, "BluetoothAdapter not initialized")
        return false
    }
}

DeviceControlActivity จะเรียกใช้ฟังก์ชัน connect() นี้เมื่อเริ่มต้นบริการแล้ว กิจกรรมต้องส่งที่อยู่อุปกรณ์ BLE ใน ตัวอย่างต่อไปนี้ ระบบจะส่งที่อยู่อุปกรณ์ไปยังกิจกรรมเป็นส่วนเสริมของ Intent

// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(
        componentName: ComponentName,
        service: IBinder
    ) {
        bluetoothService = (service as LocalBinder).getService()
        bluetoothService?.let { bluetooth ->
            if (!bluetooth.initialize()) {
                Log.e(TAG, "Unable to initialize Bluetooth")
                finish()
            }
            // perform device connection
            deviceAddress?.let { bluetooth.connect(it) }
        }
    }

    override fun onServiceDisconnected(componentName: ComponentName) {
        bluetoothService = null
    }
}

ประกาศการเรียกกลับ GATT

เมื่อกิจกรรมบอกบริการว่าควรเชื่อมต่อกับอุปกรณ์ใดและบริการ เชื่อมต่อกับอุปกรณ์แล้ว บริการจะต้องเชื่อมต่อกับเซิร์ฟเวอร์ GATT ใน อุปกรณ์ BLE การเชื่อมต่อนี้ต้องใช้ BluetoothGattCallback เพื่อรับ การแจ้งเตือนเกี่ยวกับสถานะการเชื่อมต่อ การค้นพบบริการ การอ่านลักษณะ และการแจ้งเตือนลักษณะ

หัวข้อนี้จะเน้นที่การแจ้งเตือนสถานะการเชื่อมต่อ ดูวิธีค้นหาบริการ อ่านลักษณะ และขอการแจ้งเตือนลักษณะได้ที่โอนข้อมูล BLE

ฟังก์ชัน onConnectionStateChange() จะทริกเกอร์เมื่อการเชื่อมต่อกับเซิร์ฟเวอร์ GATT ของอุปกรณ์มีการเปลี่ยนแปลง ในตัวอย่างต่อไปนี้ มีการกำหนดการเรียกกลับในคลาส Service เพื่อให้ใช้กับ BluetoothDevice ได้เมื่อบริการเชื่อมต่อกับคลาส

private val bluetoothGattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            // successfully connected to the GATT Server
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            // disconnected from the GATT Server
        }
    }
}

เชื่อมต่อกับบริการ GATT

เมื่อประกาศ BluetoothGattCallback แล้ว บริการจะใช้ออบเจ็กต์ BluetoothDevice จากฟังก์ชัน connect() เพื่อเชื่อมต่อกับบริการ GATT ในอุปกรณ์ได้

ใช้ฟังก์ชัน connectGatt() ซึ่งต้องมีออบเจ็กต์ Context, แฟล็กบูลีน autoConnect และ BluetoothGattCallback ในตัวอย่างนี้ แอปเชื่อมต่อกับอุปกรณ์ BLE โดยตรง ดังนั้นจึงมีการส่ง false สำหรับ autoConnect

นอกจากนี้ยังมีการเพิ่มพร็อพเพอร์ตี้ BluetoothGatt ด้วย ซึ่งช่วยให้บริการปิด การเชื่อมต่อเมื่อไม่ จำเป็นอีกต่อไป

class BluetoothLeService : Service() {

    // ...
    private var bluetoothGatt: BluetoothGatt? = null

    // ...
    fun connect(address: String): Boolean {
        bluetoothAdapter?.let { adapter ->
            try {
                val device = adapter.getRemoteDevice(address)
                // connect to the GATT server on the device
                bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
                return true
            } catch (exception: IllegalArgumentException) {
                Log.w(TAG, "Device not found with provided address.  Unable to connect.")
                return false
            }
        } ?: run {
            Log.w(TAG, "BluetoothAdapter not initialized")
            return false
        }
    }
}

การอัปเดตการออกอากาศ

เมื่อเซิร์ฟเวอร์เชื่อมต่อหรือยกเลิกการเชื่อมต่อกับเซิร์ฟเวอร์ GATT เซิร์ฟเวอร์จะต้องแจ้ง กิจกรรมของสถานะใหม่ ซึ่งทำได้หลายวิธี ตัวอย่างต่อไปนี้ใช้ broadcasts เพื่อส่ง ข้อมูลจากบริการไปยังกิจกรรม

บริการจะประกาศฟังก์ชันเพื่อออกอากาศสถานะใหม่ ฟังก์ชันนี้รับสตริงการดำเนินการซึ่งส่งไปยังออบเจ็กต์ Intent ก่อนที่จะออกอากาศไปยังระบบ

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

เมื่อฟังก์ชันการออกอากาศพร้อมใช้งานแล้ว ฟังก์ชันนี้จะใช้ภายใน BluetoothGattCallback เพื่อส่งข้อมูลเกี่ยวกับสถานะการเชื่อมต่อกับเซิร์ฟเวอร์ GATT ประกาศค่าคงที่และสถานะการเชื่อมต่อปัจจุบันของบริการ ในบริการที่แสดงการดำเนินการ Intent

class BluetoothLeService : Service() {

    private var connectionState = STATE_DISCONNECTED

    private val bluetoothGattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // successfully connected to the GATT Server
                connectionState = STATE_CONNECTED
                broadcastUpdate(ACTION_GATT_CONNECTED)
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // disconnected from the GATT Server
                connectionState = STATE_DISCONNECTED
                broadcastUpdate(ACTION_GATT_DISCONNECTED)
            }
        }
    }

    // ...
    companion object {
        const val ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED"
        const val ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"

        private const val STATE_DISCONNECTED = 0
        private const val STATE_CONNECTED = 2
    }
}

ฟังข้อมูลอัปเดตในกิจกรรม

เมื่อบริการออกอากาศการอัปเดตการเชื่อมต่อแล้ว กิจกรรมจะต้อง ใช้ BroadcastReceiver ลงทะเบียนตัวรับนี้เมื่อตั้งค่ากิจกรรม และยกเลิกการลงทะเบียนเมื่อ กิจกรรมออกจากหน้าจอ การฟังเหตุการณ์จากบริการ กิจกรรมจะอัปเดตอินเทอร์เฟซผู้ใช้ได้ตาม สถานะการเชื่อมต่อปัจจุบันกับอุปกรณ์ BLE

class DeviceControlActivity : AppCompatActivity() {

    private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            when (intent.action) {
                BluetoothLeService.ACTION_GATT_CONNECTED -> {
                    connected = true
                    updateConnectionState(R.string.connected)
                }
                BluetoothLeService.ACTION_GATT_DISCONNECTED -> {
                    connected = false
                    updateConnectionState(R.string.disconnected)
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter())
        bluetoothService?.let { service ->
            deviceAddress?.let { address ->
                val result = service.connect(address)
                Log.d(TAG, "Connect request result=$result")
            }
        }
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(gattUpdateReceiver)
    }

    private fun makeGattUpdateIntentFilter(): IntentFilter {
        return IntentFilter().apply {
            addAction(BluetoothLeService.ACTION_GATT_CONNECTED)
            addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED)
        }
    }
}

ในโอนข้อมูล BLE ระบบจะใช้ BroadcastReceiver เพื่อสื่อสารการค้นพบบริการ รวมถึงข้อมูลลักษณะจากอุปกรณ์ด้วย

ปิดการเชื่อมต่อ GATT

ขั้นตอนสำคัญอย่างหนึ่งเมื่อต้องจัดการกับการเชื่อมต่อบลูทูธคือการปิดการเชื่อมต่อเมื่อใช้งานเสร็จแล้ว โดยเรียกใช้close() ฟังก์ชันในออบเจ็กต์ BluetoothGatt ในตัวอย่างต่อไปนี้ บริการ จะมีการอ้างอิงถึง BluetoothGatt เมื่อกิจกรรมยกเลิกการเชื่อมโยงกับบริการ ระบบจะปิดการเชื่อมต่อเพื่อป้องกันไม่ให้แบตเตอรี่ของอุปกรณ์หมด

class BluetoothLeService : Service() {

    // ...
    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }
}