Skip to lesson content
BackBluetooth LE Unplugged™

Bluetooth LE Unplugged™

Novel BitsNovel Bits
Novel Bits Learning Hub
  • 1.1 Welcome & Course Overview6 min
  • 1.2 Setting Up Your BleuIO Dongles10 min
  • 1.3 Understanding AT Commands5 min
  • 1.4 Bluetooth LE Roles5 min
  • 1.5 Your First Scan6 min
  • 1.6 Exploring Scan Results7 min
  • 2.1 Bluetooth LE vs. Classic Bluetooth8 min
  • 2.2 The Protocol Stack, Layer by Layer8 min
  • 2.3 GAP: Roles, Modes, and Discovery7 min
  • 2.4 GATT: The Data Model7 min
  • 2.5 How AT Commands Map to the Stack7 min
  • 3.1 How Bluetooth LE Advertising Works10 min
  • 3.2 Building Custom Advertising Data14 min
  • 3.3 Scan Response and Extended Data11 min
  • 3.4 iBeacon Advertising9 min
  • 3.5 Scan Filtering and RSSI14 min
  • 3.6 Advertising Parameters Deep Dive12 min
  • 4.1 The Connection Process4 min
  • 4.2 Your First Connection10 min
  • 4.3 Connection Parameters Explained10 min
  • 4.4 Low-Latency vs. Low-Power Configurations10 min
  • 4.5 Connection Failures and Recovery9 min
  • 5.1 The GATT Hierarchy5 min
  • 5.2 Discovering the GATT Database7 min
  • 5.3 UUIDs, Handles, and Properties6 min
  • 5.4 Creating a Custom GATT Service16 min
  • 5.5 Standard vs. Custom Services5 min
  • 6.1 Reading Characteristics8 min
  • 6.2 Writing Characteristics13 min
  • 6.3 Notifications: Server-Pushed Data14 min
  • 6.4 Indications vs. Notifications14 min
  • 6.5 Bidirectional Communication with SPS10 min
  • 7.1 Connection Troubleshooting7 min
  • 7.2 GATT Error Codes8 min
  • 7.3 Role and State Confusion8 min
  • 7.4 Factory Reset Procedure7 min
  • 7.5 Troubleshooting Checklist11 min
  • 8.1 Bluetooth LE Security Overview12 min
  • 8.2 Just Works Pairing12 min
  • 8.3 Passkey Entry Pairing14 min
  • 8.4 Numeric Comparison Pairing15 min
  • 8.5 Bonding and Reconnection19 min
  • 8.6 Security Levels and Protected Characteristics17 min
  • 9.1 MTU Negotiation19 min
  • 9.2 Throughput Optimization17 min
  • 9.3 Write Without Response and Throughput Tuning17 min
  • 9.4 Power Optimization10 min
  • 9.5 Advertising Interval Optimization10 min
  • 9.6 PHY Options and Limitations7 min
  • 9.7 Performance Comparison Summary16 min
  • 10.1 Bluetooth LE Address Types8 min
  • 10.2 Resolvable Private Addresses11 min
  • 10.3 Identity Resolution and Bonded Scanning14 min
  • 10.4 Privacy in Production14 min
  • 11.1 Python Serial Communication18 min
  • 11.2 Building a Command Helper15 min
  • 11.3 Automated Scanning13 min
  • 11.4 Using the bleuio Library14 min
  • 11.5 Automated Connect-Read-Disconnect14 min
  • 11.6 Data Logging and CSV Export12 min
  • 11.7 Auto-Execute Commands14 min
  • 11.8 Error Handling and Robustness14 min
  • 12.1 CTF Introduction and Setup7 min
  • 12.2 Challenge: Hidden Device5 min
  • 12.3 Challenge: GATT Treasure Hunt5 min
  • 12.4 Challenge: Crack the Code6 min
  • 12.5 Challenge: The Whisper6 min
  • 12.6 Challenge: The Impostor6 min
  • 12.7 Challenge Debrief and Bonus Challenges9 min
  • 13.1 Sniffer Hardware and Software Setup19 min
  • 13.2 Installing Wireshark and the nRF Sniffer Plugin15 min
  • 13.3 Capturing Advertising Packets9 min
  • 13.4 Capturing Connection Establishment8 min
  • 13.5 Capturing Read and Write Operations8 min
  • 13.6 Capturing Notifications and Indications9 min
  • 13.7 Wireshark Basics for Bluetooth LE14 min
  • 13.8 Capture Exercise: Full Lifecycle11 min
  • 14.1 Advanced Wireshark Filters for Bluetooth LE13 min
  • 14.2 Filtering by Operation and Handle11 min
  • 14.3 Analyzing Connection Parameter Negotiation8 min
  • 14.4 Tracing a Read/Write Cycle8 min
  • 14.5 Tracing Notification Subscriptions8 min
  • 14.6 Capturing Just Works Pairing7 min
  • 14.7 Capturing Passkey and Numeric Comparison9 min
  • 14.8 Building a Capture Analysis Workflow13 min
  • 14.9 Capture Analysis Cheat Sheet6 min
  • 15.1 Correlating Sniffer and AT Command Output8 min
  • 15.2 Debugging Connection Failures8 min
  • 15.3 Debugging Parameter Rejections8 min
  • 15.4 Debugging GATT Errors8 min
  • 15.5 Android vs. iOS Connection Behavior11 min
  • 15.6 Production Debugging Workflows12 min
  • 15.7 Debugging Toolkit Summary10 min
