34 -اندرويد : Connectivity -بلوتوث Bluetooth

ملاحظة : الموضوع عبارة عن حلقة من حلقات سلسلة برمجة وتطوير اندرويد Android Development

اندرويد : بلوتوث Bluetooth

السلام عليكم ورحمة الله وبركاته

لمحة سريعة :connectivity

تمكن واجهات التطبيقات البرمجية APIs الخاصة ببلوتوث ضمن اندرويد تطبيقك من انجاز عمليات نقل المعطيات اللاسلكي مع بقية التجهيزات.

ضمن هذه المقالة سوف نتحدث عن :

  1. الاساسيات The Basics
  2. سماحيات بلوتوث Bluetooth Permissions
  3. اعداد بلوتوث Setting Up Bluetooth
  4. ايجاد الاجهزة Finding Devices
  5. الاستعلام عن الاجهزة المقترنة Querying paired devices
  6. استكشاف الاجهزة Discovering devices
  7. توصيل الاجهزة Connecting Devices
  8. الاتصال كخادم Connecting as a server
  9. الاتصال كزبون Connecting as a client
  10. الادارة والتحكم بالاتصال Managing a Connection
  11. العمل مع الملفات Working with Profiles
  12. Vendor-specific AT commands
  13. Health Device Profile

الصفوف الاساسية والمهمة

  1. BluetoothAdapter
  2. BluetoothDevice
  3. BluetoothSocket
  4. BluetoothServerSocket

بعض الامثلة ذات العلاقة بالموضوع

  1. Bluetooth Chat
  2. Bluetooth HDP (Health Device Profile)

تتضمن منصة عمل اندرويد دعم لمكدس شكبة بلوتوث Bluetooth network stack, والذي يمكن بدوره الجهاز من تبادل المعطيات بشكل لاسلكي مع بقية اجهزة بلوتوث.

يزود اطار عمل التطبيق امكانية النفاذ إلى خدمات بلوتوث عبر واجهات التطبيقات البرمجية الخاصة ببلوتوث ضمن اندرويد Android Bluetooth APIs. تمكن هذه الواجهات APIs التطبيقات من التواصل بشكل لاسلكي مع بقية الجهزة الي تدعم بلوتوث, لتصبح بذلك ميزات الاتصال اللاسلكي بين نقطة – نقطة أو (بين عدة نقاط) متاحة.

بامكان تطبيقك انجاز المهام التالية عبر استخدام Bluetooth APIs

  • البحث عن اجهزة بلوتوث اخرىScan for other Bluetooth devices
  • الاستعلام من محول بلوتوث الداخلي عن اجهزة بلوتوث المتقرنة Query the local Bluetooth adapter for paired Bluetooth devices
  • انشاء قنوات RFCOMM.Establish RFCOMM channels
  • الاتصال مع بقية الاجهزة عبر خدمة الاستكشاف   Connect to other devices through service discovery
  • نقل المعطيات من وإلى بقية الاجهزة Transfer data to and from other devices
  • التحكم وادارة الاتصالات المتعددة Manage multiple connections

الاساسيات The Basics

تتحدث هذه المقالة عن آلية استخدام واجهات التطبيقات البرمجية الخاصة ببلوتوث ضمن اندرويد Android Bluetooth APIs وذلك بهدف انجاز المهام الاساسية الاربعة الضرورية للتواصل عبر استخدام بلوتوث:

  1. إعداد بلوتوث setting up Bluetooth
  2. ايجاد الاجهزة التي – بكل الاحوال – باحد حالتين : اما مقترنة paired او متاحة ضمن المنطقة الحالية.
  3. الاتصال بين الاجهزة connecting devices
  4. نقل المعطيات بين الاجهزة transferring data between devices

تتواجد كل واجهات بلوتوث Bluetooth APIs ضمن الحزمة  android.bluetooth.

فيما يلي ملخص للصفوف classes والواجهات interfaces التي تحتاجها لإنشاء اتصال بلوتوث:

BluetoothAdapter

تمثل محول بلوتوث محلي local Bluetooth adapter(Bluetooth radio). يعتبر BluetoothAdapter  بمثابة المدخل الاساسي لكل اساليب التواصل والتواصل عبر البلوتوث Bluetooth interactions.

باستخدام BluetoothAdapter يصبح بإمكانك

  • اكتشاف اجهزة البلوتوث الاخرى
  • والاستعلام عن قائمة بالاجهزة المتقرنة paired devices
  • استنساخ BluetoothDevice  عبر استخدام عنوان MAC معروف.
  • إنشاء BluetoothServerSocket  للتصنت والاستماع على الاتصالات بين بقية الاجهزة

BluetoothDevice

ويمثل جهاز بلوتوث عن بعد remote Bluetooth device.

استخدم BluetoothDevice لطلب الاتصال مع جهاز عن بعد وذلك عبر BluetoothSocket أوعبر الاستعلام  عن معلومات الجهاز مثل الاسم name او العنوان  address او الصف class او حالة الاقتران bonding state.

BluetoothSocket

وتمثل واجهة ل Bluetooth socket (بشكل مشابه ل TCP Socket).

عبارة عن نقطة الاتصال التي تمكن تطبيقك من تبادل المعطيات مع بقية اجهزة بلوتوث عبر InputStream  أو OutputStream.

BluetoothServerSocket

وتمثل server socket  مفتوحة تتنصت للطلبات القادمة( بشكل مشابه ل ServerSocketTCP ).

لكي تقوم باجراء اتصال بين جهازي اندرويد, يجب على احد الجهازين ان يفتح server socket  عبر هذا الصف BluetoothServerSocket class. عندما يجري جهاز بلوتوث عن بعد طلب اتصال لهذا الجهاز (أي الجهاز الذي يحوي على BluetoothServerSocket) فإن BluetoothServerSocket سوف يعيد BluetoothSocket متصلة , وذلك عندما يتم قبول الاتصال.

