Buttplug sex toy control library
1pub mod communication;
2use std::{collections::HashSet, fmt::Debug, sync::Arc, time::Duration};
3
4use async_trait::async_trait;
5use buttplug_core::errors::ButtplugDeviceError;
6use buttplug_server_device_config::{Endpoint, ProtocolCommunicationSpecifier};
7use futures::future::BoxFuture;
8use futures_util::FutureExt;
9use getset::{CopyGetters, Getters};
10use instant::Instant;
11use serde::{Deserialize, Serialize};
12use tokio::sync::{RwLock, broadcast};
13use uuid::Uuid;
14
15/// Parameters for reading data from a [Hardware](crate::device::Hardware) endpoint
16///
17/// Low level read command structure, used by
18/// [ButtplugProtocol](crate::device::protocol::ButtplugProtocol) implementations when working with
19/// [Hardware](crate::device::Hardware) structures.
20#[derive(PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize, CopyGetters)]
21#[getset(get_copy = "pub")]
22pub struct HardwareReadCmd {
23 /// Feature ID for reading
24 #[serde(default)]
25 command_id: Uuid,
26 /// Endpoint to read from
27 endpoint: Endpoint,
28 /// Amount of data to read from endpoint
29 length: u32,
30 /// Timeout for reading data
31 timeout_ms: u32,
32}
33
34impl HardwareReadCmd {
35 /// Creates a new DeviceReadCmd instance
36 pub fn new(command_id: Uuid, endpoint: Endpoint, length: u32, timeout_ms: u32) -> Self {
37 Self {
38 command_id,
39 endpoint,
40 length,
41 timeout_ms,
42 }
43 }
44}
45
46/// Parameters for writing data to a [Hardware](crate::device::Hardware) endpoint
47///
48/// Low level write command structure, used by
49/// [ButtplugProtocol](crate::device::protocol::ButtplugProtocol) implementations when working with
50/// [Hardware](crate::device::Hardware) structures.
51#[derive(Eq, Debug, Clone, Serialize, Deserialize, Getters, CopyGetters)]
52pub struct HardwareWriteCmd {
53 /// Feature ID for this command. As a write command can possibly write to multiple features in one
54 /// call, this can have multiple feature IDs.
55 #[getset(get = "pub")]
56 #[serde(default)]
57 command_id: HashSet<Uuid>,
58 /// Endpoint to write to
59 #[getset(get_copy = "pub")]
60 endpoint: Endpoint,
61 /// Data to write to endpoint
62 #[getset(get = "pub")]
63 data: Vec<u8>,
64 /// Only used with Bluetooth LE writing. If true, use WriteWithResponse commands when sending data to device.
65 #[getset(get_copy = "pub")]
66 write_with_response: bool,
67}
68
69impl PartialEq for HardwareWriteCmd {
70 fn eq(&self, other: &Self) -> bool {
71 self.endpoint() == other.endpoint()
72 && self.data() == other.data()
73 && self.write_with_response() == other.write_with_response()
74 }
75}
76
77impl HardwareWriteCmd {
78 /// Create a new DeviceWriteCmd instance.
79 pub fn new(
80 command_id: &[Uuid],
81 endpoint: Endpoint,
82 data: Vec<u8>,
83 write_with_response: bool,
84 ) -> Self {
85 Self {
86 command_id: HashSet::from_iter(command_id.iter().cloned()),
87 endpoint,
88 data,
89 write_with_response,
90 }
91 }
92}
93
94/// Parameters for subscribing to a [Hardware](crate::device::Hardware) endpoint
95///
96/// Low level subscribe structure, used by
97/// [ButtplugProtocol](crate::device::protocol::ButtplugProtocol) implementations when working with
98/// [Hardware](crate::device::Hardware) structures.
99///
100/// While usually related to notify/indicate characteristics on Bluetooth LE devices, can be used
101/// with any read endpoint to signal that any information received should be automatically passed to
102/// the protocol implementation.
103#[derive(Eq, Debug, Clone, Copy, Serialize, Deserialize, CopyGetters)]
104#[getset(get_copy = "pub")]
105pub struct HardwareSubscribeCmd {
106 /// Feature ID for this command
107 #[getset(get_copy = "pub")]
108 #[serde(default)]
109 command_id: Uuid,
110 /// Endpoint to subscribe to notifications from.
111 endpoint: Endpoint,
112}
113
114impl PartialEq for HardwareSubscribeCmd {
115 fn eq(&self, other: &Self) -> bool {
116 self.endpoint() == other.endpoint()
117 }
118}
119
120impl HardwareSubscribeCmd {
121 /// Create a new DeviceSubscribeCmd instance
122 pub fn new(command_id: Uuid, endpoint: Endpoint) -> Self {
123 Self {
124 command_id,
125 endpoint,
126 }
127 }
128}
129
130/// Parameters for unsubscribing from a [Hardware](crate::device::Hardware) endpoint that has
131/// previously been subscribed.
132///
133/// Low level subscribe structure, used by
134/// [ButtplugProtocol](crate::device::protocol::ButtplugProtocol) implementations when working with
135/// [Hardware](crate::device::Hardware) structures.
136#[derive(Eq, Debug, Clone, Copy, Serialize, Deserialize, CopyGetters)]
137#[getset(get_copy = "pub")]
138pub struct HardwareUnsubscribeCmd {
139 #[serde(default)]
140 command_id: Uuid,
141 endpoint: Endpoint,
142}
143
144impl PartialEq for HardwareUnsubscribeCmd {
145 fn eq(&self, other: &Self) -> bool {
146 self.endpoint() == other.endpoint()
147 }
148}
149
150impl HardwareUnsubscribeCmd {
151 /// Create a new DeviceUnsubscribeCmd instance
152 pub fn new(command_id: Uuid, endpoint: Endpoint) -> Self {
153 Self {
154 command_id,
155 endpoint,
156 }
157 }
158}
159
160/// Enumeration of all possible commands that can be sent to a
161/// [Hardware](crate::device::Hardware).
162#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
163pub enum HardwareCommand {
164 Write(HardwareWriteCmd),
165 // Read not included here because it needs to be called directly so the response can be handled.
166 Subscribe(HardwareSubscribeCmd),
167 Unsubscribe(HardwareUnsubscribeCmd),
168}
169
170impl HardwareCommand {
171 pub fn overlaps(&self, command: &HardwareCommand) -> bool {
172 // There is probably a cleaner way to write these match branches to drop the if/else and default
173 // out to false, but I can't figure it out right now.
174 match self {
175 HardwareCommand::Write(c) => {
176 if let HardwareCommand::Write(write) = command {
177 c.command_id().intersection(&write.command_id).count() > 0
178 } else {
179 false
180 }
181 }
182 HardwareCommand::Subscribe(c) => {
183 if let HardwareCommand::Subscribe(sub) = command {
184 c.command_id() == sub.command_id
185 } else {
186 false
187 }
188 }
189 HardwareCommand::Unsubscribe(c) => {
190 if let HardwareCommand::Unsubscribe(sub) = command {
191 c.command_id() == sub.command_id
192 } else {
193 false
194 }
195 }
196 }
197 }
198}
199
200impl From<HardwareWriteCmd> for HardwareCommand {
201 fn from(msg: HardwareWriteCmd) -> Self {
202 HardwareCommand::Write(msg)
203 }
204}
205
206impl From<HardwareSubscribeCmd> for HardwareCommand {
207 fn from(msg: HardwareSubscribeCmd) -> Self {
208 HardwareCommand::Subscribe(msg)
209 }
210}
211
212impl From<HardwareUnsubscribeCmd> for HardwareCommand {
213 fn from(msg: HardwareUnsubscribeCmd) -> Self {
214 HardwareCommand::Unsubscribe(msg)
215 }
216}
217
218#[derive(Debug, Clone, Getters)]
219#[getset(get = "pub")]
220pub struct HardwareReading {
221 endpoint: Endpoint,
222 data: Vec<u8>,
223}
224
225impl HardwareReading {
226 pub fn new(endpoint: Endpoint, data: &[u8]) -> Self {
227 Self {
228 endpoint,
229 data: data.to_vec(),
230 }
231 }
232}
233
234/// Events that can be emitted from a [Hardware](crate::device::Hardware).
235#[derive(Debug, Clone)]
236pub enum HardwareEvent {
237 /// Device received data
238 Notification(String, Endpoint, Vec<u8>),
239 /// Device disconnected
240 Disconnected(String),
241}
242
243/// Hardware implementation and communication portion of a
244/// [ButtplugDevice](crate::device::ButtplugDevice) instance. The Hardware contains a
245/// HardwareInternal, which handles all of the actual hardware communication. However, the struct
246/// also needs to carry around identifying information, so we wrap it in this type instead of
247/// requiring that all implementors of deal with name/address/endpoint accessors.
248#[derive(CopyGetters, Getters)]
249pub struct Hardware {
250 /// Device name
251 #[getset(get = "pub")]
252 name: String,
253 /// Device address
254 #[getset(get = "pub")]
255 address: String,
256 /// Communication endpoints
257 #[getset(get = "pub")]
258 endpoints: Vec<Endpoint>,
259 /// Minimum time between two packets being sent to the device. Used to deal with congestion across
260 /// protocols like Bluetooth LE, which have guaranteed delivery but can be overloaded due to
261 /// connection intervals.
262 #[getset(get_copy = "pub")]
263 message_gap: Option<Duration>,
264 /// Internal implementation details
265 internal_impl: Box<dyn HardwareInternal>,
266 /// Requires a keepalive signal to be sent by the Server Device class
267 #[getset(get_copy = "pub")]
268 requires_keepalive: bool,
269 last_write_time: Arc<RwLock<Instant>>,
270}
271
272impl Hardware {
273 pub fn new(
274 name: &str,
275 address: &str,
276 endpoints: &[Endpoint],
277 message_gap: &Option<Duration>,
278 requires_keepalive: bool,
279 internal_impl: Box<dyn HardwareInternal>,
280 ) -> Self {
281 Self {
282 name: name.to_owned(),
283 address: address.to_owned(),
284 endpoints: endpoints.into(),
285 message_gap: *message_gap,
286 internal_impl,
287 requires_keepalive,
288 last_write_time: Arc::new(RwLock::new(Instant::now())),
289 }
290 }
291
292 pub async fn time_since_last_write(&self) -> Duration {
293 Instant::now().duration_since(*self.last_write_time.read().await)
294 }
295
296 /// Returns a receiver for any events the device may emit.
297 ///
298 /// This uses a broadcast channel and can be called multiple times to create multiple streams if
299 /// needed.
300 pub fn event_stream(&self) -> broadcast::Receiver<HardwareEvent> {
301 self.internal_impl.event_stream()
302 }
303
304 /// Disconnect from the device (if it is connected)
305 pub fn disconnect(&self) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
306 self.internal_impl.disconnect()
307 }
308
309 pub fn parse_message(
310 &self,
311 command: &HardwareCommand,
312 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
313 match command {
314 HardwareCommand::Write(cmd) => self.write_value(cmd),
315 HardwareCommand::Subscribe(cmd) => self.subscribe(cmd),
316 HardwareCommand::Unsubscribe(cmd) => self.unsubscribe(cmd),
317 }
318 }
319
320 /// Read a value from the device
321 pub fn read_value(
322 &self,
323 msg: &HardwareReadCmd,
324 ) -> BoxFuture<'static, Result<HardwareReading, ButtplugDeviceError>> {
325 self.internal_impl.read_value(msg)
326 }
327
328 /// Write a value to the device
329 pub fn write_value(
330 &self,
331 msg: &HardwareWriteCmd,
332 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
333 let write_fut = self.internal_impl.write_value(msg);
334 if self.requires_keepalive {
335 let last_write_time = self.last_write_time.clone();
336 async move {
337 *last_write_time.write().await = Instant::now();
338 write_fut.await
339 }
340 .boxed()
341 } else {
342 write_fut
343 }
344 }
345
346 /// Subscribe to a device endpoint, if it exists
347 pub fn subscribe(
348 &self,
349 msg: &HardwareSubscribeCmd,
350 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
351 self.internal_impl.subscribe(msg)
352 }
353
354 /// Unsubscribe from a device endpoint, if it exists
355 pub fn unsubscribe(
356 &self,
357 msg: &HardwareUnsubscribeCmd,
358 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
359 self.internal_impl.unsubscribe(msg)
360 }
361}
362
363/// Internal representation of device implementations
364///
365/// This trait is implemented by
366/// [DeviceCommunicationManager](crate::server::device::communication_manager::DeviceCommunicationManager) modules
367/// to represent and communicate with devices. It provides an abstract way to represent devices
368/// without having to consider what type of communication bus they may be using.
369pub trait HardwareInternal: Sync + Send {
370 /// Disconnect from the device (if it is connected)
371 fn disconnect(&self) -> BoxFuture<'static, Result<(), ButtplugDeviceError>>;
372 /// Returns a receiver for any events the device may emit.
373 fn event_stream(&self) -> broadcast::Receiver<HardwareEvent>;
374 /// Read a value from the device
375 fn read_value(
376 &self,
377 msg: &HardwareReadCmd,
378 ) -> BoxFuture<'static, Result<HardwareReading, ButtplugDeviceError>>;
379 /// Write a value to the device
380 fn write_value(
381 &self,
382 msg: &HardwareWriteCmd,
383 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>>;
384 /// Subscribe to a device endpoint, if it exists
385 fn subscribe(
386 &self,
387 msg: &HardwareSubscribeCmd,
388 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>>;
389 /// Unsubscribe from a device endpoint, if it exists
390 fn unsubscribe(
391 &self,
392 msg: &HardwareUnsubscribeCmd,
393 ) -> BoxFuture<'static, Result<(), ButtplugDeviceError>>;
394}
395
396#[async_trait]
397pub trait HardwareConnector: Sync + Send + Debug {
398 /// Return the hardware identifier for the device. Depends on the communication bus type, so may
399 /// be a bluetooth name, serial port name, etc...
400 fn specifier(&self) -> ProtocolCommunicationSpecifier;
401 async fn connect(&mut self) -> Result<Box<dyn HardwareSpecializer>, ButtplugDeviceError>;
402}
403
404#[async_trait]
405pub trait HardwareSpecializer: Sync + Send {
406 /// Try to initialize a device.
407 ///
408 /// Given a
409 /// [ProtocolDeviceConfiguration](crate::server::device::configuration::ProtocolDeviceConfiguration)
410 /// which will contain information about what a protocol needs to communicate with a device, try
411 /// to identify all required endpoints on the hardware.
412 async fn specialize(
413 &mut self,
414 protocol: &[ProtocolCommunicationSpecifier],
415 ) -> Result<Hardware, ButtplugDeviceError>;
416}
417
418/// Used in cases where there's nothing to specialize for the protocol.
419pub struct GenericHardwareSpecializer {
420 hardware: Option<Hardware>,
421}
422
423impl GenericHardwareSpecializer {
424 pub fn new(hardware: Hardware) -> Self {
425 Self {
426 hardware: Some(hardware),
427 }
428 }
429}
430
431#[async_trait]
432impl HardwareSpecializer for GenericHardwareSpecializer {
433 async fn specialize(
434 &mut self,
435 _: &[ProtocolCommunicationSpecifier],
436 ) -> Result<Hardware, ButtplugDeviceError> {
437 Ok(self.hardware.take().expect("This should only be run once"))
438 }
439}