| // Copyright 2023 Google LLC | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| use bt_common::debug_command::CommandRunner; | |
| use bt_common::debug_command::CommandSet; | |
| use bt_common::gen_commandset; | |
| use bt_gatt::{ | |
| client::{PeerService, PeerServiceHandle}, | |
| Client, | |
| }; | |
| use crate::*; | |
| gen_commandset! { | |
| PacsCmd { | |
| Print = ("print", [], [], "Print the current PACS status"), | |
| } | |
| } | |
| pub struct PacsDebug<T: bt_gatt::GattTypes> { | |
| client: T::Client, | |
| } | |
| impl<T: bt_gatt::GattTypes> PacsDebug<T> { | |
| pub fn new(client: T::Client) -> Self { | |
| Self { client } | |
| } | |
| } | |
| impl<T: bt_gatt::GattTypes> CommandRunner for PacsDebug<T> { | |
| type Set = PacsCmd; | |
| type Error = bt_gatt::types::Error; | |
| type Event = (); | |
| fn run_command( | |
| &self, | |
| _cmd: Self::Set, | |
| _args: Vec<String>, | |
| ) -> impl futures::Future<Output = Result<(), Self::Error>> { | |
| async { | |
| // Since there is only one command, Print, we just print | |
| // all the characteristics that are at the remote PACS server. | |
| let handles = self.client.find_service(PACS_UUID).await?; | |
| for handle in handles { | |
| let service = handle.connect().await?; | |
| let chrs = service.discover_characteristics(None).await?; | |
| for chr in chrs { | |
| match chr.uuid { | |
| SourcePac::UUID => { | |
| let source_pac = SourcePac::try_read::<T>(chr, &service).await?; | |
| println!("{source_pac:?}"); | |
| } | |
| SinkPac::UUID => { | |
| let sink_pac = SinkPac::try_read::<T>(chr, &service).await?; | |
| println!("{sink_pac:?}"); | |
| } | |
| SinkAudioLocations::UUID => { | |
| let locations = | |
| SinkAudioLocations::try_read::<T>(chr, &service).await?; | |
| println!("{locations:?}"); | |
| } | |
| SourceAudioLocations::UUID => { | |
| let locations = | |
| SourceAudioLocations::try_read::<T>(chr, &service).await?; | |
| println!("{locations:?}"); | |
| } | |
| AvailableAudioContexts::UUID => { | |
| let contexts = | |
| AvailableAudioContexts::try_read::<T>(chr, &service).await?; | |
| println!("{contexts:?}"); | |
| } | |
| SupportedAudioContexts::UUID => { | |
| let contexts = | |
| SupportedAudioContexts::try_read::<T>(chr, &service).await?; | |
| println!("{contexts:?}"); | |
| } | |
| _x => println!("Unrecognized Chr {}", chr.uuid.recognize()), | |
| } | |
| } | |
| } | |
| Ok::<(), bt_gatt::types::Error>(()) | |
| } | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| use bt_common::debug_command::CliCommand; | |
| use bt_gatt::test_utils::{FakeClient, FakeTypes}; | |
| use log::LevelFilter; | |
| fn setup_fake_pacs_debug() -> PacsDebug<FakeTypes> { | |
| PacsDebug::new(FakeClient::new()) | |
| } | |
| #[test] | |
| fn verbosity_controls_and_flags() { | |
| let debug = setup_fake_pacs_debug(); | |
| log::set_max_level(LevelFilter::Info); | |
| // Verbose with no args displays current verbosity without changing it | |
| let res = futures::executor::block_on(debug.run(CliCommand::Verbose, vec![])); | |
| assert!(res.is_ok()); | |
| assert_eq!(log::max_level(), LevelFilter::Info); | |
| // Update verbosity to Debug | |
| let res = | |
| futures::executor::block_on(debug.run(CliCommand::Verbose, vec!["debug".to_string()])); | |
| assert!(res.is_ok()); | |
| assert_eq!(log::max_level(), LevelFilter::Debug); | |
| // Update verbosity to Trace | |
| let res = | |
| futures::executor::block_on(debug.run(CliCommand::Verbose, vec!["trace".to_string()])); | |
| assert!(res.is_ok()); | |
| assert_eq!(log::max_level(), LevelFilter::Trace); | |
| // Update verbosity to Off | |
| let res = | |
| futures::executor::block_on(debug.run(CliCommand::Verbose, vec!["off".to_string()])); | |
| assert!(res.is_ok()); | |
| assert_eq!(log::max_level(), LevelFilter::Off); | |
| // Invalid level does not change verbosity | |
| let res = futures::executor::block_on( | |
| debug.run(CliCommand::Verbose, vec!["invalid_level".to_string()]), | |
| ); | |
| assert!(res.is_ok()); | |
| assert_eq!(log::max_level(), LevelFilter::Off); | |
| // Reset back to Info for scoped flags testing | |
| log::set_max_level(LevelFilter::Info); | |
| // Executing Print with -v flag: | |
| // 1. debug.run() strips the flag before run_command. | |
| // 2. Temporarily elevates max log level during execution. | |
| // 3. Executes successfully. | |
| let res = futures::executor::block_on(debug.run(PacsCmd::Print, vec!["-v".to_string()])); | |
| assert!(res.is_ok()); | |
| // Persistent verbosity remains Info after scoped execution completes | |
| assert_eq!(log::max_level(), LevelFilter::Info); | |
| // Executing Print with -vv flag elevates to Trace | |
| let res = futures::executor::block_on(debug.run(PacsCmd::Print, vec!["-vv".to_string()])); | |
| assert!(res.is_ok()); | |
| assert_eq!(log::max_level(), LevelFilter::Info); | |
| } | |
| } |