or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

backend-management.mdbgapi-backend.mddevice-operations.mderror-handling.mdgatttool-backend.mdindex.mdutilities.md

gatttool-backend.mddocs/

0

# GATTTool Backend

1

2

GATTTool-specific backend implementation for Linux BlueZ with CLI integration and advanced debugging capabilities. The GATTToolBackend provides native Linux BLE connectivity using the system's BlueZ stack and gatttool command-line utility.

3

4

## Capabilities

5

6

### Initialization and Configuration

7

8

Initialize GATTTool backend with BlueZ adapter configuration and CLI parameters.

9

10

```python { .api }

11

def __init__(self, hci_device: str = "hci0", gatttool_logfile: str = None,

12

cli_options: list = None, search_window_size: int = None, max_read: int = None):

13

"""

14

Initialize GATTTool backend.

15

16

Args:

17

hci_device: Bluetooth adapter identifier (e.g., 'hci0', 'hci1')

18

gatttool_logfile: Path to log gatttool CLI interactions

19

cli_options: Additional gatttool command-line options

20

search_window_size: Pexpect search buffer size for characteristic discovery

21

max_read: Maximum bytes to read from gatttool output

22

"""

23

24

def start(self, reset_on_start: bool = True, initialization_timeout: int = 3):

25

"""

26

Start GATTTool backend and initialize CLI session.

27

28

Args:

29

reset_on_start: Reset Bluetooth adapter on startup

30

initialization_timeout: CLI initialization timeout in seconds

31

32

Raises:

33

BLEError: gatttool not found or initialization failed

34

"""

35

```

36

37

**Usage Example:**

38

39

```python

40

import pygatt

41

42

# Basic initialization

43

adapter = pygatt.GATTToolBackend()

44

45

# Advanced configuration

46

adapter = pygatt.GATTToolBackend(

47

hci_device="hci1", # Use second Bluetooth adapter

48

gatttool_logfile="/tmp/gatttool.log", # Enable CLI logging

49

search_window_size=2048, # Larger buffer for many characteristics

50

max_read=4096 # Increase read buffer

51

)

52

53

adapter.start(reset_on_start=True, initialization_timeout=5)

54

```

55

56

### System Integration

57

58

Direct integration with Linux BlueZ stack and system-level Bluetooth management.

59

60

```python { .api }

61

def scan(self, timeout: int = 10, run_as_root: bool = False) -> list:

62

"""

63

Perform BLE device scan using BlueZ HCI commands.

64

65

Args:

66

timeout: Scan duration in seconds

67

run_as_root: Run scan with sudo privileges (may be required)

68

69

Returns:

70

list: Discovered devices with address, name, and RSSI

71

72

Raises:

73

BLEError: Scan failed, possibly due to permissions

74

75

Note:

76

May require root privileges depending on system configuration

77

"""

78

79

def reset(self):

80

"""

81

Reset the Bluetooth adapter using HCI commands.

82

83

Useful for recovering from stuck states or connection issues.

84

85

Raises:

86

BLEError: Reset failed or adapter not available

87

"""

88

89

def clear_bond(self, address: str = None):

90

"""

91

Clear stored bonding information using bluetoothctl.

92

93

Args:

94

address: Specific device address, or None for all bonds

95

96

Note:

97

Uses system-level bluetoothctl command to manage bonds

98

"""

99

```

100

101

**Usage Example:**

102

103

```python

104

import pygatt

105

106

adapter = pygatt.GATTToolBackend()

107

adapter.start()

108

109

# Scan with elevated privileges if needed

110

devices = adapter.scan(timeout=15, run_as_root=True)

111

for device in devices:

112

print(f"Found: {device['address']} - {device.get('name', 'Unknown')}")

113

114

# Reset adapter if having connection issues

115

adapter.reset()

116

```

117

118

### Low-Level CLI Access

119

120

Direct access to gatttool command-line interface for advanced operations and debugging.

121

122

