blob: f312d89e028587106ddb832e0d89e2d1145f7821 [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::{bail, Error, ResultExt},
fidl_fuchsia_bluetooth_gatt as gatt,
fidl_fuchsia_power::{
PowerManagerMarker, PowerManagerWatcherMarker, PowerManagerWatcherRequest,
PowerManagerWatcherRequestStream,
},
fuchsia_app::client::connect_to_service,
fuchsia_async::{
self as fasync,
futures::{select, FutureExt, TryStreamExt},
},
parking_lot::Mutex,
pin_utils::pin_mut,
std::collections::HashSet,
};
const BATTERY_SERVICE_UUID: &str = "0000180f-0000-1000-8000-00805f9b34fb";
const BATTERY_LEVEL_UUID: &str = "00002A19-0000-1000-8000-00805f9b34fb";
const BATTERY_LEVEL_ID: u64 = 0;
/// Struct to manage all the shared state of the tool.
struct BatteryState {
inner: Mutex<BatteryStateInner>,
}
impl BatteryState {
pub fn new(service: gatt::LocalServiceProxy) -> BatteryState {
BatteryState {
inner: Mutex::new(BatteryStateInner {
level: 0,
service,
peers: HashSet::new(),
}),
}
}
/// Add a new peer to the set of peers interested in notifications
/// on changes to the battery level.
pub fn add_peer(&self, peer_id: String) {
self.inner.lock().peers.insert(peer_id);
}
/// Remove a peer from the set of peers interested in notifications
/// on changes to the battery level.
pub fn remove_peer(&self, peer_id: &str) {
self.inner.lock().peers.remove(peer_id);
}
/// Get the last reported level of the battery as a percentage.
pub fn get_level(&self) -> u8 {
self.inner.lock().level
}
/// Set the level to the given value and notify any interested peers
/// of the change.
pub fn set_level(&self, level: u8) -> Result<(), Error> {
let mut inner = self.inner.lock();
if inner.level != level {
println!("Battery percentage changed ({}%)", level);
for peer_id in inner.peers.iter() {
inner.service.notify_value(
BATTERY_LEVEL_ID,
&peer_id,
&mut vec![level].into_iter(),
false,
)?;
}
}
inner.level = level;
Ok(())
}
}
/// Inner data fields used for the `BatteryState` struct.
struct BatteryStateInner {
/// 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.
peers: HashSet<String>,
}
/// Handle a stream of incoming gatt battery service requests.
/// Returns when the channel backing the stream closes or an error occurs while handling requests.
async fn gatt_service_delegate(
state: &BatteryState, mut stream: gatt::LocalServiceDelegateRequestStream,
) -> Result<(), Error> {
while let Some(request) = await!(stream.try_next()).context("error running service delegate")? {
use fidl_fuchsia_bluetooth_gatt::LocalServiceDelegateRequest::*;
match request {
OnCharacteristicConfiguration {
peer_id,
notify,
indicate,
..
} => {
println!(
"Peer configured characteristic (notify: {}, indicate: {}, id: {})",
notify, indicate, peer_id
);
if notify {
state.add_peer(peer_id);
} else {
state.remove_peer(&peer_id);
}
}
OnReadValue { responder, .. } => {
let mut value_iter = vec![state.get_level()].into_iter();
responder.send(Some(&mut value_iter), gatt::ErrorCode::NoError)?;
}
OnWriteValue { responder, .. } => {
// Writing to the battery level characteristic is not permitted.
responder.send(gatt::ErrorCode::NotPermitted)?;
}
OnWriteWithoutResponse { .. } => {}
}
}
Ok(())
}
/// Handle a stream of incoming power notifications, updating state.
/// Returns when the channel backing the stream closes or an error occurs while handling requests.
async fn power_manager_watcher(
state: &BatteryState, mut stream: PowerManagerWatcherRequestStream,
) -> Result<(), Error> {
while let Some(PowerManagerWatcherRequest::OnChangeBatteryStatus { battery_status, .. }) =
await!(stream.try_next()).context("error running power manager watcher")?
{
let level = battery_status.level.round() as u8;
state.set_level(level)?;
}
Ok(())
}
fn main() -> Result<(), Error> {
let mut exec = fasync::Executor::new()?;
// Create endpoints for the required services.
let (power_watcher_client, power_watcher_request_stream) =
fidl::endpoints::create_request_stream::<PowerManagerWatcherMarker>()?;
let (delegate_client, delegate_request_stream) =
fidl::endpoints::create_request_stream::<gatt::LocalServiceDelegateMarker>()?;
let (service_proxy, service_server) = fidl::endpoints::create_proxy()?;
let gatt_server = connect_to_service::<gatt::Server_Marker>()?;
let power_manager_server = connect_to_service::<PowerManagerMarker>()?;
// Initialize internal state.
let state = BatteryState::new(service_proxy);
// Require encryption to access the battery level.
let read_sec = Box::new(gatt::SecurityRequirements {
encryption_required: true,
authentication_required: false,
authorization_required: false,
});
let update_sec = Box::new(gatt::SecurityRequirements {
encryption_required: true,
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 mut service_info = gatt::ServiceInfo {
id: 0,
primary: true,
type_: BATTERY_SERVICE_UUID.to_string(),
characteristics: Some(vec![characteristic]),
includes: None,
};
// Register the local power watcher with the power manager service.
power_manager_server.watch(power_watcher_client)?;
let main_fut = async move {
// Publish the local gatt service delegate with the gatt service.
let status = await!(gatt_server.publish_service(
&mut service_info,
delegate_client,
service_server
))?;
if let Some(error) = status.error {
bail!(
"Failed to publish battery service to gatt server: {:?}",
error
);
}
println!("Published Battery Service to local device database.");
// Start the gatt service delegate and power watcher server.
let service_delegate = gatt_service_delegate(&state, delegate_request_stream);
let power_watcher = power_manager_watcher(&state, power_watcher_request_stream);
pin_mut!(service_delegate);
pin_mut!(power_watcher);
select! {
res = service_delegate.fuse() => res?,
res = power_watcher.fuse() => res?,
};
Ok(())
};
exec.run_singlethreaded(main_fut)
}