message: refactor: Converted PrettyLove to use GenericCommandManager
  Also added tests to the protocol
diff: @@ -11,10 +11,12 @@ use crate::{
  };
  use futures::future::{self, BoxFuture};
  use std::sync::Arc;
  +use async_lock::Mutex;
  #[derive(ButtplugProtocolProperties)]
  pub struct PrettyLove {
  name: String,
  + manager: Arc<Mutex<GenericCommandManager>>,
  message_attributes: MessageAttributesMap,
  stop_commands: Vec<ButtplugDeviceCommandMessageUnion>,
  }
  @@ -30,6 +32,7 @@ impl ButtplugProtocol for PrettyLove {
  name: name.to_owned(),
  message_attributes,
  stop_commands: manager.get_stop_commands(),
  + manager: Arc::new(Mutex::new(manager)),
  })
  }
  @@ -46,20 +49,72 @@ impl ButtplugProtocolCommandHandler for PrettyLove {
  fn handle_vibrate_cmd(
  &self,
  device: Arc<Box<dyn DeviceImpl>>,
  - msg: messages::VibrateCmd,
  + message: messages::VibrateCmd,
  ) -> ButtplugDeviceResultFuture {
  - // TODO Convert to using generic command manager
  - let mut speed = (msg.speeds[0].speed * 3.0) as u8;
  - if speed == 0 {
  - speed = 0xff;
  + // Store off result before the match, so we drop the lock ASAP.
  + let manager = self.manager.clone();
  + Box::pin(async move {
  + let result = manager.lock().await.update_vibration(&message, false)?;
  + if let Some(cmds) = result {
  + if let Some(speed) = cmds[0] {
  + device.write_value(DeviceWriteCmd::new(
  + Endpoint::Tx,
  + vec![0x00, speed as u8],
  + false,
  + )).await?;
  + }
  }
  - let msg = DeviceWriteCmd::new(Endpoint::Tx, [0x00, speed].to_vec(), false);
  - let fut = device.write_value(msg);
  - Box::pin(async {
  - fut.await?;
  Ok(messages::Ok::default().into())
  })
  }
  }
  -// TODO Write tests
  +
  +#[cfg(test)]
  +mod test {
  + use crate::{
  + core::messages::{StopDeviceCmd, VibrateCmd, VibrateSubcommand},
  + device::{DeviceImplCommand, DeviceWriteCmd, Endpoint},
  + test::{check_recv_value, new_bluetoothle_test_device},
  + util::async_manager,
  + };
  +
  + #[test]
  + pub fn test_prettylove_protocol() {
  + async_manager::block_on(async move {
  + let (device, test_device) = new_bluetoothle_test_device("Aogu BLE Device").await.unwrap();
  + let command_receiver = test_device
  + .get_endpoint_channel(&Endpoint::Tx)
  + .unwrap()
  + .receiver;
  + device
  + .parse_message(VibrateCmd::new(0, vec![VibrateSubcommand::new(0, 0.5)]).into())
  + .await
  + .unwrap();
  + check_recv_value(
  + &command_receiver,
  + DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0x00, 0x02], false)),
  + )
  + .await;
  + assert!(command_receiver.is_empty());
  +
  + // Since we only created one subcommand, we should only receive one command.
  + device
  + .parse_message(VibrateCmd::new(0, vec![VibrateSubcommand::new(0, 0.5)]).into())
  + .await
  + .unwrap();
  + assert!(command_receiver.is_empty());
  +
  + device
  + .parse_message(StopDeviceCmd::new(0).into())
  + .await
  + .unwrap();
  + check_recv_value(
  + &command_receiver,
  + DeviceImplCommand::Write(DeviceWriteCmd::new(Endpoint::Tx, vec![0x00, 0x00], false)),
  + )
  + .await;
  + assert!(command_receiver.is_empty());
  + });
  + }
  +}
  