دستگاه های بلوتوث را وصل کنید

برای ایجاد ارتباط بین دو دستگاه، باید هر دو مکانیزم سمت سرور و سمت کلاینت را پیاده سازی کنید زیرا یک دستگاه باید سوکت سرور را باز کند و دیگری باید با استفاده از آدرس MAC دستگاه سرور، اتصال را آغاز کند. دستگاه سرور و دستگاه سرویس گیرنده هر کدام BluetoothSocket مورد نیاز را به روش های مختلف دریافت می کنند. هنگامی که اتصال ورودی پذیرفته می شود، سرور اطلاعات سوکت را دریافت می کند. وقتی کلاینت یک کانال RFCOMM را به سرور باز می کند، اطلاعات سوکت را ارائه می دهد.

سرور و کلاینت زمانی به یکدیگر متصل تلقی می شوند که هر کدام یک BluetoothSocket متصل در یک کانال RFCOMM داشته باشند. در این مرحله، هر دستگاه می‌تواند جریان‌های ورودی و خروجی را دریافت کند و انتقال داده‌ها آغاز شود، که در بخش انتقال داده‌های بلوتوث مورد بحث قرار گرفته است. در این بخش نحوه برقراری ارتباط بین دو دستگاه توضیح داده شده است.

مطمئن شوید که مجوزهای بلوتوث مناسب را دارید و قبل از تلاش برای یافتن دستگاه‌های بلوتوث ، برنامه خود را برای بلوتوث تنظیم کنید .

تکنیک های اتصال

یکی از تکنیک های پیاده سازی این است که به طور خودکار هر دستگاه را به عنوان یک سرور آماده کنید تا هر دستگاه یک سوکت سرور باز داشته باشد و برای اتصالات گوش کند. در این حالت، هر یک از دستگاه‌ها می‌توانند با دیگری ارتباط برقرار کرده و مشتری شوند. از طرف دیگر، یک دستگاه می تواند صریحاً اتصال را میزبانی کند و یک سوکت سرور را در صورت تقاضا باز کند، و دستگاه دیگر اتصال را آغاز می کند.


شکل 1. گفتگوی جفت شدن بلوتوث.

به عنوان یک سرور متصل شوید

وقتی می خواهید دو دستگاه را به هم وصل کنید، یکی باید با نگه داشتن یک BluetoothServerSocket باز به عنوان سرور عمل کند. هدف سوکت سرور گوش دادن به درخواست‌های اتصال ورودی و ارائه BluetoothSocket متصل پس از پذیرش درخواست است. هنگامی که BluetoothSocket از BluetoothServerSocket به دست می آید، BluetoothServerSocket را می توان - و باید - دور انداخت، مگر اینکه بخواهید دستگاه اتصالات بیشتری را بپذیرد.

برای راه‌اندازی سوکت سرور و پذیرش اتصال، مراحل زیر را انجام دهید:

  1. با تماس listenUsingRfcommWithServiceRecord(String, UUID) یک BluetoothServerSocket دریافت کنید.

    رشته یک نام قابل شناسایی از سرویس شما است که سیستم به طور خودکار آن را در یک ورودی پایگاه داده جدید سرویس کشف پروتکل (SDP) روی دستگاه می نویسد. نام دلخواه است و به سادگی می تواند نام برنامه شما باشد. شناسه منحصر به فرد جهانی (UUID) نیز در ورودی SDP گنجانده شده است و اساس توافقنامه اتصال با دستگاه مشتری را تشکیل می دهد. یعنی زمانی که مشتری سعی می کند با این دستگاه متصل شود، یک UUID را حمل می کند که به طور منحصر به فرد سرویسی را که می خواهد با آن ارتباط برقرار کند، شناسایی می کند. این UUID ها باید مطابقت داشته باشند تا اتصال پذیرفته شود.

    UUID یک فرمت استاندارد ۱۲۸ بیتی برای شناسه رشته ای است که برای شناسایی منحصر به فرد اطلاعات استفاده می شود. یک UUID برای شناسایی اطلاعاتی استفاده می شود که باید در یک سیستم یا یک شبکه منحصر به فرد باشند زیرا احتمال تکرار یک UUID عملاً صفر است. به طور مستقل و بدون استفاده از یک مرجع متمرکز تولید می شود. در این مورد، برای شناسایی منحصر به فرد سرویس بلوتوث برنامه شما استفاده می شود. برای استفاده از یک UUID با برنامه خود، می‌توانید از یکی از بسیاری از تولیدکنندگان UUID تصادفی در وب استفاده کنید، سپس یک UUID را با fromString(String) مقداردهی کنید.

  2. گوش دادن به درخواست های اتصال را با فراخوانی accept() شروع کنید.

    این یک تماس مسدود کننده است. زمانی که یک اتصال پذیرفته شده باشد یا یک استثنا رخ داده باشد، برمی گردد. اتصال تنها زمانی پذیرفته می شود که یک دستگاه راه دور یک درخواست اتصال حاوی UUID ارسال کرده باشد که با درخواست ثبت شده در این سوکت سرور شنود مطابقت دارد. در صورت موفقیت آمیز بودن، accept() یک BluetoothSocket متصل را برمی گرداند.

  3. مگر اینکه بخواهید اتصالات اضافی را بپذیرید، با close() تماس بگیرید.

    این فراخوانی متد، سوکت سرور و تمام منابع آن را آزاد می‌کند، اما BluetoothSocket متصل را که توسط accept() برگردانده شده است، نمی‌بندد. برخلاف TCP/IP، RFCOMM تنها به یک کلاینت متصل در هر کانال اجازه می‌دهد، بنابراین در بیشتر موارد منطقی است که بلافاصله پس از پذیرش سوکت متصل، در BluetoothServerSocket close() فراخوانی کنید.

