Bluez SPP實現程式碼分析

來源:互聯網
上載者:User
文章目錄
  • 1.1 啟動SPP服務等待遠端裝置串連的過程:
  • 1.2 主動串連遠端裝置的SPP服務的過程:
  • 2.1 serial_proxy_driver
  • 2.2 serial_port_driver

 

本文分析藍芽協議棧中藍芽轉串口(SPP)部分的實現。

1.  基本概念

Bluez提供了藍芽轉串口的功能,應用程式可以通過dbus介面控制bluez的串口功能。

1.1 啟動SPP服務等待遠端裝置串連的過程:

org.bluez.SerialProxyManager->CreateProxy         // 得到一個serial proxy

org.bluez.SerialProxy->SetSerialParameters          // 設定串口參數

org.bluez.SerialProxy->Enable                // 啟動串口

之後,bluez就會等待遠端裝置串連本地的串口服務

1.2 主動串連遠端裝置的SPP服務的過程:

當遠端裝置與本地裝置之間建立串連,並且本地裝置發現遠端裝置包含SPP功能,則Bluez將會註冊一個“org.bluez.Serial”介面的執行個體。應用程式可通過調用其中的”Connect”介面,建立與遠端裝置之間的串口串連。

2.  程式碼分析

與SPP有關的代碼在bluez的serial目錄中,其初始化函數為serial_init。

static int serial_init(void)

      。。。

      serial_manager_init(connection);

 

int serial_manager_init(DBusConnection *conn)

      。。。

      btd_register_adapter_driver(&serial_proxy_driver);

      btd_register_device_driver(&serial_port_driver);

serial_manager_init函數中包含兩個操作,一個是註冊serial_proxy_driver,另一個是註冊serial_port_driver。

2.1 serial_proxy_driver

static struct btd_adapter_driver serial_proxy_driver = {

      .name   = "serial-proxy",

      .probe  = proxy_probe,

      .remove = proxy_remove,

};

Bluez啟動後,對於每一個藍芽適配器裝置,都會調用其中的proxy_probe函數。

static int proxy_probe(struct btd_adapter *adapter)

      。。。

      proxy_register(connection, adapter);

int proxy_register(DBusConnection *conn, struct btd_adapter *btd_adapter)

      。。。

struct serial_adapter *adapter = g_new0(struct serial_adapter, 1);

adapter->conn = dbus_connection_ref(conn);

adapter->btd_adapter = btd_adapter_ref(btd_adapter);

path = adapter_get_path(btd_adapter);

// 註冊SerialProxyManager介面

g_dbus_register_interface(conn, path, SERIAL_MANAGER_INTERFACE,

       manager_methods, manager_signals, NULL,

       adapter, manager_path_unregister);

adapters = g_slist_append(adapters, adapter);

// 這裡是根據設定檔註冊預設的SerialProxyManager介面,可以跳過

serial_proxy_init(adapter);

SerialProxyManager介面的方法如下:

static GDBusMethodTable manager_methods[] = {

      { "CreateProxy",      "ss",     "s", create_proxy },

      { "ListProxies",        "",  "as",     list_proxies },

      { "RemoveProxy",         "s", "",  remove_proxy },

      { },

};

當應用程式需要建立一個serial proxy執行個體時,可以調用其中的CreateProxy方法,此方法會映射到create_proxy函數。

static DBusMessage *create_proxy(DBusConnection *conn,

                      DBusMessage *msg, void *data)

      // 得到應用程式傳下來的參數

dbus_message_get_args(msg, NULL,

                      DBUS_TYPE_STRING, &pattern,

                      DBUS_TYPE_STRING, &address,

                      DBUS_TYPE_INVALID);

      // 得到uuid

uuid_str = bt_name2string(pattern);

register_proxy(adapter, uuid_str, address, &proxy);

。。。

 

