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, the local controller, and the remote controller.

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

💡 Prerequisite: You should know what HCI (Host Controller Interface) 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 initiates the inquiry is called the inquirer. For a device to be found, it must first be set to discoverable mode — meaning its inquiry scan is enabled.

The host enables discoverable mode by sending HCI_Write_Scan_Enable with the Inquiry Scan bit set. After that the device starts listening for inquiry packets from other devices.

Figure 1 — Inquiry Procedure (HCI Message Flow)

Host A Controller A Remote Device(s) Remote device: HCI_Write_Scan_Enable (Inquiry Scan ON) → enters Discoverable Mode 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 CommandHCI EventOver-the-air (RF)

📌 Multiple devices can be packed into one Inquiry_Result_Event. When the inquiry period ends, the controller sends Inquiry_Complete_Event and stops listening.

BlueZ — Run Inquiry from Terminal

# 1. Start the Bluetooth service
sudo systemctl start bluetooth

# 2. Bring up the adapter
sudo hciconfig hci0 up

# 3. Make the remote device discoverable (run on the OTHER machine)
sudo hciconfig hci0 piscan

# 4. Run inquiry — scan for ~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)

This is exactly what hcitool inq does internally. The library call hci_inquiry() sends HCI_Inquiry to the controller and collects the Inquiry_Result events for you.

#include <stdio.h>
#include <stdlib.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));

    /* hci_inquiry() sends HCI_Inquiry and waits for 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 dev library: 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 tells the controller to repeat it automatically at a set interval — without the host needing to send a new command each time. You provide a minimum and maximum period; the controller picks a random time between them for each cycle.

A practical use: a kiosk that wants to detect when a phone enters or leaves Bluetooth range. Compare each new device list with the previous one — new entries mean a device arrived, missing entries mean it 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 Command

# Start periodic inquiry
# Opcode: OGF=0x01, OCF=0x0003 = HCI_Periodic_Inquiry_Mode
# max_period=8 units (~10s), min_period=4 units (~5s), inquiry_length=4 units
sudo hcitool cmd 0x01 0x0003  0x08 0x00  0x04 0x00  0x33 0x8B 0x9E  0x04  0x00

# Stop periodic inquiry
# Opcode: 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 Connection

In older Bluetooth versions, a full discovery took three separate exchanges: inquiry → get name → search services. With EIR (introduced in Bluetooth 2.1+EDR), the remote device can include its name, supported services, and RSSI right inside the inquiry response — reducing setup time significantly.

Figure 3 — Standard Inquiry vs EIR

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

BlueZ — Reading EIR Data

# bluetoothctl shows device name directly — that is EIR working
bluetoothctl
[bluetooth]# scan on
# Output:
# [NEW] Device 00:1A:7D:DA:71:11 MyHeadphones   <-- name came from EIR
# [NEW] Device 00:1B:DC:0F:F5:41 SomeSpeaker
/* Parse EIR data from an inquiry result 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 */
        uint8_t *data = &eir[pos + 2];

        if (type == 0x09) {              /* 0x09 = Complete Local Name */
            printf("Device name: %.*s\n", field_len - 1, data);
        }
        if (type == 0x03) {              /* 0x03 = 16-bit Service UUIDs */
            printf("Service UUID present\n");
        }
        pos += field_len + 1;
    }
}

4. Connection Establishment — Paging
HCI_Create_Connection LMP_host_connection_req HCI_Accept_Connection Connection_Complete_Event

After finding a device through inquiry, the next step is connecting to it. This procedure is called paging. The device that starts the connection is the initiator; the device being connected to is the target. The target must be in connectable mode (page scan enabled) for the connection to succeed. Once connected, the initiator becomes the Master of the piconet.

Figure 4 — Simplified Connection Establishment Flow

Host A (Initiator) Controller A (Local) Controller B (Remote) 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 next steps: Feature Exchange · AFH Enable · Authentication · Encryption HCI CommandHCI EventLMP Over-the-airInitiator (A) becomes MASTER after connection

⚠️ Master-Slave: The initiator always becomes the Master of the piconet after a successful connection. The target becomes the Slave. This can be swapped later using Role Switch if both devices support it.

BlueZ — Create Connection from Terminal

# Make the target connectable first (run on target machine)
sudo hciconfig hci0 pscan

# Connect to the target device (run on initiator)
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 to the
     * controller. The controller then sends LMP_host_connection_req
     * to the remote device over the air.
     *
     * Parameters:
     *   pkt_type   = 0x0008  (DM1 ACL packet)
     *   clock_off  = 0       (use 0 if not known from inquiry)
     *   role_switch= 0       (do not allow role switch)
     *   timeout    = 25000ms
     */
    int ret = hci_create_connection(
        sock, &remote,
        htobs(0x0008),   /* packet type */
        htobs(0),        /* clock offset */
        0x00,            /* role switch */
        &handle,
        25000
    );

    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. Make sure target is in connectable mode: sudo hciconfig hciX pscan
  4. 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 controller. Both controllers then fire a Disconnection_Complete_Event up to their respective hosts independently.

Figure 5 — Disconnection Procedure

Host A Controller A Controller B Host B Active ACL connection exists between Device A and Device B. Either side can disconnect. HCI_Disconnect Command_Status_Event LMP_detach (air) ACK (air) Disconnection_Complete ✓ Disconnection_Complete ✓ HCI CommandHCI EventLMP Over-the-airBoth hosts notified independently

BlueZ — Disconnect from Terminal

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

# Or using bluetoothctl (interactive)
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 the connection handle from: sudo hcitool con
     * Look for: "> ACL 00:1A:7D:DA:71:11  handle 1  ..."
     *
     * Reason 0x13 = "Remote User Terminated Connection"
     * This is the standard polite disconnect reason code.
     * hci_disconnect() sends HCI_Disconnect to the controller.
     * The controller then sends LMP_detach over the air.
     */
    uint16_t handle = 1;   /* replace with 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 — note the handle number next to the BD_ADDR
  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 (Inquiry) Command_Complete
Inquiry HCI_Inquiry Inquiry broadcast (RF) 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 (Initiator) 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

Go to Courses BLE Deep Dive →

Leave a Reply

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