BluetoothClass

يوصف الصفات الامكانيات العامة لجهاز بلوتوث.عبارة عن مجموعة من الخصائص( التي يمكن فقط قرائتها) والتي تعرف الصفوف الاساسية  والثانوية للجهاز مع خدماتهاmajor and minor device classes and its services .

على كل الاحوال فإن هذا الصف لا يوصف عليه بروفايل بلوتوث والخدمات المدعومة من قبل الجهاز  بشكل يمكن الاعتماد, ولكنها مفيدة في الاشارة إلى نوع الجهاز.

BluetoothProfile

عبارة عن واجهة تمثل بروفايل بلوتوث Bluetooth profile.

Bluetooth profile (بروفايل بلوتوث) : عبارة عن خصائص ومواصفات لواجهة لا سكلية تخص الاتصالات  بين الاجهزة التي تعتمد على بلوتوث.

مثال عليها : Hands-Free profile.

لمزيد من المعلومات حول البروفايلات بالامكان الاطلاع على الرابط التالي : Working with Profiles.

BluetoothHeadset

توفر دعم لسماعات بلوتوث Bluetooth headsets التي تستخدم مع اجهزة الموبايل. تضمن كل من بروفايلات  Bluetooth headsets  و Head-Free(v1.5)

BluetoothA2dp

تحدد مدى جودة الصوت الذي يمكن ارساله (عبر stream) من جهاز إلى اخر من خلال اتصال بلوتوث.

إن “A2DP”عبارة عن اختصار  ل Advanced Audio Distribution Profile.

BluetoothHealth

 Represents a Health Device Profile proxy that controls the Bluetooth service

 BluetoothHealthCallback

صف مجرد abstract class  تستخدمه لتحقيق (تنجيز implement) استدعاءات BluetoothHealth.

يجب القيام ب extend لهذا الصف وتنجيز implement توابع الاستدعاء بهدف تلقي التحديثات حول التغيرات في حالة تسجيل التطبيق application’s registration state  وحالة قناة بلوتوث Bluetooth channel state.

BluetoothHealthAppConfiguration

يمثل اعدادات التطبيق التي يسجلها تطبيق Bluetooth Health third-party  بهدف التواصل مع جهاز بلوتوث عن بعد  remote Bluetooth health device .

BluetoothProfile.ServiceListener

واجهة تقوم بتنبيه واخطار BluetoothProfile IPC clients عندما يتم اتصالهم او انفصالهم عن الخدمة(that is, the internal service that runs a particular profile).

سماحيات وصلاحيات بلوتوث Bluetooth Permissions

لكي تستطيع استخدام ميزات بلوتوث ضمن تطبيقك , يتوجب عليك ان تصرح على الأقل عن واحد من الصلاحيات التالية :

BLUETOOTH  أو BLUETOOTH_ADMIN.

يتوجب عليك ان تطلب صلاحية BLUETOOTH  لكي تستطيع ان تنجز أي نوع من انواع اتصالات بلوتوث, مثل عملية طلب اتصال requesting a connection , قبول طلب اتصال accepting a connection  , ونقل المعطيات transferring data.

يتوجب عليك ان تطلب صلاحية BLUETOOTH_ADMIN  لكي تهيئ مستكشف الاجهزة device discovery  أو للتعامل مع اعدادات بلوتوث.

اغلب التطبيقات تحتاج لهذه الصلاحية فقط  بهدف اكتشاف اجهزة  البلوتوث المحلية. اما بالنسبة لبقية الامكانيات التي تتيحها هذه الصلاحية فلا يجب استخدامها, إلا في حال كان التطبيق عبارةعن “power manager” الذي يقوم بتعديل اعدادات بلوتوث بناء على طلب المستخدم.

ملاحظة : في حال استخدمت صلاحية BLUETOOTH_ADMIN, يتوجب عليك ايضا تملك صلاحية BLUETOOTH.

يتم التصريح عن صلاحيات بلوتوث ضمن ملف manifest ضمن تطبيقك. على سبيل المثال:

<manifest ... >
  <uses-permission android:name="android.permission.BLUETOOTH" />
  ...
</manifest>

لمزيد من المعلومات حول التصريح عن صلاحيات التطبيق بالامكان مراجعة الدليل التالي : <uses-permission>

اعداد بلوتوث Setting Up Bluetooth

bluetooth conectivity

الشكل 1 : صندوق الحوار الخاص بتفعيل بلوتوث

قبل ان يستطيع تطبيقك التواصل عبر بلوتوث , انت بحاجة للتحقق من ان بلوتوث مدعوم على الجهاز, وفي حال كان مدعوما, يجب التحقق من انه مفعل.

في حال لم يكن البلوتوث مدعوما على الجهاز, عندها يتوجب عليك ازالة تفعيل ميزة البلوتوث من تطبيقك.

اما في حال كان البلوتوث مدعوما على الجهاز, ولكنه غير مفعل, عندها بإمكانك ان تطلب من المستخدم ان يفعل البلوتوث بدون مغادرة تطبيقك.

يتم الاعداد عبر الخطوتين التاليتين وذلك باستخدام BluetoothAdapter:

الحصول على BluetoothAdapter:

