blob: 17a10f4985d8c606090291d173894f5de532f0b4 [file] [log] [blame] [view]
# What is this?
Rust wrappers around the [Bumble](https://github.com/google/bumble) Python API.
Method calls are mapped to the equivalent Python, and return types adapted where
relevant.
See the CLI in `src/main.rs` or the `examples` directory for how to use the
Bumble API.
# Usage
Set up a virtualenv for Bumble, or otherwise have an isolated Python environment
for Bumble and its dependencies.
Due to Python being
[picky about how its sys path is set up](https://github.com/PyO3/pyo3/issues/1741,
it's necessary to explicitly point to the virtualenv's `site-packages`. Use
suitable virtualenv paths as appropriate for your OS, as seen here running
the `battery_client` example:
```
PYTHONPATH=..:~/.virtualenvs/bumble/lib/python3.10/site-packages/ \
cargo run --example battery_client -- \
--transport android-netsim --target-addr F0:F1:F2:F3:F4:F5
```
Run the corresponding `battery_server` Python example, and launch an emulator in
Android Studio (currently, Canary is required) to run netsim.
# CLI
Explore the available subcommands:
```
PYTHONPATH=..:[virtualenv site-packages] \
cargo run --features bumble-tools --bin bumble -- --help
```
Notable subcommands:
- `firmware realtek download`: download Realtek firmware for various chipsets so that it can be automatically loaded when needed
- `usb probe`: show USB devices, highlighting the ones usable for Bluetooth
# Development
Run the tests:
```
PYTHONPATH=.. cargo test
```
Check lints:
```
cargo clippy --all-targets
```
## Code gen
To have the fastest startup while keeping the build simple, code gen for
assigned numbers is done with the `gen_assigned_numbers` tool. It should
be re-run whenever the Python assigned numbers are changed. To ensure that the
generated code is kept up to date, the Rust data is compared to the Python
in tests at `pytests/assigned_numbers.rs`.
To regenerate the assigned number tables based on the Python codebase:
```
PYTHONPATH=.. cargo run --bin gen-assigned-numbers --features dev-tools
```
## HCI packets
Sending a command packet from a device is composed to of two major steps.
There are more generalized ways of dealing with packets in other scenarios.
### Construct the command
Pick a command from `src/internal/hci/packets.pdl` and construct its associated "builder" struct.
```rust
// The "LE Set Scan Enable" command can be found in the Core Bluetooth Spec.
// It can also be found in `packets.pdl` as `packet LeSetScanEnable : Command`
fn main() {
let device = init_device_as_desired();
let le_set_scan_enable_command_builder = LeSetScanEnableBuilder {
filter_duplicates: Enable::Disabled,
le_scan_enable: Enable::Enabled,
};
}
```
### Send the command and interpret the event response
Send the command from an initialized device, and then receive the response.
```rust
fn main() {
// ...
// `check_result` to false to receive the event response even if the controller returns a failure code
let event = device.send_command(le_set_scan_enable_command_builder.into(), /*check_result*/ false);
// Coerce the event into the expected format. A `Command` should have an associated event response
// "<command name>Complete".
let le_set_scan_enable_complete_event: LeSetScanEnableComplete = event.try_into().unwrap();
}
```
### Generic packet handling
At the very least, you should expect to at least know _which_ kind of base packet you are dealing with. Base packets in
`packets.pdl` can be identified because they do not extend any other packet. They are easily found with the regex:
`^packet [^:]* \{`. For Bluetooth LE (BLE) HCI, one should find some kind of header preceding the packet with the purpose of
packet disambiguation. We do some of that disambiguation for H4 BLE packets using the `WithPacketHeader` trait at `internal/hci/mod.rs`.
Say you've identified a series of bytes that are certainly an `Acl` packet. They can be parsed using the `Acl` struct.
```rust
fn main() {
let bytes = bytes_that_are_certainly_acl();
let acl_packet = Acl::parse(bytes).unwrap();
}
```
Since you don't yet know what kind of `Acl` packet it is, you need to specialize it and then handle the various
potential cases.
```rust
fn main() {
// ...
match acl_packet.specialize() {
Payload(bytes) => do_something(bytes),
None => do_something_else(),
}
}
```
Some packets may yet further embed other packets, in which case you may need to further specialize until no more
specialization is needed.