forked from auracaster/bumble_mirror
Merge pull request #303 from whitevegagabriel/hci-command-rs
Ability to send HCI commands from Rust
This commit is contained in:
@@ -33,7 +33,7 @@ from typing import (
|
|||||||
Union,
|
Union,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
from functools import wraps
|
from functools import wraps, partial
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
|
|
||||||
from .colors import color
|
from .colors import color
|
||||||
@@ -410,3 +410,20 @@ class FlowControlAsyncPipe:
|
|||||||
self.resume_source()
|
self.resume_source()
|
||||||
|
|
||||||
self.check_pump()
|
self.check_pump()
|
||||||
|
|
||||||
|
|
||||||
|
async def async_call(function, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Immediately calls the function with provided args and kwargs, wrapping it in an async function.
|
||||||
|
Rust's `pyo3_asyncio` library needs functions to be marked async to properly inject a running loop.
|
||||||
|
|
||||||
|
result = await async_call(some_function, ...)
|
||||||
|
"""
|
||||||
|
return function(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_async(function):
|
||||||
|
"""
|
||||||
|
Wraps the provided function in an async function.
|
||||||
|
"""
|
||||||
|
return partial(async_call, function)
|
||||||
|
|||||||
229
rust/Cargo.lock
generated
229
rust/Cargo.lock
generated
@@ -80,6 +80,37 @@ version = "1.0.75"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argh"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219"
|
||||||
|
dependencies = [
|
||||||
|
"argh_derive",
|
||||||
|
"argh_shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argh_derive"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a"
|
||||||
|
dependencies = [
|
||||||
|
"argh_shared",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.29",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argh_shared"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -130,6 +161,15 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.6.2"
|
version = "1.6.2"
|
||||||
@@ -145,6 +185,7 @@ name = "bumble"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bytes",
|
||||||
"clap 4.4.1",
|
"clap 4.4.1",
|
||||||
"directories",
|
"directories",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -158,6 +199,8 @@ dependencies = [
|
|||||||
"nix",
|
"nix",
|
||||||
"nom",
|
"nom",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
|
"pdl-derive",
|
||||||
|
"pdl-runtime",
|
||||||
"pyo3",
|
"pyo3",
|
||||||
"pyo3-asyncio",
|
"pyo3-asyncio",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -178,9 +221,9 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.4.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
@@ -262,6 +305,16 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codespan-reporting"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||||
|
dependencies = [
|
||||||
|
"termcolor",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -284,6 +337,15 @@ version = "0.8.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam"
|
name = "crossbeam"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -351,6 +413,26 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "directories"
|
name = "directories"
|
||||||
version = "5.0.1"
|
version = "5.0.1"
|
||||||
@@ -559,6 +641,16 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@@ -1064,12 +1156,100 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pdl-compiler"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee66995739fb9ddd9155767990a54aadd226ee32408a94f99f94883ff445ceba"
|
||||||
|
dependencies = [
|
||||||
|
"argh",
|
||||||
|
"codespan-reporting",
|
||||||
|
"heck",
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"syn 2.0.29",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pdl-derive"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "113e4a1215c407466b36d2c2f6a6318819d6b22ccdd3acb7bb35e27a68806034"
|
||||||
|
dependencies = [
|
||||||
|
"codespan-reporting",
|
||||||
|
"pdl-compiler",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.29",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pdl-runtime"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d36c2f9799613babe78eb5cd9a353d527daaba6c3d1f39a1175657a35790732"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"thiserror",
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_derive"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_generator"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_meta",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.29",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_meta"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"pest",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
@@ -1094,6 +1274,16 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn 2.0.29",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.66"
|
version = "1.0.66"
|
||||||
@@ -1465,6 +1655,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@@ -1711,6 +1912,18 @@ version = "0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
@@ -1738,6 +1951,12 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unindent"
|
name = "unindent"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
@@ -1767,6 +1986,12 @@ version = "0.2.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ hex = "0.4.3"
|
|||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
thiserror = "1.0.41"
|
thiserror = "1.0.41"
|
||||||
|
bytes = "1.5.0"
|
||||||
|
pdl-derive = "0.2.0"
|
||||||
|
pdl-runtime = "0.2.0"
|
||||||
|
|
||||||
# Dev tools
|
# Dev tools
|
||||||
file-header = { version = "0.1.2", optional = true }
|
file-header = { version = "0.1.2", optional = true }
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
use bumble::{
|
use bumble::{
|
||||||
adv::CommonDataType,
|
adv::CommonDataType,
|
||||||
wrapper::{
|
wrapper::{
|
||||||
core::AdvertisementDataUnit, device::Device, hci::AddressType, transport::Transport,
|
core::AdvertisementDataUnit, device::Device, hci::packets::AddressType,
|
||||||
|
transport::Transport,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use clap::Parser as _;
|
use clap::Parser as _;
|
||||||
@@ -102,7 +103,9 @@ async fn main() -> PyResult<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (type_style, qualifier) = match adv.address()?.address_type()? {
|
let (type_style, qualifier) = match adv.address()?.address_type()? {
|
||||||
AddressType::PublicIdentity | AddressType::PublicDevice => (Style::new().cyan(), ""),
|
AddressType::PublicIdentityAddress | AddressType::PublicDeviceAddress => {
|
||||||
|
(Style::new().cyan(), "")
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if addr.is_static()? {
|
if addr.is_static()? {
|
||||||
(Style::new().green(), "(static)")
|
(Style::new().green(), "(static)")
|
||||||
|
|||||||
@@ -12,9 +12,26 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use bumble::wrapper::{drivers::rtk::DriverInfo, transport::Transport};
|
use bumble::wrapper::{
|
||||||
|
controller::Controller,
|
||||||
|
device::Device,
|
||||||
|
drivers::rtk::DriverInfo,
|
||||||
|
hci::{
|
||||||
|
packets::{
|
||||||
|
AddressType, ErrorCode, ReadLocalVersionInformationBuilder,
|
||||||
|
ReadLocalVersionInformationComplete,
|
||||||
|
},
|
||||||
|
Address, Error,
|
||||||
|
},
|
||||||
|
host::Host,
|
||||||
|
link::Link,
|
||||||
|
transport::Transport,
|
||||||
|
};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use pyo3::PyResult;
|
use pyo3::{
|
||||||
|
exceptions::PyException,
|
||||||
|
{PyErr, PyResult},
|
||||||
|
};
|
||||||
|
|
||||||
#[pyo3_asyncio::tokio::test]
|
#[pyo3_asyncio::tokio::test]
|
||||||
async fn fifo_transport_can_open() -> PyResult<()> {
|
async fn fifo_transport_can_open() -> PyResult<()> {
|
||||||
@@ -35,3 +52,26 @@ async fn realtek_driver_info_all_drivers() -> PyResult<()> {
|
|||||||
assert_eq!(12, DriverInfo::all_drivers()?.len());
|
assert_eq!(12, DriverInfo::all_drivers()?.len());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyo3_asyncio::tokio::test]
|
||||||
|
async fn hci_command_wrapper_has_correct_methods() -> PyResult<()> {
|
||||||
|
let address = Address::new("F0:F1:F2:F3:F4:F5", &AddressType::RandomDeviceAddress)?;
|
||||||
|
let link = Link::new_local_link()?;
|
||||||
|
let controller = Controller::new("C1", None, None, Some(link), Some(address.clone())).await?;
|
||||||
|
let host = Host::new(controller.clone().into(), controller.into()).await?;
|
||||||
|
let device = Device::new(None, Some(address), None, Some(host), None)?;
|
||||||
|
|
||||||
|
device.power_on().await?;
|
||||||
|
|
||||||
|
// Send some simple command. A successful response means [HciCommandWrapper] has the minimum
|
||||||
|
// required interface for the Python code to think its an [HCI_Command] object.
|
||||||
|
let command = ReadLocalVersionInformationBuilder {};
|
||||||
|
let event: ReadLocalVersionInformationComplete = device
|
||||||
|
.send_command(&command.into(), true)
|
||||||
|
.await?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e: Error| PyErr::new::<PyException, _>(e.to_string()))?;
|
||||||
|
|
||||||
|
assert_eq!(ErrorCode::Success, event.get_status());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ pub(crate) fn parse(firmware_path: &path::Path) -> PyResult<()> {
|
|||||||
pub(crate) async fn info(transport: &str, force: bool) -> PyResult<()> {
|
pub(crate) async fn info(transport: &str, force: bool) -> PyResult<()> {
|
||||||
let transport = Transport::open(transport).await?;
|
let transport = Transport::open(transport).await?;
|
||||||
|
|
||||||
let mut host = Host::new(transport.source()?, transport.sink()?)?;
|
let mut host = Host::new(transport.source()?, transport.sink()?).await?;
|
||||||
host.reset(DriverFactory::None).await?;
|
host.reset(DriverFactory::None).await?;
|
||||||
|
|
||||||
if !force && !Driver::check(&host).await? {
|
if !force && !Driver::check(&host).await? {
|
||||||
@@ -203,7 +203,7 @@ pub(crate) async fn info(transport: &str, force: bool) -> PyResult<()> {
|
|||||||
pub(crate) async fn load(transport: &str, force: bool) -> PyResult<()> {
|
pub(crate) async fn load(transport: &str, force: bool) -> PyResult<()> {
|
||||||
let transport = Transport::open(transport).await?;
|
let transport = Transport::open(transport).await?;
|
||||||
|
|
||||||
let mut host = Host::new(transport.source()?, transport.sink()?)?;
|
let mut host = Host::new(transport.source()?, transport.sink()?).await?;
|
||||||
host.reset(DriverFactory::None).await?;
|
host.reset(DriverFactory::None).await?;
|
||||||
|
|
||||||
match Driver::for_host(&host, force).await? {
|
match Driver::for_host(&host, force).await? {
|
||||||
@@ -219,7 +219,7 @@ pub(crate) async fn load(transport: &str, force: bool) -> PyResult<()> {
|
|||||||
pub(crate) async fn drop(transport: &str) -> PyResult<()> {
|
pub(crate) async fn drop(transport: &str) -> PyResult<()> {
|
||||||
let transport = Transport::open(transport).await?;
|
let transport = Transport::open(transport).await?;
|
||||||
|
|
||||||
let mut host = Host::new(transport.source()?, transport.sink()?)?;
|
let mut host = Host::new(transport.source()?, transport.sink()?).await?;
|
||||||
host.reset(DriverFactory::None).await?;
|
host.reset(DriverFactory::None).await?;
|
||||||
|
|
||||||
Driver::drop_firmware(&mut host).await?;
|
Driver::drop_firmware(&mut host).await?;
|
||||||
|
|||||||
161
rust/src/internal/hci/mod.rs
Normal file
161
rust/src/internal/hci/mod.rs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
pub use pdl_runtime::{Error, Packet};
|
||||||
|
|
||||||
|
use crate::internal::hci::packets::{Acl, Command, Event, Sco};
|
||||||
|
use pdl_derive::pdl;
|
||||||
|
|
||||||
|
#[allow(missing_docs, warnings, clippy::all)]
|
||||||
|
#[pdl("src/internal/hci/packets.pdl")]
|
||||||
|
pub mod packets {}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
/// HCI Packet type, prepended to the packet.
|
||||||
|
/// Rootcanal's PDL declaration excludes this from ser/deser and instead is implemented in code.
|
||||||
|
/// To maintain the ability to easily use future versions of their packet PDL, packet type is
|
||||||
|
/// implemented here.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) enum PacketType {
|
||||||
|
Command = 0x01,
|
||||||
|
Acl = 0x02,
|
||||||
|
Sco = 0x03,
|
||||||
|
Event = 0x04,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for PacketType {
|
||||||
|
type Error = PacketTypeParseError;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0x01 => Ok(PacketType::Command),
|
||||||
|
0x02 => Ok(PacketType::Acl),
|
||||||
|
0x03 => Ok(PacketType::Sco),
|
||||||
|
0x04 => Ok(PacketType::Event),
|
||||||
|
_ => Err(PacketTypeParseError::InvalidPacketType { value }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PacketType> for u8 {
|
||||||
|
fn from(packet_type: PacketType) -> Self {
|
||||||
|
match packet_type {
|
||||||
|
PacketType::Command => 0x01,
|
||||||
|
PacketType::Acl => 0x02,
|
||||||
|
PacketType::Sco => 0x03,
|
||||||
|
PacketType::Event => 0x04,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows for smoother interoperability between a [Packet] and a bytes representation of it that
|
||||||
|
/// includes its type as a header
|
||||||
|
pub(crate) trait WithPacketType<T: Packet> {
|
||||||
|
/// Converts the [Packet] into bytes, prefixed with its type
|
||||||
|
fn to_vec_with_packet_type(self) -> Vec<u8>;
|
||||||
|
|
||||||
|
/// Parses a [Packet] out of bytes that are prefixed with the packet's type
|
||||||
|
fn parse_with_packet_type(bytes: &[u8]) -> Result<T, PacketTypeParseError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors that may arise when parsing a packet that is prefixed with its type
|
||||||
|
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||||
|
pub(crate) enum PacketTypeParseError {
|
||||||
|
#[error("The slice being parsed was empty")]
|
||||||
|
EmptySlice,
|
||||||
|
#[error("Packet type ({value:#X}) is invalid")]
|
||||||
|
InvalidPacketType { value: u8 },
|
||||||
|
#[error("Expected packet type: {expected:?}, but got: {actual:?}")]
|
||||||
|
PacketTypeMismatch {
|
||||||
|
expected: PacketType,
|
||||||
|
actual: PacketType,
|
||||||
|
},
|
||||||
|
#[error("Failed to parse packet after header: {error}")]
|
||||||
|
PacketParse { error: Error },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for PacketTypeParseError {
|
||||||
|
fn from(error: Error) -> Self {
|
||||||
|
Self::PacketParse { error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WithPacketType<Self> for Command {
|
||||||
|
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||||
|
prepend_packet_type(PacketType::Command, self.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||||
|
parse_with_expected_packet_type(Command::parse, PacketType::Command, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WithPacketType<Self> for Acl {
|
||||||
|
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||||
|
prepend_packet_type(PacketType::Acl, self.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||||
|
parse_with_expected_packet_type(Acl::parse, PacketType::Acl, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WithPacketType<Self> for Sco {
|
||||||
|
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||||
|
prepend_packet_type(PacketType::Sco, self.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||||
|
parse_with_expected_packet_type(Sco::parse, PacketType::Sco, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WithPacketType<Self> for Event {
|
||||||
|
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||||
|
prepend_packet_type(PacketType::Event, self.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||||
|
parse_with_expected_packet_type(Event::parse, PacketType::Event, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepend_packet_type(packet_type: PacketType, mut packet_bytes: Vec<u8>) -> Vec<u8> {
|
||||||
|
packet_bytes.insert(0, packet_type.into());
|
||||||
|
packet_bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_with_expected_packet_type<T: Packet, F, E>(
|
||||||
|
parser: F,
|
||||||
|
expected_packet_type: PacketType,
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> Result<T, PacketTypeParseError>
|
||||||
|
where
|
||||||
|
F: Fn(&[u8]) -> Result<T, E>,
|
||||||
|
PacketTypeParseError: From<E>,
|
||||||
|
{
|
||||||
|
let (first_byte, packet_bytes) = bytes
|
||||||
|
.split_first()
|
||||||
|
.ok_or(PacketTypeParseError::EmptySlice)?;
|
||||||
|
let actual_packet_type = PacketType::try_from(*first_byte)?;
|
||||||
|
if actual_packet_type == expected_packet_type {
|
||||||
|
Ok(parser(packet_bytes)?)
|
||||||
|
} else {
|
||||||
|
Err(PacketTypeParseError::PacketTypeMismatch {
|
||||||
|
expected: expected_packet_type,
|
||||||
|
actual: actual_packet_type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
6253
rust/src/internal/hci/packets.pdl
Normal file
6253
rust/src/internal/hci/packets.pdl
Normal file
File diff suppressed because it is too large
Load Diff
94
rust/src/internal/hci/tests.rs
Normal file
94
rust/src/internal/hci/tests.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use crate::internal::hci::{
|
||||||
|
packets::{Event, EventBuilder, EventCode, Sco},
|
||||||
|
parse_with_expected_packet_type, prepend_packet_type, Error, Packet, PacketType,
|
||||||
|
PacketTypeParseError, WithPacketType,
|
||||||
|
};
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prepends_packet_type() {
|
||||||
|
let packet_type = PacketType::Event;
|
||||||
|
let packet_bytes = vec![0x00, 0x00, 0x00, 0x00];
|
||||||
|
let actual = prepend_packet_type(packet_type, packet_bytes);
|
||||||
|
assert_eq!(vec![0x04, 0x00, 0x00, 0x00, 0x00], actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_empty_slice_should_error() {
|
||||||
|
let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Event, &[]);
|
||||||
|
assert_eq!(Err(PacketTypeParseError::EmptySlice), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_packet_type_should_error() {
|
||||||
|
let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Event, &[0xFF]);
|
||||||
|
assert_eq!(
|
||||||
|
Err(PacketTypeParseError::InvalidPacketType { value: 0xFF }),
|
||||||
|
actual
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_mismatched_packet_type_should_error() {
|
||||||
|
let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Acl, &[0x01]);
|
||||||
|
assert_eq!(
|
||||||
|
Err(PacketTypeParseError::PacketTypeMismatch {
|
||||||
|
expected: PacketType::Acl,
|
||||||
|
actual: PacketType::Command
|
||||||
|
}),
|
||||||
|
actual
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_packet_should_error() {
|
||||||
|
let actual = parse_with_expected_packet_type(Sco::parse, PacketType::Sco, &[0x03]);
|
||||||
|
assert!(actual.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_packet_roundtrip_with_type() {
|
||||||
|
let event_packet = EventBuilder {
|
||||||
|
event_code: EventCode::InquiryComplete,
|
||||||
|
payload: None,
|
||||||
|
}
|
||||||
|
.build();
|
||||||
|
let event_packet_bytes = event_packet.clone().to_vec_with_packet_type();
|
||||||
|
let actual =
|
||||||
|
parse_with_expected_packet_type(Event::parse, PacketType::Event, &event_packet_bytes)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(event_packet, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct FakePacket;
|
||||||
|
|
||||||
|
impl FakePacket {
|
||||||
|
fn parse(_bytes: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Packet for FakePacket {
|
||||||
|
fn to_bytes(self) -> Bytes {
|
||||||
|
Bytes::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_vec(self) -> Vec<u8> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,3 +18,4 @@
|
|||||||
//! to discover.
|
//! to discover.
|
||||||
|
|
||||||
pub(crate) mod drivers;
|
pub(crate) mod drivers;
|
||||||
|
pub(crate) mod hci;
|
||||||
|
|||||||
34
rust/src/wrapper/common.rs
Normal file
34
rust/src/wrapper/common.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Shared resources found under bumble's common.py
|
||||||
|
use pyo3::{PyObject, Python, ToPyObject};
|
||||||
|
|
||||||
|
/// Represents the sink for some transport mechanism
|
||||||
|
pub struct TransportSink(pub(crate) PyObject);
|
||||||
|
|
||||||
|
impl ToPyObject for TransportSink {
|
||||||
|
fn to_object(&self, _py: Python<'_>) -> PyObject {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the source for some transport mechanism
|
||||||
|
pub struct TransportSource(pub(crate) PyObject);
|
||||||
|
|
||||||
|
impl ToPyObject for TransportSource {
|
||||||
|
fn to_object(&self, _py: Python<'_>) -> PyObject {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
66
rust/src/wrapper/controller.rs
Normal file
66
rust/src/wrapper/controller.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Controller components
|
||||||
|
use crate::wrapper::{
|
||||||
|
common::{TransportSink, TransportSource},
|
||||||
|
hci::Address,
|
||||||
|
link::Link,
|
||||||
|
wrap_python_async, PyDictExt,
|
||||||
|
};
|
||||||
|
use pyo3::{
|
||||||
|
intern,
|
||||||
|
types::{PyDict, PyModule},
|
||||||
|
PyObject, PyResult, Python,
|
||||||
|
};
|
||||||
|
use pyo3_asyncio::tokio::into_future;
|
||||||
|
|
||||||
|
/// A controller that can send and receive HCI frames via some link
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Controller(pub(crate) PyObject);
|
||||||
|
|
||||||
|
impl Controller {
|
||||||
|
/// Creates a new [Controller] object. When optional arguments are not specified, the Python
|
||||||
|
/// module specifies the defaults. Must be called from a thread with a Python event loop, which
|
||||||
|
/// should be true on `tokio::main` and `async_std::main`.
|
||||||
|
///
|
||||||
|
/// For more info, see https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_asyncio/#event-loop-references-and-contextvars.
|
||||||
|
pub async fn new(
|
||||||
|
name: &str,
|
||||||
|
host_source: Option<TransportSource>,
|
||||||
|
host_sink: Option<TransportSink>,
|
||||||
|
link: Option<Link>,
|
||||||
|
public_address: Option<Address>,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let controller_ctr = PyModule::import(py, intern!(py, "bumble.controller"))?
|
||||||
|
.getattr(intern!(py, "Controller"))?;
|
||||||
|
|
||||||
|
let kwargs = PyDict::new(py);
|
||||||
|
kwargs.set_item("name", name)?;
|
||||||
|
kwargs.set_opt_item("host_source", host_source)?;
|
||||||
|
kwargs.set_opt_item("host_sink", host_sink)?;
|
||||||
|
kwargs.set_opt_item("link", link)?;
|
||||||
|
kwargs.set_opt_item("public_address", public_address)?;
|
||||||
|
|
||||||
|
// Controller constructor (`__init__`) is not (and can't be) marked async, but calls
|
||||||
|
// `get_running_loop`, and thus needs wrapped in an async function.
|
||||||
|
wrap_python_async(py, controller_ctr)?
|
||||||
|
.call((), Some(kwargs))
|
||||||
|
.and_then(into_future)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,12 +14,16 @@
|
|||||||
|
|
||||||
//! Devices and connections to them
|
//! Devices and connections to them
|
||||||
|
|
||||||
|
use crate::internal::hci::WithPacketType;
|
||||||
use crate::{
|
use crate::{
|
||||||
adv::AdvertisementDataBuilder,
|
adv::AdvertisementDataBuilder,
|
||||||
wrapper::{
|
wrapper::{
|
||||||
core::AdvertisingData,
|
core::AdvertisingData,
|
||||||
gatt_client::{ProfileServiceProxy, ServiceProxy},
|
gatt_client::{ProfileServiceProxy, ServiceProxy},
|
||||||
hci::{Address, HciErrorCode},
|
hci::{
|
||||||
|
packets::{Command, ErrorCode, Event},
|
||||||
|
Address, HciCommandWrapper,
|
||||||
|
},
|
||||||
host::Host,
|
host::Host,
|
||||||
l2cap::LeConnectionOrientedChannel,
|
l2cap::LeConnectionOrientedChannel,
|
||||||
transport::{Sink, Source},
|
transport::{Sink, Source},
|
||||||
@@ -27,18 +31,73 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
|
exceptions::PyException,
|
||||||
intern,
|
intern,
|
||||||
types::{PyDict, PyModule},
|
types::{PyDict, PyModule},
|
||||||
IntoPy, PyObject, PyResult, Python, ToPyObject,
|
IntoPy, PyErr, PyObject, PyResult, Python, ToPyObject,
|
||||||
};
|
};
|
||||||
use pyo3_asyncio::tokio::into_future;
|
use pyo3_asyncio::tokio::into_future;
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
|
/// Represents the various properties of some device
|
||||||
|
pub struct DeviceConfiguration(PyObject);
|
||||||
|
|
||||||
|
impl DeviceConfiguration {
|
||||||
|
/// Creates a new configuration, letting the internal Python object set all the defaults
|
||||||
|
pub fn new() -> PyResult<DeviceConfiguration> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
PyModule::import(py, intern!(py, "bumble.device"))?
|
||||||
|
.getattr(intern!(py, "DeviceConfiguration"))?
|
||||||
|
.call0()
|
||||||
|
.map(|any| Self(any.into()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new configuration from the specified file
|
||||||
|
pub fn load_from_file(&mut self, device_config: &path::Path) -> PyResult<()> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
self.0
|
||||||
|
.call_method1(py, intern!(py, "load_from_file"), (device_config,))
|
||||||
|
})
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToPyObject for DeviceConfiguration {
|
||||||
|
fn to_object(&self, _py: Python<'_>) -> PyObject {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A device that can send/receive HCI frames.
|
/// A device that can send/receive HCI frames.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Device(PyObject);
|
pub struct Device(PyObject);
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
|
/// Creates a Device. When optional arguments are not specified, the Python object specifies the
|
||||||
|
/// defaults.
|
||||||
|
pub fn new(
|
||||||
|
name: Option<&str>,
|
||||||
|
address: Option<Address>,
|
||||||
|
config: Option<DeviceConfiguration>,
|
||||||
|
host: Option<Host>,
|
||||||
|
generic_access_service: Option<bool>,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let kwargs = PyDict::new(py);
|
||||||
|
kwargs.set_opt_item("name", name)?;
|
||||||
|
kwargs.set_opt_item("address", address)?;
|
||||||
|
kwargs.set_opt_item("config", config)?;
|
||||||
|
kwargs.set_opt_item("host", host)?;
|
||||||
|
kwargs.set_opt_item("generic_access_service", generic_access_service)?;
|
||||||
|
|
||||||
|
PyModule::import(py, intern!(py, "bumble.device"))?
|
||||||
|
.getattr(intern!(py, "Device"))?
|
||||||
|
.call((), Some(kwargs))
|
||||||
|
.map(|any| Self(any.into()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a Device per the provided file configured to communicate with a controller through an HCI source/sink
|
/// Create a Device per the provided file configured to communicate with a controller through an HCI source/sink
|
||||||
pub fn from_config_file_with_hci(
|
pub fn from_config_file_with_hci(
|
||||||
device_config: &path::Path,
|
device_config: &path::Path,
|
||||||
@@ -66,6 +125,29 @@ impl Device {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends an HCI command on this Device, returning the command's event result.
|
||||||
|
pub async fn send_command(&self, command: &Command, check_result: bool) -> PyResult<Event> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
self.0
|
||||||
|
.call_method1(
|
||||||
|
py,
|
||||||
|
intern!(py, "send_command"),
|
||||||
|
(HciCommandWrapper(command.clone()), check_result),
|
||||||
|
)
|
||||||
|
.and_then(|coroutine| into_future(coroutine.as_ref(py)))
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.and_then(|event| {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let py_bytes = event.call_method0(py, intern!(py, "__bytes__"))?;
|
||||||
|
let bytes: &[u8] = py_bytes.extract(py)?;
|
||||||
|
let event = Event::parse_with_packet_type(bytes)
|
||||||
|
.map_err(|e| PyErr::new::<PyException, _>(e.to_string()))?;
|
||||||
|
Ok(event)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Turn the device on
|
/// Turn the device on
|
||||||
pub async fn power_on(&self) -> PyResult<()> {
|
pub async fn power_on(&self) -> PyResult<()> {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
@@ -236,7 +318,7 @@ impl Connection {
|
|||||||
kwargs.set_opt_item("mps", mps)?;
|
kwargs.set_opt_item("mps", mps)?;
|
||||||
self.0
|
self.0
|
||||||
.call_method(py, intern!(py, "open_l2cap_channel"), (), Some(kwargs))
|
.call_method(py, intern!(py, "open_l2cap_channel"), (), Some(kwargs))
|
||||||
.and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py)))
|
.and_then(|coroutine| into_future(coroutine.as_ref(py)))
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
.map(LeConnectionOrientedChannel::from)
|
.map(LeConnectionOrientedChannel::from)
|
||||||
@@ -244,13 +326,13 @@ impl Connection {
|
|||||||
|
|
||||||
/// Disconnect from device with provided reason. When optional arguments are not specified, the
|
/// Disconnect from device with provided reason. When optional arguments are not specified, the
|
||||||
/// Python module specifies the defaults.
|
/// Python module specifies the defaults.
|
||||||
pub async fn disconnect(&mut self, reason: Option<HciErrorCode>) -> PyResult<()> {
|
pub async fn disconnect(&mut self, reason: Option<ErrorCode>) -> PyResult<()> {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let kwargs = PyDict::new(py);
|
let kwargs = PyDict::new(py);
|
||||||
kwargs.set_opt_item("reason", reason)?;
|
kwargs.set_opt_item("reason", reason)?;
|
||||||
self.0
|
self.0
|
||||||
.call_method(py, intern!(py, "disconnect"), (), Some(kwargs))
|
.call_method(py, intern!(py, "disconnect"), (), Some(kwargs))
|
||||||
.and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py)))
|
.and_then(|coroutine| into_future(coroutine.as_ref(py)))
|
||||||
})?
|
})?
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
@@ -259,7 +341,7 @@ impl Connection {
|
|||||||
/// Register a callback to be called on disconnection.
|
/// Register a callback to be called on disconnection.
|
||||||
pub fn on_disconnection(
|
pub fn on_disconnection(
|
||||||
&mut self,
|
&mut self,
|
||||||
callback: impl Fn(Python, HciErrorCode) -> PyResult<()> + Send + 'static,
|
callback: impl Fn(Python, ErrorCode) -> PyResult<()> + Send + 'static,
|
||||||
) -> PyResult<()> {
|
) -> PyResult<()> {
|
||||||
let boxed = ClosureCallback::new(move |py, args, _kwargs| {
|
let boxed = ClosureCallback::new(move |py, args, _kwargs| {
|
||||||
callback(py, args.get_item(0)?.extract()?)
|
callback(py, args.get_item(0)?.extract()?)
|
||||||
|
|||||||
@@ -14,84 +14,62 @@
|
|||||||
|
|
||||||
//! HCI
|
//! HCI
|
||||||
|
|
||||||
|
pub use crate::internal::hci::{packets, Error, Packet};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
internal::hci::WithPacketType,
|
||||||
|
wrapper::hci::packets::{AddressType, Command, ErrorCode},
|
||||||
|
};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
exceptions::PyException, intern, types::PyModule, FromPyObject, PyAny, PyErr, PyObject,
|
exceptions::PyException,
|
||||||
PyResult, Python, ToPyObject,
|
intern, pyclass, pymethods,
|
||||||
|
types::{PyBytes, PyModule},
|
||||||
|
FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// HCI error code.
|
|
||||||
pub struct HciErrorCode(u8);
|
|
||||||
|
|
||||||
impl<'source> FromPyObject<'source> for HciErrorCode {
|
|
||||||
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
|
||||||
Ok(HciErrorCode(ob.extract()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToPyObject for HciErrorCode {
|
|
||||||
fn to_object(&self, py: Python<'_>) -> PyObject {
|
|
||||||
self.0.to_object(py)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides helpers for interacting with HCI
|
/// Provides helpers for interacting with HCI
|
||||||
pub struct HciConstant;
|
pub struct HciConstant;
|
||||||
|
|
||||||
impl HciConstant {
|
impl HciConstant {
|
||||||
/// Human-readable error name
|
/// Human-readable error name
|
||||||
pub fn error_name(status: HciErrorCode) -> PyResult<String> {
|
pub fn error_name(status: ErrorCode) -> PyResult<String> {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
PyModule::import(py, intern!(py, "bumble.hci"))?
|
PyModule::import(py, intern!(py, "bumble.hci"))?
|
||||||
.getattr(intern!(py, "HCI_Constant"))?
|
.getattr(intern!(py, "HCI_Constant"))?
|
||||||
.call_method1(intern!(py, "error_name"), (status.0,))?
|
.call_method1(intern!(py, "error_name"), (status.to_object(py),))?
|
||||||
.extract()
|
.extract()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Bluetooth address
|
/// A Bluetooth address
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Address(pub(crate) PyObject);
|
pub struct Address(pub(crate) PyObject);
|
||||||
|
|
||||||
impl Address {
|
impl Address {
|
||||||
|
/// Creates a new [Address] object
|
||||||
|
pub fn new(address: &str, address_type: &AddressType) -> PyResult<Self> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
PyModule::import(py, intern!(py, "bumble.device"))?
|
||||||
|
.getattr(intern!(py, "Address"))?
|
||||||
|
.call1((address, address_type.to_object(py)))
|
||||||
|
.map(|any| Self(any.into()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The type of address
|
/// The type of address
|
||||||
pub fn address_type(&self) -> PyResult<AddressType> {
|
pub fn address_type(&self) -> PyResult<AddressType> {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let addr_type = self
|
self.0
|
||||||
.0
|
|
||||||
.getattr(py, intern!(py, "address_type"))?
|
.getattr(py, intern!(py, "address_type"))?
|
||||||
.extract::<u32>(py)?;
|
.extract::<u8>(py)?
|
||||||
|
.try_into()
|
||||||
let module = PyModule::import(py, intern!(py, "bumble.hci"))?;
|
.map_err(|addr_type| {
|
||||||
let klass = module.getattr(intern!(py, "Address"))?;
|
PyErr::new::<PyException, _>(format!(
|
||||||
|
"Failed to convert {addr_type} to AddressType"
|
||||||
if addr_type
|
))
|
||||||
== klass
|
})
|
||||||
.getattr(intern!(py, "PUBLIC_DEVICE_ADDRESS"))?
|
|
||||||
.extract::<u32>()?
|
|
||||||
{
|
|
||||||
Ok(AddressType::PublicDevice)
|
|
||||||
} else if addr_type
|
|
||||||
== klass
|
|
||||||
.getattr(intern!(py, "RANDOM_DEVICE_ADDRESS"))?
|
|
||||||
.extract::<u32>()?
|
|
||||||
{
|
|
||||||
Ok(AddressType::RandomDevice)
|
|
||||||
} else if addr_type
|
|
||||||
== klass
|
|
||||||
.getattr(intern!(py, "PUBLIC_IDENTITY_ADDRESS"))?
|
|
||||||
.extract::<u32>()?
|
|
||||||
{
|
|
||||||
Ok(AddressType::PublicIdentity)
|
|
||||||
} else if addr_type
|
|
||||||
== klass
|
|
||||||
.getattr(intern!(py, "RANDOM_IDENTITY_ADDRESS"))?
|
|
||||||
.extract::<u32>()?
|
|
||||||
{
|
|
||||||
Ok(AddressType::RandomIdentity)
|
|
||||||
} else {
|
|
||||||
Err(PyErr::new::<PyException, _>("Invalid address type"))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,12 +112,45 @@ impl Address {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BT address types
|
impl ToPyObject for Address {
|
||||||
#[allow(missing_docs)]
|
fn to_object(&self, _py: Python<'_>) -> PyObject {
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
self.0.clone()
|
||||||
pub enum AddressType {
|
}
|
||||||
PublicDevice,
|
}
|
||||||
RandomDevice,
|
|
||||||
PublicIdentity,
|
/// Implements minimum necessary interface to be treated as bumble's [HCI_Command].
|
||||||
RandomIdentity,
|
/// While pyo3's macros do not support generics, this could probably be refactored to allow multiple
|
||||||
|
/// implementations of the HCI_Command methods in the future, if needed.
|
||||||
|
#[pyclass]
|
||||||
|
pub(crate) struct HciCommandWrapper(pub(crate) Command);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl HciCommandWrapper {
|
||||||
|
fn __bytes__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
let bytes = PyBytes::new(py, &self.0.clone().to_vec_with_packet_type());
|
||||||
|
Ok(bytes.into_py(py))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn op_code(&self) -> u16 {
|
||||||
|
self.0.get_op_code().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToPyObject for AddressType {
|
||||||
|
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||||
|
u8::from(self).to_object(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'source> FromPyObject<'source> for ErrorCode {
|
||||||
|
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||||
|
ob.extract()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToPyObject for ErrorCode {
|
||||||
|
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||||
|
u8::from(self).to_object(py)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,12 @@
|
|||||||
|
|
||||||
//! Host-side types
|
//! Host-side types
|
||||||
|
|
||||||
use crate::wrapper::transport::{Sink, Source};
|
use crate::wrapper::{
|
||||||
use pyo3::{intern, prelude::PyModule, types::PyDict, PyObject, PyResult, Python};
|
transport::{Sink, Source},
|
||||||
|
wrap_python_async,
|
||||||
|
};
|
||||||
|
use pyo3::{intern, prelude::PyModule, types::PyDict, PyObject, PyResult, Python, ToPyObject};
|
||||||
|
use pyo3_asyncio::tokio::into_future;
|
||||||
|
|
||||||
/// Host HCI commands
|
/// Host HCI commands
|
||||||
pub struct Host {
|
pub struct Host {
|
||||||
@@ -29,13 +33,23 @@ impl Host {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Host
|
/// Create a new Host
|
||||||
pub fn new(source: Source, sink: Sink) -> PyResult<Self> {
|
pub async fn new(source: Source, sink: Sink) -> PyResult<Self> {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
PyModule::import(py, intern!(py, "bumble.host"))?
|
let host_ctr =
|
||||||
.getattr(intern!(py, "Host"))?
|
PyModule::import(py, intern!(py, "bumble.host"))?.getattr(intern!(py, "Host"))?;
|
||||||
.call((source.0, sink.0), None)
|
|
||||||
.map(|any| Self { obj: any.into() })
|
let kwargs = PyDict::new(py);
|
||||||
})
|
kwargs.set_item("controller_source", source.0)?;
|
||||||
|
kwargs.set_item("controller_sink", sink.0)?;
|
||||||
|
|
||||||
|
// Needed for Python 3.8-3.9, in which the Semaphore object, when constructed, calls
|
||||||
|
// `get_event_loop`.
|
||||||
|
wrap_python_async(py, host_ctr)?
|
||||||
|
.call((), Some(kwargs))
|
||||||
|
.and_then(into_future)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.map(|any| Self { obj: any })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a reset command and perform other reset tasks.
|
/// Send a reset command and perform other reset tasks.
|
||||||
@@ -61,6 +75,12 @@ impl Host {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToPyObject for Host {
|
||||||
|
fn to_object(&self, _py: Python<'_>) -> PyObject {
|
||||||
|
self.obj.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Driver factory to use when initializing a host
|
/// Driver factory to use when initializing a host
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DriverFactory {
|
pub enum DriverFactory {
|
||||||
|
|||||||
38
rust/src/wrapper/link.rs
Normal file
38
rust/src/wrapper/link.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Link components
|
||||||
|
use pyo3::{intern, types::PyModule, PyObject, PyResult, Python, ToPyObject};
|
||||||
|
|
||||||
|
/// Link bus for controllers to communicate with each other
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Link(pub(crate) PyObject);
|
||||||
|
|
||||||
|
impl Link {
|
||||||
|
/// Creates a [Link] object that transports messages locally
|
||||||
|
pub fn new_local_link() -> PyResult<Self> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
PyModule::import(py, intern!(py, "bumble.link"))?
|
||||||
|
.getattr(intern!(py, "LocalLink"))?
|
||||||
|
.call0()
|
||||||
|
.map(|any| Self(any.into()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToPyObject for Link {
|
||||||
|
fn to_object(&self, _py: Python<'_>) -> PyObject {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,13 +22,17 @@
|
|||||||
|
|
||||||
// Re-exported to make it easy for users to depend on the same `PyObject`, etc
|
// Re-exported to make it easy for users to depend on the same `PyObject`, etc
|
||||||
pub use pyo3;
|
pub use pyo3;
|
||||||
|
pub use pyo3_asyncio;
|
||||||
|
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
|
intern,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
types::{PyDict, PyTuple},
|
types::{PyDict, PyTuple},
|
||||||
};
|
};
|
||||||
pub use pyo3_asyncio;
|
|
||||||
|
|
||||||
pub mod assigned_numbers;
|
pub mod assigned_numbers;
|
||||||
|
pub mod common;
|
||||||
|
pub mod controller;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod drivers;
|
pub mod drivers;
|
||||||
@@ -36,6 +40,7 @@ pub mod gatt_client;
|
|||||||
pub mod hci;
|
pub mod hci;
|
||||||
pub mod host;
|
pub mod host;
|
||||||
pub mod l2cap;
|
pub mod l2cap;
|
||||||
|
pub mod link;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
@@ -119,3 +124,11 @@ impl ClosureCallback {
|
|||||||
(self.callback)(py, args, kwargs).map(|_| py.None())
|
(self.callback)(py, args, kwargs).map(|_| py.None())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps the Python function in a Python async function. `pyo3_asyncio` needs functions to be
|
||||||
|
/// marked async to properly inject a running loop.
|
||||||
|
pub(crate) fn wrap_python_async<'a>(py: Python<'a>, function: &'a PyAny) -> PyResult<&'a PyAny> {
|
||||||
|
PyModule::import(py, intern!(py, "bumble.utils"))?
|
||||||
|
.getattr(intern!(py, "wrap_async"))?
|
||||||
|
.call1((function,))
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
//! HCI packet transport
|
//! HCI packet transport
|
||||||
|
|
||||||
|
use crate::wrapper::controller::Controller;
|
||||||
use pyo3::{intern, types::PyModule, PyObject, PyResult, Python};
|
use pyo3::{intern, types::PyModule, PyObject, PyResult, Python};
|
||||||
|
|
||||||
/// A source/sink pair for HCI packet I/O.
|
/// A source/sink pair for HCI packet I/O.
|
||||||
@@ -67,6 +68,18 @@ impl Drop for Transport {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Source(pub(crate) PyObject);
|
pub struct Source(pub(crate) PyObject);
|
||||||
|
|
||||||
|
impl From<Controller> for Source {
|
||||||
|
fn from(value: Controller) -> Self {
|
||||||
|
Self(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The sink side of a [Transport].
|
/// The sink side of a [Transport].
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Sink(pub(crate) PyObject);
|
pub struct Sink(pub(crate) PyObject);
|
||||||
|
|
||||||
|
impl From<Controller> for Sink {
|
||||||
|
fn from(value: Controller) -> Self {
|
||||||
|
Self(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user