| # 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. |