Chapters

17 Python BLE: Scanning and Connections

bluetooth-ble
bt
impl
python
networking
wireless
bluetooth
ble
esp32
lab

17.1 Start With the Decision

A BLE scanner can see many adverts but still miss the device a service needs. The code must filter first, then connect with a clear timeout.

17.2 Route Overview

This is part 1 of 3. Continue with Python BLE: RSSI Smoothing and Zones.

17.3 Part Objectives

  • Filter BLE advertisements by stable device evidence.
  • Manage Python scan and connection state with explicit timeouts.
In 60 Seconds

BLE is the low-energy branch of Bluetooth. RSSI is a number that shows the received signal level. It can help sort rough zones, but it does not give an exact range.

Imagine a museum computer that listens for nearby exhibit tags. The program must find the right tag, connect only when needed, read the expected value, and continue after the tag moves away.

Start with the Python program’s role. Decide whether it scans, connects, reads, writes, or listens for updates. Filter by a stable device or service clue, not only a friendly name. Then test no device, several devices, a lost connection, a changed value, and a clean stop.

Keep waits and callbacks short so one slow task does not freeze the rest of the program. Record reconnect rules and the exact service value used. Signal strength can support a rough zone, but it is not a tape measure. Walls, bodies, radio power, and device position can move the reading.

Go deeper in two steps. The Practitioner sections build the scanner, service explorer, and reconnect flow. Under the Hood explains runtime limits and the maths behind rough signal zones.

Write the program job in one line. For the museum case it might be, “Find tag A, read its heat value, and store one fresh row.” That line keeps a scan demo from growing into an unsafe service by accident.

Make the device clue clear. Use a known service ID, device ID, or signed app field where the product supports one. A name may be absent or copied. Keep the full scan row when two units look alike. Ask a person to resolve doubt.

Split the code into small steps. Start the scan. Apply the filter. Stop the scan. Connect. Find the service. Find the value. Read or subscribe. Check the data. Close the link. Give each step a time limit and a clear error.

Use async work with care. An awaited task lets other work run while it waits. A long block in a callback can hold up new data and clean shutdown. Keep callbacks short. Send slow work to a queue with a size limit.

Test no tag, one tag, and many tags. Test a tag that leaves during connect. Test a value that has the wrong size. Test a service that changed. Test a stop signal during each step. The program should end without a stuck scan or hidden task.

For long runs, add a reconnect rule. Set a wait that grows after each fail and has a limit. Clear old state before the next link. Mark data as stale while the tag is gone. Do not show the last value as if it were live.

Treat signal level as one clue. Record the tag, phone or adapter, radio power, room, body position, and sample window. Smooth only enough to support the named zone. Keep an unknown zone when readings overlap.

Keep a run record. Include the code version, library version, adapter, platform, service ID, value ID, filter, retry rule, and test cases. Recheck after any of these change. A script that works once is a start, not a support plan. BLE development in Python uses the bleak library for cross-platform async scanning, connecting, and GATT interaction. Production apps need RSSI filtering, GATT service exploration, and exponential smoothing for proximity — zone-based classification (immediate/near/far) is far more reliable than precise distance calculations due to RSSI variability.

Key Concepts
  • bleak (Bluetooth Low Energy platform Agnostic Klient): Python async BLE library supporting Windows (WinRT), macOS (CoreBluetooth), and Linux (BlueZ) backends
  • BleakClient: bleak class representing a connection to a BLE peripheral; provides methods for service discovery, read, write, start_notify, stop_notify
  • BleakScanner: bleak class for BLE device discovery; supports filtering by service UUID, device name, and RSSI threshold
  • asyncio.run(): Python coroutine runner; required for bleak operations which are all async; use asyncio.get_event_loop() for integration with existing async frameworks
  • UUID String Format: bleak accepts both 16-bit UUIDs as “0000xxxx-0000-1000-8000-00805f9b34fb” (128-bit expanded form) and short “xxxx” strings; use full 128-bit format for custom services
  • characteristic.properties: bleak property set of enabled operations: {‘read’, ‘write’, ‘notify’, ‘indicate’, ‘write-without-response’} — check before attempting operations
  • client.start_notify(uuid, callback): Registers a Python callback function called when a BLE notification arrives; callback receives (sender_handle, bytearray_data)
  • GATT Error Codes in bleak: BleakError wraps ATT error codes; common causes: device not paired (0x05), wrong UUID (0x01), characteristic not found (service discovery needed)

