blob: e0a53571ab313484224de5b7d8c407bcfb743ac0 [file] [log] [blame]
// Copyright 2017 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.
#![deny(warnings)]
extern crate failure;
extern crate fidl;
extern crate fuchsia_app as component;
extern crate fuchsia_async as async;
extern crate fuchsia_zircon as zx;
extern crate futures;
extern crate fidl_bluetooth as bt;
extern crate fidl_bluetooth_gatt as gatt;
extern crate fidl_bluetooth_low_energy as le;
extern crate fidl_fuchsia_power;
extern crate parking_lot;
mod cancelable_future;
use component::client::connect_to_service;
use cancelable_future::{Cancelable, CancelHandle};
use failure::{Error, Fail};
use futures::prelude::*;
use futures::future::{FutureResult, ok as fok};
use fidl_fuchsia_power::{
PowerManager,
PowerManagerMarker,
PowerManagerWatcher,
PowerManagerWatcherImpl,
PowerManagerWatcherMarker,
};
use gatt::ServiceDelegate;
use le::PeripheralDelegate;
use parking_lot::Mutex;
use std::collections::HashSet;
use std::fmt;
use std::sync::Arc;
const BATTERY_LEVEL_ID: u64 = 0;
const BATTERY_SERVICE_UUID: &'static str = "0000180f-0000-1000-8000-00805f9b34fb";
const BATTERY_LEVEL_UUID: &'static str = "00002A19-0000-1000-8000-00805f9b34fb";
// Name used when advertising.
const DEVICE_NAME: &'static str = "FX BLE Battery";
// TODO(TO-930): extract this somewhere common
fn catch_and_log_err<F>(ctx: &'static str, f: F) -> FutureResult<(), Never>
where F: FnOnce() -> Result<(), fidl::Error>
{
let res = f();
if let Err(e) = res {
eprintln!("Error running fidl handler {}: {:?}", ctx, e);
}
fok(())
}
#[derive(Debug)]
struct BluetoothError(bt::Error);
impl fmt::Display for BluetoothError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.0.description {
Some(msg) => f.write_str(msg),
None => write!(f, "unknown bluetooth error"),
}
}
}
impl Fail for BluetoothError {}
struct BatteryState {
// The current battery percentage.
level: u8,
// The proxy we use to send GATT characteristic value notifications.
service: gatt::LocalServiceProxy,
// A set of remote LE device IDs that have subscribed to battery level
// notifications.
configs: HashSet<String>,
}
fn gatt_service_delegate(state: Arc<Mutex<BatteryState>>, channel: async::Channel)
-> impl Future<Item = (), Error = Never>
{
gatt::ServiceDelegateImpl {
state,
on_characteristic_configuration: |state, _, peer_id, notify, indicate, _|
catch_and_log_err("on_characteristic_configuration", || {
println!(
"Peer configured characteristic (notify: {}, indicate: {}, id: {})",
notify,
indicate,
peer_id
);
let configs = &mut state.lock().configs;
if notify {
configs.insert(peer_id);
} else {
configs.remove(&peer_id);
}
Ok(())
}
),
on_read_value: |state, _, _, resp| catch_and_log_err("on_read_value", || {
resp.send(
&mut Some(vec![state.lock().level]),
&mut gatt::ErrorCode::NoError,
)
}),
on_write_value: |_state, _, _, _, resp| catch_and_log_err("on_write_value", || {
resp.send(&mut gatt::ErrorCode::NotPermitted)
}),
on_write_without_response: |_state, _, _, _, resp| catch_and_log_err("on_write_without_response", || {
Ok(())
}),
}
.serve(channel)
.recover(|e| eprintln!("error running gatt service delegate: {:?}", e))
}
fn power_manager_watcher(state: Arc<Mutex<BatteryState>>, channel: async::Channel)
-> impl Future<Item = (), Error = Never>
{
PowerManagerWatcherImpl {
state,
on_change_battery_status: |state, battery_status, _|
catch_and_log_err("on_change_battery_status", || {
let state = &mut state.lock();
let level = battery_status.level.round() as u8;
// Notify subscribed clients if the integer value of the battery level has changed.
if state.level != level {
println!("Battery percentage changed ({}%)", level);
for peer_id in &state.configs {
let _ = state.service.notify_value(
&mut BATTERY_LEVEL_ID,
&mut peer_id.clone(),
&mut vec![level],
&mut false,
);
}
}
state.level = level;
Ok(())
}
),
}
.serve(channel)
.recover(|e| eprintln!("error running power manager watcher: {:?}", e))
}
// Start LE advertising to listen for connections. Advertising is stopped when a
// central connects and restarted when it disconnects.
fn start_advertising(state_rc: Arc<Mutex<BatteryPeripheralState>>) -> Result<(), Error> {
println!("Listening for BLE centrals...");
let ad = le::AdvertisingData {
name: Some(DEVICE_NAME.to_owned()),
tx_power_level: None,
appearance: None,
service_uuids: Some(vec![BATTERY_SERVICE_UUID.to_string()]),
service_data: None,
manufacturer_specific_data: None,
solicited_service_uuids: None,
uris: None,
};
let (delegate_local, delegate_remote) = zx::Channel::create()?;
let delegate_local = async::Channel::from_channel(delegate_local)?;
let delegate_ptr = fidl::endpoints2::ClientEnd::<le::PeripheralDelegateMarker>::new(delegate_remote);
let mut state = state_rc.lock();
let start_adv = state.peripheral.start_advertising(
&mut ad,
&mut None,
&mut Some(delegate_ptr),
&mut 60,
&mut false,
);
state.delegate_handle = CancelHandle::new();
let start_server =
Cancelable::new(
peripheral_delegate(state_rc.clone(), delegate_local),
&state.delegate_handle,
);
// Spin up the PeripheralDelegate server.
async::spawn(start_adv.and_then(|_| start_server).recover(
|e| eprintln!("Failed to start advertising {:?}", e)));
Ok(())
}
struct BatteryPeripheralState {
peripheral: le::PeripheralProxy,
delegate_handle: CancelHandle,
}
fn peripheral_delegate(state: Arc<Mutex<BatteryPeripheralState>>, channel: async::Channel)
-> impl Future<Item = (), Error = Never>
{
le::PeripheralDelegateImpl {
state,
on_central_connected: |_state, _, central, _| catch_and_log_err("on_central_connected", || {
println!("Central connected: {}", central.identifier);
Ok(())
}),
on_central_disconnected: |state, device_id, _| catch_and_log_err("on_central_disconnected", || {
println!("Central disconnected: {}", device_id);
{
let state = state.lock();
state.delegate_handle.cancel();
}
if let Err(e) = start_advertising(state.clone()) {
eprintln!("@@ Failed to start advertising {:?}", e);
}
Ok(())
}),
}
.serve(channel)
.recover(|e| eprintln!("error running peripheral delegate: {:?}", e))
}
fn main() {
if let Err(e) = main_res() {
println!("Error: {:?}", e);
}
}
fn main_res() -> Result<(), Error> {
let listen = &match std::env::args().nth(1) {
Some(flag) => {
match flag.as_ref() {
"--listen" => true,
"--help" => {
println!("Options:\n --listen: listen for BLE connections");
return Ok(());
}
_ => {
println!("invalid argument: {}", flag);
return Ok(());
}
}
}
None => false,
};
let mut exec = async::Executor::new()?;
let server = connect_to_service::<gatt::Server_Marker>()?;
let power = connect_to_service::<PowerManagerMarker>()?;
// No security is required.
let read_sec = Box::new(gatt::SecurityRequirements {
encryption_required: false,
authentication_required: false,
authorization_required: false,
});
let update_sec = Box::new(gatt::SecurityRequirements {
encryption_required: false,
authentication_required: false,
authorization_required: false,
});
// Build a GATT Battery service.
let characteristic = gatt::Characteristic {
id: BATTERY_LEVEL_ID,
type_: BATTERY_LEVEL_UUID.to_string(),
properties: gatt::PROPERTY_READ | gatt::PROPERTY_NOTIFY,
permissions: Some(Box::new(gatt::AttributePermissions {
read: Some(read_sec),
write: None,
update: Some(update_sec),
})),
descriptors: None,
};
let service_info = gatt::ServiceInfo {
id: 0,
primary: true,
type_: BATTERY_SERVICE_UUID.to_string(),
characteristics: Some(vec![characteristic]),
includes: None,
};
// Register a power watcher to monitor the power level.
let (power_watcher_local, power_watcher_remote) = zx::Channel::create()?;
let power_watcher_local = async::Channel::from_channel(power_watcher_local)?;
let mut watcher_ptr = fidl::endpoints2::ClientEnd::<PowerManagerWatcherMarker>::new(power_watcher_remote);
power.watch(&mut watcher_ptr)?;
// Publish service and register service delegate.
let (service_local, service_remote) = zx::Channel::create()?;
let service_local = async::Channel::from_channel(service_local)?;
let mut service_server = fidl::endpoints2::ServerEnd::<gatt::LocalServiceMarker>::new(service_remote);
let service_proxy = gatt::LocalServiceProxy::new(service_local);
let (delegate_local, delegate_remote) = zx::Channel::create()?;
let delegate_local = async::Channel::from_channel(delegate_local)?;
let mut delegate_ptr = fidl::endpoints2::ClientEnd::<gatt::ServiceDelegateMarker>::new(delegate_remote);
let publish = server
.publish_service(&mut service_info, &mut delegate_ptr, &mut service_server)
.map_err(|e| Error::from(e.context("Publishing service error")))
.and_then(|status| match status.error {
None => Ok(()),
Some(e) => Err(Error::from(BluetoothError(*e))),
});
// This stores the current battery level and a list of peer device IDs that
// have subscribed to battery level notifications.
let state = Arc::new(Mutex::new(BatteryState {
level: 0 as u8,
service: service_proxy,
configs: HashSet::new(),
}));
// Spin up the power watcher to start handling events.
let power_watcher_server = power_manager_watcher(state.clone(), power_watcher_local);
// Set up the GATT service delegate.
let service_delegate_server = gatt_service_delegate(state.clone(), delegate_local);
// Listen for incoming connections if the user requested it. Otherwise, this
// will simply publish the GATT service without advertising.
if listen {
let peripheral = connect_to_service::<le::PeripheralMarker>()?;
let peripheral_state = Arc::new(Mutex::new(BatteryPeripheralState {
peripheral: peripheral,
delegate_handle: CancelHandle::new(),
}));
start_advertising(peripheral_state)?;
}
let main_fut = publish.and_then(|()| power_watcher_server.join(service_delegate_server));
exec.run(main_fut, /* threads */ 2)
.map(|((), ())| ())
}