Inquiry and Connection process in bluetooth – bluez tutorial in c

Bluetooth Classic: Inquiry & Connection
How devices discover, connect, and disconnect — with HCI flow diagrams and BlueZ code
🔍
Inquiry
🔗
Connection
🔌
Disconnection
💻
BlueZ Code

What This Post Covers

Before two Bluetooth Classic devices can exchange data, three things must happen in order: discovery (inquiry), connection (paging), and eventually disconnection. Each step maps to specific HCI commands and LMP messages exchanged between the host, local controller, and remote controller.

This post walks through all three procedures with exact message flow, HCI commands involved, and working BlueZ terminal commands and C code you can run on Linux.

💡 Prerequisite: You should know what HCI is and the difference between host and controller. Check the Bluetooth Stack Layers post if needed.

Key Terms in This Post

HCI_Inquiry Inquiry Scan BD_ADDR EIR HCI_Create_Connection LMP_host_connection_req Page Scan HCI_Disconnect LMP_detach Discoverable Mode Connectable Mode BlueZ hcitool hciconfig

1. Inquiry — Finding Nearby Devices
Discoverable Mode HCI_Inquiry Inquiry_Result_Event Class_Of_Device

Inquiry is how a Bluetooth device finds out who else is nearby. The device that starts the inquiry is called the inquirer. For a device to respond, it must first enable discoverable mode (inquiry scan ON). Once inquiry is running, the controller collects all responses and reports them to the host via Inquiry_Result events.

Figure 1 — Inquiry Procedure (HCI Message Flow)

Host A Controller A Remote Device(s)
Remote enables Discoverable Mode: HCI_Write_Scan_Enable (Inquiry Scan ON)

HCI_Inquiry

Command_Status_Event

Inquiry broadcast (air)

Inquiry Response (air)

Each discoverable device returns: BD_ADDR + Class_Of_Device + Clock_Offset

Inquiry_Result_Event(s)

Inquiry_Complete_Event

HCI Command HCI EventOver-the-air (RF)
📌 Multiple devices can be reported in one Inquiry_Result_Event. When the inquiry window ends, Inquiry_Complete_Event is sent and the controller stops scanning.

BlueZ — Run Inquiry from Terminal

# 1. Start Bluetooth service
sudo systemctl start bluetooth

# 2. Bring up the adapter
sudo hciconfig hci0 up

# 3. On the REMOTE device — enable discoverable mode
sudo hciconfig hci0 piscan

# 4. On the INITIATOR — run inquiry (~10 seconds)
sudo hcitool inq

# Sample output:
# Inquiring ...
# 00:1A:7D:DA:71:11    clock offset: 0x7e29   class: 0x5a020c
# 00:1B:DC:0F:F5:41    clock offset: 0x6d15   class: 0x240414

BlueZ — Inquiry in C (HCI Socket)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

int main() {
    int dev_id = hci_get_route(NULL);   /* first available adapter */
    int sock   = hci_open_dev(dev_id);

    if (dev_id < 0 || sock < 0) {
        perror("Failed to open HCI device");
        return 1;
    }

    int          max_rsp = 255;
    int          len     = 8;   /* 8 x 1.28s = ~10 second inquiry */
    int          flags   = IREQ_CACHE_FLUSH;
    inquiry_info *devs   = malloc(max_rsp * sizeof(inquiry_info));

    /* Sends HCI_Inquiry → collects Inquiry_Result events */
    int found = hci_inquiry(dev_id, len, max_rsp, NULL, &devs, flags);

    char addr[19];
    for (int i = 0; i < found; i++) {
        ba2str(&(devs + i)->bdaddr, addr);
        printf("[%d] BD_ADDR: %s\n", i + 1, addr);
    }

    free(devs);
    close(sock);
    return 0;
}
  1. Install: sudo apt install libbluetooth-dev
  2. Compile: gcc inquiry.c -o inquiry -lbluetooth
  3. Run: sudo ./inquiry

2. Periodic Inquiry — Auto-Repeat Discovery
HCI_Periodic_Inquiry_Mode HCI_Exit_Periodic_Inquiry_Mode Presence Detection

Normal inquiry runs once. Periodic inquiry makes the controller repeat it automatically at a configurable interval — without the host needing to send a new command each cycle. You provide a minimum and maximum period; the controller picks a random time between them for each cycle. This randomisation prevents all nearby devices from scanning simultaneously.