17.4 Minimum Viable Understanding

BLE development in Python centers on the bleak library, which provides cross-platform async APIs for scanning, connecting, and interacting with BLE devices. Production BLE applications need RSSI filtering for reliable device discovery, GATT service exploration for data access, and exponential smoothing for stable proximity detection — zone-based classification (immediate/near/far) is far more reliable than precise distance calculations due to inherent RSSI variability.

17.5 Learning Objectives

By the end of this chapter, you will be able to:

  • Implement Production BLE Scanners: Configure device filtering by RSSI threshold and name patterns using the bleak library
  • Analyze GATT Services: Connect to BLE devices and enumerate services and characteristics to assess device capabilities
  • Distinguish Beacon Protocols: Compare iBeacon and Eddystone advertisement packet structures and justify protocol selection for a given use case
  • Design Proximity Detection Systems: Apply RSSI smoothing algorithms and construct zone-based presence detection with exponential moving averages
  • Develop Async BLE Applications: Construct event-driven Python programs using asyncio and diagnose disconnection handling for production reliability
Chapter Roadmap
  • In 60 Seconds
  • Key Concepts
  • Quick Check: Implementation
  • Minimum Viable Understanding
  • For Beginners: Python BLE Development
  • Python BLE Implementation Pattern
  • Prerequisites
  • BLE Scanner with Device Filtering
  • Try It: BLE RSSI Filter Simulator
  • Checkpoint: Scanner Evidence
  • BLE GATT Server Explorer
  • Try It: GATT Service UUID Lookup
  • BLE Beacon Manager
  • Try It: Beacon Advertisement Decoder
  • Checkpoint: Services and Advertisements
  • BLE Proximity Detector

17.6 For Beginners: Python BLE Development

What you’ll learn: Production-ready Python implementations for common BLE tasks using the bleak library.

Prerequisites:

Why Python for BLE? Python’s bleak library provides cross-platform BLE support (Windows, macOS, Linux) with clean async APIs, making it ideal for gateways, data collection, and prototyping.

Use Python BLE code where the device doing the Bluetooth work has enough compute, storage, and operating-system support to run a gateway or test tool.

  • Start with scanning filters: service UUID, name pattern, and minimum RSSI.
  • Connect only after you have selected a specific target from scan results.
  • Discover services before reading or subscribing to characteristics.
  • Treat RSSI distance as an estimate; classify broad zones instead of promising exact meters.
  • Build reconnection handling from the first prototype because BLE links can drop normally.

In this pattern, the Python host is the BLE Central and GATT client. While scanning it is acting as an Observer; after selection it initiates the connection and reads, writes, or subscribes to the peripheral’s GATT server. If the product requirement says a phone must discover the Python process or read a characteristic served by it, that is a Peripheral/GATT-server requirement and belongs on firmware or a platform-specific stack, not plain Bleak.

17.7 Prerequisites

Before working through these implementations:

  • BLE Code Examples and Simulators: Basic Python scanner and GATT concepts
  • Python Environment: A currently supported Python version for your chosen bleak release, installed with pip install bleak

17.8 BLE Scanner with Device Filtering

A production scanner with RSSI filtering and statistics:

Expected scanner output should include:

  • The scan duration and minimum RSSI threshold, for example 15.0s and -70 dBm.
  • Each matching device name, address or platform identifier, latest RSSI, and rough distance estimate.
  • A final count of devices that passed the filter.
  • Per-device statistics such as sample count, RSSI range, mean RSSI, and standard deviation.

The implementation filters devices by minimum RSSI threshold, collects multiple samples per device, and calculates statistics for more reliable readings.

For implementation review, keep a repeatable scanner log: adapter used, scan timeout, target name or service UUID, RSSI threshold, advertisements received, advertisements rejected by filters, and the selected device identifier. The script should keep a short sample window and select the strongest stable match instead of connecting to the first advertisement packet it sees.

Try It: BLE RSSI Filter Simulator

Adjust the RSSI threshold and observe which simulated BLE devices pass the filter. Devices with RSSI below the threshold are filtered out as too distant.

Radio RemiCheckpoint: Scanner Evidence

You now know:

  • A production scanner log should include the adapter, scan timeout, target filter, RSSI threshold, advertisements accepted, advertisements rejected, and selected device identifier.
  • The chapter’s scanner example uses values like 15.0s and -70 dBm to make discovery repeatable instead of anecdotal.
  • Bleak code should select a specific target after filtering; connecting to the first packet is weaker evidence than a stable sample window.