static int register_proxy(struct serial_adapter *adapter,

                      const char *uuid_str, const char *address,

                      struct serial_proxy **proxy)

      // 根據應用程式傳下來的位址名稱,判斷地址類型。類型可以是unix socket、串口或者tcp socket。此socket用於bluez與應用程式之間的串口資料通訊。如果應用程式發送資料,就寫入此socket;如果應用程式接收資料,就由Bluez寫入此socket。address到類型的轉換細節可以參考函數的具體實現。

      type = addr2type(address);

      。。。

      // 以下是根據地址類型進入相應的初始化函數

      switch (type) {

      case UNIX_SOCKET_PROXY:

           err = proxy_socket_register(adapter, uuid_str, address, proxy);

           break;

      case TTY_PROXY:

           err = proxy_tty_register(adapter, uuid_str, address, NULL,

                            proxy);

           break;

      case TCP_SOCKET_PROXY:

           err = proxy_tcp_register(adapter, uuid_str, address, proxy);

           break;

      default:

           err = -EINVAL;

      }

      。。。

由於三種地質類型的初始化大同小異,所以這裡只研究unix socket類型的情況:

當上層應用使用unix socket時,由應用程式建立一個此類型的socket,然後把socket地址傳給Bluez,Bluez同樣會根據此地址建立socket。之後,Bluez和應用程式之間就可以通過此socket像操作管道一樣互相傳遞資料。在Bluez的實現中,此socket被用於傳遞藍芽的串口資料。

static int proxy_socket_register(struct serial_adapter *adapter,

                      const char *uuid128, const char *address,

                      struct serial_proxy **proxy)

      struct serial_proxy *prx;

prx->address = g_strdup(address);

      prx->uuid128 = g_strdup(uuid128);

      prx->type = UNIX_SOCKET_PROXY;

      adapter_get_address(adapter->btd_adapter, &prx->src);

      prx->adapter = adapter;

      register_proxy_object(prx);

static int register_proxy_object(struct serial_proxy *prx)

      。。。

      g_dbus_register_interface(adapter->conn, path,

                            SERIAL_PROXY_INTERFACE,

                            proxy_methods, NULL, NULL,

                            prx, proxy_path_unregister);

      。。。

以上就是create_proxy的全部操作。到這裡僅僅建立了一個串口proxy,但此proxy還未使能。使能操作由應用程式調用上面新註冊的介面SERIAL_PROXY_INTERFACE中的Enable方法完成。

static GDBusMethodTable proxy_methods[] = {

      { "Enable",             "",  "",  proxy_enable },

      { "Disable",             "",  "",  proxy_disable },

      { "GetInfo",            "",  "a{sv}",proxy_get_info },

      { "SetSerialParameters",  "syys",  "",  proxy_set_serial_params },

      { },

};

static DBusMessage *proxy_enable(DBusConnection *conn,

                      DBusMessage *msg, void *data)

      。。。

enable_proxy(prx);

。。。

static int enable_proxy(struct serial_proxy *prx)

      。。。

      // 監聽RFCOMM通道

      prx->io = bt_io_listen(BT_IO_RFCOMM, NULL, confirm_event_cb, prx,

                      NULL, &gerr,

                      BT_IO_OPT_SOURCE_BDADDR, &prx->src,

                      BT_IO_OPT_INVALID);

      // 當調用listen時,Bluez(本地藍芽適配器)會分配一個channel,然後在這個channel上等待對方串連。這個channel很重要,因為藍芽協議中根據channel號區分RFCOMM上的各種應用

      bt_io_get(prx->io, BT_IO_RFCOMM, &gerr,

                  BT_IO_OPT_CHANNEL, &prx->channel,

                 BT_IO_OPT_INVALID);

。。。

sdp_record_t *record;

// 分配一個SDP record,這裡把channel號設定到了SDP record裡面

record = proxy_record_new(prx->uuid128, prx->channel);

// 將record加入到SDP service中,到這裡遠端裝置就能夠看到本地裝置的藍芽服務了。該服務在RFCOMM的prx->channel中

add_record_to_server(&prx->src, record);

prx->record_id = record->handle;

。。。

到這裡,本地裝置已經準備好接受遠端裝置的串口串連,對方串連時,上面調用的bt_io_listen中的confirm_event_cb回呼函數將被調用。

在confirm_event_cb中,為執行鑒權操作,鑒權成功後調用bt_io_accept建立串連。Accept成功後,回呼函數connect_event_cb將被調用。

static void connect_event_cb(GIOChannel *chan, GError *conn_err, gpointer data)

      。。。

      switch (prx->type)

      case UNIX_SOCKET_PROXY:

           sk = unix_socket_connect(prx->address);

           break;

      case TTY_PROXY:

           。。。

      case TCP_SOCKET_PROXY:

           。。。

      g_io_add_watch(prx->rfcomm,

                 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,

                 forward_data, prx);

      g_io_add_watch(prx->local,

                 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,

                 forward_data, prx);

      。。。

