เชื่อมต่อ Bluetooth Device ด้วย Android Bluetooth Low Energy (BLE)
ในชีวิตประจำวันเรามีการส่งข้อมูลผ่าน 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 เองได้นะ
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