Trong bài này, chúng ta sẽ tìm hiểu các chuẩn giao tiếp đơn giãn của Bluetooth và cách thức để tạo ra một class đơn giãn bao đóng được công nghệ Bluetooth.

Công nghệ không dây

Hiện nay, trên thế giới có các công nghệ không dây nổi tiếng như: infraree(hồng ngoại), wifi, bluetooth và zigbee. Hồng ngoại là công nghệ được sử dụng phổ biến trong điều khiển tivi từ xa hoặc điều hòa không khí điều khiển từ xa, nơi mà giao tiếp được trỏ đến các thiết bị đích. Công nghệ wifi thì được sử dụng rộng rãi trong các lĩnh vực truyền thông và công nghệ thông tin. Zigbee là công nghệ mới nhất, nó rẻ hơn tất cả các phương tiện truyền thông không dây khác. Công nghệ Bluetooth là công nghệ được sử dụng hầu hết trong các thông tin liên lạc tạm thời, đặc biệt là bên trong các thiết bị di động, palm,pocket PCi, vv. Nó có thể được sử dụng để trao đổi các đối tượng, các gói tin, hoặc một stream đơn giản.

Các loại giao tiếp Bluetooth

Có 3 loại giao tiếp trong công nghệ Bluetooth:

  • OBEX: Viết tắt bởi “Object Exchange” giao thức giao tiếp được sử dụng để trao đổi dữ liệu vật lý như các tập tin, hình ảnh, và kể cả các định dạng nhị phân.
  • L2CAP: Viết tắt bởi “Logical Link Control and Adaptation Protocol” được sử dụng để gửi các gói dữ liệu giữa máy chủ và máy khách.
  • RFCOMM: Viết tắt bởi ” Radio Frequency COMMunication” được sử dụng cho luồng dữ liệu đơn giản.

Java Bluetooth API

Bluetooth API thuộc JSR 72, và nó có khả năng cung cấp cả 3 loại giao tiếp kể trên, đó là: OBEX, L2CAP và RFCOMM. Bài viết này sẽ tập trung vào giao thức đơn giãn nhất là RFCOMM và mô tả cách thức gửi dữ liệu giữa các thiết bị thông qua giao thức này.

Client và Server

Kỹ thuật giao tiếp giữa các thiết bị sẽ được thực hiện theo quy tắc client-server. Đó là, bạn sẽ mở server và sau đó chờ client kết nối tới, sau đó cả client và server sẽ giao tiếp với nhau. Trong Bluetooth, bạn cũng làm kỹ thuật tương tự, đó là ứng dụng phải cho phép người dùng lựa chọn nó hoặc là client hoặc server.

Giải thích code

1.Server

Mỗi thiết bị Bluetooth có các đối tượng Bluetooth cục bộ giúp giao tiếp giữa các thiết bị. Trong JSR82, gọi phương thức LocalDevice.getLocalDevice () sẽ trả về đối tượng của thiết bị Bluetooth cục bộ. Sau đó đối tượng này sẽ gọi phương thức setDiscoverable(DiscoveryAgent.GIAC), trong đó chế độ thiết lập là GIAC. Nói một cách đơn giản, bằng cách làm này, bạn cho phép thiết bị hiện tại tìm kiếm các thiết bị khác.

Để mở các kết nối Bluetooth, bạn cần phải xây dựng một chuỗi URL Bluetooth và sẽ được gọi bên trong phương thức Connector.open (URL), phương thức này sẽ trả về đối tượng StreamConnectionNotifier. Thực tế thì URL chính là cách để khởi tạo các giao thức giao tiếp cho Bluetooth, giống như trên một hộp tìm kiếm của Internet Explorer. Bạn chỉ cần gõ http://www.address.com, trong đó “http://” chính là giao thức và phần còn lại là địa chỉ. Tương tự, trong Bluetooth bạn sẽ làm như sau:

URL = “btspp://localhost:” + UUID + “;name=rfcommtest;authorize=true”;
Ở đây, btspp:// cũng tương tự như http://, và phần còn lại là một định danh ID duy nhất để xác định rằng nó chỉ có một địa chỉ duy nhất mà thôi.

Sau khi StreamConnectionNotifier đã được khởi tạo, nó phải gọi phương thức acceptAndOpen() để mở giao tiếp và trả về đối tượng StreamConnection. Tuy nhiên, nếu không có một kết nối client nào được tìm thấy, nó sẽ chặn các tiến trình khác và chờ đợi.