تعتبر ال BluetoothAdapter  مطلوبة واساسية في كل فعاليات بلوتوث Bluetooth activity.ويعيد هذا الصف  BluetoothAdapter  الذي يمثل محول البلوتوث الخاص بالجهاز ذاته device’s own Bluetooth adapter(the Bluetooth radio).
يوجد فقط محول بلوتوث وحيد Bluetooth adapter للنظام باكمله, وباستطاعة تطبيقك التفاعل معه باستخدام this object. اذا كانت القيمة المعادة من قبل التابع getDefaultAdapter() مساوية ل Null, فهذا يعني بأن الجهاز لا يدعم بلوتوث وتنتهي القصة عند هذه النقطة. على سبيل المثال:

 
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    // Device does not support Bluetooth
}

تفعيل البلوتوث Enable Bluetooth

في المرحلة التالية : انت بحاجة للتحقق من ان البلوتوث مفعل على الجهاز.

نقوم باستدعاء التابع isEnabled() للتحقق فيما اذا كان البلوتوث حاليا مفعل. اذا كانت القيمة المعادة من هذا التابع مساوية ل false , عندها يكون البلوتوث غير مفعل. لكي تطلب ان يكون البلوتوث مفعلا عليك استدعاء التابع startActivityForResult()مع حدث ال intent  التالي : ACTION_REQUEST_ENABLE. سوف يقوم هذا بارسال طلب لتفعيل البلوتوث من خلال اعدادات النظام (بدون ايقاف تطبيقك). على سبيل المثال:

if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

عندها سوف يظهر صندوق حوار يطلب من المستخدم السماح بتفعيل البلوتوث, كما هو مبين في الشكل رقم 1. في حال وافق المستخدم وضغط على “yes”عندها سوف يبدأ النظام بتفعيل البلوتوث وسيعود التركيز إلى تطبيقك ما إن تنتهي الاجرائية (أو تفشل).

 في المثال السابق – تم تميرير الثابت REQUEST_ENABLE_BT إلى التابع startActivityForResult()وهو عبارة عن عدد صحيح معرف محليا (ويجب ان تكون قيمته اكبر من 0), ومن ثم يعيد النظام تمرير هذا الثابت مرة اخرى إليك وذلك ضمن تحقيق التابع onActivityResult()وهو بمثابة معامل “ترميز الطلب requestCode”.

في حال نجحت عملية تفعيل البلوتوث, عندها سوف تتلقى فعاليتك ترميز النتيجة التالي  RESULT_OK  وذلك ضمن استدعاء التابع onActivityResult().  في حال لم يتم تفعيل البلوتوث على الجهاز بسبب خطأ ما ( او في حال ضغط المستخدم على خيار “No”) عندها فإن ترميز النتيجة هو التالي : RESULT_CANCELED.

بشكل خياري , باستطاع تطبيقك ايضا ان يتصنت على ACTION_STATE_CHANGED (الخاصة ب Intent  البث broadcast Intent), والتي سوف يقوم النظام ببثها ما إن تتغير حالة البلوتوث. يحوي هذا البث على حقول اضافية : EXTRA_STATE و EXTRA_PREVIOUS_STATE تحوي على حالة البلوتوث القديمة والحديثة بالترتيب. القيم المحتملة لهذه الحقول الاضافية هي التالية :

يعتبر الانصات لهذا البث مفيد جدا لاكتشاف التغيرات التي تطرأ على حالة البلوتوث اثناء عمل تطبيقك.

ملاحظة : إن تفعيل الاستكشاف discoverability سوف يفعل البلوتوث بشكل تلقائي. في حال كنت تخطط بشكل دائم لتفعيل مستكشف الاجهزة قبل تنفيذ فعالية البلوتوث , عندها بإمكانك تجاوز الخطوة 2 اعلاه. بالامكان قراءة المزيد حول enabling discoverability ادناه.

ايجاد الاجهزة Finding Devices

بإمكانك عبر استخدام BluetoothAdapterايجاد اجهزة بلوتوث اما عبر مستكشف الاجهزة device discovery او عبر الاستعلام عن قائمة الاجهزة المقترنة paired (bonded)devices.

استكشاف الاجهزة Device discovery  : عبارة عن اجرائية مسح تقوم بالبحث ضمن المنطقة المحلية عن اجهزة البلوتوث المفعلة ومن ثم تطلب بعض المعلومات عن كل واحد منها (يشار في بعض الاحيان إلى هذه العملية ب ” discovering” , “inquiring” , “scanning”  ).

على كل الاحوال, سوف تستجيب اجهزة البلوتوث ضمن المنطقة المحلية إلى طلب الاكتشاف فقط في حال كانت مفعلة لأن يتم اكتشافها. في حال كان الجهاز قابل للاستكشاف, عندها فإنه سوف يستجيب لطلب الاستكشاف discovery request عبر مشاركة بعض المعلومات , مثل اسم الجهاز device name, الصف class , بالاضافة إلى عنوان MAC الفريد. باستخدام هذه المعلومات, يصبح بإمكان الجهاز الذي يقوم بالاستكشاف ان يختار ان يبدأ بعملية اتصال مع الاجهزة  التي تم ايجادها.

ما إن يتم لأول مرة انشاء الاتصال مع جهاز عن بعد, حتى يظهر طلب اقتران pairing request  بشكل اوتوماتيكي للمستخدم. عندما يقترن الجهاز, فإن المعلومات الاساسية حول الجهاز (مثل اسم الجهاز, الصف , عنوان ال MAC) سيتم حفظها ويمكن قرائتها بواسطة واجهات بلوتوث Bluetooth APIs.

باستخدام عنوان ال MAC المعروف لجهاز عن بعد , يصبح بالامكان بدء اتصال مع ذاك الجهاز في أي وقت بدون القيام بعملية البحث (حيث يعتبر الجهاز ضمن المجال).

يجب ان تتذكر بأن هنالك اختلاف بين كون الجهاز مقترن , وبين كونه متصل.