Practical use: A kiosk detecting when phones enter or leave its range. Compare each new list with the previous — new entries = device arrived, missing entries = device left.

Figure 2 — Periodic Inquiry Timeline

t=0 : HCI_Periodic_Inquiry_Mode sent HCI_Exit_Periodic_Inquiry_Mode
Inquiry 1 wait Inquiry 2 wait Inquiry 3 stopped

Controller runs inquiries automatically. Host only stops it when needed.

BlueZ — Periodic Inquiry via Raw HCI

# Start periodic inquiry
# OGF=0x01, OCF=0x0003 = HCI_Periodic_Inquiry_Mode
# max_period=8 units, min_period=4 units, inq_len=4, num_rsp=0 (unlimited)
sudo hcitool cmd 0x01 0x0003  0x08 0x00  0x04 0x00  0x33 0x8B 0x9E  0x04  0x00

# Stop periodic inquiry
# OGF=0x01, OCF=0x0004 = HCI_Exit_Periodic_Inquiry_Mode
sudo hcitool cmd 0x01 0x0004

3. Extended Inquiry Response (EIR)
Bluetooth 2.1+EDR Device Name in Inquiry RSSI Faster Setup

In older Bluetooth, full discovery needed three separate exchanges: inquiry, then get name, then search services. With EIR (added in Bluetooth 2.1+EDR), the remote device includes its name, supported services, and RSSI right inside the inquiry response — doing everything in one shot.

Figure 3 — Standard Inquiry vs EIR

❌ Without EIR — 3 round-trips ✅ With EIR — 1 round-trip
1. Inquiry → BD_ADDR only
2. HCI_Remote_Name_Request
3. SDP Service Search
1. Inquiry → BD_ADDR + Name + Services + RSSI
Ready to connect!
No extra round-trips needed

BlueZ — Reading EIR Data

# EIR is transparent in bluetoothctl — device name shows in scan
bluetoothctl
[bluetooth]# scan on
# [NEW] Device 00:1A:7D:DA:71:11 MyHeadphones  <-- name from EIR
# [NEW] Device 00:1B:DC:0F:F5:41 SomeSpeaker
/* Parse EIR fields from an inquiry_info_with_rssi record */
#include <stdint.h>
#include <stdio.h>

void parse_eir(uint8_t *eir, int eir_len) {
    int pos = 0;
    while (pos < eir_len) {
        uint8_t field_len = eir[pos];
        if (field_len == 0) break;

        uint8_t type = eir[pos + 1];       /* EIR data type code */
        uint8_t *data = &eir[pos + 2];

        if (type == 0x09)                  /* 0x09 = Complete Local Name */
            printf("Name: %.*s\n", field_len - 1, data);

        if (type == 0x03)                  /* 0x03 = 16-bit UUIDs */
            printf("Service UUID found\n");

        pos += field_len + 1;
    }
}

4. Connection Establishment — Paging
HCI_Create_Connection LMP_host_connection_req HCI_Accept_Connection Connection_Complete_Event

After inquiry, the next step is connecting. This procedure is called paging. The device that starts it is the initiator; the device being connected to is the target. The target must first enable connectable mode (page scan ON). Once connected, the initiator automatically becomes the Master of the piconet.

Figure 4 — Simplified Connection Establishment Flow

Host A
(Initiator)
Controller A Controller B Host B
(Target)
Host B: HCI_Write_Scan_Enable (Page Scan ON) → Controller B enters Connectable Mode

HCI_Create_Connection

Command_Status_Event

LMP_host_connection_req

Connection_Request_Event

HCI_Accept_Conn_Request

LMP_accepted + LMP_setup_complete ↔ LMP_setup_complete (over air between controllers)

Connection_Complete ✓

Connection_Complete ✓

Optional: Feature Exchange · AFH Enable · Authentication · Encryption
HCI Command HCI Event LMP Over-the-air ★ Initiator (A) becomes MASTER
⚠️ Master-Slave: The initiator always becomes the Master of the piconet. The target becomes the Slave. A Role Switch can swap this later if both devices support it.

BlueZ — Create Connection from Terminal

# On the TARGET — enable connectable mode
sudo hciconfig hci0 pscan

