เชื่อมต่อ Bluetooth Device ด้วย Android Bluetooth Low Energy (BLE)

Taweewong Tocharoen
Nextzy
Published in
4 min readFeb 14, 2020

--

ในชีวิตประจำวันเรามีการส่งข้อมูลผ่าน Bluetooth กับอุปกรณ์รอบตัวเรากันอยู่แล้ว ไม่ว่าจะเป็น ลำโพง, หูฟัง, smart watch, mouse, keyboard ซึ่งเจ้า Bluetooth เนี่ยก็มีพัฒนาการมาเรื่อยๆ ใน Android เราใช้กันตั้งแต่ Bluetooth version 2.0 แล้วก็มี version 3, 4, 5 ตามกันมา ในแต่ละ version ก็จะมีความเร็วในการรับส่งข้อมูลเพิ่มขึ้นและมี profile ใหม่ๆ เพิ่มมากขึ้นด้วย ถ้าพูดถึง profile ที่เราพบเห็นได้บ่อยๆ ก็จะมี

A2DP (Advanced Audio Distribution Profile) ใช้สำหรับทำ Sound Streaming กับอุปกรณ์พวกลำโพง หรือหูฟัง

HID (Human Interface Device Profile) ใช้ควบคุมอุปกรณ์เช่น mouse, keyboard

GATT (Generic Attribute Profile) ใช้อ่านหรือเขียนข้อมูลผ่าน Bluetooth Low Energy

BLE (Bluetooth Low Energy)

เอาล่ะ ได้เวลาเข้าเรื่องของเรากันแล้ว พระเอกของเราก็คือเจ้า BLE ซึ่งเป็น protocol สำหรับส่งข้อมูลแบบใช้พลังงานต่ำ ใช้เชื่อมต่อกับอุปกรณ์ที่ต้องการประหยัดพลังงาน เช่นพวก wearable device ทั้งหลาย แต่ว่า BLE จะมีใน Bluetooth version 4.0 ขึ้นไป และใช้ได้ตั้งแต่ Android 4.3 ขึ้นไปเท่านั้น เพราะฉะนั้นหากจะใช้ BLE ก็ตรวจสอบกับอุปกรณ์ของคุณก่อนว่าใช้ได้หรือไม่ (จริงๆ สมัยนี้ก็น่าจะใช้กันได้หมดแล้วแหละ)

แต่เดี๋ยวก่อน! เข้าใจกันก่อนนะว่า BLE เป็นชื่อรวมๆ ของการส่งข้อมูลแบบใช้พลังงานต่ำ แต่ profile ของ bluetooth ที่เราใช้ในการส่งข้อมูลผ่าน BLE นั้นมีชื่อว่า GATT

จากตรงนี้ผมจะขอพูดถึงส่วนประกอบต่างๆ ที่ใช้ใน BLE นะครับ

GATT (Generic Attribute Profile)

GATT เป็น bluetooth profile เหมือนกับ A2DP และ HID นั่นแหละ แต่ GATT เป็นตัวกำหนดการอ่านหรือเขียนข้อมูลผ่าน Bluetooth Low Energy หรือพูดง่ายๆ คือเป็น profile สำหรับอ่านหรือเขียนข้อมูลลงพวก wearable device นั่นแหละ

Service

คือสิ่งบอกว่าเรากำลังจะรับส่งข้อมูลกับ feature อะไร เช่น เราเรียกใช้ Heart rate service เราก็จะได้ข้อมูลของ Heart rate มา แต่ใน service นั้นยังมีส่วนย่อยลงไปอีกเรียกว่า Characteristic

Characteristic