ان يكون الجهاز مقترن paired يعني بأن كلا الجهازين يعلمان بوجود بعضهما البعض, ويوجد بينهما shared link-key يمكن استخدمها بهدف التوثق authentication, وقادران على انشاء اتصال مشفر بين بعضهما البعض.

اما ان يكون الجهاز متصل connected فهذا يعني بأن الاجهزة تتشارك حاليا قناة RFCOMM وهي قادرة على ارسال المعطيات لبعضها البعض.

إن واجهات بلوتوث ضمن اندرويد الحالية Android Bluetooth API’s تتطلب ان يكون الجهازان مقترنان paired قبل انشاء اتصال من نوع RFCOMM.(يتم انجاز الاقتران بشكل اوتوماتيكي عندما تبدأ باتصال مشفر عبر Bluetooth APIs).

توصف المقاطع التالية كيفية ايجاد الاجهزة التي تم الاقتران بها, او اكتشاف الاجهزة الجديدة عبر استخدام “مكتشف الاجهزة” device discovery.

ملاحظة :لا تعتبر الاجهزة التي تحوي نظام تشغيل اندرويد قابلة للاكشاف بشكل افتراضي . يستطيع المستخدم ان يجعل الجهاز قابل للاستكشاف لوقت محدود عبر اعدادات النظام. سيتم فيما بعض مناقشة “تفعيل امكانية الاستكشاف  enable discoverabilityفيما بعد”.

الاستعلام عن الاجهزة المتقرنة querying paired devices

قبل القيام باجرائية استكشاف الاجهزة, يجدر الاستعلام عن الاجهزة المتقرنة لمعرفة فيما اذا كان الجهاز المطلوب ضمنها للتو. للقيام بذلك , يتم استدعاء التابع getBondedDevices() . سوف يعيد هذا التابع مجموعة من BluetoothDevices  تمثل الاجهزة المقترنة.

على سبيل المثال, بالإمكان الاستعلام عن كل الاجهزة المقترنة ومن ثم يتم اظهار اسم كل جهاز للمستخدم , باستخدام ArrayAdapter:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
    // Loop through paired devices
    for (BluetoothDevice device : pairedDevices) {
        // Add the name and address to an array adapter to show in a ListView
        mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
}

كل ما يحتاجه غرض BluetoothDevice لكي يبدأ اتصال هو عنوان ال MAC.

في هذا المثال, يتم حفظه على انه جزء من ArrayAdapter التي يتم اظهارها للمستخدم.

يمكن فيما بعد استخلاص عنوان ال MAC لكي نبدأ الاتصال. بالامكان تعلم المزيد حول انشاء الاتصال ضمن قسم Connecting Devices.

استكشاف الاجهزة Discovering devices

لكي تبدأ باستكشاف الاجهزة, بإمكانك ببساطة استدعاء التابع startDiscovery().تعتبر هذه الاجرائية غير متزامنة asynchronous  , وسوف يعيد هذا التابع فورا قيمة منطقية تشير فيما اذا كان الاستكشاف قد بدأ بشكل ناحج ام لا. عادة ما تتضمن اجرائية الاستكشاف استعلام ومسح يستمر حوالي 12 ثانية , ويتبعه مسح لكل جهاز يتم العثور عليه بهدف استعادة اسم البلوتوث.

يتوجب على تطبيقك ان يسجل BroadcastReceiver  من اجل intent  ACTION_FOUNDلكي يتلقى المعلومات حول الاجهزة المستكشفة.

Your application must register a BroadcastReceiver for the ACTION_FOUND Intent in order to receive information about each device discovered

بالنسبة لكل جهاز, سوف يقوم النظام ببث intent  من نوع ACTION_FOUND.  تحمل هذه ال intent الحقول الاضافية EXTRA_DEVICEو EXTRA_CLASS , وتحوي BluetoothDevice  و BluetoothClass, بالترتيب. على سبيل المثال, فيما يلي مثال يوضح كيف بإمكانك التسجيل بهدف التعامل مع البث عندما يتم استكشاف الاجهزة:

  

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

كل ما هو مطلوب من غرض BluetoothDevice  لكي يستطيع ان يبدأ الاتصال هو عنوان ال MAC.ضمن المثال السابق, يتم حفظ MAC address كجزء من ArrayAdapter الذي يتم اظهاره للمستخدم.

يمكن فيما بعد استخلاص عنوان ال MAC لكي نبدأ الاتصال.بالامكان تعلم المزيد حول انشاء الاتصال في القسم الخاص ب “الاتصال بين الاجهزة Connecting Devices“.

تنبيه : تعتبر عملية استكشاف الاجهزة اجرائية ثقيلة بالنسبة لمحول البلوتوث Bluetooth adapter وسوف تستهلك الكثير من الموارد. لذلك ما ان تجد جهاز لتتصل معه , تاكد من ايقاف عملية البحث عبر استدعاء التابع  cancelDiscovery()قبل محاولة القيام بالاتصال. ايضا في حال كنت للتو تملك اتصال مع جهاز ما , عندها فإن انجاز عملية الاستكشاف سوف يقوم بتقليل عرض الحزمة bandwidth المتاح بشكل كبير وخصوصا بالنسبة للاتصال, لذلك لا يتوجب عليك القيام بالبحث واستكشاف الاجهزة مادمت متصلا مع احد الاجهزة.

تفعيل الاستكشاف Enabling discoverability

في حال كنت ترغب بان تجعل جهاز محلي قابل للاستكشاف من قبل بقية الاجهزة , قم باستدعاء التابع startActivityForResult(Intent, int) مع حدث ال Intent  التالي   ACTION_REQUEST_DISCOVERABLE.  سوف يقوم هذا بارسال اشعار بطلب تفعيل نمط الاستكشاف عبر اعدادات النظام ( بدون ايقاف تطبيقك).

