blob: 93df7e758da14852a5be5b51791d00ba9455eede [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![feature(futures_api, async_await, await_macro)]
use {
failure::{Error, Fail, ResultExt},
fidl::encoding::OutOfLine,
fidl_fuchsia_bluetooth_le::{CentralMarker, CentralProxy, ScanFilter},
fuchsia_bluetooth::{assigned_numbers::find_service_uuid, error::Error as BTError},
fuchsia_async::{
self as fasync,
temp::Either::{Left, Right},
},
futures::{future, prelude::*},
getopts::Options,
};
use crate::central::{listen_central_events, CentralState};
mod central;
mod gatt;
fn do_scan(
args: &[String], central: &CentralProxy,
) -> (Option<u64>, bool, impl Future<Output = Result<(), Error>>) {
let mut opts = Options::new();
opts.optflag("h", "help", "");
// Options for scan/connection behavior.
opts.optopt(
"s",
"scan-count",
"number of scan results to return before scanning is stopped",
"SCAN_COUNT",
);
opts.optflag(
"c",
"connect",
"connect to the first connectable scan result",
);
// Options for filtering scan results.
opts.optopt("n", "name-filter", "filter by device name", "NAME");
opts.optopt("u", "uuid-filter", "filter by UUID", "UUID");
let matches = match opts.parse(args) {
Ok(m) => m,
Err(fail) => {
return (None, false, Left(future::ready(Err(fail.into()))));
}
};
if matches.opt_present("h") {
let brief = "Usage: ble-central-tool scan (--connect|--scan-count=N) [--name-filter=NAME] \
[--uuid-filter=UUID]";
print!("{}", opts.usage(&brief));
return (
None,
false,
Left(future::ready(Err(BTError::new("invalid input").into()))),
);
}
let remaining_scan_results: Option<u64> = match matches.opt_str("s") {
Some(num) => match num.parse() {
Err(_) | Ok(0) => {
println!("{} is not a valid input \
- the value must be a positive non-zero number", num);
return (
None,
false,
Left(future::ready(Err(BTError::new("invalid input").into()))),
);
}
Ok(num) => Some(num),
}
None => None,
};
let connect: bool = matches.opt_present("c");
if remaining_scan_results.is_some() && connect {
println!("Cannot use both -s and -c options at the same time");
return (
None,
false,
Left(future::ready(Err(BTError::new("invalid input").into()))),
);
}
let uuids = match matches.opt_str("u") {
None => None,
Some(val) => {
let uuids = find_service_uuid(&val)
.map(|sn| sn.number.to_string())
.or_else(|| if val.len() == 36 { Some(val.clone()) } else { None })
.map(|uuid| vec![uuid]);
if uuids.is_none() {
println!("invalid service UUID: {}", val);
return (
None,
false,
Left(future::ready(Err(BTError::new("invalid input").into()))),
);
}
uuids
}
};
let name = matches.opt_str("n");
let mut filter = if uuids.is_some() || name.is_some() {
Some(ScanFilter {
service_uuids: uuids,
service_data_uuids: None,
manufacturer_identifier: None,
connectable: None,
name_substring: name,
max_path_loss: None,
})
} else {
None
};
let fut = Right(
central
.start_scan(filter.as_mut().map(OutOfLine))
.map_err(|e| e.context("failed to initiate scan").into())
.and_then(|status| future::ready(match status.error {
None => Ok(()),
Some(e) => Err(BTError::from(*e).into()),
})),
);
(remaining_scan_results, connect, fut)
}
async fn do_connect<'a>(args: &'a [String], central: &'a CentralProxy)
-> Result<(), Error>
{
if args.len() != 1 {
println!("connect: peer-id is required");
return Err(BTError::new("invalid input").into());
}
let (_, server_end) = fidl::endpoints::create_endpoints()?;
let status = await!(central.connect_peripheral(&args[0], server_end))
.context("failed to connect to peripheral")?;
match status.error {
None => Ok(()),
Some(e) => Err(BTError::from(*e).into()),
}
}
fn usage(appname: &str) -> () {
eprintln!(
"usage: {} <command>
commands:
scan: Scan for nearby devices and optionally connect to \
them (pass -h for additional usage)
connect: Connect to a peer using its ID",
appname
);
}
fn main() -> Result<(), Error> {
let args: Vec<String> = std::env::args().collect();
let appname = &args[0];
if args.len() < 2 {
usage(appname);
return Ok(());
}
let mut executor = fasync::Executor::new().context("error creating event loop")?;
let central_svc = fuchsia_app::client::connect_to_service::<CentralMarker>()
.context("Failed to connect to BLE Central service")?;
let state = CentralState::new(central_svc);
let command = &args[1];
let fut = async {
match command.as_str() {
"scan" => {
let fut = {
let mut central = state.write();
let (remaining_scan_results, connect, fut) = do_scan(&args[2..], central.get_svc());
central.remaining_scan_results = remaining_scan_results;
central.connect = connect;
fut
};
await!(fut)?
}
"connect" => {
let svc = state.read().get_svc().clone();
await!(do_connect(&args[2..], &svc))?
},
_ => {
println!("Invalid command: {}", command);
usage(appname);
return Err(BTError::new("invalid input").into());
}
}
await!(listen_central_events(state));
Ok(())
};
executor.run_singlethreaded(fut)
}