fork
Configure Feed
Select the types of activity you want to include in your feed.
Buttplug sex toy control library
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}