AI-friendly CLI tool for Bluetooth device control.
AirCtl enables AI agents to control devices through a command-line interface, supporting both BLE (Bluetooth Low Energy) and BT Classic (BR/EDR) protocols.
- AI-First Design: JSON output by default,
-hfor human-readable - Auto-Connect: Device commands automatically connect if needed
- Daemon Architecture: Background daemon maintains BLE connections for fast operations
- LLM-Friendly Snapshots:
--last Nreturns immediately instead of streaming - AI Hint Errors: All errors include actionable recovery hints for autonomous recovery
- Standard BLE Profiles: Built-in Heart Rate, Battery, Device Information
- Device Discovery: One-stop capabilities and info commands
- UUID Name Mapping: Human-readable names for standard BLE UUIDs
- BT Classic (BR/EDR): RFCOMM connection for Serial Port Profile devices
- Cross-Platform BLE: Windows, Linux, macOS | BT Classic: Windows, Linux
pip install airctl
# BLE: Scan and discover device
airctl ble scan -t 10
# Device info (one command, auto-connect)
airctl device quick-scan <address>
# Returns: name, manufacturer, battery, services, capabilities
# Read battery
airctl device battery <address>
# Monitor heart rate (auto-connect + subscribe + parse)
airctl device heart-rate <address>
# BT Classic: Serial port devices
airctl bt scan -t 10
airctl bt connect <address> --channel 1┌─────────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ (airctl ble/bt/device/daemon/config commands) │
└───────────────────────────┬─────────────────────────────────────┘
│ JSON-RPC over IPC (BLE only)
▼
┌─────────────────────────────────────────────────────────────────┐
│ BLE Daemon (Background) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Connection │ │ Event │ │ Device Manager │ │
│ │ Manager │ │ Stream │ │ (multi-device support) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Task Manager │ │
│ │ (periodic read/write/scan operations) │ │
│ └─────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Bleak Library (BLE) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ BT Classic (Direct System Calls) │
│ - Windows: WSA + BluetoothFind APIs │
│ - Linux: BlueZ via D-Bus / hctltool │
└─────────────────────────────────────────────────────────────────┘
| Category | Commands | Description |
|---|---|---|
| BLE Protocol | ble |
Scan, connect, GATT read/write/notify |
| Device Discovery | ble info, ble capabilities |
Structured device info and capabilities |
| BT Classic Protocol | bt |
Scan, SDP discovery, RFCOMM connect |
| Device Profiles | device |
Heart rate, battery, device info, quick-scan |
airctl daemon status # Check daemon status
airctl daemon start # Start daemon manually
airctl daemon start --log-level DEBUG
airctl daemon stop
airctl daemon restart# One-stop device discovery: auto-connect + identity + battery + capabilities
airctl device quick-scan <address>
# Structured device info: identity, battery, subscriptions, MTU
airctl ble info <address>
# Full capabilities: all services/characteristics with names and operation flags
airctl ble capabilities <address># Scanning
airctl ble scan [-t 10] [--service-uuids UUID1,UUID2]
airctl ble scan -n "Heart Rate"
# Connection
airctl ble connect <address> [-t 30] [-a alias]
airctl ble disconnect <address>
airctl ble list
# GATT Operations
airctl ble services <address>
airctl ble characteristics <address> [-s UUID]
airctl ble read <address> -u UUID [-f hex|base64|text]
airctl ble read <address> -H HANDLE
airctl ble write <address> -u UUID -d "hex:..."
airctl ble write <address> -H HANDLE -d "hex:..."
# Notifications
airctl ble notify subscribe <address> -u UUID
airctl ble notify unsubscribe <address> -u UUID
# Event Snapshots (LLM-friendly, returns immediately)
airctl ble events --last 5
airctl ble events --last 10 --address <addr>
airctl ble events --last 5 --type notification
# Background Tasks
airctl ble task start-read <address> -u UUID -i 5
airctl ble task start-write <address> -u UUID -d "hex:01" -i 10
airctl ble task start-scan -i 30 --timeout 5
airctl ble task list
airctl ble task stop <task_id>
airctl ble task result <task_id> --last 5All device commands automatically connect if not connected and disconnect when done.
# Battery: auto-connect + read + status
airctl device battery <address>
# {"battery_level": 85, "status": "normal"}
# Device info: identity (manufacturer, model, firmware)
airctl device device-info <address>
# {"identity": {"manufacturer_name": "Huawei", "model_number": "WATCH GT 4"}}
# Heart rate: auto-connect + subscribe + parse
airctl device heart-rate <address> [-d 30]
# {"latest_heart_rate": 72, "measurement_count": 5, "measurements": [...]}
# Full monitor: subscribe all notifications + periodic reads
airctl device monitor <address> [-d 30] [-i 5]
# {"subscribed_count": 3, "read_count": 12, "notifications": [...]}
# Identify device coordinator
airctl device identify <address>
# List coordinators and features
airctl device list-coordinators
airctl device features
airctl device kindsBT Classic uses direct system calls (not the daemon).
airctl bt scan [-t 10] # Scan for devices
airctl bt scan --timeout 10 -h # Human-readable output
airctl bt services <address> # List RFCOMM services
airctl bt connect <address> --channel 1 # Connect via RFCOMM
airctl bt connect <address> --service-uuid <UUID> # Auto-detect channel
airctl bt list # List connected devices
airctl bt disconnect <address>| Use Case | Protocol | Example |
|---|---|---|
| Device discovery | BLE | airctl device quick-scan <addr> |
| Standard profiles (heart rate, battery) | BLE | airctl device heart-rate <addr> |
| Raw GATT read/write/notify | BLE | airctl ble read/notify <addr> |
| RFCOMM serial communication | BT Classic | airctl bt connect <addr> |
| Device with SPP (Serial Port Profile) | BT Classic | HC-05/06 modules, GPS devices |
Use snapshot queries (--last N) instead of streaming for AI agents:
# Snapshot: returns immediately
airctl ble events --last 5
# {"events": [...], "count": 5}
# Task results snapshot
airctl ble task start-read <addr> -u 2A19 -i 5
# {"task_id": "abc123", "type": "periodic_read"}
airctl ble task result abc123 --last 5
# {"task_id": "abc123", "results": [...], "count": 5}Workflow for AI Agents:
airctl device quick-scan <addr>— One command to discover everythingairctl device heart-rate <addr>— One command for heart rate monitoring- If error: check
hintfield and follow the recovery steps
All errors include an actionable hint field for autonomous recovery:
{
"error": true,
"message": "Device 24:81:C7:1B:3B:46 not connected",
"hint": "Run 'airctl ble connect 24:81:C7:1B:3B:46' first, then retry this command."
}| Error | Hint |
|---|---|
| Device not connected | Run 'airctl ble connect <addr>' first |
| Device not found | Run 'airctl ble scan -t 10' to discover |
| Characteristic not found | Run 'airctl ble characteristics <addr>' |
| Insufficient authentication | Pair the device with your system first |
| Operation timed out | Restart daemon: airctl daemon stop && airctl daemon start |
| Lost connection to daemon | Run 'airctl daemon start' |
airctl config list
airctl config alias set <address> <name>
airctl config alias list
airctl config alias remove <name>
airctl config preset list
airctl config preset set <name> --service UUID --char UUID
airctl config preset remove <name>The -d / --data parameter supports:
| Format | Example | Description |
|---|---|---|
hex: |
hex:010203 |
Hexadecimal bytes |
text: |
text:hello |
UTF-8 text |
base64: |
base64:AQID |
Base64 encoded |
| (default) | 010203 |
Hexadecimal |
Standard BLE UUIDs are automatically mapped to human-readable names:
{
"services": [
{
"uuid": "0000180f-0000-1000-8000-00805f9b34fb",
"name": "Battery",
"characteristics": [
{"uuid": "00002a19-...", "name": "Battery Level", "readable": true}
]
}
]
}The -f / --format parameter controls output format:
| Format | Example | Description |
|---|---|---|
hex |
010203 |
Hexadecimal string |
base64 |
AQID |
Base64 encoded |
text |
Hello |
UTF-8 text |
| UUID | Name | Description |
|---|---|---|
| 1800 | Generic Access | Device name and appearance |
| 180A | Device Information | Manufacturer, model, firmware |
| 180D | Heart Rate | Heart Rate service |
| 180F | Battery | Battery service |
| 2A00 | Device Name | BLE device name (read/write) |
| 2A19 | Battery Level | Battery percentage (read) |
| 2A24 | Model Number | Device model (read) |
| 2A26 | Firmware Revision | Firmware version (read) |
| 2A29 | Manufacturer Name | Device manufacturer (read) |
| 2A37 | Heart Rate Measurement | Heart rate data (notify) |
| 2A38 | Body Sensor Location | Sensor position (read) |
- Alias Naming: Use underscores (
my_sensor) not hyphens (my-sensor) in aliases - BLE Authentication: Some characteristics require pairing — pair the device with your system first
- BLE Connection Stability: Some devices (e.g., smartwatches) disconnect after ~12s idle. Device commands handle this automatically with auto-reconnect
- Short UUID Support: Both short (
2A19) and full (00002a19-...) UUID formats are supported - Heart Rate Notifications: Only sent when the device is actively measuring (e.g., during exercise)
- Python 3.10+
- Bluetooth adapter
- Windows: Windows 10 v16299+ | Linux: BlueZ 5.55+ | macOS: macOS 10.15+
pip install airctlOr from source:
git clone https://github.com/skinapi2025/AirCtl.git
cd AirCtl
pip install -e ".[dev]"pytest tests/ -vMIT License