Prev

4.2 Your First Connection

Introduction

You now understand the theory behind how Bluetooth LE connections work. Let's put it into practice and connect your two dongles for the first time. By the end of this lesson, you'll have established a connection, verified it from both sides, and performed a clean disconnect.

Everything you've learned so far comes together in this lesson. Advertising, scanning, and roles all play a part in establishing a connection.

Step 1: Prepare the Peripheral

Let's get the peripheral dongle ready. Switch to peripheral mode, set a recognizable name in the advertising data, and start advertising:

ATR

Wait ~15 seconds for the dongle to reboot and your serial terminal to reconnect (the USB port may re-enumerate), then:

AT+PERIPHERAL
OK

AT+ADVDATA=0B:09:50:45:52:49:50:48:45:52:41:4C
OK

ADVERTISING DATA: 0B095045524950484552414C

AT+ADVSTART
Advertising type: GAP_CONN_MODE_UNDIRECTED Advertising interval minimum: 1100 maximum: 1100

ADVERTISING...

The peripheral is now broadcasting with the name "PERIPHERAL" on the three advertising channels. If you completed Module 3, this should feel familiar.

Step 2: Scan and Connect from the Central

On the central, reset to a clean state:

ATR

Switch to central mode:

AT+CENTRAL
OK

Scan for the peripheral. You'll need the peripheral dongle's MAC address for this command; if you don't have it handy, run AT+GETMAC on the peripheral first to see it (the response line starts with OWN MAC ADDRESS:). Substitute that address wherever <PERIPHERAL-ADDRESS> appears:

AT+SCANTARGET=[0]<PERIPHERAL-ADDRESS>=3
SCANNING TARGET DEVICE...

[<PERIPHERAL-ADDRESS>] Device Data [ADV]: 0201060B095045524950484552414C

[<PERIPHERAL-ADDRESS>] Device Data [RESP]: 0709426C6575494F

[<PERIPHERAL-ADDRESS>] Device Data [ADV]: 0201060B095045524950484552414C

[<PERIPHERAL-ADDRESS>] Device Data [RESP]: 0709426C6575494F

[<PERIPHERAL-ADDRESS>] Device Data [ADV]: 0201060B095045524950484552414C

[<PERIPHERAL-ADDRESS>] Device Data [RESP]: 0709426C6575494F

SCAN COMPLETE

There's your peripheral in the scan results. Now let's connect to it. You have two options:

Option A: Connect by MAC address. Use the full address with the address type prefix in brackets:

AT+GAPCONNECT=[0]<PERIPHERAL-ADDRESS>
Trying to connect...

CONNECTED.

handle_evt_gap_connected: conn_idx=0000 address=<PERIPHERAL-ADDRESS> CI max is 24. 

Target conn indx changed to=0000

Peripheral exchanged tx data length is 251, rx data length is 251.

Peripheral updated CI min is 24, CI max is 24.
	0001 serv 0x1800
	0002 char 0x1800 prop=02 (-R------)
	0003 ---- 0x2a00
	0004 char 0x1800 prop=02 (-R------)
	0005 ---- 0x2a01
	0006 char 0x1800 prop=02 (-R------)
	0007 ---- 0x2a04
	0008 char 0x1800 prop=02 (-R------)
	0009 ---- 0x2aa6
	000a serv 0x1801

MTU CHANGED: 512

Peripheral updated CI min is 24, CI max is 24.

MTU CHANGED: 512
	000b serv 0783b03e-8535-b5a0-7140-a304d2495cb7
	000c char 0783b03e-8535-b5a0-7140-a304d2495cb7 prop=10 (----N---)
	000d ---- 0783b03e-8535-b5a0-7140-a304d2495cb8
... (55 more lines trimmed for readability) ...