```python { .api }

123

def sendline(self, command: str):

124

"""

125

Send raw command to gatttool CLI.

126

127

Args:

128

command: gatttool command string

129

130

Returns:

131

Command response output

132

133

Raises:

134

BLEError: Command failed or CLI not available

135

"""

136

137

def char_read(self, uuid: str, timeout: int = 1) -> bytearray:

138

"""

139

Read characteristic using gatttool CLI.

140

141

Args:

142

uuid: Characteristic UUID

143

timeout: Read timeout in seconds

144

145

Returns:

146

bytearray: Characteristic value

147

"""

148

149

def char_read_handle(self, handle: int, timeout: int = 4) -> bytearray:

150

"""

151

Read characteristic by handle using gatttool.

152

153

Args:

154

handle: Characteristic handle

155

timeout: Read timeout in seconds

156

157

Returns:

158

bytearray: Characteristic value

159

"""

160

161

def char_write_handle(self, handle: int, value: bytearray,

162

wait_for_response: bool = True, timeout: int = 30):

163

"""

164

Write to characteristic using gatttool CLI.

165

166

Args:

167

handle: Characteristic handle

168

value: Data to write

169

wait_for_response: Wait for write confirmation

170

timeout: Write timeout in seconds

171

172

Raises:

173

BLEError: Write failed or timed out

174

"""

175

```

176

177

**Usage Example:**

178

179

```python

180

import pygatt

181

182

adapter = pygatt.GATTToolBackend()

183

adapter.start()

184

device = adapter.connect('01:23:45:67:89:ab')

185

186

# Send raw gatttool command

187

adapter.sendline('char-desc') # List characteristic descriptors

188

189

# Direct characteristic operations

190

value = adapter.char_read('00002a19-0000-1000-8000-00805f9b34fb', timeout=2)

191

adapter.char_write_handle(42, bytearray([0x01, 0x00]), timeout=5)

192

```

193

194

### Service Discovery

195

196

Comprehensive GATT service and characteristic discovery with BlueZ integration.

197

198

```python { .api }

199

def discover_characteristics(self, timeout: int = 5) -> dict:

200

"""

201

Discover characteristics using gatttool CLI.

202

203

Args:

204

timeout: Discovery timeout in seconds

205

206

Returns:

207

dict: Mapping of UUID to Characteristic objects

208

209

Note:

210

Uses gatttool's 'char-desc' command for comprehensive discovery

211

"""

212

213

def exchange_mtu(self, mtu: int, timeout: int = 1) -> int:

214

"""

215

Exchange MTU using gatttool CLI.

216

217

Args:

218

mtu: Requested MTU size

219

timeout: Exchange timeout in seconds

220

221

Returns:

222

int: Negotiated MTU size

223

"""

224

```

225

226

### Connection Management

227

228

Advanced connection handling with auto-reconnect and disconnect callback support.

229

230

```python { .api }

231

def connect(self, address: str, timeout: float = DEFAULT_CONNECT_TIMEOUT_S,

232

address_type=BLEAddressType.public, auto_reconnect: bool = False) -> GATTToolBLEDevice:

233

"""

234

Connect to device with GATTTool-specific options.

235

236

Args:

237

address: Device MAC address

238

timeout: Connection timeout in seconds

239

address_type: BLEAddressType.public or BLEAddressType.random

240

auto_reconnect: Automatically reconnect on connection loss

241

242

Returns:

243

GATTToolBLEDevice: Connected device with GATTTool features

244

245

Raises:

246

NotConnectedError: Connection failed

247

"""

248

249

def kill(self):

250

"""

251

Terminate any running scan processes cleanly.

252

253

Useful for stopping scans that may interfere with connections.

254

"""

255

```

256

257

**Usage Example:**

258

259

```python

260

# Connect with auto-reconnect for robust applications

261

device = adapter.connect('01:23:45:67:89:ab',

262

auto_reconnect=True,

263

timeout=10)

264

265

# Stop any background scans

266

adapter.kill()

267

```

268

269

## GATTTool Device Features

270

271

The GATTToolBLEDevice class extends BLEDevice with GATTTool-specific enhancements:

272

273

### Disconnect Callbacks

274

275

```python { .api }

276

def register_disconnect_callback(self, callback):

277

"""

278

Register callback for disconnect events.

279

280

Args:

281

callback: Function called on disconnect: callback(device)

282

283

Useful for implementing reconnection logic or cleanup operations.

284

"""

285

286

def remove_disconnect_callback(self, callback):

287

"""

288

Remove previously registered disconnect callback.

289

290

Args:

291

callback: Callback function to remove

292

"""

293

```

294

295

**Usage Example:**

296

297

