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