1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Counterpart to the Python example `battery_server.py`.
16 //!
17 //! Start an Android emulator from Android Studio, or otherwise have netsim running.
18 //!
19 //! Run the server from the project root:
20 //! ```
21 //! PYTHONPATH=. python examples/battery_server.py \
22 //!     examples/device1.json android-netsim
23 //! ```
24 //!
25 //! Then run this example from the `rust` directory:
26 //!
27 //! ```
28 //! PYTHONPATH=..:/path/to/virtualenv/site-packages/ \
29 //!     cargo run --example battery_client -- \
30 //!     --transport android-netsim \
31 //!     --target-addr F0:F1:F2:F3:F4:F5
32 //! ```
33 
34 use bumble::wrapper::{
35     device::{Device, Peer},
36     hci::{packets::AddressType, Address},
37     profile::BatteryServiceProxy,
38     transport::Transport,
39     PyObjectExt,
40 };
41 use clap::Parser as _;
42 use log::info;
43 use owo_colors::OwoColorize;
44 use pyo3::prelude::*;
45 
46 #[pyo3_asyncio::tokio::main]
main() -> PyResult<()>47 async fn main() -> PyResult<()> {
48     env_logger::builder()
49         .filter_level(log::LevelFilter::Info)
50         .init();
51 
52     let cli = Cli::parse();
53 
54     let transport = Transport::open(cli.transport).await?;
55 
56     let address = Address::new("F0:F1:F2:F3:F4:F5", AddressType::RandomDeviceAddress)?;
57     let device = Device::with_hci("Bumble", address, transport.source()?, transport.sink()?)?;
58 
59     device.power_on().await?;
60 
61     let conn = device.connect(&cli.target_addr).await?;
62     let mut peer = Peer::new(conn)?;
63     for mut s in peer.discover_services().await? {
64         s.discover_characteristics().await?;
65     }
66     let battery_service = peer
67         .create_service_proxy::<BatteryServiceProxy>()?
68         .ok_or(anyhow::anyhow!("No battery service found"))?;
69 
70     let mut battery_level_char = battery_service
71         .battery_level()?
72         .ok_or(anyhow::anyhow!("No battery level characteristic"))?;
73     info!(
74         "{} {}",
75         "Initial Battery Level:".green(),
76         battery_level_char
77             .read_value()
78             .await?
79             .extract_with_gil::<u32>()?
80     );
81     battery_level_char
82         .subscribe(|_py, args| {
83             info!(
84                 "{} {:?}",
85                 "Battery level update:".green(),
86                 args.get_item(0)?.extract::<u32>()?,
87             );
88             Ok(())
89         })
90         .await?;
91 
92     // wait until user kills the process
93     tokio::signal::ctrl_c().await?;
94     Ok(())
95 }
96 
97 #[derive(clap::Parser)]
98 #[command(author, version, about, long_about = None)]
99 struct Cli {
100     /// Bumble transport spec.
101     ///
102     /// <https://google.github.io/bumble/transports/index.html>
103     #[arg(long)]
104     transport: String,
105 
106     /// Address to connect to
107     #[arg(long)]
108     target_addr: String,
109 }
110