بشكل افتراضي , سوف يصبح الجهاز قابل للاستكشاف لمدة 120 ثانية.بإمكانك تعريف وتحديد فترة زمنية اخرى عبر اضافة EXTRA_DISCOVERABLE_DURATIONضمن المعاملات الاضافة extra الخاصة بتلك ال intent. اقصى قيمة للفترة الزمنية الخاصة التي يكون فيها الاستكشاف مفعلا تبلغ 3600 ثانية, اما القيمة 0 فهي تعني بأن الجهاز دوما قابل للاستكشاف. واي قيمة ضمن المجال 0 للقيمة 3600 فإنها سوف تسند بشكل اوتوماتيكي إلى القيمة 120 ثانية. على سبيل المثال, يقوم الكود ادناه بتحديد قيمة الفترة ب 300 :

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
bluetooth request

الشكل 2 : صندوق الحوار الخاص بتفعيل الاستكشاف.

سوف يتم عرض صندوق حوار, يطلب من المستخدم الاذن لجعل الجهاز قابل للاستكشاف, كما هو موضح ضمن الشكل 2. في حال ضغط المستخدم على كلمة “Yes” عندها سوف يصبح الجهاز قابل للاستكشاف وذلك ضمن الفترة المحددة من الزمن.

بعدها سوف تتلقى فعاليتك الاستدعاء onActivityResult())  مع ترميز نتيجة result code  مساوي للتفرة التي كان فيها الجهاز قابل للاستكشاف.

اما في حال ضغط المستخدم على “No” اوحدث خلل ما , عندها سوف يكون ترميز النتيجة مساوي ل RESULT_CANCELED.

ملاحظة : في حال لم يكن البلوتوث مفعل على الجهاز, عندها فإن تفعيل استكشاف الجهاز سوف يفعل بشكل اوتوماتيكي بلوتوث.

سوف يبقى الجهاز بشكل صامت ضمن نمط الاستكشاف خلال الفترة الزمنية المحددة. في حال كنت ترغب بأن يتم اعلامك عند تغير نمط الاستكشاف , عليك بتسجيل BroadcastReceiver لل Intent : ACTION_SCAN_MODE_CHANGED

سوف يحوي هذا ايضا على حقول اضافية  EXTRA_SCAN_MODEو EXTRA_PREVIOUS_SCAN_MODE, والتي تخبرك بقيم نمط المسح الجديدة والقديمة  بالترتيب.

القيم المحتملة لكل منها هي التالية :

SCAN_MODE_CONNECTABLE_DISCOVERABLE

SCAN_MODE_CONNECTABLE

SCAN_MODE_NONE

والتي تشير بأن الجهاز إما ضمن نمط الاستكشاف, او ليس ضمن نمط الاستكشاف ولكن لا يزال قادر على استقبال الاتصالات, او ليس ضمن نمط الاستكشاف وغير قادر على استقبال الاتصالات بالترتيب.

لا تحتاج إلى تفعيل استكشاف الجهاز في حال كنت ترغب ببدء الاتصال مع جهاز بعيد.

إن تفعيل الاستكشاف ضروري فقط عندما ترغب بأن يستضيف تطبيقك server socket التي سوف تقوم بدورها بتلقي الاتصالات connections, لأن الاجهزة عن بعد يجب ان تكون قادرة على اكتشاف الاجهزة التي ترغب الاتصال معها قبل ان تبدأ الاتصال معها.

الاتصال بالاجهزة Connecting Devices

لكي تكون قادرا على انشاء اتصال بين تطبيقك الخاص بك على جهازين, يتجب عليك ان تقوم بتنجيز implement كل من آليتي server-side  و client-side, لأن احد الجهازين يجب ان يفتح server socket , ويتوجب على الجهاز الآخر ان يبدأ الاتصال initiate the connection (وذلك باستخدام عنوان ال MAC الخاص بالجهاز الذي يعمل كمخدم ليبدأ الاتصال).

يعتبر كل من جهاز المخدم server  وجهاز الزبون client متصلان ببعضهما البعض عندما يملك كل منها BluetoothSocket  متصلة (connected)على نفس قناة RFCOMM.

عند هذه النقطة, يستطيع كل من الجهازين الحصول على مجاري دخل وخرج input/output streams ويمكن عند هذه النقطة بدء نقل المعطيات data transfer, والذي ستتم مناقشته ضمن القسم الذي يتحدث عن “ادارة الاتصالManaging a Connection“.

يوصف هذا الفصل كيفية بدء اتصال بين جهازين.

يحصل كل من جهاز المخدم server device  وجهاز العميل client device على BluetoothSocket  بطرق مختلفة.

يتلقى المخدم BluetoothSocket  عندما يتم قبول طلب اتصال قادم إليه.

يتلقى العميل BluetoothSocket  عندما يقوم بفتح قناة RFCOMM  مع المخدم server.

bluetooth pairing request

الشكل 3 :صندوق حوار اقتران البلوتوث

   

 أحد التقنيات المستخدمة لتنجيز ذلك هي بأن نجهز كل جهاز من الاجهزة على انه المخدم server, وبذلك فإن كل جهاز من الاجهزة يكون لديه امكانية بدء اتصال مع الجهاز الآخر الذي بدوره سوف يصبح العميل.

الطريقة البديلة هي التالية :بإمكان احد الاجهزة ان يستضيف host الاتصال (connection)  بشكل صريح ويقوم بفتح server socket بحسب الطلب , وبإمكان الجهاز الاخر ان يبدأ الاتصال ببساطة.