# On the INITIATOR — connect to target
sudo hcitool cc 00:1A:7D:DA:71:11

# Verify connection is up
sudo hcitool con
# Output:
# Connections:
#   > ACL 00:1A:7D:DA:71:11   handle 1   state 1   lm MASTER

BlueZ — Create Connection in C

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int      dev_id = hci_get_route(NULL);
    int      sock   = hci_open_dev(dev_id);
    bdaddr_t remote;
    uint16_t handle;

    str2ba("00:1A:7D:DA:71:11", &remote);

    /*
     * hci_create_connection() sends HCI_Create_Connection.
     * The controller then sends LMP_host_connection_req over the air.
     * Parameters: packet_type, clock_offset, role_switch, timeout_ms
     */
    int ret = hci_create_connection(
        sock, &remote,
        htobs(0x0008),   /* DM1 packet type */
        htobs(0),        /* clock offset (0 if unknown) */
        0x00,            /* role switch: 0 = don't allow */
        &handle,
        25000            /* timeout in ms */
    );

    if (ret < 0) perror("Connection failed");
    else printf("Connected! Handle: %d\n", handle);

    close(sock);
    return 0;
}
  1. Install: sudo apt install libbluetooth-dev
  2. Compile: gcc connect.c -o connect -lbluetooth
  3. Run: sudo ./connect

5. Disconnection
HCI_Disconnect LMP_detach Disconnection_Complete_Event

Either side can end an active connection at any time. The host sends HCI_Disconnect to its controller. The controller sends LMP_detach over the air to the remote. Both controllers then send Disconnection_Complete_Event to their respective hosts independently.

Figure 5 — Disconnection Procedure

Host A Controller A Controller B Host B
Active ACL connection exists. Either side may initiate disconnect.

HCI_Disconnect

Command_Status_Event

LMP_detach (air)

ACK (air)

Disconnection_Complete ✓

Disconnection_Complete ✓

HCI Command HCI EventLMP Over-the-air · Both hosts notified independently

BlueZ — Disconnect from Terminal

# Disconnect by BD_ADDR
sudo hcitool dc 00:1A:7D:DA:71:11

# Or with bluetoothctl
bluetoothctl
[bluetooth]# disconnect 00:1A:7D:DA:71:11
# [DEL] Device 00:1A:7D:DA:71:11 MyDevice

BlueZ — Disconnect in C

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int dev_id = hci_get_route(NULL);
    int sock   = hci_open_dev(dev_id);

    /*
     * Get handle first: sudo hcitool con
     * "> ACL 00:1A:7D:DA:71:11  handle 1  ..."
     *
     * HCI_OE_USER_ENDED_CONNECTION = 0x13
     * "Remote User Terminated Connection" — standard polite reason.
     * hci_disconnect() sends HCI_Disconnect → controller sends LMP_detach.
     */
    uint16_t handle = 1;  /* replace with your actual handle */
    int ret = hci_disconnect(sock, handle, HCI_OE_USER_ENDED_CONNECTION, 10000);

    if (ret < 0) perror("Disconnect failed");
    else printf("Disconnected cleanly.\n");

    close(sock);
    return 0;
}
  1. Get handle: sudo hcitool con
  2. Compile: gcc disconnect.c -o disconnect -lbluetooth
  3. Run: sudo ./disconnect

6. Quick Reference — Commands & Events
Procedure HCI Command LMP Message HCI Event
Enable Discoverable HCI_Write_Scan_Enable (Inq) Command_Complete
Inquiry HCI_Inquiry Inquiry broadcast Inquiry_Result, Inquiry_Complete
Periodic Inquiry HCI_Periodic_Inquiry_Mode Inquiry broadcast (repeat) Inquiry_Result (per cycle)
Enable Connectable HCI_Write_Scan_Enable (Page) Command_Complete
Connect HCI_Create_Connection LMP_host_connection_req Connection_Complete
Accept Connection HCI_Accept_Connection_Request LMP_accepted Connection_Complete
Disconnect HCI_Disconnect LMP_detach Disconnection_Complete

Continue Learning Bluetooth

Explore the full Bluetooth stack — Radio to GATT — with free courses on EmbeddedPathashala

Prev_lec Next_lec

Leave a Reply

Your email address will not be published. Required fields are marked *