trouble-host 0.7.0

An async Rust BLE host
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# Callback-Based GATT Characteristics Guide

## Overview

TrouBLE now supports callback-based GATT characteristics where characteristic values are not stored in the attribute table. Instead, values are computed or retrieved on-demand through user-provided async handlers.

## Why Callback-Based Characteristics?

**Problems with storage-based characteristics:**
- Must pre-allocate storage buffers for all characteristics
- Values cannot be computed dynamically
- Cannot perform async I/O during reads (sensor reads, flash reads)
- Wastes memory storing values that could be computed

**Benefits of callback-based characteristics:**
- **No storage overhead** - values generated on-demand
- **Async operations** - read from sensors, flash, databases
- **Dynamic values** - compute values based on current state
- **Flexible** - full control over read/write logic

## Architecture

### User-Event-Based Handlers

Instead of storing handler trait objects (which aren't object-safe in no_std), **your `gatt_events_task` function IS the handler**:

```
Client Read Request
AttributeServer generates GattEvent::Read
Your gatt_events_task receives event
Check event.handle() to identify characteristic
Execute your async handler code
Call event.respond(data) with response
Response sent to client
```

## Usage

### 1. Define Callback-Based Characteristic

```rust
use trouble_host::prelude::*;

// Create attribute table
let mut table = AttributeTable::new();

// Add service
let mut service = table.add_service(Service::new(SERVICE_UUID));

// Add callback-based characteristic (no storage buffer needed!)
let battery_level = service
    .add_characteristic_with_handler::<u8, _>(
        characteristic::BATTERY_LEVEL,
        &[CharacteristicProp::Read, CharacteristicProp::Notify],
    )
    .build();

let service_handle = service.build();
```

**Key difference:** No `&'d mut [u8]` storage buffer needed!

### 2. Handle Events in Your Event Loop

```rust
async fn gatt_events_task<P: PacketPool>(
    server: &Server,
    conn: &GattConnection<'_, '_, P>,
) -> Result<(), Error> {
    let battery_level = server.battery_service.level;

    loop {
        match conn.next().await {
            GattConnectionEvent::Disconnected { reason } => break,
            GattConnectionEvent::Gatt { event } => {
                match event {
                    // Handle callback-based characteristic reads
                    GattEvent::Read(read) if read.handle() == battery_level.handle => {
                        // Perform async operation
                        let level = read_battery_from_adc().await;

                        // Respond with data
                        read.respond(&[level])?.send().await;
                    }

                    // Handle callback-based characteristic writes
                    GattEvent::Write(write) if write.handle() == some_control.handle => {
                        let data = write.data();

                        // Perform async operation
                        write_to_flash(data).await?;

                        // Accept the write
                        write.accept()?.send().await;
                    }

                    // For storage-based characteristics, use accept()
                    _ => {
                        event.accept()?.send().await;
                    }
                }
            }
            _ => {}
        }
    }
    Ok(())
}
```

### 3. Send Notifications

Notifications work the same way as before:

```rust
async fn notify_task<P: PacketPool>(
    characteristic: &Characteristic<u8>,
    conn: &GattConnection<'_, '_, P>,
) {
    loop {
        let value = read_battery_from_adc().await;

        // Notify does NOT store the value, just sends it
        characteristic.notify(conn, &value).await.ok();

        Timer::after_secs(60).await;
    }
}
```

## Complete Example

```rust
use trouble_host::prelude::*;

// Define service structure
#[gatt_server]
struct MyServer {
    battery_service: BatteryService,
}

#[gatt_service(uuid = service::BATTERY)]
struct BatteryService {
    // Callback-based characteristic - no storage!
    #[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify)]
    level: u8,
}

async fn run<C: Controller>(controller: C) {
    let mut resources = HostResources::new();
    let stack = trouble_host::new(controller, &mut resources);
    let Host { mut peripheral, runner, .. } = stack.build();

    // Create server (no storage buffers needed)
    let server = MyServer::new_with_config(GapConfig::Peripheral(
        PeripheralConfig {
            name: "MyDevice",
            appearance: &appearance::GENERIC_COMPUTER,
        }
    )).unwrap();

    join(
        runner.run(),
        async {
            loop {
                // Advertise and accept connection
                let conn = advertise(&mut peripheral, &server).await?;

                // Handle GATT events
                gatt_events_task(&server, &conn).await.ok();
            }
        }
    ).await;
}

async fn gatt_events_task<P: PacketPool>(
    server: &MyServer,
    conn: &GattConnection<'_, '_, P>,
) -> Result<(), Error> {
    let battery_level = server.battery_service.level;

    loop {
        match conn.next().await {
            GattConnectionEvent::Disconnected { reason } => break,
            GattConnectionEvent::Gatt { event } => {
                match event {
                    GattEvent::Read(read) if read.handle() == battery_level.handle => {
                        // Read from ADC (async operation)
                        let level = adc_read_battery().await;
                        info!("Battery read: {}", level);

                        // Respond with current value
                        read.respond(&[level])?.send().await;
                    }
                    _ => {
                        // Default: accept all other events
                        event.accept()?.send().await;
                    }
                }
            }
            _ => {}
        }
    }
    Ok(())
}

// Separate task for periodic notifications
async fn notification_task<P: PacketPool>(
    server: &MyServer,
    conn: &GattConnection<'_, '_, P>,
) {
    let battery_level = server.battery_service.level;

    loop {
        Timer::after_secs(60).await;

        let level = adc_read_battery().await;
        battery_level.notify(conn, &level).await.ok();
    }
}
```

## API Reference

### ReadEvent Methods

- **`handle() -> u16`** - Get the handle being read
- **`accept() -> Result<Reply>`** - Process using AttributeServer (reads from storage)
- **`reject(err: AttErrorCode) -> Result<Reply>`** - Reject with error code
- **`respond(data: &[u8]) -> Result<Reply>`** - Provide custom response data
- **`into_payload() -> GattData`** - Get raw payload for custom processing

### WriteEvent Methods

- **`handle() -> u16`** - Get the handle being written
- **`data() -> &[u8]`** - Get the raw data being written
- **`value<T: FromGatt>(&Characteristic<T>) -> Result<T>`** - Parse typed value
- **`accept() -> Result<Reply>`** - Process using AttributeServer (writes to storage)
- **`reject(err: AttErrorCode) -> Result<Reply>`** - Reject with error code

### ServiceBuilder Methods

- **`add_characteristic_with_handler<T, U>(uuid, props) -> CharacteristicBuilder`**
  - Add a callback-based characteristic
  - No storage buffer parameter
  - Handler logic provided in `gatt_events_task`

### Characteristic Methods (unchanged)

- **`notify(conn, value) -> Result<()>`** - Send notification (doesn't store value)
- **`indicate(conn, value) -> Result<()>`** - Send indication (doesn't store value)

## Migration from Storage-Based

### Before (Storage-Based):

```rust
// Must allocate storage buffer
let mut battery_storage = [0u8; 1];

let battery_level = service
    .add_characteristic(
        characteristic::BATTERY_LEVEL,
        &[CharacteristicProp::Read, CharacteristicProp::Notify],
        10u8,  // Initial value
        &mut battery_storage,  // Storage buffer
    )
    .build();

// In event handler - no custom logic, just gets notified
GattEvent::Read(read) => {
    // Can't perform async operations here
    read.accept()?.send().await;
}

// To update value, must write to storage first
server.set(&battery_level, &new_value)?;
battery_level.notify(conn, &new_value).await?;
```

### After (Callback-Based):

```rust
// No storage buffer needed!
let battery_level = service
    .add_characteristic_with_handler::<u8, _>(
        characteristic::BATTERY_LEVEL,
        &[CharacteristicProp::Read, CharacteristicProp::Notify],
    )
    .build();

// In event handler - full async control
GattEvent::Read(read) if read.handle() == battery_level.handle => {
    // Can perform async operations!
    let value = read_from_adc().await;
    read.respond(&[value])?.send().await;
}

// To notify, just send value directly
let value = read_from_adc().await;
battery_level.notify(conn, &value).await?;
```

## Performance Considerations

### Memory

- **Storage-based**: Memory used = all characteristics * max characteristic size
- **Callback-based**: Memory used = 0 (just metadata)

Example: 10 characteristics of 20 bytes each
- Storage-based: 200 bytes
- Callback-based: 0 bytes

### CPU

- **Storage-based**: Fast (just memory copy)
- **Callback-based**: Depends on handler (async I/O may be slower but more flexible)

### When to Use Each

**Use storage-based when:**
- Value is small and static
- Value changes infrequently
- No I/O needed to get value
- Want simplest code

**Use callback-based when:**
- Value requires I/O to compute (sensor, flash, etc.)
- Value is dynamic or computed
- Want to save memory
- Need async operations during read/write

## Advanced Patterns

### Combining Storage and Callback Characteristics

```rust
let mut service = table.add_service(Service::new(SERVICE_UUID));

// Storage-based: simple, static value
let mut device_name_storage = [0u8; 20];
let device_name = service
    .add_characteristic(
        characteristic::DEVICE_NAME,
        &[CharacteristicProp::Read],
        "MyDevice",
        &mut device_name_storage,
    )
    .build();

// Callback-based: dynamic, requires I/O
let temperature = service
    .add_characteristic_with_handler::<i16, _>(
        characteristic::TEMPERATURE,
        &[CharacteristicProp::Read, CharacteristicProp::Notify],
    )
    .build();
```

### Handling Long Reads (ReadBlob)

The `respond()` method automatically handles both `ATT_READ_REQ` and `ATT_READ_BLOB_REQ`:

```rust
GattEvent::Read(read) if read.handle() == long_value.handle => {
    // For long values, client may read in chunks
    // respond() handles this automatically
    let full_value = get_long_value().await;
    read.respond(&full_value)?.send().await;
}
```

### Error Handling

```rust
GattEvent::Read(read) if read.handle() == my_char.handle => {
    match try_read_value().await {
        Ok(value) => {
            read.respond(&value)?.send().await;
        }
        Err(_) => {
            // Return ATT error code
            read.reject(AttErrorCode::UNLIKELY_ERROR)?.send().await;
        }
    }
}
```

## Troubleshooting

### "Read returns READ_NOT_PERMITTED"

This means `accept()` was called on a callback-based characteristic. Use `respond()` instead.

### "How do I update the stored value?"

Callback-based characteristics don't have stored values. The value is generated on each read. To "update" the value, change what your handler returns.

### "Can I mix callback and storage characteristics?"

Yes! Use `add_characteristic()` for storage-based and `add_characteristic_with_handler()` for callback-based in the same service.

### "Do I need to implement traits?"

No! The handler is just your code in the `gatt_events_task` match statement. Check the handle and respond accordingly.