handle_evt_gattc_browse_completed: conn_idx=0000 status=0

handle_evt_gattc_write_completed: conn_idx=0000 handle=0014 status=0

handle_evt_gattc_notification: conn_idx=0000 handle=0014 length=1

Value received: 
Hex: 0x01
Size: 1
Stuck on "Trying to connect..."?

If the connection hangs for more than a few seconds, the peripheral is probably not advertising or you have the wrong address. Run AT+CANCELCONNECT to abort the attempt and get back to a clean state:

AT+CANCELCONNECT
OK

Then check: is the peripheral advertising? (AT+GAPSTATUS on the peripheral should show "Advertising"). Is the address correct? (Run AT+GETMAC on the peripheral to verify).

Connect succeeds, then disconnects immediately (reason 0x08)?

If you see CONNECTED. followed within a second or two by a disconnect that reports reason: 08, that's HCI error code 0x08 (Connection Timeout), meaning the Link Layer's supervision timeout fired because the controller didn't see any packets from the peer in the configured window (1 second by default on BleuIO connections; we'll cover this parameter in Lesson 4.3). It's the controller's way of saying "the link went silent, I'm tearing it down."

In practice, this almost always points to a wedged firmware state on one of the dongles, often after a long idle period or a previous bad disconnect. The fix is usually ATR: it does a soft reset of the firmware that clears the state machine cleanly. I've seen cases where physically unplugging and replugging the dongle alone isn't enough on its own; ATR reaches state that a hardware power cycle doesn't always reach.

To recover, in this order:

  1. Run ATR on each dongle. Wait ~15 seconds for the dongle to reboot and your serial terminal to reconnect (the USB port may re-enumerate). This clears it most of the time.
  2. If ATR alone doesn't clear it, physically unplug both dongles, wait ~10 seconds, plug them back in (avoid USB hubs; plug directly into the laptop), then run ATR again.
  3. If you're still hitting it, quit and relaunch your serial terminal application so the OS-level USB serial state resets too, then re-run the lesson flow from AT+PERIPHERAL / AT+CENTRAL.

If the disconnect persists after ATR and a clean replug, your laptop's USB power management may be suspending the bus (more common on Windows laptops on battery power) or a USB hub between the laptop and the dongles is dropping packets. Switching to a different USB port often clears it. We'll come back to disconnect reason codes more systematically in Lesson 4.5.

Option B: Connect by scan result index (shortcut). If you've just run a broad scan with AT+GAPSCAN=N (as you did in Module 1) and the peripheral appeared as result [01], you can connect by that index instead of typing the full MAC:

AT+GAPCONNECT=1!
Trying to connect...

CONNECTED.

The ! tells the dongle to look up the address from the most recent AT+GAPSCAN results. Note that AT+SCANTARGET (used in Step 2 above) doesn't produce numbered results, so the ! shortcut only works after a broad scan. I recommend using the MAC address method when you know the exact address, and the index shortcut for quick testing during development.

Important: The [0] prefix means a public address (manufacturer-assigned). If a device shows [1] in scan results, it uses a random address, and you must connect with [1] instead. Using the wrong address type will cause the connection to fail. We'll explore this more in Lesson 4.5.

Understanding the Connection Output

When the connection succeeds, the central terminal will display a significant amount of output. Don't be alarmed; I've seen this wall of text confuse many beginners, but it's all normal. Let's walk through what you'll see.

Central (white): Here's what the connection output looks like:

CONNECTED.

handle_evt_gap_connected: conn_idx=0000 address=<PERIPHERAL-ADDRESS> CI max is 24.

Target conn indx changed to=0000

Peripheral exchanged tx data length is 251, rx data length is 251.
	0001 serv 0x1800
	0002 char 0x1800 prop=02 (-R------)
	0003 ---- 0x2a00
	0004 char 0x1800 prop=02 (-R------)
	0005 ---- 0x2a01
	0006 char 0x1800 prop=02 (-R------)
	0007 ---- 0x2a04
	0008 char 0x1800 prop=02 (-R------)
	0009 ---- 0x2aa6
	000a serv 0x1801

Peripheral updated CI min is 24, CI max is 24.

MTU CHANGED: 512

	000b serv 0783b03e-8535-b5a0-7140-a304d2495cb7
	000c char 0783b03e-8535-b5a0-7140-a304d2495cb7 prop=10 (----N---)
	000d ---- 0783b03e-8535-b5a0-7140-a304d2495cb8
	000e desc 0x2902
	000f desc 0x2901
	...

handle_evt_gattc_write_completed: conn_idx=0000 handle=000e status=0

handle_evt_gattc_notification: conn_idx=0000 handle=0014 length=1

