| name | bleswiftcli-macos-bluetooth |
| description | A macOS command-line tool for Bluetooth Low Energy operations—scan, connect, pair, read, write, inspect GATT, and L2CAP channels. |
| triggers | ["scan for bluetooth devices on mac","connect to a ble peripheral","read bluetooth characteristic value","write to ble device","inspect gatt services","pair with bluetooth low energy device","set up l2cap channel","bluetooth le command line tool"] |
BLESwiftCLI — macOS Bluetooth LE Command-Line Tool
Skill by ara.so — Devtools Skills collection.
ble is a macOS command-line tool for Bluetooth Low Energy operations: scanning, connecting, pairing, reading/writing characteristics and descriptors, inspecting GATT databases, and opening L2CAP channels. Built on BLESwift and swift-argument-parser.
Installation
Homebrew
brew install kylebrowning/tap/ble
Mint
mint install kylebrowning/BLESwiftCLI
From Source
swift build -c release
cp .build/release/ble /usr/local/bin/
Permissions: On first run, macOS prompts for Bluetooth access. Grant it to your terminal app (Terminal, iTerm, etc.) in System Settings → Privacy & Security → Bluetooth.
Core Concepts
- Peripherals are identified by UUID or name substring
- Services and characteristics use 16-bit SIG UUIDs (e.g.
180F, 2A19) or full 128-bit UUIDs
- Connections are held open only while the CLI process runs
- Pairing is triggered by accessing encrypted characteristics—no explicit pair API
- Output: data (scan results, values, JSON) → stdout; status/progress → stderr
Commands
Scanning for Devices
Scan for all nearby BLE devices:
ble scan
Scan for specific service (e.g. Heart Rate 180D) with unlimited time:
ble scan -s 180D --timeout 0
Live RSSI updates with duplicate advertisements:
ble scan --allow-duplicates
Filter by minimum signal strength:
ble scan --min-rssi -70
JSON output for scripting:
ble scan --json | jq '.name, .rssi, .uuid'
Output format (interactive): Live table sorted by signal strength, showing name, RSSI (color-coded green/yellow/red), peripheral UUID, advertised services, manufacturer data.
Output format (piped/JSON): One line per advertisement event.
Connecting to a Peripheral
Connect by UUID:
ble connect 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
Connect by name substring (resolves via scan):
ble connect "Kyle's Sensor"
Connect and auto-reconnect on disconnect:
ble connect mydevice -s 180D --reconnect
The connection stays open until Ctrl-C or the process exits. Lifecycle events stream to stderr.
Pairing with a Device
CoreBluetooth has no explicit pair API. Pairing is triggered by accessing a characteristic that requires encryption:
ble pair mydevice -s <service-uuid> -c <protected-characteristic-uuid>
To trigger pairing via write:
ble pair mydevice -s FFF0 -c FFF1 --write 0x01
This connects, attempts the read/write, and macOS shows the pairing dialog. Approve it to bond the device.
Unpair: System Settings → Bluetooth, click info button on device, select "Forget This Device".
Inspecting GATT Database
Enumerate all services, characteristics, and descriptors:
ble inspect mydevice
Include current values of readable characteristics:
ble inspect mydevice --read
JSON output:
ble inspect mydevice --json | jq
Example output:
Service 180F — Battery
2A19 — Battery Level [read, notify] = 0x5A (1 byte, uint 90, "Z")
Descriptor 2902 — Client Characteristic Configuration
Reading Characteristics
One-time read:
ble read mydevice -s 180F -c 2A19
Subscribe to notifications:
ble read mydevice -s 180F -c 2A19 --notify
Read specific number of notifications:
ble read mydevice -s 180F -c 2A19 --notify --count 5
Read a descriptor:
ble read mydevice -s 180F -c 2A19 -d 2901
Output: Hex representation plus interpretations (byte count, little-endian uint, UTF-8 string).
Writing Characteristics
Write hex value:
ble write mydevice -s FFF0 -c FFF1 --hex 0x01FF
Write string:
ble write mydevice -s FFF0 -c FFF1 --string "hello"
Write from payload file (YAML/JSON):
ble write mydevice -p command.yaml
Write and wait for notification on another characteristic:
ble write mydevice -p command.yaml --expect-reply-on FFF2
Dry-run (print encoded bytes without writing):
ble write mydevice -p command.yaml --dry-run
Write a descriptor:
ble write mydevice -s FFF0 -c FFF1 -d 2901 --string "label"
The tool automatically checks characteristic properties and chooses write-with-response or write-without-response. Use --without-response to force it.
Payload Files
Structured payloads in YAML or JSON. Fields are encoded in order and concatenated.
Example (command.yaml):
service: 180F
characteristic: 2A19
writeType: withResponse
fields:
- { type: u8, value: 1 }
- { type: u16le, value: 5000 }
- { type: i32be, value: -70 }
- { type: string, value: "hello" }
- { type: hex, value: "DEADBEEF" }
- { type: pad, length: 2 }
Supported types:
- Integers:
u8, u16, u32, u64, i8, i16, i32, i64
- Endianness:
u16le (little-endian, default), u16be (big-endian)
string: UTF-8 encoded
hex: raw hex bytes
pad: zero-fill N bytes
Command-line flags (-s, -c) override the file's service/characteristic.
L2CAP Channels
Open a connection-oriented L2CAP channel:
ble l2cap mydevice --psm 0x0080
Send hex data once, then stream incoming:
ble l2cap mydevice --psm 128 --send-hex 0x01FF
Raw binary output (for piping):
ble l2cap mydevice --psm 128 --raw > capture.bin
The channel stays open until Ctrl-C or the peripheral closes it.
Common Patterns
Find and Connect to a Specific Device Type
ble scan -s 180D --timeout 5
ble connect "Polar H10" --reconnect
Read Battery Level
ble read mydevice -s 180F -c 2A19
Monitor Heart Rate Notifications
ble read mydevice -s 180D -c 2A37 --notify
Send Multi-Field Command
Create command.yaml:
service: FFF0
characteristic: FFF1
fields:
- { type: u8, value: 0x02 }
- { type: u16le, value: 1000 }
Execute:
ble write mydevice -p command.yaml
Automated Testing / Scripting
#!/bin/bash
DEVICE=$(ble scan -s 180D --timeout 5 --json | jq -r '.uuid' | head -n1)
ble connect "$DEVICE" &
sleep 2
ble read "$DEVICE" -s 180D -c 2A37 --notify --count 10
Full GATT Inspection
ble inspect mydevice --read
ble inspect mydevice --json | jq '.services[].characteristics[].uuid'
Pair and Then Write to Protected Characteristic
ble pair mydevice -s FFF0 -c FFF1 --write 0x00
ble write mydevice -s FFF0 -c FFF1 --hex 0x0102
Troubleshooting
"Bluetooth access denied"
Grant Bluetooth permission to your terminal app in System Settings → Privacy & Security → Bluetooth.
"Peripheral not found"
- Ensure the device is powered on and in range
- Try
ble scan first to confirm it's advertising
- Use the exact UUID or a unique name substring
"Characteristic does not support write"
The characteristic's properties don't include write or writeWithoutResponse. Use ble inspect to confirm properties.
"Value exceeds MTU"
The payload is larger than the negotiated maximum write length. Split the data or reduce the payload size. The tool warns about this but doesn't auto-split.
"Connection timeout"
- Device may be out of range or turned off
- macOS Bluetooth stack may be busy—try toggling Bluetooth off/on in System Settings
- Some devices bond to one host at a time—unpair from other devices
Pairing dialog doesn't appear
The characteristic isn't marked as requiring encryption. Check the device's GATT specification or try a different characteristic known to be protected.
No notifications received
- Verify the characteristic supports notify/indicate:
ble inspect mydevice
- Some devices require enabling notifications via the Client Characteristic Configuration Descriptor (CCCD
2902)—the tool does this automatically
Integration with Other Tools
Pipe to jq for JSON filtering
ble scan --json | jq 'select(.rssi > -60) | {name, uuid, rssi}'
Capture raw L2CAP stream
ble l2cap mydevice --psm 128 --raw | xxd
Log notifications to file
ble read mydevice -s 180D -c 2A37 --notify > heart_rate.log
Use in scripts with error handling
#!/bin/bash
set -e
DEVICE="MyDevice"
if ! ble connect "$DEVICE" --timeout 5 2>&1 | grep -q "Connected"; then
echo "Failed to connect"
exit 1
fi
ble write "$DEVICE" -p command.yaml
Environment and Configuration
- NO_COLOR: Set to disable colored output
- Piping automatically disables colors and switches to line-by-line output
- Timeout defaults: scan 10s, connect 30s (override with
--timeout)
Development and Testing
Build from source:
git clone https://github.com/kylebrowning/BLESwiftCLI.git
cd BLESwiftCLI
swift build
.build/debug/ble --help
Run tests (no hardware required):
swift test
Tests use BLESwift's FakeCentral and FakePeripheral for logic validation.
Reference
Key takeaway: Use ble scan to discover, ble inspect to explore GATT, ble read/write for data, and payload files for complex structured writes.