با گسترش روزافزون گجت های پوشیدنی، تجهیزات پزشکی هوشمند و دستگاه های اینترنت اشیا (IoT)، تسلط بر پروتکل های ارتباطی بی سیم به یکی از کلیدی ترین مهارت های مهندسی در توسعه نرم افزار اندروید تبدیل شده است.
پروتکل Bluetooth Low Energy (BLE) که از نسخه اندروید ۴.۳ به هسته سیستم عامل افزوده شد، استانداردی انقلابی برای تبادل کم حجم داده با مصرف حداقل انرژی از باتری دستگاه است.
بر خلاف بلوتوث سنتی که اتصال پیوسته و سنگین با مصرف بالای باتری ایجاد می کرد، فناوری BLE دستگاه ها را قادر می سازد ماه ها یا حتی سال ها با یک باتری سکه ای به ارسال داده بپردازند.
آمارها نشان می دهد بیش از ۷۵ درصد سخت افزارهای سلامتی، ردیاب های تناسب اندام و سنسورهای صنعتی از معماری GATT برای تعامل با تلفن های همراه بهره می برند.
با این حال، پیاده سازی بی عیب و نقص پشته ارتباطی BLE در اندروید به دلیل ماهیت ناهمگام (Asynchronous)، صف بندی عملیات ها و خطاهای سیستمی مانند خطای معروف status 133، همواره یکی از پیچیده ترین چالش های توسعه دهندگان بوده است.
در این راهنمای جامع و کاربردی، از مفاهیم پایه پروتکل GATT و مدیریت مجوزهای مدرن در اندروید ۱۲ به بعد تا اسکن هوشمند، اتصال پایدار، مذاکره MTU، رمزنگاری و اتصال به رابط کاربری در Jetpack Compose را با کدهای کاتلین پروداکشن بررسی می کنیم.
Generic Attribute Profile (GATT) عمل می کند. گوشی اندروید نقش مرکزی (Central) را داشته و با اسکن فیلترشده سنسورهای پیرامونی (Peripheral)، به سرویس ها و مشخصه های دستگاه متصل می شود و از طریق توصیف گر CCCD 0x2902 دریافت بلادرنگ اعلان ها را فعال می سازد.
۱. انقلاب اینترنت اشیا و چرایی اهمیت بلوتوث کم مصرف در مهندسی مدرن
امروزه گوشی های هوشمند به هاب مرکزی کنترل انواع حسگرها، ضربان سنج ها، گجت های خانگی و تگ های ردیابی تبدیل شده اند. در چنین محیط هایی برقراری ارتباطات رادیویی سنتی که نیازمند استریم ممتد داده هستند، باتری گوشی و سنسور را در چند ساعت تخلیه می کند.
پروتکل BLE برای برطرف ساختن این محدودیت طراحی شده است. فلسفه معماری این استاندارد بر پایه ارسال بسته های کوچک داده در فواصل زمانی مشخص و بازگشت فوری پردازنده رادیویی به حالت خواب عمیق (Deep Sleep) بنا نهاده شده است.
توسعه دهندگان حرفه ای با درک عمیق این پروتکل می توانند اپلیکیشن هایی بسازند که حتی در پس زمینه سیستم عامل نیز بدون ایجاد لگ یا مصرف باتری آزاردهنده، ارتباط پایدار با سخت افزار را حفظ کنند. این تسلط فنی پایه اصلی پیاده سازی پروژه های صنعتی و تجهیزات پایش بیمار در محیط های درمانی مدرن به شمار می رود.
جهت درک الگو های معماری ماژولار و ساختار ارتباطی لایه های شبکه، مطالعه راهنمای جامع آموزش برنامه نویسی اندروید به عنوان پیش نیاز توصیه می گردد.
۲. تفاوت بنیادین میان بلوتوث کلاسیک و بلوتوث کم مصرف در اندروید
برای انتخاب پروتکل صحیح در پروژه، شناخت تفاوت های بنیادین میان این دو شاخه از فناوری بلوتوث ضروری است:
بلوتوث کلاسیک (Classic Bluetooth بر پایه BR/EDR) برای کاربردهایی که نیازمند پهنای باند بالا و انتقال مداوم داده هستند (مانند هدفون های صوتی، انتقال فایل و اسپیکرها) توسعه یافته است. در این حالت کانال رادیویی به صورت مستمر باز است.
در نقطه مقابل، Bluetooth Low Energy برای تبادل بسته های وضعیت یا پیام های سنسوری با حجم چند ده بایت بهینه شده است. زمان برقراری ارتباط در BLE تنها چند میلی ثانیه است، در حالی که اتصال بلوتوث کلاسیک ممکن است چند ثانیه زمان ببرد.
برای بررسی نحوه استقرار سرویس های ایزوله و لایه های تجاری پایدار، پیشنهاد می شود مقاله معماری تمیز در مهندسی نرم افزار را در طراحی این ماژول ها به کار گیرید.
۳. جدول مقایسه جامع: بلوتوث کلاسیک در برابر Bluetooth Low Energy
جدول زیر مهم ترین پارامترهای فنی و کاربردی این دو فناوری را در محیط سیستم عامل اندروید مقایسه می کند:
| معیار فنی | بلوتوث کم مصرف (BLE) | بلوتوث کلاسیک (Classic) |
|---|---|---|
| توان مصرفی باتری | بسیار اندک (۰.۰۱ تا ۰.۵ وات) | زیاد (۱ وات به بالا) |
| سرعت اتصال اولیه | فوق العاده سریع (زیر ۱۰ میلی ثانیه) | کند (حدود ۱۰۰ تا ۱۰۰۰ میلی ثانیه) |
| نرخ انتقال داده (Data Rate) | ۱ تا ۲ مگابیت بر ثانیه | ۲ تا ۳ مگابیت بر ثانیه |
| معماری تبادل داده | پروتکل کلاینت/سرور GATT بر پایه صفات | سوکت های رادیویی RFCOMM و L۲CAP |
| طول عمر باتری سنسور | ماه ها تا سال ها با باتری سکه ای | چند ساعت تا چند روز |
| حداکثر برد عملیاتی | تا ۱۰۰ متر در فضای باز | حدود ۱۰ تا ۳۰ متر |
همان گونه که جدول نشان می دهد، اگر برنامه شما با تجهیزات هوشمند و داده های سنسوری سروکار دارد، BLE انتخاب بی چون و چرا و بی رقیب است.
۴. مدیریت مجوزهای دسترسی بلوتوث در اندروید ۱۲ و نسخه های جدیدتر
گوگل در اندروید ۱۲ (API ۳۱) ساختار مجوزهای بلوتوث را متحول ساخت و وابستگی اجباری بلوتوث به دسترسی مکانی (Location) را برای اکثر کاربردها حذف کرد:
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- مجوزهای اندروید 12 به بعد -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
استفاده از پرچم neverForLocation در صفت usesPermissionFlags به سیستم عامل اعلام می کند که برنامه از نتایج اسکن بلوتوث برای استخراج موقعیت جغرافیایی کاربر استفاده نمی کند.
با این تنظیم هوشمندانه، نیازی به اخذ مجوز حساس ACCESS_FINE_LOCATION از کاربر نخواهید داشت که این امر نرخ پذیرش کاربر و حریم خصوصی را ارتقا می بخشد.
۵. کالبدشکافی معماری سلسله مراتبی GATT: پروفایل ها، سرویس ها و مشخصه ها
تبادل داده در بلوتوث کم مصرف بر پایه معماری Generic Attribute Profile (GATT) انجام می شود. این ساختار از بالا به پایین به سه بخش تقسیم می شود:
۱. پروفایل (Profile): مجموعه ای از مشخصات استاندارد که نحوه کاربری را تعیین می کند (مانند پروفایل Heart Rate یا Battery Status).
۲. سرویس ها (Services): گروه بندی منطقی داده ها که با شناسه یکتای جهانی UUID مشخص می شوند. به عنوان مثال، سرویس اطلاعات باتری دارای UUID استاندارد 0000180F-0000-1000-8000-00805F9B34FB است.
۳. مشخصه ها (Characteristics): نقاط پایانی اصلی داده ها که حاوی مقادیر بایت، سطوح دسترسی (Read، Write، Notify) و توصیف گرها (Descriptors) هستند.
توصیف گرها اطلاعات فراداده ای مشخصه را فراهم می کنند؛ معروف ترین آن ها Client Characteristic Configuration Descriptor (CCCD) با کد اختصاصی 0x2902 است که کلید اصلی فعال سازی اعلان های پوش نوتیفیکیشن سخت افزاری محسوب می گردد.
۶. اسکن کم مصرف دستگاه های مجاور با BluetoothLeScanner و فیلترهای UUID
اسکن مداوم بلوتوث یکی از مخرب ترین عملیات ها برای باتری گوشی است. برای یک اسکن اصولی و بهینه از ScanFilter و ScanSettings استفاده می کنیم:
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.os.ParcelUuid
fun startFilteredScan(bluetoothLeScanner: android.bluetooth.le.BluetoothLeScanner) {
val serviceUuid = ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
val filter = ScanFilter.Builder()
.setServiceUuid(serviceUuid)
.build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(0)
.build()
val callback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
val rssi = result.rssi
// پردازش نتیجه اسکن رادیویی
}
}
bluetoothLeScanner.startScan(listOf(filter), settings, callback)
}
حالت SCAN_MODE_LOW_LATENCY برای زمان هایی است که برنامه در پیشخوان اجرا شده و کاربر به دنبال دستگاه می گردد. به محض کشف دستگاه مقصد، باید سریعاً متد stopScan را فراخوانی کنید تا فرکانس رادیویی خاموش شود.
۷. برقراری اتصال پایدار با متد connectGatt و پیاده سازی BluetoothGattCallback
برای برقراری اتصال، از متد connectGatt بر روی شیء BluetoothDevice استفاده می شود. تمام رویدادها از طریق یک کلاس کالبک دریافت می گردند:
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothProfile
import android.content.Context
class BleConnectionManager(private val context: Context) {
private var gatt: BluetoothGatt? = null
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
// کشف سرویس ها پس از اتصال موفق
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
gatt.close()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// دسترسی به لیست سرویس های کشف شده
}
}
}
fun connect(device: android.bluetooth.BluetoothDevice) {
gatt = device.connectGatt(context, false, gattCallback, android.bluetooth.BluetoothDevice.TRANSPORT_LE)
}
}
استفاده از پارامتر TRANSPORT_LE حیاتی است؛ این دستور به سیستم عامل اجبار می کند که منحصراً از کانال های رادیویی کم مصرف استفاده کند و از امتحان پروتکل های سنگین کلاسیک خودداری نماید.
۸. خواندن و نوشتن اطلاعات در مشخصه های GATT با رعایت قوانین ناهمگام
ارسال و دریافت مقادیر بایت در مشخصه ها نیازمند رعایت سازگاری با نسخه های مختلف اندروید است. در اندروید ۱۳ (API ۳۳) متدهای نوشتن و خواندن بازنویسی شده اند:
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.os.Build
fun writeToCharacteristic(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
data: ByteArray
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
gatt.writeCharacteristic(
characteristic,
data,
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
)
} else {
@Suppress("DEPRECATION")
characteristic.value = data
@Suppress("DEPRECATION")
gatt.writeCharacteristic(characteristic)
}
}
نوع WRITE_TYPE_DEFAULT تضمین می کند که سخت افزار جانبی پس از دریافت بایت ها یک پیام تاییدیه (ACK) صادر کند.
در نقطه مقابل، نوع WRITE_TYPE_NO_RESPONSE بدون انتظار برای تایید، بسته بعدی را ارسال می کند که برای استریم سریع داده یا بهروزرسانی های فشرده مناسب تر است.
۹. فعال سازی اعلان های زنده (Notifications) با توصیف گر CCCD (۰x۲۹۰۲)
برای دریافت بلادرنگ اطلاعات از حسگرها (مثلاً دریافت نرخ ضربان قلب در هر ثانیه)، فعال سازی متد setCharacteristicNotification به تنهایی کافی نیست؛ باید توصیف گر پیکربندی سخت افزاری نیز نوشته شود:
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import java.util.UUID
val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
fun enableNotifications(gatt: BluetoothGatt, char: BluetoothGattCharacteristic) {
gatt.setCharacteristicNotification(char, true)
val descriptor = char.getDescriptor(CCCD_UUID)
if (descriptor != null) {
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
}
نوشتن مقدار ENABLE_NOTIFICATION_VALUE به تراشه سخت افزاری دستور می دهد که به محض آماده شدن نمونه جدید در حسگر، بسته های رادیویی را به صورت خودکار به سمت گوشی هدایت نماید.
۱۰. افزایش پهنای باند و سرعت انتقال با مذاکره سایز MTU تا ۵۱۲ بایت
به صورت پیش فرض، حداکثر واحد انتقال (MTU) در بلوتوث کم مصرف تنها ۲۳ بایت است. با کسر ۳ بایت هدر پروتکل ATT، در هر پکت تنها ۲۰ بایت دیتای خالص قابل انتقال است.
برای ارسال بسته های بزرگ تر یا به روزرسانی فریمور سخت افزار، باید بلافاصله پس از اتصال تقاضای افزایش MTU را ارسال کنید:
fun requestOptimalMtu(gatt: BluetoothGatt) {
// درخواست حداکثر اندازه استاندارد 512 بایت
gatt.requestMtu(512)
}
نتیجه این مذاکره در کالبک onMtuChanged دریافت می شود. افزایش MTU تا ۵۱۲ بایت سرعت انتقال دیتا را تا بیش از ۱۰ برابر افزایش داده و تعداد دفعات ارسال پیام های رادیویی را کاهش می دهد.
علاوه بر MTU، تنظیم اولویت اتصال (Connection Priority) نیز در افزایش توان عملیاتی بسیار موثر است. با فراخوانی متد زیر می توانید فواصل ارسال پکت ها را به حداقل برسانید:
fun setHighThroughputMode(gatt: BluetoothGatt) {
// کاهش فاصله زمانی اتصال به ۱۱.۲۵ میلی ثانیه برای انتقال فوق سریع
gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
}
حالت CONNECTION_PRIORITY_HIGH برای ارسال فایل های حجیم یا بهروزرسانی فریمور (OTA) بسیار ایده آل است، اما پس از اتمام انتقال باید مجدداً به حالت CONNECTION_PRIORITY_BALANCED بازگردید تا باتری بیهوده مصرف نشود.
۱۱. حل چالش مرگبار عدم همزمانی در BLE با صف بندی کورتین ها و Mutex
بزرگ ترین تله توسعه BLE در سیستم عامل اندروید این است: پشته بلوتوث اندروید نمی تواند بیش از یک عملیات خواندن یا نوشتن را در یک لحظه پردازش کند.
اگر قبل از دریافت پاسخ یک متد، عملیات دیگری را فراخوانی کنید، سیستم عامل بدون پرتاب اکسپشن، عملیات جدید را نادیده می گیرد یا کالبک خطای GATT_ERROR را تحویل می دهد:
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class BleCommandQueue {
private val mutex = Mutex()
suspend fun <T> execute(action: suspend () -> T): T {
return mutex.withLock {
action()
}
}
}
استفاده از Mutex و کورتین های کاتلین تضمین می کند که تمام دستورات به صورت صفی منظم و تک به تک به کنترلر بلوتوث ارسال شوند و پایداری سیستم در تبادلات فشرده حفظ گردد.
جهت تسلط عمیق بر مباحث جریان های داده و مدیریت وضعیت های غیرهمزمان، مقاله مدیریت همزمانی در کاتلین با کورتین ها را مطالعه فرمایید.
۱۲. امنیت ارتباطات رادیویی: جفت سازی سخت افزاری (Bonding) و تبادل کلیدهای رمزنگاری
در سناریوهای پزشکی، قفل های هوشمند دیجیتال و سیستم های پرداخت، تبادل داده بر روی بستر باز رادیویی خطر استراق سمع (Sniffing) و حملات جعل هویت را افزایش می دهد.
استاندارد BLE چندین سطح امنیتی برای ایجاد پیوند امن (Bonding) ارائه می کند:
۱. روش Just Works: تبادل کلید بدون احراز هویت دستی کاربر که در برابر شنود غیرفعال مقاوم است اما در برابر حملات Man-in-the-Middle آسیب پذیر می ماند.
۲. روش Passkey Entry: کاربر کدی ۶ رقمی که روی صفحه نمایش سنسور نمایش داده می شود را در اپلیکیشن اندروید وارد می کند.
۳. روش Numeric Comparison: هر دو دستگاه عددی یکسان را نشان می دهند و کاربر صحت آن را با فشردن دکمه تایید می کند.
هنگامی که مشخصه ای نیاز به دسترسی امن داشته باشد (PROPERTY_READ_ENCRYPTED)، با فراخوانی متد خواندن، سیستم عامل اندروید به صورت خودکار دیالوگ جفت سازی را به کاربر نمایش می دهد.
۱۳. اسکن فوق کم مصرف در پس زمینه با PendingIntent بدون مصرف بیهوده باتری
اگر اپلیکیشن شما باید به محض نزدیک شدن کاربر به یک تگ فروشگاهی یا تجهیزات صنعتی واکنش نشان دهد، نگه داشتن یک Service فعال در پس زمینه باتری را تخلیه کرده و توسط سیستم مدیریت انرژی اندروید متوقف می شود.
راهکار استاندارد گوگل، استفاده از متد startScan به همراه شیء PendingIntent است:
import android.app.PendingIntent
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.Intent
fun startBackgroundBeaconScan(context: Context, scanner: BluetoothLeScanner) {
val intent = Intent(context, BleScanBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
1001,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build()
val filter = ScanFilter.Builder()
.setDeviceName("SmartBeacon")
.build()
scanner.startScan(listOf(filter), settings, pendingIntent)
}
با این مکانیزم، پشته رادیویی سخت افزار در سطح میکروکنترلر به جستجو ادامه می دهد و تنها زمانی که دستگاهی با فیلتر منطبق کشف شود، پردازنده اصلی گوشی را بیدار کرده و برودکست رسیور شما را فعال می سازد.
۱۴. حالت فرستنده (Peripheral Mode) و تبلیغ سرویس ها با BluetoothLeAdvertiser
گوشی های مدرن اندروید نه تنها می توانند به عنوان کلاینت به سایر گجت ها متصل شوند، بلکه خود می توانند نقش یک سرور GATT یا بیکون تبلیغاتی (Advertiser) را بر عهده بگیرند:
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.BluetoothLeAdvertiser
import android.os.ParcelUuid
fun startAdvertising(advertiser: BluetoothLeAdvertiser) {
val settings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.setConnectable(true)
.build()
val data = AdvertiseData.Builder()
.setIncludeDeviceName(true)
.addServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
.build()
val callback = object : AdvertiseCallback() {
override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) {
// شروع موفق پخش داده های رادیویی
}
}
advertiser.startAdvertising(settings, data, callback)
}
این قابلیت پایه و اساس سیستم های انتقال محتوای نزدیک، کارت های حضور و غیاب دیجیتال و ارتباط آفلاین میان دو گوشی اندروید را شکل می دهد.
۱۵. مدیریت اتصال مجدد خودکار با الگوریتم عقب نشینی نمایی (Exponential Backoff)
در شرایط عملیاتی، قطع ناگهانی ارتباط رادیویی BLE به دلیل تداخل امواج فرکانسی، جابجایی فیزیکی کاربر یا اتمام موقت باتری سنسور رویدادی متداول است.
تلاش بی وقفه و ممتد برای اتصال مجدد باعث مصرف شدید باتری گوشی و قفل شدن کنترلر بلوتوث می شود. بهترین استراتژی مهندسی، پیاده سازی الگوریتم عقب نشینی نمایی است:
import kotlinx.coroutines.delay
suspend fun reconnectWithBackoff(
maxAttempts: Int = 5,
initialDelayMs: Long = 1000,
connectAction: suspend () -> Boolean
): Boolean {
var currentDelay = initialDelayMs
repeat(maxAttempts) { attempt ->
val success = connectAction()
if (success) return true
delay(currentDelay)
currentDelay = (currentDelay * 2).coerceAtMost(30000)
}
return false
}
با این الگو، فاصله میان تلاش های اتصال از ۱ ثانیه شروع شده و به صورت تصاعدی تا حداکثر ۳۰ ثانیه افزایش می یابد که مصرف انرژی پردازنده را به حداقل ممکن می رساند.
۱۶. پروتکل های استاندارد Bluetooth SIG: کالبدشکافی سرویس های باتری و اطلاعات دستگاه
کنسرسیوم بلوتوث (Bluetooth SIG) ده ها سرویس استاندارد عمومی با UUID های ۱۶ بیتی تعریف کرده است که در تمام تجهیزات معتبر پیاده سازی می شوند.
دو سرویس از پرکاربردترین این استانداردها، سرویس سطح باتری (Battery Service ۰x۱۸۰F) و سرویس اطلاعات سازنده (Device Information Service ۰x۱۸۰A) هستند:
import java.util.UUID
val BATTERY_LEVEL_CHAR_UUID: UUID = UUID.fromString("00002A19-0000-1000-8000-00805F9B34FB")
val MODEL_NUMBER_CHAR_UUID: UUID = UUID.fromString("00002A24-0000-1000-8000-00805F9B34FB")
fun parseBatteryLevel(value: ByteArray): Int {
return if (value.isNotEmpty()) value[0].toInt() and 0xFF else 0
}
fun parseModelNumber(value: ByteArray): String {
return String(value, Charsets.UTF_8)
}
پارس کردن این مقادیر استاندارد به اپلیکیشن اجازه می دهد بدون نیاز به پروتکل های اختصاصی، درصد دقیق شارژ باتری و شماره مدل سخت افزار را روی نمایشگر به کاربر نشان دهد.
۱۷. اتصال رویدادهای BLE به رابط کاربری مدرن در فریمورک Jetpack Compose
برای نمایش وضعیت دستگاه، کیفیت سیگنال (RSSI) و مقادیر دریافتی در رابط کاربری اعلانی کامپوز، لاجیک اتصال را در یک ViewModel کپسوله کرده و مقادیر را به صورت StateFlow منتشر می کنیم:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
data class BleUiState(
val isConnected: Boolean = false,
val deviceName: String = "",
val heartRate: Int = 0,
val rssi: Int = 0
)
class BleViewModel : ViewModel() {
private val _uiState = MutableStateFlow(BleUiState())
val uiState = _uiState.asStateFlow()
fun updateHeartRate(rate: Int) {
_uiState.value = _uiState.value.copy(heartRate = rate)
}
}
با این ساختار مدرن، کامپوننت های Composable بدون درگیر شدن با جزییات سخت افزاری بلوتوث، تنها با مشاهده جریان داده، رابط کاربری را با رویکرد Material ۳ بازترکیب می نمایند.
جهت بررسی الگو های معماری کامپوز و مدیریت وضعیت های پیچیده در برنامه، مقاله مرجع آموزش جامع Jetpack Compose را بررسی بفرمایید.
۱۸. کالبدشکافی خطای مرموز status ۱۳۳ و راهکارهای طلایی رفع قطعی
هر برنامه نویسی که با بلوتوث اندروید کار کرده باشد، با خطای بدنام status = 133 (GATT_ERROR) برخورد داشته است. این خطا یک کد عمومی است که دلایل متعددی دارد:
- تکمیل ظرفیت پیوندهای رادیویی: سیستم عامل اندروید حداکثر ۷ اتصال همزمان BLE را می پذیرد. اگر اتصالات قبلی را با
gatt.close()نبسته باشید، خطای ۱۳۳ رخ می دهد. - اسکن همزمان با اتصال: هرگز در زمان فراخوانی
connectGattفرآیند اسکن رادیویی را فعال نگه ندارید. همیشه ابتدا اسکن را متوقف کنید. - تاخیر زمانی در برقراری پیوند: برخی دستگاه ها نیازمند فاصله زمانی ۵۰۰ میلی ثانیه میان قطع اتصال قبلی و تلاش برای اتصال مجدد هستند.
- کش کثیف سخت افزاری در سیستم عامل: پاکسازی کش داخلی با فراخوانی متد مخفی
refresh()از طریق انعکاس به رفع این خطا کمک می کند.
STATE_DISCONNECTED تغییر می کند، باید بلافاصله متد gatt.close() را فراخوانی کنید؛ رها کردن شیء gatt بدون آزادسازی باعث نشت منابع سخت افزاری و از کار افتادن کامل بلوتوث دستگاه می شود.
۱۹. تست های خودکار شبیه سازی دستگاه های BLE در پایپ لاین CI/CD با MockK
وابستگی کدهای BLE به سخت افزار فیزیکی نباید مانع از تست خودکار منطق برنامه در سرور هایی ابری و بیلد پایپ لاین شود:
import io.mockk.mockk
import io.mockk.verify
import org.junit.Test
class BleProcessorTest {
@Test
fun parseHeartRateData_extractsBpmCorrectly() {
val rawBytes = byteArrayOf(0x00, 0x48) // بایت فلگ و مقدار 72 ضربان
val result = parseHeartRateBytes(rawBytes)
assert(result == 72)
}
}
برای آشنایی با فریمورک های پیشرفته تست و ایجاد شبیه سازهای رفتاری، مقاله مرجع آموزش تست نویسی در اندروید با MockK را در چرخه توسعه به کار بگیرید.
۲۰. چک لیست نهایی استقرار کدهای ارتباطی BLE در محیط های عملیاتی
پیش از انتشار نهایی نسخه برنامه در مارکت ها، موارد زیر را به عنوان چک لیست کیفی کنترل فرمایید:
- بررسی روشن بودن آداپتور سخت افزاری بلوتوث پیش از آغاز اسکن با متد
BluetoothAdapter.isEnabled. - تنظیم تایم اوت مشخص (حداکثر ۱۰ ثانیه) برای توقف خودکار اسکن در صورتی که دستگاه پیدا نشود.
- استفاده از سیستم صف بندی دستورات با Mutex جهت جلوگیری از ارسال همزمان درخواست ها.
- مدیریت صحیح آزادسازی منابع رادیویی در زمان چرخه حیات خروج از برنامه با
gatt.disconnect()وgatt.close(). - پشتیبانی از افزایش اندازه پکت ها با متد
requestMtuبرای بهبود چشمگیر سرعت انتقال.
gatt.requestMtu(517) را فراخوانی نمایید. با افزایش اندازه بسته از ۲۳ بایت پیش فرض به ۵۱۷ بایت، پهنای باند موثر انتقال اطلاعات تا بیش از ۱۰ برابر افزایش یافته و زمان فعال ماندن چیپ رادیویی بلوتوث به شدت کاهش می یابد که این امر منجر به صرفه جویی چشمگیر در مصرف باتری گجت متصل می گردد.
پرسش های متداول پیرامون بلوتوث کم مصرف و پروتکل BLE در اندروید
آیا برای کار با بلوتوث کم مصرف در اندروید ۱۲ به بعد دسترسی به GPS الزامی است؟
خیر، اگر در فایل مانیفست صفت android:usesPermissionFlags="neverForLocation" را برای مجوز BLUETOOTH_SCAN تنظیم کنید، دیگر نیازی به درخواست دسترسی به موقعیت مکانی (Location) از کاربر نخواهد بود.
دلیل اصلی بروز خطای status = ۱۳۳ در زمان اتصال چیست؟
این خطا معمولاً به علت اسکن همزمان در هنگام اتصال، عدم فراخوانی gatt.close() در اتصالات قبلی، یا پر شدن سقف اتصالات فعال سیستم عامل اندروید رخ می دهد.
تفاوت Notification با Indication در استاندارد GATT چیست؟
در حالت Notification دستگاه داده ها را بدون انتظار برای تاییدیه کلاینت ارسال می کند که سرعت بالاتری دارد؛ اما در Indication کلاینت باید دریافت هر بسته را با پیام ACK تایید کند که قابلیت اطمینان بیشتری دارد.
توصیف گر CCCD چیست و چرا برای دریافت داده ها ضروری است؟
توصیف گر CCCD با شناسه 0x2902 یک کلید سخت افزاری درون سنسور است که تا زمانی که مقدار اعلانی درون آن نوشته نشود، دستگاه جانبی مجاز به استریم دیتای سنسوری به سمت گوشی نخواهد بود.
حداکثر حجم داده قابل ارسال در یک بسته BLE چقدر است؟
به صورت پیش فرض حجم بسته تنها ۲۳ بایت (۲۰ بایت دیتای خالص) است؛ اما با مذاکره و ارسال متد requestMtu(512) می توان این حجم را تا ۵۱۲ بایت در هر تراکنش رادیویی افزایش داد.
چرا ارسال همزمان دو مشخصه در اندروید با شکست مواجه می شود؟
پشته بلوتوث اندروید دارای ساختار صف بندی داخلی تک درگاهی است و نمی تواند دو عملیات خواندن یا نوشتن را به طور همزمان هندل کند؛ باید از مکانیزم های صف مانند Mutex کورتین ها استفاده شود.
تفاوت متد disconnect با close در کلاس BluetoothGatt چیست؟
متد disconnect تنها ارتباط رادیویی را قطع می کند اما منابع سخت افزاری را در حافظه نگه می دارد؛ متد close کلاینت را به طور کامل نابود کرده و منابع سیستمی را آزاد می سازد.
آیا می توان از BLE برای استریم صدا و موزیک با کیفیت استفاده کرد؟
پروتکل سنتی BLE پهنای باند کافی برای موزیک باکیفیت ندارد؛ اما استاندارد جدید LE Audio بر پایه کدک LC۳ که در نسخه های اخیر معرفی شده است، استریم صوتی کم مصرف را ممکن می سازد.


امتیاز و نظرات
هنوز نظری ثبت نشده است. اولین نفر باشید.