ลองนึกภาพว่า Service เป็นกล่องที่บอกหมวดหมู่ของข้อมูล Characteristic ก็คือค่าของข้อมูลที่อยู่ในกล่องนั้น ยกตัวอย่าง Heart rate service จะมีค่าต่างๆ ให้เราเรียก เช่น Heart Rate Measurement, Body Sensor Location และ Heart Rate Control Point ซึ่งค่าพวกนี้ก็คือ Characteristic นั่นเอง ใน Characteristic แต่ละตัวก็จะมี permission properties (read, write, notify, indicate) เพื่อบอกว่าตัวมันสามารถทำอะไรได้บ้าง

  • read : อ่านข้อมูลจาก device
  • write : เขียนข้อมูลลง device
  • notify : ส่งข้อมูลมาบอกเมื่อมีการ update
  • indicate : เหมือนกับ notify แต่ว่าทางผู้ที่ได้รับข้อมูลจะต้องส่ง acknowledge กลับไปเพื่อให้รู้ว่าได้รับข้อมูลแล้ว

Descriptor

Descriptor จะอยู่ใน Characteristic โดยที่ตัวมันจะขยายข้อมูลใน Characteristic อีกทีนึง เช่น มีคำอธิบายข้อมูลเพิ่มเติม, บอก range ของข้อมูล หรือหน่วยวัดของมูล

หากสงสัยว่า Service หรือ Characteristic มีอะไรบ้างก็สามารถเข้าไปดูได้ที่

หากคุณกดเข้าใน link ด้านบนแล้วคุณจะเห็น Assigned Number นั่นคือ ID ประจำตัวของแต่ละ Service และ Characteristic ที่เราจะเอาไปใช้เพื่อเรียกหา Service หรือ Characteristic นั้นๆ แต่เราไม่สามารถเอาเลขนั้นมาใช้ตรงๆ ได้ เราต้องแปลงเป็น UUID ซะก่อน ถ้าหากคุณขี้เกียจแปลงเองผมขอแนะนำ

https://gist.github.com/sam016/4abe921b5a9ee27f67b3686910293026

ใน link นี้ได้สรุปมาให้คุณเรียบร้อยแล้ว แต่หากคุณอยากแปลงเองก็ตามนี้เลย

Java

public UUID convertFromInteger(int i) {
final long MSB = 0x0000000000001000L;
final long LSB = 0x800000805f9b34fbL;
long value = i & 0xFFFFFFFF;
return new UUID(MSB | (value << 32), LSB);
}

Kotlin

fun convertFromInteger(i: Int): UUID? {
val MSB = 0x0000000000001000L
val LSB = -0x7fffff7fa064cb05L
val value = (i and ((-0x1).toLong()).toInt()).toLong()
return UUID(MSB or (value shl 32), LSB)
}

It’s Code time!

เอาล่ะ ถึงเวลาที่ทุกคนรอคอย และเหมือนเดิมตรงนี้ผมจะขอเขียนเป็นภาษา Kotlin นะครับ

Permission

android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.ACCESS_FINE_LOCATION

สำคัญมาก! อย่าลืมขอ ACCESS_FINE_LOCATION จาก user ด้วยล่ะ

Setup

อย่างแรกที่เราต้องสร้างเลยก็คือ BluetoothManager วิธีการเรียกออกมาก็ตามนี้เลย ง่ายมากๆ

val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()

Scan for devices

เราจะเรียกใช้คำสั่ง startDiscovery() เพื่อทำการ scan หา device รอบๆ

เมื่อเราสั่ง scan เราจะสามารถดู device ที่เรา scan เจอได้ใน receiver

เพื่อความชัวร์เราสามารถสั่งเปิด Bluetooth เองได้นะ

จะขึ้น popup แบบนี้เพื่อให้ user เปิด bluetooth ก่อน

Connect to device

เมื่อเราได้ instant ของ device จากการ scan มาแล้วเราสามารถสั่ง connect() เพื่อเชื่อมต่อกับ device นั่นได้เลย

device.connectGatt(context, autoConnect, gattCallback)

หรือถ้าเราไม่อยากเก็บ device ทั้งก้อนเราสามารถสั่ง connect โดยใช้แค่ address ก็ได้

bluetoothAdapter
.getRemoteDevice(device.address)
.connectGatt(context, autoConnect, gattCallback)

ส่วนจะ connect สำเร็จหรือไม่เราสามารถดูได้ที่ BluetoothGattCallback และเมื่อเรา connect device ได้แล้ว อย่างที่ได้บอกไปตอนต้นว่าเวลาที่เราจะขอข้อมูลจาก device เราจะต้องรู้ว่าเราจะขอข้อมูลจาก service อะไร