ملاحظة : في حال لم يكن كلا الجهازين قد اقرنا من قبل, عندها فإن اطار عمل اندرويد سوف يقوم بشكل اوتوماتيكي  بعرض تنبيه لطلب اقتران أو صندوق حوار للمستخدم خلال اجرائية الاتصال, كما هو مبين ضمن الشكل 3. لذلك عند محاولة الاتصال بين الاجهزة , لا يحتاج تطبيقك لأن يهتم فيما اذا كان الجهازين مقترنين ام لا. وسيتم حجب محاولات الاتصال عبر RFCOMM حتى يتم الاقتران بنجاح, او انه سيفشل في حال رفض المستخدم طلب الاقتران , او في حال فشل الاقتران , او في حال نفذ الوقت.

الاتصال كمخدم Connecting as a server

عندما ترغب بوصل جهازين, فإنه يتوجب على احد الجهازين ان يتصرف كمخدم وذلك عبر عقد BluetoothServerSocketمفتوحة.

الهدف من server socket هي الانصات إلى طلبات الاتصال القادمة , وعندما يتم قبول احد هذه الطلبات, يقوم المخدم بتزويد connected BluetoothSocket.  عندما يتم الحصول على BluetoothSocket  من BluetoothServerSocket,  فإنه يتم تجاهل ال BluetoothServerSocket, إلا في حال كنت ترغب بالحصول على المزيد من الاتصالات(connections).

حول UUID

UUID  : المعرف الفريد عالميا A universally Unique Identifier,

يستخدم كمعرف فريد للمعطيات.

الميزة المهمة ل UUID هي انه كبير بما فيه الكفاية ليصبح بإمكانك ان تختار أي قيمة عشوائية ضمن مجاله بدون ان تخاف من تضارب القيم.

في حالتنا, يستخدم كمعرف identifier فريد لخدمة بلوتوث الخاصة بتطبيقاتنا.

لكي تحصل على UUID لكي تستخدمه ضمن تطبيقك, بإمكانك استخدام واحد من العديد من مولدات UUID العشوائية الموجودة على الانترنت, ومن ثم تقوم بتهيئة UUID  ب fromString(String).

فيما يلي الاجرائية الاساسية لاعداد server socket ولكي تقبل الاتصال accept a connection:

  1. الحصول على BluetoothServerSocket  عبر استدعاء listenUsingRfcommWithServiceRecord(String, UUID).
    السلسلة المحرفية عبارة عن اسم قابل للتمييز لخدمتك identifiable name , و سوف يقوم النظام بشكل اوتوماتيكي بالكتابة ضمن مدخل جديد ضمن قاعدة لمعطيات (SDP:Service Discovery Protocol) على الجهاز(الاسم عشوائي وببساطة يمكن ان يكون اسم تطبيقك).
    يتم تضمين ال UUID ضمن مدخل SDP وسيكون بمثابة العنصر الاساسي فيما يخص معاملات (المحاورات التي تجري بين الجهازين ) الاتصال مع الجهاز الزبون.
    عندما يحاول الزبون الاتصال مع الجهاز, فإنه سوف يحمل ال UUID الذي يميز بشكل فريد الخدمة التي ترغب بالاتصال بها.
    يجب ان تكون هذه ال UUIDs متطابقة لكي يتم قبول الاتصال(في الخطوة التالية).
  2. البدء بالتصنت لطلبات الاتصال عبر استدعاء  accept().
    هذا الاستدعاء يمثل استدعاء حجب blocking call (أي انه يقوم بمنع أي نوع اخر من التفاعلات ضمن التطبيق).يعود هذا الاستدعاء في احد حالتين , اما عندما يتم قبول الاتصال , او عند حدوث استثناء ما. يتم قبول الاتصال فقط عندما يرسل جهاز بعيد طلب اتصال ب UUID مطابقة لتلك المسجلة مع server socket  الذي يقوم بالتنصت. عند نجاح العملية سوف يعيد التابعaccept() connected BluetoothSocket.
  3. في حال لم ترغب بقبول المزيد من الاتصالات , عندها يتوجب عليك استدعاء التابعclose().
    يقوم هذا التابع بتحرير server socket وكل الموارد, ولكنه لا يقوم باغلاق connected BluetoothSocket التي تمت اعادتها من قبل التابع accept().على نقيض  TCP/IP فإن RFCOMM يسمح فقط لزبون متصل واحد على القناة في نفس الوقت, لذلك فإنه في اغلب الحالات من المنطقي استدعاء close()على  BluetoothServerSocketفورا بعد قبول a connected socket.

لا يتوجب ان يتم استدعاء التابع accept()في نفس thread  الخاص بواحهة المستخدم الخاصة بالفعالية الاساسية وذلك لانه عبارة عن استدعاء حجب blocking call الذي سيقوم بدوره بمنع أي نوع اخر من التفاعلات ضمن التطبيق. عادة من المنطقي ان نقوم بكل العمل المتضمن ل BluetoothServerSocket  أو BluetoothSocket  ضمن مجرى جديد New thread تتم ادارته والتحكم به من قبل تطبيقك.

لكي نجهض وننهي استدعاء حضر ما blocked call , ,يتم استدعاء التابع accept()على غرض BluetoothServerSocket  (أو BluetoothSocket ) من مسرى آخرanother thread  و بذلك سوف يتم انهاء استدعاء الحضر blocked call  بشكل فوري (وسوف يعود return).
لاحظ بأن كل التوابع الخاصة ب BluetoothServerSocket  أو BluetoothSocket عبارة عن توابع thread-safe.

مثال:

فيما يلي مثال على مسرى بسيط لمكون مخدم server component مسؤول عن تلقي طلبات الاتصال القادمة

