buttplug/server/device/protocol/
satisfyer.rs

1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved.
4//
5// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
6// for full license information.
7
8use crate::{
9  core::{
10    errors::ButtplugDeviceError,
11    message::{self, Endpoint},
12  },
13  server::device::{
14    configuration::{ProtocolCommunicationSpecifier, UserDeviceDefinition, UserDeviceIdentifier},
15    hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd},
16    protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer},
17  },
18  util::{async_manager, sleep},
19};
20use async_trait::async_trait;
21use std::{
22  sync::{
23    atomic::{AtomicU8, Ordering},
24    Arc,
25  },
26  time::Duration,
27};
28
29pub mod setup {
30  use crate::server::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory};
31  #[derive(Default)]
32  pub struct SatisfyerIdentifierFactory {}
33
34  impl ProtocolIdentifierFactory for SatisfyerIdentifierFactory {
35    fn identifier(&self) -> &str {
36      "satisfyer"
37    }
38
39    fn create(&self) -> Box<dyn ProtocolIdentifier> {
40      Box::new(super::SatisfyerIdentifier::default())
41    }
42  }
43}
44
45#[derive(Default)]
46pub struct SatisfyerIdentifier {}
47
48#[async_trait]
49impl ProtocolIdentifier for SatisfyerIdentifier {
50  async fn identify(
51    &mut self,
52    hardware: Arc<Hardware>,
53    specifier: ProtocolCommunicationSpecifier,
54  ) -> Result<(UserDeviceIdentifier, Box<dyn ProtocolInitializer>), ButtplugDeviceError> {
55    if let ProtocolCommunicationSpecifier::BluetoothLE(s) = specifier {
56      for md in s.manufacturer_data().iter() {
57        if let Some(data) = md.data() {
58          let device_identifier = format!(
59            "{}",
60            u32::from_be_bytes(data.to_vec().try_into().unwrap_or([0; 4]))
61          );
62          info!(
63            "Satisfyer Device Identifier (from advertisement): {:?} {}",
64            data, device_identifier
65          );
66
67          return Ok((
68            UserDeviceIdentifier::new(hardware.address(), "satisfyer", &Some(device_identifier)),
69            Box::new(SatisfyerInitializer::default()),
70          ));
71        }
72      }
73    }
74
75    let result = hardware
76      .read_value(&HardwareReadCmd::new(Endpoint::RxBLEModel, 128, 500))
77      .await?;
78    let device_identifier = format!(
79      "{}",
80      u32::from_be_bytes(result.data().to_vec().try_into().unwrap_or([0; 4]))
81    );
82    info!(
83      "Satisfyer Device Identifier (from RxBLEModel): {:?} {}",
84      result.data(),
85      device_identifier
86    );
87    return Ok((
88      UserDeviceIdentifier::new(hardware.address(), "satisfyer", &Some(device_identifier)),
89      Box::new(SatisfyerInitializer::default()),
90    ));
91  }
92}
93
94#[derive(Default)]
95pub struct SatisfyerInitializer {}
96
97#[async_trait]
98impl ProtocolInitializer for SatisfyerInitializer {
99  async fn initialize(
100    &mut self,
101    hardware: Arc<Hardware>,
102    device_definition: &UserDeviceDefinition,
103  ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
104    let msg = HardwareWriteCmd::new(Endpoint::Command, vec![0x01], true);
105    let info_fut = hardware.write_value(&msg);
106    info_fut.await?;
107
108    let feature_count = device_definition
109      .features()
110      .iter()
111      .filter(|x| x.actuator().is_some())
112      .count();
113    Ok(Arc::new(Satisfyer::new(hardware, feature_count)))
114  }
115}
116
117pub struct Satisfyer {
118  feature_count: usize,
119  last_command: Arc<Vec<AtomicU8>>,
120}
121
122fn form_command(feature_count: usize, data: Arc<Vec<AtomicU8>>) -> Vec<u8> {
123  data[0..feature_count]
124    .iter()
125    .map(|d| vec![d.load(Ordering::SeqCst); 4])
126    .collect::<Vec<Vec<u8>>>()
127    .concat()
128}
129
130// Satisfyer toys will drop their connections if they don't get an update within ~10 seconds.
131// Therefore we try to send a command every ~1s unless something is sent/updated sooner.
132async fn send_satisfyer_updates(
133  device: Arc<Hardware>,
134  feature_count: usize,
135  data: Arc<Vec<AtomicU8>>,
136) {
137  loop {
138    let command = form_command(feature_count, data.clone());
139    if let Err(e) = device
140      .write_value(&HardwareWriteCmd::new(Endpoint::Tx, command, false))
141      .await
142    {
143      error!(
144        "Got an error from a satisfyer device, exiting control loop: {:?}",
145        e
146      );
147      break;
148    }
149    sleep(Duration::from_secs(1)).await;
150  }
151}
152
153impl Satisfyer {
154  fn new(hardware: Arc<Hardware>, feature_count: usize) -> Self {
155    let last_command = Arc::new(
156      (0..feature_count)
157        .map(|_| AtomicU8::new(0))
158        .collect::<Vec<AtomicU8>>(),
159    );
160    let last_command_clone = last_command.clone();
161    async_manager::spawn(async move {
162      send_satisfyer_updates(hardware, feature_count, last_command_clone).await;
163    });
164
165    Self {
166      feature_count,
167      last_command,
168    }
169  }
170}
171
172impl ProtocolHandler for Satisfyer {
173  fn needs_full_command_set(&self) -> bool {
174    true
175  }
176
177  fn handle_scalar_cmd(
178    &self,
179    commands: &[Option<(message::ActuatorType, u32)>],
180  ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
181    if self.feature_count != commands.len() {
182      return Err(ButtplugDeviceError::DeviceFeatureCountMismatch(
183        self.feature_count as u32,
184        commands.len() as u32,
185      ));
186    }
187    for (i, item) in commands.iter().enumerate() {
188      let command_val = item.as_ref().unwrap().1 as u8;
189      self.last_command[i].store(command_val, Ordering::SeqCst);
190    }
191    let data = form_command(self.feature_count, self.last_command.clone());
192
193    Ok(vec![HardwareWriteCmd::new(Endpoint::Tx, data, false).into()])
194  }
195}