unix_socket_connect函數中將根據address建立一個unix socket,並執行了connect。注意到上層應用同樣建立了這麼一個socket,如果此時上層應用調用select等待在此socket中,那麼這裡調用connect後,上層應用將得到通知,從而得知與遠端裝置的串連已經建立。

接下來再看看g_io_add_watch的調用。這裡調用兩次,一次是針對遠端裝置的socket;一次是針對與上層應用之間的socket,即剛建立的unix socket。這兩個socket之間有一個配對關係,一個用於與遠端裝置的串口資料轉送,另一個用於與上層應用的資料轉送。這兩個通道關聯後就建立了遠端裝置與上層應用之間的串連,這個關聯就由兩次g_io_add_watch調用的回呼函數forward_data實現。

forward_data被調用時,說明相應的通道上有資料,其虛擬碼如下:

1.    得到通道上的資料

2.    將資料發送到另一個通道。

通過此種方式,兩個通道之間實現了關聯。

2.2 serial_port_driver

static struct btd_device_driver serial_port_driver = {

      .name   = "serial-port",

      .uuids   = BTD_UUIDS(RFCOMM_UUID_STR),

      .probe  = port_probe,

      .remove      = port_remove,

};

當包含UUID RFCOMM_UUID_STR的遠端裝置串連到本地時,Bluez就會調用serial_port_driver的port_probe函數。

static int port_probe(struct btd_device *device, GSList *uuids)

      while (uuids) {

           serial_probe(device, uuids->data);

           uuids = uuids->next;

      }

static int serial_probe(struct btd_device *device, const char *uuid)

      const sdp_record_t * rec = btd_device_get_record(device, uuid);

      // 從SDP record中得到channel號,此channel用於串口通訊

      int ch = sdp_get_proto_port(protos, RFCOMM_UUID);

      。。。

port_register(connection, path, &src, &dst, uuid, ch);

 

int port_register(DBusConnection *conn, const char *path, bdaddr_t *src,

                 bdaddr_t *dst, const char *uuid, uint8_t channel)

      。。。

      create_serial_device(conn, path, src, dst);

      。。。

static struct serial_device *create_serial_device(DBusConnection *conn,

                            const char *path, bdaddr_t *src,

                            bdaddr_t *dst)

      // 建立一個serial_device

      Struct serial_device* device = g_new0(struct serial_device, 1);

      。。。

      // 註冊一個SERIAL_PORT_INTERFACE介面

      g_dbus_register_interface(conn, path, SERIAL_PORT_INTERFACE,

                      port_methods, NULL, NULL, device, path_unregister);

以上為本地裝置發現一個支援SPP的遠端裝置之後執行的操作,最後就是對這個裝置建立了一個SERIAL_PORT_INTERFACE介面的執行個體。此介面的定義為:

static GDBusMethodTable port_methods[] = {

      { "Connect",    "s", "s", port_connect, G_DBUS_METHOD_FLAG_ASYNC },

      { "Disconnect", "s", "",  port_disconnect },

      { }

};

當上層應用需要主動串連遠端裝置,而不是被動等待遠端裝置串連本地裝置時,就可以調用這裡的connect方法,此方法最終映射到port_connect函數。

Port_connect函數最終是調用到bt_io_connect函數。

bt_io_connect (BT_IO_RFCOMM, rfcomm_connect_cb, port,

                      NULL, NULL,

                      BT_IO_OPT_SOURCE_BDADDR, &device->src,

                      BT_IO_OPT_DEST_BDADDR, &device->dst,

                      BT_IO_OPT_CHANNEL, port->channel,

                      BT_IO_OPT_INVALID);

其中指定的串連成功後的回呼函數為rfcomm_connect_cb。

在rfcomm_connect_cb中,調用了ioctl RFCOMMCREATEDEV。

此ioctl會在/dev目錄下建立一個裝置節點rfcomm%d。此裝置節點的名稱會被傳給上層應用。這樣,上層應用就能通過此節點發送、接收資料,其資料與遠端裝置之間通過藍芽串口傳輸。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.