privateclassAcceptThreadextendsThread{
    privatefinalBluetoothServerSocket mmServerSocket;

    publicAcceptThread(){
        // Use a temporary object that is later assigned to mmServerSocket,
        // because mmServerSocket is final
        BluetoothServerSocket tmp =null;
        try{
            // MY_UUID is the app's UUID string, also used by the client code
            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        }catch(IOException e){}
        mmServerSocket = tmp;
    }

    publicvoid run(){
        BluetoothSocket socket =null;
        // Keep listening until exception occurs or a socket is returned
        while(true){
            try{
                socket = mmServerSocket.accept();
            }catch(IOException e){
                break;
            }
            // If a connection was accepted
            if(socket !=null){
                // Do work to manage the connection (in a separate thread)
                manageConnectedSocket(socket);
                mmServerSocket.close();
                break;
            }
        }
    }

    /** Will cancel the listening socket, and cause the thread to finish */
    publicvoid cancel(){
        try{
            mmServerSocket.close();
        }catch(IOException e){}
    }}

في المثال السابق, المطلوب قبول طلب اتصال وحيد connection, ما إن يتم قبول طلب اتصال ما ويتم الحصول على BluetoothSocket, حتى يقوم التطبيق بارسال BluetoothSocketالمطلوبة إلى مسرى منفصل , ويغلق  BluetoothServerSocket ويكسر الحلقة.

لاحظ بأنه عندما يعيد التابع accept(), ال BluetoothSocket, فإن ال socket تكون متصلة للتو , لذلك لست بحاجة لاستدعاء التابع connect() (كما تفعل عندما تكون من طرف الزبون client –side).

التابع manageConnectedSocket() عبارة عن تابع متخيل ضمن التطبيق, مهمته القيام بتهيئة المسرى initiate the thread  بهدف نقل المعطيات , والتي سيتم مناقشة موضعها ضمن القسم الذي يتحدث عن “ادارة الاتصال about Managing a Connection“.

يتوجب اغلاق BluetoothServerSocket  ما إن تنتهي من التنصت على الاتصالات القادمة incoming connections. على سبيل المثال, يتم استدعاء التابع close()ما إن يتم الحصول على BluetoothSocket.

ربما قد ترغب ايضا ان تضع تابع عام ضمن المسرى thread يستطيع ان يغلق private BluetoothSocket عند الحدث التي ترغب عنده بايقاف التنصت على server socket.

الاتصال كعميل Connecting as a client

لكي تبدأ الاتصال initiate a connection  مع جهاز عن بعد ( مع جهاز يستحوز على open server socket), يتوجب عليك في البداية ان تحصل على غرض BluetoothDevice  الذي يمثل جهاز عن بعد.

(تمت تغطية موضوع الحصول على BluetoothDevice   ضمن المقطع “ايجاد الاجهزة Finding Devices. اعلاه).

ومن ثم يجب استخدام BluetoothDevice  بهدف الحصول على  BluetoothSocket  وبدء الاتصال.

فيما يلي الاجرائية الاساسية :

  1. باستخدام BluetoothDevice, يتم الحصول على BluetoothSocket  عبر استدعاء التابع createRfcommSocketToServiceRecord(UUID).
    يقوم هذا بتهيئة BluetoothSocket  التي سوف تقوم بالاتصال ب BluetoothDevice.
    إن UUID الممرر هنا يجب ان يطابق ال UUID المستخدم من قبل الجهاز المخدم server device عندما يفتح BluetoothServerSocket  الخاصة به(عبر listenUsingRfcommWithServiceRecord(String, UUID)).

إن عملية استخدام نفس ال UUID هي ببساطة عملية تثبيت قيمة السلسلة المحرفية ل UUID ضمن تطبيقك ومن ثم الاشارة إليها من ترميزي (كودي) المخدم server  والعميل client.

  • بدء الاتصال عبر استدعاء التابع connect().
    عند هذا الاستدعاء, سيقوم النظام بتنفيذ عملية بحث SDP lookup على الجهاز الاخر بهدف المطابقة بين UUID. في حال كانت نتيجة البحث ناحجة , وقبل الجهاز الاخر عن بعد طلب الاتصال , عندها فإنه سوف يقوم بمشاركة قناة RFCOMM مع الجهاز المرسل لطلب الاتصال (الجهاز العميل) بهدف الاستخدام اثناء الاتصال , وسوف يتم اعادة connect().
    يعتبر هذا الاستدعاء بمثابة استدعاء حظر blocking call, في حال-  لاي سبب من الاسباب-  فشل الاتصال , او انتهى وقت التابع connect() (بعد 12 ثانية), عندها فإنه سوف يلقي استثناء exception.

وبما ان التابع عبارة عن استدعاء حظر blocking call, لذلك فإن اجراء الاتصال هذا يجب ان يتم دوما ضمن مسرى منفصل separate thread بعيدا عن مسرى الفعالية الاساسية.

ملاحظة : يجب دوما التاكد من ان الجهاز لا ينفذ عملية استكشاف للاجهزة اثناء استدعاء التابع connect() .
في حال كانت عملية البحث جارية , عندها فإن محاولات الاتصال سوف تتباطئ بشكل ملحوظ  ونسبة احتمال فشلها كبيرة.

مثال :

فيما يلي مثال على مسرى يقوم بتهيئة اتصال بلوتوث:

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;

    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;

        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }

    public void run() {
        // Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();

        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }

        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }

    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