Bây giờ, bạn có thể sử dụng hai phương thức của đối tượng StreamConnection, đó là: openOutputStream() hoặc openInputStream(). Cả hai được sử dụng để gửi và nhận dữ liệu.

m_strUrl= "btspp://localhost:" + RFCOMM_UUID + ";
   name=rfcommtest;authorize=true";
 // m_StrmConn = BTFACADE.waitForClient(SERVICE_NBR);
 try
{
   m_LclDevice = LocalDevice.getLocalDevice();
    m_LclDevice.setDiscoverable(DiscoveryAgent.GIAC);
    m_StrmNotf = (StreamConnectionNotifier)Connector.open(m_strUrl);
    //Now it will start waiting for the client connection
   m_StrmConn = m_StrmNotf.acceptAndOpen();
    m_bInitServer = true;
    m_Output = m_StrmConn.openOutputStream();
   m_Input  = m_StrmConn.openInputStream();
}
catch (BluetoothStateException e)
{
   System.err.println( "BluetoothStateException: " + e.getMessage() );
}
catch (IOException ex)
{
   ex.printStackTrace();
}
catch(Exception e)
{
   System.err.println( "Exception: " + e.getMessage() );
}

2.Client

Để tạo client, UPI phải làm theo một số quy tắc để đạt được mục tiêu của bạn, đó là thực thi giao diện DiscoveryListener. Giao diện này cung cấp 4 phương thức sau:

  • void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod)
  • void servicesDiscovered(int transID, ServiceRecord[] records)
  • void serviceSearchCompleted(int transID, int respCode)
  • void inquiryCompleted(int discType)

Đầu tiên, bạn phải quét để tìm kiếm các thiết bị Bluetooth xung quanh bạn. Bạn phải nhận thông tin thiết bị cục bộ và thông qua nó để lấy đối tượng DiscoveryAgent để bắt đầu tìm hiểu về các thiết bị có sẵn.

public void SearchAvailDevices()
{
   try
   {
      //First, get the local device and obtain the discovery agent.
      m_LclDevice = LocalDevice.getLocalDevice();
       m_DscrAgent=  m_LclDevice.getDiscoveryAgent();
       m_DscrAgent.startInquiry(DiscoveryAgent.GIAC,this);
   }
   catch (BluetoothStateException ex)
   {
      System.out.println("Problem in searching the Bluetooth devices");
      ex.printStackTrace();
   }
 }

Đối với client, class thực thi giao diện DiscoveryListener phải cài đặt bốn phương thức kể trên. Đầu tiên, phương thức deviceDiscovered sẽ phát hiện các thiết bị Bluetooth được tìm thấy. Sau đó, nhiệm vụ của bạn là tìm kiếm các dịch vụ có sẵn trên thiết bị là: OBEX, RFCOMM hay L2CAP.

public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod)
{
   try
   {
      // Device information
      System.out.println("Major Device Class and information : " +
                         cod.getMajorDeviceClass() +
                         " Minor Device Class: " +
                         cod.getMinorDeviceClass());
      System.out.println("Bluetooth Address of the device: " +
                         btDevice.getBluetoothAddress());
      System.out.println("Friendly Name: " +
                         btDevice.getFriendlyName(true));
       // Now its our responsibility to search its services
      UUID uuidSet[] = new UUID[1];
      uuidSet[0]     = RFCOMM_UUID;
      int searchID  = m_DscrAgent.searchServices(null,uuidSet,
                                                 btDevice,this);
   }
   catch (Exception e)
   {
      System.out.println("Device Discovered Error: " + e);
   }
 }

Ở đây, m_DscrAgent chính là đối tượng DiscoveryAgent để tìm kiếm các dịch vụ sẵn có của thiết bị đầu tiên mà bạn tìm thấy.

public void servicesDiscovered(int transID, ServiceRecord[] records)
{
    for (int i = 0; i < records.length; i++)
   {
      m_strUrl = records[i].getConnectionURL(ServiceRecord.
         AUTHENTICATE_ENCRYPT, false);
       System.out.println(m_strUrl);
   //we have found our service protocol
   if(m_strUrl.startsWith("btspp"))
   {
      m_bServerFound = true;
      m_bInitClient=true;
      break;
   }
 }

Phương thức servicesDiscovered() ở trên được kích hoạt khi các dịch vụ trên thiết bị được tìm thấy. Tại đây, bạn dừng vòng lặp trên giao thức đầu tiên mà bạn tìm thấy như Bluetooth.

Sau khi phương thức tìm kiếm dịch vụ hoàn tất, phương thức serviceSearchComplete(int transID, int respCode) được kích hoạt, từ đó bạn có thể khởi tạo khi bạn tìm thấy dịch vụ muốn tìm kiếm.

public void serviceSearchCompleted(int transID, int respCode)
{
   if(m_bServerFound)
   {
      try
      //lets the communication start by setting the URL and sending
      //the client response
      {
         m_StrmConn = (StreamConnection) Connector.open(m_strUrl);
          m_Output   = m_StrmConn.openOutputStream();
         m_Input    = m_StrmConn.openInputStream();
          m_Output.write(CLIENT_RESPONSE.length());
         m_Output.write(CLIENT_RESPONSE.getBytes());
          System.out.println("serviceSearchCompleted");
      }
      catch (IOException ex)
      {
         ex.printStackTrace();
      }
    }
}

Ở đoạn code trên, bạn mở kết nối tới server thông qua phương thức Connector.open(m_strUrl). Sau đó, bạn mở stream Input/Output để giao tiếp với server.  Để gửi dữ liệu tới server, trước tiên bạn cần gửi độ dài của dữ liệu, rồi sau đó mới tới dữ liệu. Bằng cách này, client hay server sẽ biết được kích thước dữ liệu.

m_Output.write(CLIENT_RESPONSE.length());
m_Output.write(CLIENT_RESPONSE.getBytes());

m_Output được sử dụng để gửi dữ liệu đến đối tượng được kết nối, CLIENT_RESPONSE đơn giãn chỉ là dữ liệu. Về phía server, phương thức acceptAndOpen() cho phép bắt đầu quá trình giao tiếp.

Gửi và nhận dữ liệu

Khi client và server bắt đầu làm việc, quá trình giao tiếp được thực hiện thông qua 2 phương thức: sendMessages() và receiveMessages().

public void sendMessages(String v_strData)
{
   if((m_bInitClient) || (m_bInitServer) )
   {
      try
      {
         m_Output.write(v_strData.length());
         m_Output.write(v_strData.getBytes());
       }
      catch (IOException ex)
      {
         ex.printStackTrace();
      }
    }
 }

public String  recieveMessages()
{
   byte[] data = null;
    try
   {
       int length = m_Input.read();
      data       = new byte[length];
      length     = 0;
       while (length != data.length)
      {
         int ch = m_Input.read(data, length, data.length - length);
          if (ch == -1)
         {
            throw new IOException("Can't read data");
         }
         length += ch;
      }
    }
   catch (IOException e)
   {
      System.err.println(e);
   }
     return new String(data);
}

Đến lúc này, cả client và server đều có thể sử dụng được 2 phương thức này mà ko gặp bất kỳ vấn đề gì.

Xây dựng lớp ClientServer

Lớp ClientServer trong bài này rất linh động, một đối tượng tạo ra từ lớp này có thể là client hoặc server. Ta tạo đối tượng này như sau:

ClientServer Obj = new ClientServer(true);

Nếu tham số trong constructor của lớp ClientServer là true, điều này có nghĩa là obj là server, ngược lại là client.

Phần còn lại thì sử dụng 2 phương thức sendMessages() và receiveMessages() để giao tiếp. Trước khi thoát khỏi ứng dụng, bạn phải gọi phương thức obj.closeAll().

public void destroyApp (boolean vô điều kiện)
{
   m_bRunThread=false;
   m_BlueObj.CloseAll();
}
 public void run()
{
   while(m_bRunThread)
   {
      try
      {
         if(m_BlueObj==null)
         {
            //it should be called once when object is null
            m_BlueObj=new ClientServer(m_bIsServer);
         }
          String str = m_BlueObj.RecieveMessages();
          System.out.println(str);
          if(m_bIsServer)
            m_BlueObj.SendMessages("Hi there, it's Mr. Server");
         else
            m_BlueObj.SendMessages("Hi there, it's Mr. Client");
          Thread.sleep(100);
      }
      catch(Exception ex)
      {
         System.out.println(ex.getMessage());
      }
    }    //end while
   }

Download code
Bạn thấy bài viết này thế nào?