Scanning answers “which devices are worth investigating.” The next step is to connect to one selected peripheral and prove which services and characteristic operations it actually exposes.

17.9 BLE GATT Server Explorer

Connect to a BLE device and enumerate its services and characteristics:

Expected explorer output should identify the device and list each discovered service with its characteristics:

  • Heart Rate Service: 0000180d-0000-1000-8000-00805f9b34fb
    • Heart Rate Measurement characteristic 00002a37-0000-1000-8000-00805f9b34fb
    • Common properties: read and notify
  • Battery Service: 0000180f-0000-1000-8000-00805f9b34fb
    • Battery Level characteristic 00002a19-0000-1000-8000-00805f9b34fb
    • Common properties: read and notify

Standard service UUIDs:

  • 0x180D - Heart Rate Service
  • 0x180F - Battery Service
  • 0x181A - Environmental Sensing
  • 0x1816 - Cycling Speed and Cadence

Review evidence should show service discovery before any read, write, or subscription attempt. Check characteristic properties before writing and subscribe only to characteristics that expose notify or indicate. For decoded sensor values, log the raw bytes and the decoded value; for example, a signed 16-bit temperature value of 2234 centi-degrees represents 22.34 C.

Try It: GATT Service UUID Lookup

Select a standard BLE GATT service to see its UUID, characteristics, and typical use case. This demonstrates the service discovery process that the GATT Explorer performs.

17.10 BLE Beacon Manager

Parse and manage iBeacon and Eddystone beacon advertisements:

Expected beacon-manager output should summarize:

  • Total decoded beacons.
  • Count by beacon type, such as iBeacon and Eddystone-URL.
  • iBeacon fields: calibrated TX power, proximity UUID, major value, and minor value.
  • Eddystone-URL fields: calibrated TX power and decoded URL.

Beacon Protocol Differences:

  • iBeacon: Apple-defined advertisement format using a 128-bit proximity UUID plus major and minor fields. It does not carry URLs or telemetry in the standard iBeacon frame.
  • Eddystone: Google-defined advertisement family with UID, URL, TLM, and EID frame types. UID frames use a namespace plus instance identifier, URL frames broadcast compact web links, and TLM frames carry telemetry.
Try It: Beacon Advertisement Decoder

Configure a simulated beacon and see how its advertisement packet is structured. Compare iBeacon and Eddystone formats to understand the protocol differences.

Radio RemiCheckpoint: Services and Advertisements

You now know:

  • Service discovery comes before reads, writes, or subscriptions; properties such as READ, WRITE, NOTIFY, and INDICATE decide which operation is valid.
  • Standard services in this chapter include Heart Rate 0x180D, Battery 0x180F, Environmental Sensing 0x181A, and Cycling Speed 0x1816.
  • iBeacon uses a proximity UUID plus major and minor; Eddystone uses UID, URL, TLM, or EID frames depending on the deployment goal.

Once the advertisement and GATT surface are explicit, the chapter moves from identity to location. RSSI is useful, but only after you treat it as a noisy signal.

17.11 BLE Proximity Detector

Zone-based proximity detection with RSSI smoothing:

A typical approach trace should show the raw RSSI, smoothed RSSI, estimated distance, and current zone at each sample. For example, a device moving closer may start at -75 dBm in the far zone, cross into the near zone around -68 dBm, and only enter the immediate zone after the smoothed RSSI rises above the immediate threshold.

Zone Thresholds:

  • Immediate: RSSI above -55 dBm, usually less than 0.5m.
  • Near: RSSI from about -55 dBm to -70 dBm, usually 0.5m to 3m.
  • Far: RSSI below -70 dBm, usually more than 3m.

RSSI Smoothing Algorithm:

The exponential moving average (EMA) filter reduces RSSI noise:

  • Formula: smoothed_rssi = alpha * new_rssi + (1 - alpha) * prev_smoothed
  • A higher alpha reacts faster but lets more noise through.
  • A lower alpha is steadier but takes longer to follow real movement.
  • Values around 0.2 to 0.3 are common starting points for zone-based proximity.

17.12 Continue to the Next Part

Carry this evidence into Python BLE: RSSI Smoothing and Zones, which begins with Phoebe’s Field Notes: Why RSSI Distance Moves in Multipliers.