لاحظ بأن استدعاء التابع cancelDiscovery()قد تم قبل انشاء الاتصال. يجب عليك دوما القيام بذلك قبل الاتصال , ومن الآمن استدعاءه حتى قبل التحقق الفعلي فيما اذا كان البحث عن الاجهزة جاريا ام لا( ولكن اذا كنت ترغب بالتحقق بالامكان استدعاء التابع isDiscovering().

 التابع manageConnectedSocket() عبارة عن تابع تخيلي ضمن تطبيقك الهدف منه هو تهيئة المسرى بهدف نقل المعطيات, وهذا ما ستتم مناقشته ضمن مقطع “ادارة الاتصال Managing a Connection“.

عندما تنتهي من BluetoothSocket, دوما قم باستدعاء التابع close()بهدف التنظيف. ان ذلك يفعّل القيام بشكل اوتوماتيكي باغلاق كل ال connected socket وينظف كل الوارد الداخلية.

ادارة الاتصال Managing a Connection

عندما تحقق بنجاح الاتصال بين جهازين (او اكثر), عندها سيكون لدى كل جهاز من الاجهزة connected BluetoothSocket. هنا تبدأ الحكاية , حيث يصبح بالامكان مشاركة المعطيات بين الاجهزة.

إن الاجرائية العامة لنقل معطيات عشوائية بين الاجهزة بسيطة , وتتم عبر الاستفادة من BluetoothSocket.

  1. الحصول على InputStream  و OutputStream   بهدف التعامل مع عمليات النقل عبر ال socket, عبر  getInputStream(), وgetOutputStream() بالترتيب.
  2. قراءة وكتابة المعطيات للمجاري streams عبر استخدام كل من التابعين read(byte[]) وwrite(byte[]) .

هذا هو كل شيء !!

طبعا هنالك تفاصيل لها علاقة بتنجيز الموضوع يجب ان تاخذ بعين الاعتبار.
اولا وقبل كل شيء, يجب ان تستخدم مسرى مخصص dedicated thread لكل مجاري القراءة والكتابه هذه .هذا مهم جدا لأن كل من تابعي read(byte[])write(byte[])يعتبران استدعاءات حظر.

  read(byte[])سوف يقوم بالحظر حتى يجد شيء يقوم بقرائته من المجرى stream.

write(byte[])لا يقوم بالحظر عادة , ولكن يمكن ان يقوم بحظر متحكمات التدفق في حال لم يكن الجهاز الاخر يستدعي التابع read(byte[])بالسرعة الكافية , وكانت ال buffers الوسيطة ممتلأة.

اذن الحلقة الاساسية ضمن المسرى thread يجب ان تكون مخصصة للقراءة من InputStream.

ويمكن استخدام تابع عام منفصل بهدف بدء الكتابة من OutputStream.

مثال :

فيما يلي مثال يوضح الفكرة :

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()

        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                // Send the obtained bytes to the UI activity
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }

    /* Call this from the main activity to send data to the remote device */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

يستحصل الباني على المجاري streams  الضرورية , وما ان يتنفذ, حتى يبدأ المجرى thread بانتظار المعطيات القادمة عبر InuptStream.

عندما يعود التابع read(byte[])مع البايتات القادمة من المجرى stream , عندها يتم ارسال المعطيات إلى الفعالية الاساسية باستخدام مرجع عضو  member Handler ضمن الصف الاب. ومن ثم يعود وينتظر المزيد من البايتات القادمة من المسرىstream.

ان عملية ارسال المعطيات الصادرة عملية سهلة جدا , تتم ببساطة عبر استدعاء التابع write() ضمن المسرى من الفعالية الاساسية ونمرر عبره البايتات التي نرغب بارسالها.

ومن ثم يستدعي التابع write(byte[]) لارسال المعطيات إلى الجهاز عن بعد.

يعتبر التابع cancel() الخاص بالمسرى مهم جدا لانهاء الاتصال في أي وقت عبر اغلاق BluetoothSocket. يجب دوما استدعاء هذا التابع عندما تنتهي من استخدام اتصال البلوتوث.

للحصول على ديمو حول استخدام واجهات بلوتوث APIs , بالامكان مراجعة الرابط التالي: Bluetooth Chat sample app.

التعامل مع البروفايل Working with Profile

انطلاقا من نسخة اندرويد 3.0 , تضمنت واجهات بلوتوث دعم للتعامل مع بروفايلات بلوتوث.

بروفايل بلوتوث Bluetooth profile: عبارة عن مواصفات وخصائص لواجهة لاسلكية wireless interface specification  خاصة بالاتصالات بين الاجهزة التي تعتمد على بلوتوث .

عند هذه النقطة وبهذا القدر ننهي حلقة اليوم , للمزيد من التفاصيل بالامكان مراجعة المراجع التي تم ذكرها , واولها من الدروس الوارة في الموقع الرسمي لاندرويد

وإلى لقاء قريب في الحلقة القادمة , وإلى ذلك الحين استودعكم الله والسلام عليكم ورحمة الله وبركاته

الترجمة

المصطلح

مخدم

Server

زبون

Client

اتصال

Connection

مجاري(تدفقات) دخل أو خرج

input/output streams

مسرى

thread

استثناء

exception

استدعاء حظر

blocking call

بروفايل

Profile

طرف المخدم

Server-side

طرف العميل

Client-side

, , , , , , , , , , , , , , , , , , , , , , , , , ,

  1. أضف تعليق

اترك رد

Please log in using one of these methods to post your comment:

WordPress.com Logo

أنت تعلق بإستخدام حساب WordPress.com. تسجيل خروج   / تغيير )

صورة تويتر

أنت تعلق بإستخدام حساب Twitter. تسجيل خروج   / تغيير )

Facebook photo

أنت تعلق بإستخدام حساب Facebook. تسجيل خروج   / تغيير )

Google+ photo

أنت تعلق بإستخدام حساب Google+. تسجيل خروج   / تغيير )

Connecting to %s

%d مدونون معجبون بهذه: