diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ced74f7..434f5d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Install dependencies run: | - sudo apt-get install -y libudev1 + sudo apt-get install -y libudev-dev - name: Format run: cargo fmt --check - name: Build diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb1e0d..431ebc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed ### Changed +- Refactored API to be fully async ### Removed diff --git a/Cargo.lock b/Cargo.lock index f5a363d..3b5da95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,6 +89,15 @@ dependencies = [ "winit", ] +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -234,7 +243,7 @@ dependencies = [ "polling 2.8.0", "rustix 0.37.27", "slab", - "socket2", + "socket2 0.4.10", "waker-fn", ] @@ -308,7 +317,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -337,13 +346,13 @@ checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46" [[package]] name = "async-trait" -version = "0.1.76" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -406,6 +415,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -468,6 +492,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "bluer" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aed7969360669bf94a0f4b5e478a98ce0cdde2f9e4a26cecef5fcee53ab6e373" +dependencies = [ + "custom_debug", + "dbus", + "dbus-crossroads", + "dbus-tokio", + "displaydoc", + "futures", + "hex", + "lazy_static", + "libc", + "log", + "macaddr", + "nix 0.27.1", + "num-derive", + "num-traits", + "pin-project", + "serde", + "serde_json", + "strum", + "tokio", + "tokio-stream", + "uuid", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -491,7 +544,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -700,6 +753,59 @@ dependencies = [ "typenum", ] +[[package]] +name = "custom_debug" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89e0ae2c2a42be29595d05c50e3ce6096c0698a97e021c3289790f0750cc8e2" +dependencies = [ + "custom_debug_derive", +] + +[[package]] +name = "custom_debug_derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a9f3941234c9f62ceaa2782974827749de9b0a8a6487275a278da068e1baf7" +dependencies = [ + "proc-macro2", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "futures-channel", + "futures-util", + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "dbus-crossroads" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4c83437187544ba5142427746835061b330446ca8902eabd70e4afb8f76de0" +dependencies = [ + "dbus", +] + +[[package]] +name = "dbus-tokio" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007688d459bc677131c063a3a77fb899526e17b7980f390b69644bdbc41fad13" +dependencies = [ + "dbus", + "libc", + "tokio", +] + [[package]] name = "derivative" version = "2.2.0" @@ -727,6 +833,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "dlib" version = "0.5.2" @@ -853,7 +970,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1000,12 +1117,48 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -1040,6 +1193,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -1058,8 +1222,10 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1133,6 +1299,12 @@ dependencies = [ "windows", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "gl_generator" version = "0.14.0" @@ -1340,6 +1512,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + [[package]] name = "jni" version = "0.21.1" @@ -1398,6 +1576,15 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "pkg-config", +] + [[package]] name = "libloading" version = "0.7.4" @@ -1479,6 +1666,12 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "macaddr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baee0bbc17ce759db233beb01648088061bf678383130602a298e6998eedb2d8" + [[package]] name = "mach2" version = "0.4.2" @@ -1652,7 +1845,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1685,6 +1878,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_enum" version = "0.5.11" @@ -1724,14 +1927,17 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] name = "nxtusb" version = "0.1.0" dependencies = [ + "async-trait", + "bluer", "eframe", + "futures", "gilrs", "num-derive", "num-traits", @@ -1739,6 +1945,7 @@ dependencies = [ "strum", "strum_macros", "thiserror", + "tokio", ] [[package]] @@ -1796,6 +2003,15 @@ dependencies = [ "objc", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1871,6 +2087,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1961,18 +2197,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2070,6 +2306,12 @@ dependencies = [ "libusb1-sys", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustix" version = "0.37.27" @@ -2103,6 +2345,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + [[package]] name = "same-file" version = "1.0.6" @@ -2154,7 +2402,18 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +dependencies = [ + "itoa", + "ryu", + "serde", ] [[package]] @@ -2165,7 +2424,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -2257,6 +2516,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -2280,6 +2549,9 @@ name = "strum" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] [[package]] name = "strum_macros" @@ -2291,7 +2563,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -2307,15 +2579,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.9.0" @@ -2346,7 +2630,7 @@ checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -2389,6 +2673,45 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.5" @@ -2425,7 +2748,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -2481,6 +2804,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "url" version = "2.5.0" @@ -2497,6 +2826,9 @@ name = "uuid" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom", +] [[package]] name = "vcpkg" @@ -2559,7 +2891,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -2593,7 +2925,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3179,7 +3511,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 49f11ae..021907b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,19 +19,30 @@ name = "gamepad" required-features = ["examples"] [features] -default = ["usb"] -examples = ["strum", "dep:eframe", "dep:gilrs"] +default = ["usb", "bluetooth"] +examples = [ + "strum", + "dep:eframe", + "dep:gilrs", +] strum = ["dep:strum", "dep:strum_macros"] usb = ["dep:rusb"] +bluetooth = ["dep:bluer", "tokio/rt"] [dependencies] num-derive = "0.4" num-traits = "0.2" thiserror = "1" +futures = "0.3" +tokio = { version = "1", features = ["sync"] } +async-trait = "0.1.77" # USB support rusb = { version = "0.9", optional = true } +# Bluetooth support +bluer = { version = "0.16", features = ["bluetoothd", "rfcomm"], optional = true } + strum = { version = "0.25", optional = true } strum_macros = { version = "0.25", optional = true } @@ -40,3 +51,7 @@ eframe = { version = "0.24", optional = true } # required for gamepad gilrs = { version = "0.10", optional = true } + +[dev-dependencies] +# used for all examples +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } diff --git a/README.md b/README.md index 9287822..841d2d1 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ use nxtusb::{motor::*, *}; const POWER: i8 = 80; -fn main() -> nxtusb::Result<()> { - let nxt = Nxt::first_usb()?; +#[tokio::main] +async fn main() -> nxtusb::Result<()> { + let nxt = Nxt::first_usb().await?; println!("Running motor A at {POWER}"); nxt.set_output_state( @@ -21,7 +22,7 @@ fn main() -> nxtusb::Result<()> { 0, RunState::Running, RUN_FOREVER, - )?; + ).await?; std::thread::sleep(std::time::Duration::from_secs(5)); @@ -34,9 +35,9 @@ fn main() -> nxtusb::Result<()> { 0, RunState::Running, RUN_FOREVER, - )?; + ).await?; - let bat = nxt.get_battery_level()?; + let bat = nxt.get_battery_level().await?; println!("Battery level is {bat} mV"); Ok(()) diff --git a/examples/bluetooth.rs b/examples/bluetooth.rs new file mode 100644 index 0000000..0a94d4b --- /dev/null +++ b/examples/bluetooth.rs @@ -0,0 +1,8 @@ +use nxtusb::Nxt; + +fn main() -> nxtusb::Result<()> { + // let device = nxtusb::Bluetooth::new(); + // let _nxt = Nxt::init(device); + + Ok(()) +} diff --git a/examples/deviceinfo.rs b/examples/deviceinfo.rs index 6ee4be9..3120a06 100644 --- a/examples/deviceinfo.rs +++ b/examples/deviceinfo.rs @@ -1,20 +1,21 @@ use nxtusb::Nxt; -fn main() -> nxtusb::Result<()> { - let nxt = Nxt::all_usb()?; +#[tokio::main] +async fn main() -> nxtusb::Result<()> { + let nxt = Nxt::all_usb().await?; println!("Found {} NXT bricks", nxt.len()); for (idx, nxt) in nxt.iter().enumerate() { println!("## Brick {idx}:"); - let bat = nxt.get_battery_level()?; + let bat = nxt.get_battery_level().await?; println!("Battery level is {bat} mV"); - let info = nxt.get_device_info()?; + let info = nxt.get_device_info().await?; println!("Device info:\n{info:?}"); - let versions = nxt.get_firmware_version()?; + let versions = nxt.get_firmware_version().await?; println!("Versions:\n{versions:?}"); } diff --git a/examples/display.rs b/examples/display.rs index f9c855e..6e152e5 100644 --- a/examples/display.rs +++ b/examples/display.rs @@ -1,11 +1,12 @@ use nxtusb::{system::*, *}; -fn main() -> nxtusb::Result<()> { - let nxt = Nxt::first_usb()?; +#[tokio::main] +async fn main() -> nxtusb::Result<()> { + let nxt = Nxt::first_usb().await?; println!("Read display"); - let screen = nxt.get_display_data()?; + let screen = nxt.get_display_data().await?; dbg!(screen); diff --git a/examples/gamepad.rs b/examples/gamepad.rs index a0d6c80..66fecd7 100644 --- a/examples/gamepad.rs +++ b/examples/gamepad.rs @@ -43,21 +43,24 @@ impl Robot { self.changed } - pub fn send(&mut self) -> Result { - self.nxt.set_output_state( - OutPort::BC, - self.speed, - OutMode::ON, - RegulationMode::Idle, - self.steering, - RunState::Running, - RUN_FOREVER, - )?; + pub async fn send(&mut self) -> Result { + self.nxt + .set_output_state( + OutPort::BC, + self.speed, + OutMode::ON, + RegulationMode::Idle, + self.steering, + RunState::Running, + RUN_FOREVER, + ) + .await?; Ok(()) } } -fn main() -> Result { +#[tokio::main] +async fn main() -> Result { let mut gilrs = Gilrs::new().unwrap(); // Iterate over all connected gamepads @@ -65,7 +68,7 @@ fn main() -> Result { println!("{} is {:?}", gamepad.name(), gamepad.power_info()); } - let nxt = Nxt::first_usb()?; + let nxt = Nxt::first_usb().await?; let mut active_gamepad = None; let mut robot = Robot::new(nxt); @@ -101,7 +104,7 @@ fn main() -> Result { last_update = Instant::now(); if robot.changed() { println!("{robot:?}"); - robot.send()?; + robot.send().await?; } } diff --git a/examples/gui.rs b/examples/gui.rs index e559501..4b8f748 100644 --- a/examples/gui.rs +++ b/examples/gui.rs @@ -1,6 +1,7 @@ use eframe::egui; use nxtusb::{motor::*, sensor::*, system::*, *}; use std::{sync::mpsc, time::Duration}; +use tokio::runtime::Runtime; const POLL_DELAY: Duration = Duration::from_millis(300); const DISPLAY_PX_SCALE: usize = 4; @@ -18,6 +19,7 @@ struct App { sensors: Vec, sensor_poll_handle: SensorPollHandle, display: Option, + rt: Runtime, } struct Motor { @@ -51,6 +53,7 @@ impl App { sensors: Vec::new(), sensor_poll_handle: SensorPollHandle::new(cc.egui_ctx.clone()), display: None, + rt: Runtime::new().unwrap(), } } } @@ -91,7 +94,8 @@ impl eframe::App for App { if ui.button("Refresh").clicked() { self.nxt_selected = None; self.nxt_available.clear(); - match Nxt::all() { + let all = self.rt.block_on(Nxt::all_usb()); + match all { Ok(avail) => self.nxt_available = avail, Err(e) => println!("Error: {e}"), } @@ -113,9 +117,9 @@ impl eframe::App for App { .and_then(|idx| self.nxt_available.get(idx)) { ui.separator(); - motor_ui(ui, nxt, &mut self.motors); + motor_ui(ui, &self.rt, nxt, &mut self.motors); ui.separator(); - sensor_ui(ui, nxt, &mut self.sensors); + sensor_ui(ui, &self.rt, nxt, &mut self.sensors); if let Some(display) = &self.display { ui.separator(); display_ui(ui, display); @@ -125,7 +129,12 @@ impl eframe::App for App { } } -fn motor_ui(ui: &mut egui::Ui, nxt: &Nxt, motors: &mut Vec) { +fn motor_ui( + ui: &mut egui::Ui, + rt: &Runtime, + nxt: &Nxt, + motors: &mut Vec, +) { for mot in motors { ui.horizontal(|ui| { let old = mot.power; @@ -142,7 +151,7 @@ fn motor_ui(ui: &mut egui::Ui, nxt: &Nxt, motors: &mut Vec) { if mot.power != old { // it has changed - nxt.set_output_state( + rt.block_on(nxt.set_output_state( mot.port, mot.power, OutMode::ON | OutMode::REGULATED, @@ -150,14 +159,19 @@ fn motor_ui(ui: &mut egui::Ui, nxt: &Nxt, motors: &mut Vec) { 0, RunState::Running, RUN_FOREVER, - ) + )) .unwrap(); } }); } } -fn sensor_ui(ui: &mut egui::Ui, nxt: &Nxt, sensors: &mut Vec) { +fn sensor_ui( + ui: &mut egui::Ui, + rt: &Runtime, + nxt: &Nxt, + sensors: &mut Vec, +) { for sens in sensors { ui.horizontal(|ui| { let old_typ = sens.sensor_type; @@ -194,11 +208,11 @@ fn sensor_ui(ui: &mut egui::Ui, nxt: &Nxt, sensors: &mut Vec) { ui.label(format!("Value: {sens}")); if sens.sensor_type != old_typ || sens.sensor_mode != old_mode { - nxt.set_input_mode( + rt.block_on(nxt.set_input_mode( sens.port, sens.sensor_type, sens.sensor_mode, - ) + )) .unwrap(); } }); @@ -262,6 +276,9 @@ impl SensorPollHandle { let mut nxt = None; let mut old_values = Vec::new(); let mut old_screen = [0u8; DISPLAY_DATA_LEN]; + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); loop { if let Ok(new) = nxt_rx.try_recv() { nxt = new; @@ -271,7 +288,8 @@ impl SensorPollHandle { if let Some(nxt) = &nxt { let mut values = Vec::with_capacity(4); for port in InPort::iter() { - values.push(nxt.get_input_values(port).unwrap()); + values + .push(rt.block_on(nxt.get_input_values(port)).unwrap()); } if values != old_values { old_values = values.clone(); @@ -279,7 +297,7 @@ impl SensorPollHandle { ctx.request_repaint(); } - let screen = nxt.get_display_data().unwrap(); + let screen = rt.block_on(nxt.get_display_data()).unwrap(); if screen != old_screen { val_tx .send(Message::Display(Box::new( diff --git a/examples/ls.rs b/examples/ls.rs index 7bfff3b..cba6980 100644 --- a/examples/ls.rs +++ b/examples/ls.rs @@ -1,14 +1,15 @@ use nxtusb::*; -fn main() -> nxtusb::Result<()> { - let nxt = Nxt::first_usb()?; +#[tokio::main] +async fn main() -> nxtusb::Result<()> { + let nxt = Nxt::first_usb().await?; println!("List files"); - let mut handle = nxt.file_find_first(".")?; + let mut handle = nxt.file_find_first(".").await?; loop { println!("{handle:?}"); - handle = nxt.file_find_next(&handle)?; + handle = nxt.file_find_next(&handle).await?; } } diff --git a/examples/poll_touch_sensor.rs b/examples/poll_touch_sensor.rs index 65f8439..90bb154 100644 --- a/examples/poll_touch_sensor.rs +++ b/examples/poll_touch_sensor.rs @@ -1,15 +1,17 @@ use nxtusb::{sensor::*, *}; -fn main() -> nxtusb::Result<()> { - let nxt = Nxt::first_usb()?; +#[tokio::main] +async fn main() -> nxtusb::Result<()> { + let nxt = Nxt::first_usb().await?; println!("Set input mode"); - nxt.set_input_mode(InPort::S1, SensorType::Switch, SensorMode::Bool)?; + nxt.set_input_mode(InPort::S1, SensorType::Switch, SensorMode::Bool) + .await?; println!("Start polling"); loop { - let val = nxt.get_input_values(InPort::S1)?; + let val = nxt.get_input_values(InPort::S1).await?; println!("{val:?}"); std::thread::sleep(std::time::Duration::from_millis(200)); diff --git a/examples/run_motor_a.rs b/examples/run_motor_a.rs index b7720ba..29d8a12 100644 --- a/examples/run_motor_a.rs +++ b/examples/run_motor_a.rs @@ -2,8 +2,9 @@ use nxtusb::{motor::*, *}; const POWER: i8 = 80; -fn main() -> nxtusb::Result<()> { - let nxt = Nxt::first_usb()?; +#[tokio::main] +async fn main() -> nxtusb::Result<()> { + let nxt = Nxt::first_usb().await?; println!("Running motor A at {POWER}"); nxt.set_output_state( @@ -14,7 +15,8 @@ fn main() -> nxtusb::Result<()> { 0, RunState::Running, RUN_FOREVER, - )?; + ) + .await?; std::thread::sleep(std::time::Duration::from_secs(5)); @@ -27,9 +29,10 @@ fn main() -> nxtusb::Result<()> { 0, RunState::Running, RUN_FOREVER, - )?; + ) + .await?; - let bat = nxt.get_battery_level()?; + let bat = nxt.get_battery_level().await?; println!("Battery level is {bat} mV"); Ok(()) diff --git a/src/lib.rs b/src/lib.rs index e2ff59a..f7bbd2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub use error::{Error, Result}; use std::{ + fmt::{self, Debug, Formatter}, io::{Cursor, Write}, sync::Arc, }; @@ -30,6 +31,12 @@ pub mod sensor; mod socket; pub mod system; +#[cfg(feature = "usb")] +pub use socket::usb::Usb; + +#[cfg(feature = "bluetooth")] +pub use socket::bluetooth::Bluetooth; + use motor::{OutMode, OutPort, OutputState, RegulationMode, RunState}; use protocol::{Opcode, Packet}; use sensor::{InPort, InputValues, SensorMode, SensorType}; @@ -70,37 +77,44 @@ const DISPLAY_NUM_CHUNKS: u16 = #[derive(Clone)] pub struct Nxt { /// Socket device, e.g. USB or Bluetooth - device: Arc, + device: Arc, /// Name of the brick name: String, } +impl Debug for Nxt { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + fmt.debug_struct("NXT") + .field("name", &self.name) + .finish_non_exhaustive() + } +} + impl Nxt { /// Search for plugged-in NXT devices and establish a connection to /// the first one #[cfg(feature = "usb")] - pub fn first_usb() -> Result { + pub async fn first_usb() -> Result { let device = socket::usb::Usb::first()?; - Self::init(Arc::new(device)) + Self::init(device).await } /// Connect to all plugged-in NXT bricks and return them in a `Vec` #[cfg(feature = "usb")] - pub fn all_usb() -> Result> { + pub async fn all_usb() -> Result> { let devices = socket::usb::Usb::all()?; - devices - .into_iter() - .map(|device| Self::init(Arc::new(device))) - .collect() + futures::future::try_join_all(devices.into_iter().map(Self::init)).await } /// Initialise an NXT struct from the given device - pub fn init(device: Arc) -> Result { + pub async fn init( + device: impl Socket + Send + Sync + 'static, + ) -> Result { let mut nxt = Self { - device, + device: Arc::new(device), name: String::new(), }; - let info = nxt.get_device_info()?; + let info = nxt.get_device_info().await?; nxt.name = info.name; Ok(nxt) } @@ -114,14 +128,14 @@ impl Nxt { /// Send the provided packet an optionally check the response status. /// Use this API if there's no useful data in the reply beyond the /// status field - fn send(&self, pkt: &Packet, check_status: bool) -> Result<()> { + async fn send(&self, pkt: &Packet, check_status: bool) -> Result<()> { let mut buf = [0; 64]; let serialised = pkt.serialise(&mut buf)?; - let written = self.device.send(serialised)?; + let written = self.device.send(serialised).await?; if written == serialised.len() { if check_status { - let _recv = self.recv(pkt.opcode)?; + let _recv = self.recv(pkt.opcode).await?; } Ok(()) } else { @@ -131,9 +145,9 @@ impl Nxt { /// Read an incoming reply packet and verify that its opcode matches /// the expected value - fn recv(&self, opcode: Opcode) -> Result { + async fn recv(&self, opcode: Opcode) -> Result { let mut buf = [0; 64]; - let buf = self.device.recv(&mut buf)?; + let buf = self.device.recv(&mut buf).await?; let mut recv = Packet::parse(buf)?; recv.check_status()?; @@ -147,23 +161,25 @@ impl Nxt { /// Send the provided packet and read the response. Use this API /// when the reply is expected to contain useful data, e.g. sensor /// values - fn send_recv(&self, pkt: &Packet) -> Result { - self.send(pkt, false)?; - self.recv(pkt.opcode) + async fn send_recv(&self, pkt: &Packet) -> Result { + self.send(pkt, false).await?; + self.recv(pkt.opcode).await } /// Convenience function to retrieve the contents of the LCD screen. /// The data is in a slightly odd format; see /// [`system::display_data_to_raster`] for details. - pub fn get_display_data(&self) -> Result<[u8; DISPLAY_DATA_LEN]> { + pub async fn get_display_data(&self) -> Result<[u8; DISPLAY_DATA_LEN]> { let out = [0; DISPLAY_DATA_LEN]; let mut cur = Cursor::new(out); for chunk_idx in 0..DISPLAY_NUM_CHUNKS { - let data = self.read_io_map( - MOD_DISPLAY, - DISPLAY_DATA_OFFSET + chunk_idx * DISPLAY_DATA_CHUNK_SIZE, - DISPLAY_DATA_CHUNK_SIZE, - )?; + let data = self + .read_io_map( + MOD_DISPLAY, + DISPLAY_DATA_OFFSET + chunk_idx * DISPLAY_DATA_CHUNK_SIZE, + DISPLAY_DATA_CHUNK_SIZE, + ) + .await?; assert_eq!(data.len(), DISPLAY_DATA_CHUNK_SIZE.into()); cur.write_all(&data)?; } @@ -172,16 +188,16 @@ impl Nxt { } /// Retrieve the current battery level, in mV - pub fn get_battery_level(&self) -> Result { + pub async fn get_battery_level(&self) -> Result { let pkt = Packet::new(Opcode::DirectGetBattLevel); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; recv.read_u16() } /// Read firmware versions from the NXT brick - pub fn get_firmware_version(&self) -> Result { + pub async fn get_firmware_version(&self) -> Result { let pkt = Packet::new(Opcode::SystemVersions); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let prot_min = recv.read_u8()?; let prot_maj = recv.read_u8()?; let fw_min = recv.read_u8()?; @@ -194,38 +210,38 @@ impl Nxt { /// Start running the program with the specified name. Returns an /// `ERR_RC_ILLEGAL_VAL` error if the file does not exist. - pub fn start_program(&self, name: &str) -> Result<()> { + pub async fn start_program(&self, name: &str) -> Result<()> { let mut pkt = Packet::new(Opcode::DirectStartProgram); pkt.push_filename(name)?; - self.send(&pkt, true) + self.send(&pkt, true).await } /// Stop the currently executing program. Returns an `ERR_NO_PROG` /// error if there is no program running. - pub fn stop_program(&self) -> Result<()> { + pub async fn stop_program(&self) -> Result<()> { let pkt = Packet::new(Opcode::DirectStopProgram); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Play the specified sound file. Returns an `ERR_RC_ILLEGAL_VAL` /// if the file does not exist - pub fn play_sound(&self, file: &str, loop_: bool) -> Result<()> { + pub async fn play_sound(&self, file: &str, loop_: bool) -> Result<()> { let mut pkt = Packet::new(Opcode::DirectPlaySoundFile); pkt.push_bool(loop_); pkt.push_filename(file)?; - self.send(&pkt, true) + self.send(&pkt, true).await } /// Play the specified tone for the given duration. - pub fn play_tone(&self, freq: u16, duration_ms: u16) -> Result<()> { + pub async fn play_tone(&self, freq: u16, duration_ms: u16) -> Result<()> { let mut pkt = Packet::new(Opcode::DirectPlayTone); pkt.push_u16(freq); pkt.push_u16(duration_ms); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Set the output state for the given individual or compound port - pub fn set_output_state( + pub async fn set_output_state( &self, port: OutPort, power: i8, @@ -243,11 +259,11 @@ impl Nxt { pkt.push_i8(turn_ratio); pkt.push_u8(run_state as u8); pkt.push_u32(tacho_limit); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Set the given input to the specified mode - pub fn set_input_mode( + pub async fn set_input_mode( &self, port: InPort, sensor_type: SensorType, @@ -257,17 +273,17 @@ impl Nxt { pkt.push_u8(port as u8); pkt.push_u8(sensor_type as u8); pkt.push_u8(sensor_mode as u8); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Retrieve the state of the specified output. Returns an /// `ERR_RC_ILLEGAL_VAL` if the port is not a valid single port /// specification - pub fn get_output_state(&self, port: OutPort) -> Result { + pub async fn get_output_state(&self, port: OutPort) -> Result { let mut pkt = Packet::new(Opcode::DirectGetOutState); pkt.push_u8(port as u8); - self.send(&pkt, false)?; - let mut recv = self.recv(Opcode::DirectGetOutState)?; + self.send(&pkt, false).await?; + let mut recv = self.recv(Opcode::DirectGetOutState).await?; let port = recv.read_u8()?.try_into()?; let power = recv.read_i8()?; let mode = recv.read_u8()?.into(); @@ -294,10 +310,10 @@ impl Nxt { } /// Retrieve the state of the specified input port - pub fn get_input_values(&self, port: InPort) -> Result { + pub async fn get_input_values(&self, port: InPort) -> Result { let mut pkt = Packet::new(Opcode::DirectGetInVals); pkt.push_u8(port as u8); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; // hdr>> s p v c ty mo raw>> norm> sc>> cal>> // [2, 7, 0, 0, 1, 0, 1, 20, ff, 3, ff, 3, 0, 0, ff, 3] let port = recv.read_u8()?.try_into()?; @@ -325,16 +341,16 @@ impl Nxt { /// Reset the scaled value of the spcified input port, e.g. clears /// the edge or pulse counter. - pub fn reset_input_scaled_value(&self, port: InPort) -> Result<()> { + pub async fn reset_input_scaled_value(&self, port: InPort) -> Result<()> { let mut pkt = Packet::new(Opcode::DirectResetInVal); pkt.push_u8(port as u8); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Write a message to the specified inbox. Returns an error if the /// inbox ID is greater than [`MAX_INBOX_ID`] or of the message is /// longer than [`MAX_MESSAGE_LEN`] bytes - pub fn message_write(&self, inbox: u8, message: &[u8]) -> Result<()> { + pub async fn message_write(&self, inbox: u8, message: &[u8]) -> Result<()> { if inbox > MAX_INBOX_ID { return Err(Error::Serialise("Invalid mailbox ID")); } @@ -349,7 +365,7 @@ impl Nxt { pkt.push_u8(message.len() as u8 + 1); pkt.push_slice(message); pkt.push_u8(0); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Reset the motor position counter. Returns an `ERR_RC_ILLEGAL_VAL` @@ -358,7 +374,7 @@ impl Nxt { /// * `relative`: /// * `TRUE`: reset position relative to last motor control block /// * `FALSE`: reset position relative to start of last program - pub fn reset_motor_position( + pub async fn reset_motor_position( &self, port: OutPort, relative: bool, @@ -366,35 +382,35 @@ impl Nxt { let mut pkt = Packet::new(Opcode::DirectResetPosition); pkt.push_u8(port as u8); pkt.push_bool(relative); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Stop playing the current sound file, if any - pub fn stop_sound_playback(&self) -> Result<()> { + pub async fn stop_sound_playback(&self) -> Result<()> { let pkt = Packet::new(Opcode::DirectStopSound); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Reset the sleep timer and return the sleep timeout - pub fn keep_alive(&self) -> Result { + pub async fn keep_alive(&self) -> Result { let pkt = Packet::new(Opcode::DirectKeepAlive); - self.send(&pkt, false)?; - let mut recv = self.recv(Opcode::DirectKeepAlive)?; + self.send(&pkt, false).await?; + let mut recv = self.recv(Opcode::DirectKeepAlive).await?; recv.read_u32() } /// Retrieve the status of the specified low speed port - pub fn ls_get_status(&self, port: InPort) -> Result { + pub async fn ls_get_status(&self, port: InPort) -> Result { let mut pkt = Packet::new(Opcode::DirectLsGetStatus); pkt.push_u8(port as u8); - self.send(&pkt, false)?; - let mut recv = self.recv(Opcode::DirectLsGetStatus)?; + self.send(&pkt, false).await?; + let mut recv = self.recv(Opcode::DirectLsGetStatus).await?; recv.read_u8() } /// Write the provided data to the low speed bus on the given port /// and read the specified number of bytes in response - pub fn ls_write( + pub async fn ls_write( &self, port: InPort, tx_data: &[u8], @@ -413,15 +429,15 @@ impl Nxt { pkt.push_u8(tx_data.len() as u8); pkt.push_u8(rx_bytes); pkt.push_slice(tx_data); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Read data from the low speed port - pub fn ls_read(&self, port: InPort) -> Result> { + pub async fn ls_read(&self, port: InPort) -> Result> { let mut pkt = Packet::new(Opcode::DirectLsRead); pkt.push_u8(port as u8); - self.send(&pkt, false)?; - let mut recv = self.recv(Opcode::DirectLsRead)?; + self.send(&pkt, false).await?; + let mut recv = self.recv(Opcode::DirectLsRead).await?; let len = recv.read_u8()?; let data = recv.read_slice(len as usize)?; Ok(data.to_vec()) @@ -429,15 +445,15 @@ impl Nxt { /// Get the name of the currently running program. Returns /// `ERR_NO_PROG` if there is no program currently running - pub fn get_current_program_name(&self) -> Result { + pub async fn get_current_program_name(&self) -> Result { let pkt = Packet::new(Opcode::DirectGetCurrProgram); - self.send(&pkt, false)?; - let mut recv = self.recv(Opcode::DirectGetCurrProgram)?; + self.send(&pkt, false).await?; + let mut recv = self.recv(Opcode::DirectGetCurrProgram).await?; recv.read_filename() } /// Read a message from the specified mailbox - pub fn message_read( + pub async fn message_read( &self, remote_inbox: u8, local_inbox: u8, @@ -447,8 +463,8 @@ impl Nxt { pkt.push_u8(remote_inbox); pkt.push_u8(local_inbox); pkt.push_bool(remove); - self.send(&pkt, false)?; - let mut recv = self.recv(Opcode::DirectMessageRead)?; + self.send(&pkt, false).await?; + let mut recv = self.recv(Opcode::DirectMessageRead).await?; let _local_inbox = recv.read_u8()?; let len = recv.read_u8()?; let data = recv.read_slice(len as usize)?; @@ -456,27 +472,35 @@ impl Nxt { } /// Open the specified file for writing and return its handle - pub fn file_open_write(&self, name: &str, len: u32) -> Result { + pub async fn file_open_write( + &self, + name: &str, + len: u32, + ) -> Result { let mut pkt = Packet::new(Opcode::SystemOpenwrite); pkt.push_filename(name)?; pkt.push_u32(len); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; Ok(FileHandle { handle, len }) } /// Write the provided data to the previously opened file - pub fn file_write(&self, handle: &FileHandle, data: &[u8]) -> Result { + pub async fn file_write( + &self, + handle: &FileHandle, + data: &[u8], + ) -> Result { let mut pkt = Packet::new(Opcode::SystemWrite); pkt.push_u8(handle.handle); pkt.push_slice(data); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let _handle = recv.read_u8()?; recv.read_u32() } /// Open the specified file in `write data` mode and return its handle - pub fn file_open_write_data( + pub async fn file_open_write_data( &self, name: &str, len: u32, @@ -484,44 +508,51 @@ impl Nxt { let mut pkt = Packet::new(Opcode::SystemOpenwritedata); pkt.push_filename(name)?; pkt.push_u32(len); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; Ok(FileHandle { handle, len }) } /// Open the specified file in `append` mode and return its handle - pub fn file_open_append_data(&self, name: &str) -> Result { + pub async fn file_open_append_data( + &self, + name: &str, + ) -> Result { let mut pkt = Packet::new(Opcode::SystemOpenappenddata); pkt.push_filename(name)?; - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; let len = recv.read_u32()?; Ok(FileHandle { handle, len }) } /// Close the specified file handle - pub fn file_close(&self, handle: &FileHandle) -> Result<()> { + pub async fn file_close(&self, handle: &FileHandle) -> Result<()> { let mut pkt = Packet::new(Opcode::SystemClose); pkt.push_u8(handle.handle); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Open the specified file for reading and return its handle - pub fn file_open_read(&self, name: &str) -> Result { + pub async fn file_open_read(&self, name: &str) -> Result { let mut pkt = Packet::new(Opcode::SystemOpenread); pkt.push_filename(name)?; - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; let len = recv.read_u32()?; Ok(FileHandle { handle, len }) } /// Read data from the previously opened file - pub fn file_read(&self, handle: &FileHandle, len: u32) -> Result> { + pub async fn file_read( + &self, + handle: &FileHandle, + len: u32, + ) -> Result> { let mut pkt = Packet::new(Opcode::SystemOpenread); pkt.push_u8(handle.handle); pkt.push_u32(len); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let _handle = recv.read_u8()?; let len = recv.read_u8()?; let data = recv.read_slice(len as usize)?; @@ -529,18 +560,21 @@ impl Nxt { } /// Delete the named file - pub fn file_delete(&self, name: &str) -> Result<()> { + pub async fn file_delete(&self, name: &str) -> Result<()> { let mut pkt = Packet::new(Opcode::SystemDelete); pkt.push_filename(name)?; - self.send(&pkt, true) + self.send(&pkt, true).await } /// Search for a file matching the specified pattern and return a /// handle to the search state - pub fn file_find_first(&self, pattern: &str) -> Result { + pub async fn file_find_first( + &self, + pattern: &str, + ) -> Result { let mut pkt = Packet::new(Opcode::SystemFindfirst); pkt.push_filename(pattern)?; - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; let name = recv.read_filename()?; let len = recv.read_u32()?; @@ -549,13 +583,13 @@ impl Nxt { /// Take a search handle and return the next match, or an error if /// there are no further matches - pub fn file_find_next( + pub async fn file_find_next( &self, handle: &FindFileHandle, ) -> Result { let mut pkt = Packet::new(Opcode::SystemFindnext); pkt.push_u8(handle.handle); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; let name = recv.read_filename()?; let len = recv.read_u32()?; @@ -563,7 +597,7 @@ impl Nxt { } /// Souce code just says `For internal use only` - pub fn file_open_read_linear( + pub async fn file_open_read_linear( &self, name: &str, len: u32, @@ -571,13 +605,13 @@ impl Nxt { let mut pkt = Packet::new(Opcode::SystemOpenreadlinear); pkt.push_filename(name)?; pkt.push_u32(len); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; Ok(FileHandle { handle, len }) } /// Souce code just says `For internal use only` - pub fn file_open_write_linear( + pub async fn file_open_write_linear( &self, name: &str, len: u32, @@ -585,17 +619,20 @@ impl Nxt { let mut pkt = Packet::new(Opcode::SystemOpenwritelinear); pkt.push_filename(name)?; pkt.push_u32(len); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; Ok(FileHandle { handle, len }) } /// Search for a module matching the specified pattern and return a /// handle to the search state - pub fn module_find_first(&self, pattern: &str) -> Result { + pub async fn module_find_first( + &self, + pattern: &str, + ) -> Result { let mut pkt = Packet::new(Opcode::SystemFindfirstmodule); pkt.push_filename(pattern)?; - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; let name = recv.read_filename()?; let id = recv.read_u32()?; @@ -612,13 +649,13 @@ impl Nxt { /// Take a search handle and return the next match, or an error if /// there are no further matches - pub fn module_find_next( + pub async fn module_find_next( &self, handle: &ModuleHandle, ) -> Result { let mut pkt = Packet::new(Opcode::SystemFindnextmodule); pkt.push_u8(handle.handle); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let handle = recv.read_u8()?; let name = recv.read_filename()?; let id = recv.read_u32()?; @@ -634,15 +671,15 @@ impl Nxt { } /// Close the provided module handle - pub fn module_close(&self, handle: &ModuleHandle) -> Result<()> { + pub async fn module_close(&self, handle: &ModuleHandle) -> Result<()> { let mut pkt = Packet::new(Opcode::SystemClosemodhandle); pkt.push_u8(handle.handle); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Read `count` bytes from the IO map belonging to the specified /// module at the given offset - pub fn read_io_map( + pub async fn read_io_map( &self, mod_id: u32, offset: u16, @@ -652,7 +689,7 @@ impl Nxt { pkt.push_u32(mod_id); pkt.push_u16(offset); pkt.push_u16(count); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let _mod_id = recv.read_u32()?; let len = recv.read_u16()?; let data = recv.read_slice(len as usize)?; @@ -661,7 +698,7 @@ impl Nxt { /// Write the provided data into the IO map belongint to the /// specified module at the given offset - pub fn write_io_map( + pub async fn write_io_map( &self, mod_id: u32, offset: u16, @@ -672,7 +709,7 @@ impl Nxt { pkt.push_u16(offset); pkt.push_u16(data.len().try_into()?); pkt.push_slice(data); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let _mod_id = recv.read_u32()?; recv.read_u16() } @@ -680,7 +717,7 @@ impl Nxt { /// Enter firmware update mode - warning, this is not recoverable /// without loading new firmware (not currently supported by this /// crate) - pub fn boot(&self, sure: bool) -> Result> { + pub async fn boot(&self, sure: bool) -> Result> { if !sure { return Err(Error::Serialise( "Are you sure? This is not recoverable", @@ -689,21 +726,21 @@ impl Nxt { let mut pkt = Packet::new(Opcode::SystemBootcmd); pkt.push_slice(b"Let's dance: SAMBA\0"); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; Ok(recv.read_slice(4)?.to_vec()) } /// Set the NXT brick's name to the provided value - pub fn set_brick_name(&self, name: &str) -> Result<()> { + pub async fn set_brick_name(&self, name: &str) -> Result<()> { let mut pkt = Packet::new(Opcode::SystemSetbrickname); pkt.push_str(name, MAX_NAME_LEN)?; - self.send(&pkt, true) + self.send(&pkt, true).await } /// Retrieve the Bluetooth address of the brick - pub fn get_bt_addr(&self) -> Result<[u8; 6]> { + pub async fn get_bt_addr(&self) -> Result<[u8; 6]> { let pkt = Packet::new(Opcode::SystemBtgetaddr); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let addr = recv.read_slice(6)?; Ok(addr.try_into().unwrap()) } @@ -713,9 +750,9 @@ impl Nxt { /// * Bluetooth address /// * Signal strength of connected bricks /// * Available flash memory - pub fn get_device_info(&self) -> Result { + pub async fn get_device_info(&self) -> Result { let pkt = Packet::new(Opcode::SystemDeviceinfo); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let name = recv.read_string(MAX_NAME_LEN)?; let bt_addr = [ recv.read_u8()?, @@ -744,26 +781,26 @@ impl Nxt { } /// Delete user flash storage - pub fn delete_user_flash(&self) -> Result<()> { + pub async fn delete_user_flash(&self) -> Result<()> { let pkt = Packet::new(Opcode::SystemDeleteuserflash); - self.send(&pkt, true) + self.send(&pkt, true).await } /// Poll the USB buffer for a command? - pub fn poll_command_length(&self, buf: BufType) -> Result { + pub async fn poll_command_length(&self, buf: BufType) -> Result { let mut pkt = Packet::new(Opcode::SystemPollcmdlen); pkt.push_u8(buf as u8); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let _buf_num = recv.read_u8()?; recv.read_u8() } /// Poll the USB buffer for a command? - pub fn poll_command(&self, buf: BufType, len: u8) -> Result> { + pub async fn poll_command(&self, buf: BufType, len: u8) -> Result> { let mut pkt = Packet::new(Opcode::SystemPollcmd); pkt.push_u8(buf as u8); pkt.push_u8(len); - let mut recv = self.send_recv(&pkt)?; + let mut recv = self.send_recv(&pkt).await?; let _buf = recv.read_u8()?; let len = recv.read_u8()?; let data = recv.read_slice(len as usize)?; @@ -771,8 +808,8 @@ impl Nxt { } /// Factory reset the bluetooth module - pub fn bluetooth_factory_reset(&self) -> Result<()> { + pub async fn bluetooth_factory_reset(&self) -> Result<()> { let pkt = Packet::new(Opcode::SystemBtfactoryreset); - self.send(&pkt, true) + self.send(&pkt, true).await } } diff --git a/src/socket.rs b/src/socket.rs index 72d3a7c..ff7e131 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -6,14 +6,18 @@ use crate::Result; #[cfg(feature = "usb")] pub mod usb; +#[cfg(feature = "bluetooth")] +pub mod bluetooth; + /// Abstraction over various socket types (namely USB and Bluetooth) to /// allow the base NXT struct to transparently use any supported backend +#[async_trait::async_trait] pub trait Socket { /// Send the provided data over the socket, returning the number of /// bytes sent - fn send(&self, data: &[u8]) -> Result; + async fn send(&self, data: &[u8]) -> Result; /// Receive data from the socket into the provided buffer, returning /// the subslice that was read into - fn recv<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf [u8]>; + async fn recv<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf [u8]>; } diff --git a/src/socket/bluetooth.rs b/src/socket/bluetooth.rs new file mode 100644 index 0000000..37b80d6 --- /dev/null +++ b/src/socket/bluetooth.rs @@ -0,0 +1,157 @@ +// //! Bluetooth protocol support + +// use super::Socket; +// use crate::{Error, Result}; +// use bluer::{ +// Adapter, AdapterEvent, Address, DeviceEvent, DiscoveryFilter, +// DiscoveryTransport, +// }; +// use futures::{pin_mut, stream::SelectAll, StreamExt}; +// use std::{collections::HashSet, sync::OnceLock}; +// use tokio::sync::{mpsc, oneshot}; + +// type BtMsg = (BtMsgType, oneshot::Sender); + +// enum BtMsgType { +// ListDiscovered, +// DiscoveredDevices { discovered: Vec
}, +// Connect { addr: Address }, +// ConnectStatus { addr: Address }, +// SendReq { addr: Address, pkt: Vec }, +// SendResp { len: usize }, +// RecvReq, +// RecvResp { pkt: Vec }, +// } + +// impl BtMsgType { +// fn send_resp(self) -> Result { +// let BtMsgType::SendResp(len) = self else { +// return Err(Error::Parse("Unexpected message type")); +// }; +// Ok(len) +// } +// fn recv_resp(self) -> Result> { +// let BtMsgType::RecvResp { pkt } = self else { +// return Err(Error::Parse("Unexpected message type")); +// }; +// Ok(pkt) +// } +// } + +// static BT_TX: OnceLock> = OnceLock::new(); + +// /// Observed device class advertised by NXT brick +// const NXT_DEVICE_CLASS: u32 = 0x804; + +// fn init_bt() -> mpsc::Sender { +// let (tx, rx) = mpsc::channel(10); + +// // spawn a tokio runtime in a background thread +// std::thread::spawn(move || { +// let rt = tokio::runtime::Builder::new_current_thread() +// .build() +// .unwrap(); +// rt.block_on(bluetooth_background_task(rx)); +// }); + +// tx +// } + +// async fn bluetooth_background_task(rx: mpsc::Receiver) { +// let session = bluer::Session::new().await.unwrap(); +// let adapter = session.default_adapter().await.unwrap(); +// adapter.set_powered(true).await.unwrap(); +// let device_events = adapter.discover_devices().await.unwrap(); +// pin_mut!(device_events); + +// let mut discovered_devices = HashSet::new(); +// loop { +// tokio::select! { +// Some(device_event) = device_events.next() => { +// handle_device_event( +// &adapter, +// &mut discovered_devices, +// device_event, +// ).await; +// } +// } +// } +// } + +// async fn handle_device_event( +// adapter: &Adapter, +// discovered_devices: &mut HashSet
, +// device_event: AdapterEvent, +// ) { +// match device_event { +// AdapterEvent::DeviceAdded(addr) => { +// println!("Device added: {addr:?}"); +// // check whether it looks like an NXT +// let device = adapter.device(addr).unwrap(); +// if device.class().await.unwrap_or_default() +// == Some(NXT_DEVICE_CLASS) +// { +// discovered_devices.insert(addr); +// } +// } +// AdapterEvent::DeviceRemoved(addr) => { +// println!("Device removed: {addr:?}"); +// discovered_devices.remove(&addr); +// } +// AdapterEvent::PropertyChanged(_) => {} +// } +// } + +pub struct Bluetooth { + // tx: mpsc::Sender, + // addr: Option
, +} + +// impl Default for Bluetooth { +// fn default() -> Self { +// Self::new() +// } +// } + +// impl Bluetooth { +// pub fn new() -> Self { +// let tx = BT_TX.get_or_init(init_bt).clone(); +// Self { tx, addr: None } +// } + +// pub fn connect(&self, addr: Address) -> Result<()> { +// let (tx, rx) = oneshot::channel(); +// self.tx.blocking_send(()) +// } +// } + +// impl Socket for Bluetooth { +// fn send(&self, data: &[u8]) -> Result { +// let (tx, rx) = oneshot::channel(); +// let Some(addr) = self.addr else { +// return Err(Error::NoBrick); +// }; +// self.tx +// .blocking_send(( +// BtMsgType::SendReq { +// addr, +// pkt: data.to_vec(), +// }, +// tx, +// )) +// .unwrap(); +// Ok(rx.blocking_recv().unwrap().send_resp().unwrap()) +// } + +// fn recv<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf [u8]> { +// let (tx, rx) = oneshot::channel(); +// self.tx.blocking_send((BtMsgType::RecvReq, tx)).unwrap(); +// let recv = rx.blocking_recv().unwrap().recv_resp().unwrap(); +// if recv.len() > buf.len() { +// Err(Error::Parse("Message longer than buffer")) +// } else { +// buf[..recv.len()].copy_from_slice(&recv); +// Ok(&buf[..recv.len()]) +// } +// } +// } diff --git a/src/socket/usb.rs b/src/socket/usb.rs index 875eb96..b5ff637 100644 --- a/src/socket/usb.rs +++ b/src/socket/usb.rs @@ -37,12 +37,13 @@ pub struct Usb { device: DeviceHandle, } +#[async_trait::async_trait] impl Socket for Usb { - fn send(&self, data: &[u8]) -> Result { + async fn send(&self, data: &[u8]) -> Result { Ok(self.device.write_bulk(WRITE_ENDPOINT, data, USB_TIMEOUT)?) } - fn recv<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf [u8]> { + async fn recv<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf [u8]> { let read = self.device.read_bulk(READ_ENDPOINT, buf, USB_TIMEOUT)?; Ok(&buf[..read]) }