Value received:
Hex: 0x02
Size: 1

handle_evt_gattc_browse_completed: conn_idx=0000 status=0

handle_evt_gattc_write_completed: conn_idx=0000 handle=0014 status=0

handle_evt_gattc_notification: conn_idx=0000 handle=0014 length=1

Value received:
Hex: 0x01
Size: 1

That's a lot of text scrolling by. Here's what's happening:

  1. CONNECTED. confirms the connection was established.
  2. CI max is 24 shows the connection interval in raw Bluetooth LE units. The value 24 means 24 x 1.25 ms = 30 ms, which is the default connection interval.
  3. Service discovery output (the lines starting with handle numbers like 0001, 0002, etc.) is the dongle automatically discovering the peripheral's GATT services and characteristics. This happens because auto-discovery (ATDS) is enabled by default.
  4. MTU CHANGED: 512 indicates the Maximum Transmission Unit was negotiated to 512 bytes.
  5. Hex: 0x01 and Hex: 0x02 notifications are flow control messages from the dongle's built-in Serial Port Service (SPS). These are not errors; the auto-discovery feature pre-enables SPS notifications.

Tip: If you'd prefer cleaner connection output without the automatic service discovery flood, you can disable it with ATDS0. For this course, I recommend leaving it on since it's useful to see what services the peripheral offers. You can always run AT+GETSERVICES manually after connecting (which we'll do in Module 5).

Step 3: Verify the Connection

Let's confirm the connection is active on both dongles.

AT+GAPSTATUS
Central role

Connected

Not Advertising

AT+GETCONN
[0]<PERIPHERAL-ADDRESS>, conn_idx=0000, Our Role: Client, bonded=false, paired=false
AT+GAPSTATUS
Peripheral role

Connected

Not Advertising

AT+GETCONN
[0]<CENTRAL-ADDRESS>, conn_idx=0000, Our Role: Server, bonded=false, paired=false

Notice a few things:

  • Both dongles show "Connected" in their status.
  • The peripheral shows "Not Advertising," confirming that advertising stopped automatically when the connection was established.
  • AT+GETCONN shows each dongle's view of the connection: the central sees itself as "Client" and the peripheral sees itself as "Server." These are the GATT roles we'll explore in Module 5.
  • Each dongle shows the other dongle's MAC address as the connected peer.

Key Takeaway: A Bluetooth LE connection is always a two-way relationship. Both devices are aware of the connection and both can verify it independently. The central initiated the connection, but once established, data can flow in both directions.

Step 4: Disconnect

On the central, perform a clean disconnect:

AT+GAPDISCONNECT
handle_evt_gap_disconnected: conn_idx=0000 address=<PERIPHERAL-ADDRESS>. 

DISCONNECTED.

Confirm the central is back to a disconnected state:

AT+GAPSTATUS
Central role

Not Connected

Not Advertising

Now switch to the peripheral and check its state:

AT+GAPSTATUS
Peripheral role

Not Connected

Advertising

Notice that the peripheral automatically resumed advertising after the disconnect. This is helpful behavior: you don't need to run AT+ADVSTART again. The peripheral is ready for another connection without any manual intervention.

Key Takeaway: Establishing a Bluetooth LE connection follows the same pattern every time: peripheral advertises, central scans, central connects by address, both enter the connected state. When the central disconnects, the peripheral resumes advertising automatically.

Command Quick Reference

CommandWhat It Does
AT+PERIPHERALSwitch to peripheral role
AT+ADVDATA=<hex>Set advertising data
AT+ADVSTARTStart advertising
AT+CENTRALSwitch to central role
AT+GAPSCAN=NScan for N seconds
AT+GAPCONNECT=[type]addressConnect to a device by MAC address
AT+GAPCONNECT=N!Connect to scan result index N
AT+GAPSTATUSCheck role and connection state
AT+GETCONNList connected devices with details
AT+GAPDISCONNECTDisconnect from the connected device
ATDS0 / ATDS1Disable/enable auto-discovery on connect

Summary

In this lesson, we:

  • Prepared the peripheral with a recognizable advertising name and started advertising
  • Connected from the central using both the MAC address method and the scan index shortcut
  • Understood the auto-discovery output that appears during connection (service listing, MTU negotiation, SPS notifications)
  • Verified the connection from both sides with AT+GAPSTATUS and AT+GETCONN
  • Performed a clean disconnect and confirmed the peripheral auto-resumes advertising

What's Next

Your dongles can now connect and disconnect on command. But the connection used default parameters (30 ms interval, no latency, 1000 ms timeout). In the next lesson, we'll explore what these connection parameters mean and how they control the behavior of your connection.