```python

298

def on_disconnect(device):

299

print(f"Device {device._address} disconnected!")

300

# Implement reconnection logic here

301

302

device = adapter.connect('01:23:45:67:89:ab')

303

device.register_disconnect_callback(on_disconnect)

304

```

305

306

## System Requirements

307

308

### Linux Dependencies

309

310

- **BlueZ 5.18+**: Bluetooth stack (tested on 5.18, 5.21, 5.35, 5.43)

311

- **gatttool**: Command-line GATT utility (part of BlueZ)

312

- **pexpect**: Python process interaction library

313

- **sudo access**: Required for some operations (scanning, reset)

314

315

### Installation

316

317

```bash

318

# Install with GATTTool support

319

pip install "pygatt[GATTTOOL]"

320

321

# Verify gatttool is available

322

which gatttool

323

324

# Check BlueZ version

325

bluetoothctl --version

326

```

327

328

### Permissions Configuration

329

330

#### udev Rules (Recommended)

331

332

Create `/etc/udev/rules.d/99-ble.rules`:

333

334

```

335

# Allow users in 'bluetooth' group to access BLE without sudo

336

KERNEL=="hci[0-9]*", GROUP="bluetooth", MODE="0664"

337

SUBSYSTEM=="bluetooth", GROUP="bluetooth", MODE="0664"

338

```

339

340

Add user to bluetooth group:

341

342

```bash

343

sudo usermod -a -G bluetooth $USER

344

```

345

346

#### Capabilities (Alternative)

347

348

Grant specific capabilities to Python interpreter:

349

350

```bash

351

sudo setcap 'cap_net_raw,cap_net_admin+eip' $(which python3)

352

```

353

354

### Troubleshooting

355

356

#### Scan Permissions

357

358

```python

359

# If regular scan fails, try with sudo

360

try:

361

devices = adapter.scan(timeout=10)

362

except pygatt.BLEError:

363

devices = adapter.scan(timeout=10, run_as_root=True)

364

```

365

366

#### Supervision Timeout Issues

367

368

```bash

369

# Increase supervision timeout for unstable connections

370

echo 1000 > /sys/kernel/debug/bluetooth/hci0/supervision_timeout

371

```

372

373

#### CLI Buffer Size

374

375

```python

376

# For devices with many characteristics

377

adapter = pygatt.GATTToolBackend(search_window_size=2048)

378

```

379

380

## Advanced Configuration

381

382

### Custom CLI Options

383

384

```python

385

# Add custom gatttool parameters

386

adapter = pygatt.GATTToolBackend(

387

cli_options=['--sec-level=medium', '--connect-timeout=30']

388

)

389

```

390

391

### Multiple Adapters

392

393

```python

394

# Use specific Bluetooth adapter

395

adapter1 = pygatt.GATTToolBackend(hci_device="hci0")

396

adapter2 = pygatt.GATTToolBackend(hci_device="hci1")

397

```

398

399

### Debug Logging

400

401

```python

402

# Enable comprehensive logging

403

import logging

404

logging.basicConfig(level=logging.DEBUG)

405

logging.getLogger('pygatt').setLevel(logging.DEBUG)

406

407

adapter = pygatt.GATTToolBackend(gatttool_logfile="/tmp/debug.log")

408

```

409

410

## Error Handling

411

412

Common GATTTool backend errors and solutions:

413

414

### Permission Errors

415

416

```python

417

try:

418

devices = adapter.scan()

419

except pygatt.BLEError as e:

420

if "permission" in str(e).lower():

421

print("Try: sudo python script.py")

422

print("Or configure udev rules for bluetooth group")

423

```

424

425

### CLI Timeout Issues

426

427

```python

428

# Increase timeouts for slow systems

429

adapter = pygatt.GATTToolBackend()

430

adapter.start(initialization_timeout=10)

431

432

# Longer read timeouts

433

value = adapter.char_read(uuid, timeout=5)

434

```

435

436

### Connection Stability

437

438

```python

439

# Enable auto-reconnect for unreliable connections

440

device = adapter.connect(address, auto_reconnect=True)

441

442

# Register disconnect handler

443

def reconnect_handler(device):

444

time.sleep(1)

445

new_device = adapter.connect(device._address, auto_reconnect=True)

446

447

device.register_disconnect_callback(reconnect_handler)

448

```