แต่ตอนนี้เรายังไม่รู้ว่าใน device ของเรามี service อะไรบ้างเราต้องทำการ discover service ซะก่อน โดยใช้คำสั่ง gatt.discoverServices()

จากตรงนี้เราจะสามารถเรียกใช้ service จาก gatt ได้แล้วครับ ลองดูตัวอย่าง code ด้านล่างนะครับ

ตามตัวอย่างนี้ผมทำเรียก Service Generic Access ของ device ออกมาแล้วก็เรียก Characteristic Device Name จาก service อีกทีนึง เท่านี้เราก็สามารถเรียกดูข้อมูล device name ของ device ได้แล้ว ซึ่ง Service และ Characteristic นั้นมีเยอะมากๆ และแต่ละ device ไม่ได้มีทุก Service อยู่แล้ว หากอยากรู้ว่า device ของเรามี Service อะไรบ้างและใน Service เหล่านั้นมี Characteristic อะไรบ้างสามารถดูได้ตามนี้เลยครับ

หากเราลอง log UUID ของ service หรือ Characteristic ออกมาเราจะได้หน้าตาแบบนี้

00001800-0000-1000-8000-00805f9b34fb

ซึ่งเราสามารถเรียก service หรือ Characteristic จาก device โดยใช้ค่าจากตรงนี้ได้

val uuid = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb")

Read Data from Device

ทีนี้พอเราสามารถเรียก device name characteristic ออกมาได้แล้วเราจะลองอ่านค่าของมันโดยกำหนด writeType ให้เป็น BluetoothGattCharacteristic.PERMISSION_READ แล้วให้ bluetooth gatt เรียกคำสั่ง readCharacteristic() ค่าที่เราอ่านได้จะถูกส่งกลับมาใน onCharacteristicRead ลองดูตาม code ตัวอย่างด้านล่างเลยครับ

Write Data to Device

การเขียนข้อมูลนั้นทำคล้ายๆ กับ read เลย เราเพียงแค่ set value ไปกับ Characteristic แล้วสั่ง write เลย ถ้า write สำเร็จแล้วเราจะได้ข้อมูลที่เราเขียนลงไปกลับมาใน onCharacteristicWrite

เนื่องจากว่าตอนที่ผมเขียนบทความผมไม่มี device ที่ไว้ลองทดสอบเพื่อประกอบการเขียนบทความน่ะครับ ก็เลยจะขอแปะไว้เป็น SOME_SERVICE_UUID กับ SOME_SERVICE_CHARACTERISTIC ถ้าคุณผู้อ่านจะลองเขียนดูก็ลองหา Characteristic ที่มี permission write นะครับ

Make device Notify data to You

สำหรับ Characteristic ที่มี permission notify เราสามารถสั่งให้มันส่งข้อมูลใหม่มาให้เราได้เมื่อมีการ update ส่วนการ enable notification นั้นเราจะต้องสั่งแบบนี้

gatt.setCharacteristicNotification(characteristic, true)

แต่เท่านั้นยังไม่พอ ภายใน Characteristic ที่ notify ได้จะมี descriptor ที่เป็น attribute สำหรับ notify อยู่ ชื่อว่า Client Characteristic Configuration ซึ่งเราจะต้อง enable ตัวนี้ด้วย เพื่อให้เห็นภาพลองดู code ตัวอย่างตามนี้ครับ

ข้อมูลที่เราสั่งให้ notify ไว้จะเข้ามาใน onCharacteristicChanged เราสามารถเรียกดูข้อมูลจากในนี้ได้

ทั้งหมดที่อยากแชร์ก็มีเท่านี้ครับ จริงๆ แล้วผมเองก็เพิ่งมาจับ BLE ได้ไม่นาน ก็ลองผิดลองถูกหาข้อมูลมาทดลองหลายๆ อย่างเอา หากมีอะไรผิดพลาดอย่างไรสามารถมาแชร์กันได้นะครับ

สุดท้ายนี้หวังว่าบทความนี้จะเป็นประโยชน์นะครับ สวัสดีครับ :D

--

--