Mulai dari Earphone Wireless yang kita pakai untuk mendengarkan musik, Smartwatch yang kita gunakan untuk memonitor kesehatan kita, sampai dengan Beacon yang terdapat di museum, bisa hadir karena adanya teknologi Bluetooth Low Energy (BLE).
Pada artikel ini, kita akan belajar mengenai Apa itu BLE, Perbedaan BLE dengan Classic Bluetooth, Generic Attribute Profile (GATT), serta bagaimana cara membuat Peripheral dan Central menggunakan framework Core Bluetooth.
Bluetooth Low Energy atau yang biasa dikenal dengan BLE merupakan protokol terbaru dari Bluetooth yang mengoptimalkan kinerja Bluetooth dalam aspek efektivitas pengunaan daya sehingga akan mengonsumsi lebih sedikit daya daripada Bluetooth versi sebelumnya (Classic Bluetooth).
Umumnya beacon atau perangkat-perangkat IoT seperti sensor suhu itu harus mengirimkan data secara berkala, maka sumber daya atau energi listrik menjadi faktor yang sangat krusial, nah teknologi BLE hadir untuk menyelesaikan permasalahan tersebut karena teknologi BLE sangat efisien dalam pemakaian baterai.
Perbedaan utama dari BLE dengan Classic Bluetooth adalah dari segi konsumsi sumber daya-nya. Sebagai perbandingan, 1 Watt Classic Bluetooth setara dengan 0.01-0.5 Watt BLE!
Kemudian latency BLE juga lebih rendah dibandingkan dengan Classic Bluetooth, BLE 6 ms, sedangkan Classic Bluetooth sekitar 100 ms.
Namun untuk jangkauan sinyal yang bisa dipancarkan lebih unggul Classic Bluetooth, yaitu sekitar 10-100 meter, sedangkan BLE maksimal hanya 10 meter saja.
Kekurangan lain dari BLE adalah mengenai Data Rate yang lebih rendah dibandingkan Classic Bluetooth yaitu sekitar 1-3 Mbps, sedangkan BLE hanya 1 Mbps.
Kemudian perbedaan terakhir adalah Classic Bluetooth perlu melakukan Pairing Device untuk melakukan transfer data, sedangkan BLE tidak perlu.
Dari beberapa perbedaan di atas, bisa disimpulkan bahwa Classic Bluetooth cocok digunakan untuk mengirimkan data antar device yang berukuran agak besar seperti video, dll dengan jarak yang agak jauh. Sedangkan BLE cocok untuk mengirimkan data secara berkala dalam waktu yang lama seperti sensor perangkat IoT dan Beacon.
Perangkat Bluetooth Low Energy (BLE) dapat memiliki peran antara menjadi Peripheral atau menjadi Central, tidak bisa keduanya. Peripheral bisa dianalogikan sebagai Server dan Central sebagai Client.
Contohnya adalah Smartwatch yang mengirimkan data detak jantung kepada HP kita, dalam contoh tersebut Smartwatch berperan sebagai Peripheral, dan HP kita berperan sebagai Central karena HP kita lah yang menerima data dari Smartwatch (Central).
Satu Peripheral dapat terhubung dengan beberapa Central, dan Satu Central dapat terhubung dengan beberapa Peripheral dalam satu waktu.
Generic Attribute Profile atau yang biasa disebut dengan GATT merupakan sebuah protokol yang mendefinisikan cara bagaimana 2 perangkat BLE untuk saling mengirim dan menerima data.
Sebuah Peripheral disebut juga sebagai GATT Server, karena berperan untuk menyediakan layanan kepada Central (GATT Client). GATT Server perlu mengimplementasikan GATT Profile dalam mendefiniskan layanan yang bisa digunakan oleh GATT Client.
Ada 5 bagian dalam GATT Profile, yaitu:
Profile merupakan bagian terluar pada GATT. Satu Profile dapat mempunyai beberapa Service. Contohnya adalah Profile Alat Pengukur Suhu mempunyai Service Suhu Celcius dan Service Suhu Fahrenheit.
Service merupakan gabungan beberapa Characteristic yang memiliki keterkaitan. Contohnya adalah Service Suhu Celcius mempunyai Characteristic Rata-Rata Suhu Seminggu Terakhir, Characteristic Suhu Real-Time, dll.
Characteristic merupakan bagian utama pada GATT, satu Characteristic merepresentasikan satu layanan atau fitur yang ada pada Peripheral (GATT Server). Contohnya adalah Characteristic Rata-Rata Suhu Seminggu Terakhir.
Value merupakan data yang terdapat pada Characteristic. Contohnya adalah Characteristic Rata-Rata Suhu Seminggu Terakhir mempunyai Value 30° Celcius.
Setiap Characteristic dapat mempunyai beberapa Descriptor. Sesuai namanya, Descriptor bertujuan untuk mendeskripsikan suatu Characteristic, misalnya fungsi dari Characteristic tersebut, format datanya, dll.
Sekarang, kita akan mengimplementasikan Bluetooth Low Energy pada iOS, Kita akan membuat aplikasi sederhana bernama "Bluetify" yang dapat berperan sebagai Peripheral atau sebagai Central.
Hasil akhirnya nanti akan seperti ini:
Untuk melakukan testing aplikasi "Bluetify" ini, kita memerlukan 2 buah iPhone, yang satu berperan sebagai Peripheral dan yang satunya sebagai Central.
Silakan download starter projectnya terlebih dahulu di link berikut, pilih branch "starter":
https://github.com/alfinsyahruddin/Bluetify/tree/starter
Jika sudah, silakan buka projectnya melalui Xcode.
Pada project "Bluetify", terdapat 2 folder utama yaitu "BluetifyKit" dan "UI".
BluetifyKit berisi logic utama yang ada pada aplikasi ini.
Folder Enums
Bluetify: Berisi konfigurasi aplikasi Bluetify, seperti Nama Peripheral, dll.
BluetifyUUID: Berisi kumpulan UUID yang ada pada Peripheral.
State: Berisi UI State aplikasi kita.
Folder Helpers
DataHelper: Digunakan untuk konversi tipe data Data ke tipe data lain dan sebaliknya.
BluetifyPeripheralKit.swift
Sebuah Class yang berfungsi untuk melakukan advertising data dan mengirim notifikasi kepada Central. Semua logic dan detail implementasi terkait dengan Peripheral akan kita tambahkan di class ini.
BluetifyCentralKit.swift
Sebuah Class yang berfungsi untuk melakukan scanning Peripheral, subscribe notifikasi dan mengirim message ke Peripheral. Semua logic dan detail implementasi terkait dengan Central akan kita tambahkan di class ini.
Untuk membuat User Interface (UI) pada project ini, kita menggunakan SwiftUI karena syntax-nya sangat ringkas dan dapat mempercepat proses pembuatan UI jika dibandingkan dengan UIKit.
Folder Core
Berisi komponen-komponen UI yang dapat digunakan di banyak tempat.
Folder Pages
Berisi semua halaman yang ada pada aplikasi Bluetify.
Untuk menggunakan fitur Bluetooth pada aplikasi kita, kita perlu menambahkan 2 permission berikut pada file Info.plist:
Info.plist1<key>NSBluetoothPeripheralUsageDescription</key>2<string>For advertising data.</string>34<key>NSBluetoothAlwaysUsageDescription</key>5<string>For transfer data between devices.</string>
Silakan buka file "BluetifyPeripheralKit.swift". Untuk membuat Peripheral, kita akan memodifikasi isi dari file ini.
Silakan tambahkan code berikut sebelum init:
1private var peripheralManager: CBPeripheralManager!
dan code berikut di dalam init untuk membuat instance dari CBPeripheralManager
1override init() {2 super.init()35 peripheralManager = CBPeripheralManager(delegate: self, queue: nil)6}
Advertising merupakan proses dari Peripheral untuk melakukan broadcast data kepada Central. Advertising bertujuan agar Central dapat mendeteksi Peripheral, melakukan koneksi dan bertukar data dengan Peripheral.
Silakan tambahkan code berikut di atas init:
1private var deviceNameCharacteristic: CBMutableCharacteristic?2private var messageCharacteristic: CBMutableCharacteristic?3private var notificationCharacteristic: CBMutableCharacteristic?
Kemudian ubah implementasi dari function startAdvertising menjadi seperti ini:
1private func startAdvertising() {2 print("[PERIPHERAL] Start Advertising...")34 peripheralManager.removeAllServices()56 self.deviceNameCharacteristic = CBMutableCharacteristic(7 type: BluetifyUUID.deviceNameCharacteristic.cbuuid,8 properties: [.read],9 value: nil,10 permissions: [.readable]11 )1213 self.deviceNameCharacteristic!.descriptors = [14 CBMutableDescriptor(15 type: CBUUID(string: CBUUIDCharacteristicUserDescriptionString),16 value: NSString(string: "Characteristic for reading peripheral device names.")17 )18 ]1920 self.messageCharacteristic = CBMutableCharacteristic(21 type: BluetifyUUID.messageCharacteristic.cbuuid,22 properties: [.write],23 value: nil,24 permissions: [.writeable]25 )2627 self.notificationCharacteristic = CBMutableCharacteristic(28 type: BluetifyUUID.notificationCharacteristic.cbuuid,29 properties: [.notify],30 value: nil,31 permissions: [.readable]32 )3334 let mainService = CBMutableService(type: BluetifyUUID.mainService.cbuuid, primary: true)3536 mainService.characteristics = [37 deviceNameCharacteristic,38 messageCharacteristic,39 notificationCharacteristic40 ].compactMap { $0 }4142 peripheralManager.add(mainService)4344 state = .advertising45 peripheralManager.startAdvertising([46 CBAdvertisementDataLocalNameKey: Bluetify.peripheralName.rawValue,47 CBAdvertisementDataServiceUUIDsKey: [BluetifyUUID.mainService.cbuuid]48 ])49}
Pada code di atas, kita membuat sebuah Peripheral yang mempunyai 1 service bernama mainService, dan mainService tersebut memiliki 3 Characteristics, yaitu:
deviceNameCharacteristic: Informasi Nama Peripheral yang dapat dibaca oleh Central.
messageCharacteristic: Message yang dapat dikirim oleh Central.
notificationCharacteristic: Notifikasi yang akan dibroadcast ke Central.
Tiap Service dan Characteristic yang kita buat, kita perlu membuat UUID-nya. Pada project ini, kamu bisa melihat UUID tersebut pada enum BluetifyUUID.
Kita juga telah menambahkan Descriptor pada deviceNameCharacteristic dengan tipe CBUUIDCharacteristicUserDescriptionString yang bertujuan untuk memberi tahu Central mengenai deskripsi dari Characteristic tersebut.
Kamu bisa melihat tipe apa saja yang bisa digunakan untuk membuat Descriptor di sini: https://developer.apple.com/documentation/corebluetooth/cbdescriptor
Function startAdvertising akan kita panggil setelah bluetooth pada iPhone menyala. Silakan tambahkan code berikut pada delegate CBPeripheralManagerDelegate:
1func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {2 print("[PERIPHERAL] State: \(peripheral.state)")34 switch peripheral.state {5 case .unknown, .unsupported, .unauthorized, .resetting, .poweredOff:6 self.state = .bluetoothUnavailable7 case .poweredOn:8 self.startAdvertising()9 @unknown default:10 self.state = .bluetoothUnavailable11 }12}
Pada saat user keluar dari halaman Peripheral Mode, kita perlu memberhentikan Advertising. Silakan ubah implementasi dari function stopAdvertising menjadi seperti ini:
1public func stopAdvertising() -> Void {2 print("[PERIPHERAL] Stop Advertising.")34 state = .bluetoothUnavailable5 peripheralManager.stopAdvertising()6}
Agar Central dapat membaca Device Name dari Peripheral, silakan tambahkan code berikut pada delegate CBPeripheralManagerDelegate:
1func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {2 let characteristic = BluetifyUUID(rawValue: request.characteristic.uuid.uuidString)34 switch characteristic {5 case .deviceNameCharacteristic:6 request.value = deviceName.asData()7 default:8 break9 }1011 peripheral.respond(to: request, withResult: .success)12}
Agar Peripheral dapat menerima Message yang dikirim dari Central, silakan tambahkan code berikut pada delegate CBPeripheralManagerDelegate:
1func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {2 for request in requests {3 let characteristic = BluetifyUUID(rawValue: request.characteristic.uuid.uuidString)45 switch characteristic {6 case .messageCharacteristic:7 guard let newMessage = request.value?.asString() else { break }8 print("[PERIPHERAL] New Message: \(newMessage)")910 message = newMessage11 default:12 break13 }1415 peripheral.respond(to: request, withResult: .success)16 }17}
Untuk mengirim notifikasi dari Peripheral ke Central, silakan ubah implementasi pada function sendNotification menjadi seperti ini:
1public func sendNotification(_ text: String) -> Void {2 guard let notificationCharacteristic else { return }34 peripheralManager.updateValue(5 text.asData(),6 for: notificationCharacteristic,7 onSubscribedCentrals: notificationCharacteristic.subscribedCentrals8 )910 print("[PERIPHERAL] Send Notification: \(text)")11}
Silakan buka file "BluetifyCentralKit.swift". Untuk membuat Central, kita akan memodifikasi isi dari file ini.
Silakan tambahkan code berikut sebelum init:
1private var centralManager: CBCentralManager!2private var peripheral: CBPeripheral?
dan code berikut di dalam init untuk membuat instance dari CBCentralManager
1override init() {2 super.init()35 centralManager = CBCentralManager(delegate: self, queue: nil)6}
Scanning merupakan proses dari Central untuk mendeteksi Peripheral. Jika Peripheral terdeteksi, maka Central bisa melakukan koneksi ke Peripheral tersebut, dan setelah terhubung dengan Peripheral, Central dapat melakukan Read, Write, dan Subscribe Notification ke pada Peripheral yang terhubung.
Silakan tambahkan code berikut di atas init:
1private var peripheral: CBPeripheral?2private var deviceNameCharacteristic: CBCharacteristic?3private var messageCharacteristic: CBCharacteristic?4private var notificationCharacteristic: CBCharacteristic?
Kemudian ubah implementasi dari function startScanning menjadi seperti ini:
1public func startScanning() -> Void {2 print("[CENTRAL] Start Scanning...")3 state = .scanning4 centralManager.scanForPeripherals(withServices: [BluetifyUUID.mainService.cbuuid])5}
Pada code di atas, kita melakukan scanning untuk mencari Peripheral yang mempunyai Service dengan UUID Main Service yang ada pada BluetifyUUID.
Function startScanning akan kita panggil setelah bluetooth pada iPhone menyala. Silakan tambahkan code berikut pada delegate CBCentralManagerDelegate:
1func centralManagerDidUpdateState(_ central: CBCentralManager) {2 print("[CENTRAL] State: \(central.state)")34 switch central.state {5 case .unknown, .unsupported, .unauthorized, .resetting, .poweredOff:6 self.state = .bluetoothUnavailable7 case .poweredOn:8 self.startScanning()9 @unknown default:10 self.state = .bluetoothUnavailable11 }12}
Pada saat user keluar dari halaman Central Mode, kita perlu memberhentikan Scanning. Silakan ubah implementasi dari function stopScanning menjadi seperti ini:
1public func stopScanning() -> Void {2 print("[CENTRAL] Stop Scanning.")3 centralManager.stopScan()4}
Setelah proses Scanning, kita perlu melakukan koneksi terhadap Peripheral yang kita inginkan agar kita bisa bertukar data dengan Peripheral tersebut.
Pada delegate CBCentralManagerDelegate, silakan tambahkan code berikut:
1func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {2 guard let peripheralName = peripheral.name else { return }34 print("[CENTRAL] Peripheral: `\(peripheralName)`")56 print("[CENTRAL] Connecting...")7 centralManager.connect(peripheral, options: nil)89 self.peripheral = peripheral10}111213func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {14 print("[CENTRAL] Connected")1516 peripheral.delegate = self17 peripheral.discoverServices(nil)1819 state = .connected20}
Setelah berhasil terkoneksi dengan Peripheral, kita perlu mengimplementasikan delegate CBPeripheralDelegate
Silakan tambahkan code berikut pada delegate CBPeripheralDelegate:
1func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {2 guard let services = peripheral.services else { return }34 for service in services {5 peripheral.discoverCharacteristics(nil, for: service)6 }7}89func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {10 guard let characteristics = service.characteristics else { return }1112 print("[CENTRAL] Total characteristics: \(characteristics.count)")1314 for characteristic in characteristics {15 if characteristic.uuid == BluetifyUUID.deviceNameCharacteristic.cbuuid {16 self.deviceNameCharacteristic = characteristic17 self.readDeviceName()18 }1920 if characteristic.uuid == BluetifyUUID.messageCharacteristic.cbuuid {21 self.messageCharacteristic = characteristic22 }2324 if characteristic.uuid == BluetifyUUID.notificationCharacteristic.cbuuid {25 self.notificationCharacteristic = characteristic26 self.subscribeNotification()27 }28 }29}
Pada code di atas, kita mencari 3 Characteristic, yaitu:
deviceNameCharacteristic: Informasi Nama Peripheral yang akan dibaca oleh Central.
messageCharacteristic: Message yang akan dikirim oleh Central.
notificationCharacteristic: Notifikasi yang dibroadcast ke Central.
Pada saat pertama kali terhubung dengan Peripheral yang kita inginkan, kita panggil function readDeviceName untuk membaca Device Name dari Peripheral dan function subscribeNotification agar dapat menerima notifikasi dari Peripheral.
Agar Central dapat membaca Device Name dari Peripheral, silakan tambahkan code berikut pada delegate CBPeripheralDelegate:
1func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {2 if characteristic.uuid == BluetifyUUID.deviceNameCharacteristic.cbuuid {3 let deviceName = characteristic.value?.asString() ?? "-"4 print("[CENTRAL] DEVICE NAME: \(deviceName)")5 self.deviceName = deviceName6 }7}
Dan ubah implementasi dari function readDeviceName menjadi seperti ini:
1public func readDeviceName() -> Void {2 guard let deviceNameCharacteristic else { return }3 peripheral?.readValue(for: deviceNameCharacteristic)4}
Agar Central dapat mengirim Message ke Peripheral, silakan ubah implementasi dari function sendMessage menjadi seperti ini:
1public func sendMessage(_ text: String) -> Void {2 guard let messageCharacteristic else { return }3 peripheral?.writeValue(text.asData(), for: messageCharacteristic, type: .withResponse)4}
Agar dapat menerima notifikasi dari Peripheral, silakan tambahkan code berikut pada function peripheral(_:didUpdateValueFor:error:):
1func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {2 if characteristic.uuid == BluetifyUUID.deviceNameCharacteristic.cbuuid {3 let deviceName = characteristic.value?.asString() ?? "-"4 print("[CENTRAL] DEVICE NAME: \(deviceName)")5 self.deviceName = deviceName6 }79 if characteristic.uuid == BluetifyUUID.notificationCharacteristic.cbuuid {10 let notification = characteristic.value?.asString() ?? "-"11 print("[CENTRAL] NOTIFICATION: \(notification)")12 self.notification = notification13 }15}
Dan ubah implementasi dari function subscribeNotification menjadi seperti ini:
1public func subscribeNotification() -> Void {2 guard let notificationCharacteristic else { return }3 peripheral?.setNotifyValue(true, for: notificationCharacteristic)4}
Sekarang kita telah belajar mengenai Apa itu BLE, Perbedaan BLE dengan Classic Bluetooth, Generic Attribute Profile (GATT), serta bagaimana cara membuat Peripheral dan Central menggunakan framework Core Bluetooth.
Selanjutnya, kamu bisa kembangkan lagi aplikasi Bluetify ini misalnya menambahkan Service dan Characteristic baru, melakukan pertukaran data di Mode Background, mengintegrasikan Core Bluetooth dengan WatchOS, dll.
Oke mungkin itu saja yang bisa saya bagikan kali ini, kalau kamu merasa artikel ini bermanfaat silakan Like & Share artikel ini ke teman-teman kamu atau jika kamu punya pertanyaan, tulis aja di kolom komentar, Thank you! 😁 🙏
Always open to new ideas. 🕊️
Articles that you might want to read.
Tutorial Image Classification menggunakan Create ML dan Vision Framework.
Menulis kode yang mudah untuk di-test pada Swift.
Dokumentasikan project-mu dengan DocC!