از آنجایی که accept() یک تماس مسدودکننده است، آن را در رشته UI اکتیویتی اصلی اجرا نکنید. اجرای آن در یک رشته دیگر تضمین می کند که برنامه شما همچنان می تواند به سایر تعاملات کاربر پاسخ دهد. معمولاً انجام تمام کارهایی که شامل BluetoothServerSocket یا BluetoothSocket در رشته جدیدی است که توسط برنامه شما مدیریت می شود، منطقی است. برای لغو یک تماس مسدود شده مانند accept() ، close() را در BluetoothServerSocket یا BluetoothSocket از رشته دیگری فراخوانی کنید. توجه داشته باشید که تمام روش‌های موجود در BluetoothServerSocket یا BluetoothSocket از نظر موضوعی ایمن هستند.

مثال

موضوع زیر یک رشته ساده شده برای مؤلفه سرور است که اتصالات ورودی را می پذیرد:

کاتلین

private inner class AcceptThread : Thread() {

   private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) {
       bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID)
   }

   override fun run() {
       // Keep listening until exception occurs or a socket is returned.
       var shouldLoop = true
       while (shouldLoop) {
           val socket: BluetoothSocket? = try {
               mmServerSocket?.accept()
           } catch (e: IOException) {
               Log.e(TAG, "Socket's accept() method failed", e)
               shouldLoop = false
               null
           }
           socket?.also {
               manageMyConnectedSocket(it)
               mmServerSocket?.close()
               shouldLoop = false
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   fun cancel() {
       try {
           mmServerSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the connect socket", e)
       }
   }
}

جاوا

private class AcceptThread extends Thread {
   private final BluetoothServerSocket mmServerSocket;

   public AcceptThread() {
       // 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 = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
       } catch (IOException e) {
           Log.e(TAG, "Socket's listen() method failed", e);
       }
       mmServerSocket = tmp;
   }

   public void run() {
       BluetoothSocket socket = null;
       // Keep listening until exception occurs or a socket is returned.
       while (true) {
           try {
               socket = mmServerSocket.accept();
           } catch (IOException e) {
               Log.e(TAG, "Socket's accept() method failed", e);
               break;
           }

           if (socket != null) {
               // A connection was accepted. Perform work associated with
               // the connection in a separate thread.
               manageMyConnectedSocket(socket);
               mmServerSocket.close();
               break;
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   public void cancel() {
       try {
           mmServerSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the connect socket", e);
       }
   }
}

در این مثال، تنها یک اتصال ورودی مورد نظر است، بنابراین به محض اینکه یک اتصال پذیرفته شد و BluetoothSocket به دست آمد، برنامه BluetoothSocket به دست آمده را به یک رشته جداگانه ارسال می کند، BluetoothServerSocket می بندد و از حلقه خارج می شود.

توجه داشته باشید که وقتی accept() BluetoothSocket را برمی گرداند، سوکت قبلاً متصل است. بنابراین، همانطور که از سمت کلاینت انجام می دهید، نباید connect() را فراخوانی کنید.

متد manageMyConnectedSocket() ویژه برنامه برای شروع رشته برای انتقال داده طراحی شده است که در مبحث انتقال داده های بلوتوث بحث شده است.

معمولاً به محض پایان یافتن گوش دادن به اتصالات ورودی، باید BluetoothServerSocket خود را ببندید. در این مثال، به محض دریافت BluetoothSocket close() فراخوانی می شود. همچنین ممکن است بخواهید یک روش عمومی در رشته خود ارائه دهید که می تواند BluetoothSocket خصوصی را در صورت نیاز به توقف گوش دادن در آن سوکت سرور ببندد.

به عنوان یک مشتری متصل شوید

برای شروع اتصال با یک دستگاه راه دور که اتصالات را در سوکت سرور باز می پذیرد، ابتدا باید یک شئ BluetoothDevice دریافت کنید که نشان دهنده دستگاه راه دور است. برای یادگیری نحوه ایجاد یک BluetoothDevice ، به یافتن دستگاه های بلوتوث مراجعه کنید. سپس باید از BluetoothDevice برای دریافت BluetoothSocket و شروع اتصال استفاده کنید.

روش اصلی به شرح زیر است:

  1. با استفاده از BluetoothDevice ، با تماس با createRfcommSocketToServiceRecord(UUID) یک BluetoothSocket دریافت کنید.

    این روش یک شی BluetoothSocket را راه اندازی می کند که به مشتری اجازه می دهد به یک BluetoothDevice متصل شود. UUID ارسال شده در اینجا باید با UUID استفاده شده توسط دستگاه سرور مطابقت داشته باشد که برای باز کردن BluetoothServerSocket آن listenUsingRfcommWithServiceRecord(String, UUID) فراخوانی کرد. برای استفاده از یک UUID منطبق، رشته UUID را در برنامه خود کدگذاری کنید و سپس از کد سرور و مشتری به آن ارجاع دهید.

  2. اتصال را با فراخوانی connect() آغاز کنید. توجه داشته باشید که این روش یک تماس مسدود کننده است.

    پس از اینکه مشتری این روش را فراخوانی کرد، سیستم یک جستجوی SDP را برای یافتن دستگاه راه دور با UUID منطبق انجام می دهد. اگر جستجو موفقیت آمیز باشد و دستگاه راه دور اتصال را بپذیرد، کانال RFCOMM را برای استفاده در طول اتصال به اشتراک می گذارد و متد connect() برمی گردد. اگر اتصال ناموفق باشد، یا اگر زمان متد connect() (پس از حدود 12 ثانیه) تمام شود، متد یک IOException را پرتاب می‌کند.

از آنجایی که connect() یک تماس مسدود کننده است، شما همیشه باید این روش اتصال را در رشته ای که از رشته فعالیت اصلی (UI) جدا است انجام دهید.

مثال

در زیر یک مثال اساسی از یک رشته کلاینت است که اتصال بلوتوث را آغاز می کند:

کاتلین

private inner class ConnectThread(device: BluetoothDevice) : Thread() {

   private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
       device.createRfcommSocketToServiceRecord(MY_UUID)
   }

   public override fun run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter?.cancelDiscovery()

       mmSocket?.let { socket ->
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           socket.connect()

           // The connection attempt succeeded. Perform work associated with
           // the connection in a separate thread.
           manageMyConnectedSocket(socket)
       }
   }

   // Closes the client socket and causes the thread to finish.
   fun cancel() {
       try {
           mmSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the client socket", e)
       }
   }
}

جاوا

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;

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

   public void run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter.cancelDiscovery();

       try {
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           mmSocket.connect();
       } catch (IOException connectException) {
           // Unable to connect; close the socket and return.
           try {
               mmSocket.close();
           } catch (IOException closeException) {
               Log.e(TAG, "Could not close the client socket", closeException);
           }
           return;
       }

       // The connection attempt succeeded. Perform work associated with
       // the connection in a separate thread.
       manageMyConnectedSocket(mmSocket);
   }

   // Closes the client socket and causes the thread to finish.
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the client socket", e);
       }
   }
}

توجه داشته باشید که در این قطعه، cancelDiscovery() قبل از تلاش برای اتصال فراخوانی می شود. همیشه باید cancelDiscovery() قبل از connect() فراخوانی کنید، به خصوص به این دلیل که cancelDiscovery() بدون در نظر گرفتن اینکه کشف دستگاه در حال حاضر در حال انجام است یا خیر موفق می شود. اگر برنامه شما باید تعیین کند که آیا کشف دستگاه در حال انجام است یا خیر، می‌توانید با استفاده از isDiscovering() بررسی کنید.

متد manageMyConnectedSocket() ویژه برنامه برای شروع رشته برای انتقال داده طراحی شده است که در بخش مربوط به انتقال داده های بلوتوث بحث شده است.

وقتی کارتان با BluetoothSocket تمام شد، همیشه close() فراخوانی کنید. با انجام این کار بلافاصله سوکت متصل بسته می شود و تمام منابع داخلی مرتبط آزاد می شوند.