Skip to content

Commit 6fdb848

Browse files
committed
feat: Add ServeU Protocol implementation
1 parent 5dfbe85 commit 6fdb848

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed

buttplug/src/server/device/protocol/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub mod satisfyer;
9595
pub mod sensee;
9696
pub mod sensee_capsule;
9797
pub mod sensee_v2;
98+
pub mod serveu;
9899
pub mod svakom;
99100
pub mod svakom_alex;
100101
pub mod svakom_alex_v2;
@@ -486,6 +487,10 @@ pub fn get_default_protocol_map() -> HashMap<String, Arc<dyn ProtocolIdentifierF
486487
&mut map,
487488
sensee_v2::setup::SenseeV2IdentifierFactory::default(),
488489
);
490+
add_to_protocol_map(
491+
&mut map,
492+
serveu::setup::ServeUIdentifierFactory::default(),
493+
);
489494
add_to_protocol_map(&mut map, svakom::setup::SvakomIdentifierFactory::default());
490495
add_to_protocol_map(
491496
&mut map,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
8+
use crate::{
9+
core::{errors::ButtplugDeviceError, message::Endpoint},
10+
server::device::{
11+
hardware::{HardwareCommand, HardwareWriteCmd},
12+
protocol::{generic_protocol_setup, ProtocolHandler},
13+
},
14+
};
15+
use std::sync::{Arc, atomic::{AtomicU8, Ordering}};
16+
17+
generic_protocol_setup!(ServeU, "serveu");
18+
19+
#[derive(Default)]
20+
pub struct ServeU {
21+
last_position: Arc<AtomicU8>
22+
}
23+
24+
impl ProtocolHandler for ServeU {
25+
26+
fn handle_linear_cmd(
27+
&self,
28+
message: crate::core::message::LinearCmdV4,
29+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
30+
let last_pos = self.last_position.load(Ordering::Relaxed);
31+
let current_cmd = message.vectors().first().ok_or(ButtplugDeviceError::DeviceFeatureCountMismatch(1, 0))?;
32+
// Need to get "units" (abstracted steps 0-100) per second, so calculate how far we need to move over our goal duration.
33+
let goal_pos =(current_cmd.position() * 100f64).ceil() as u8;
34+
self.last_position.store(goal_pos, Ordering::Relaxed);
35+
let speed_threshold = ((((goal_pos as i8) - last_pos as i8).abs()) as f64 / ((current_cmd.duration() as f64) / 1000f64)).ceil();
36+
37+
let speed = if speed_threshold <= 0.00001 {
38+
// Stop device
39+
0
40+
} else if speed_threshold <= 50.0 {
41+
(speed_threshold / 2.0).ceil() as u8
42+
} else if speed_threshold <= 750.0 {
43+
((speed_threshold - 50.0) / 4.0).ceil() as u8 + 25u8
44+
} else if speed_threshold <= 2000.0 {
45+
((speed_threshold - 750.0) / 25.0).ceil() as u8 + 200u8
46+
} else {
47+
// If we're going faster than 2000u/s, just return max value (0xFA)
48+
0xFA
49+
};
50+
51+
Ok(vec![HardwareWriteCmd::new(
52+
Endpoint::Tx,
53+
vec![0x01, goal_pos, speed],
54+
false,
55+
)
56+
.into()])
57+
}
58+
}

buttplug/tests/test_device_protocols.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ async fn load_test_case(test_file: &str) -> DeviceTestCase {
114114
#[test_case("test_deepsire.yaml" ; "DeepSire Protocol")]
115115
#[test_case("test_xuanhuan_protocol.yaml" ; "Xuanhuan Protocol")]
116116
#[test_case("test_tcode_linear_and_vibrate.yaml" ; "TCode (Linear + Vibrate)")]
117+
#[test_case("test_serveu_protocol.yaml" ; "ServeU")]
117118
#[tokio::test]
118119
async fn test_device_protocols_embedded_v3(test_file: &str) {
119120
//tracing_subscriber::fmt::init();
@@ -218,6 +219,7 @@ async fn test_device_protocols_embedded_v3(test_file: &str) {
218219
#[test_case("test_deepsire.yaml" ; "DeepSire Protocol")]
219220
#[test_case("test_xuanhuan_protocol.yaml" ; "Xuanhuan Protocol")]
220221
#[test_case("test_tcode_linear_and_vibrate.yaml" ; "TCode (Linear + Vibrate)")]
222+
#[test_case("test_serveu_protocol.yaml" ; "ServeU")]
221223
#[tokio::test]
222224
async fn test_device_protocols_json_v3(test_file: &str) {
223225
//tracing_subscriber::fmt::init();
@@ -293,6 +295,7 @@ async fn test_device_protocols_json_v3(test_file: &str) {
293295
#[test_case("test_deepsire.yaml" ; "DeepSire Protocol")]
294296
#[test_case("test_xuanhuan_protocol.yaml" ; "Xuanhuan Protocol")]
295297
#[test_case("test_tcode_linear_and_vibrate.yaml" ; "TCode (Linear + Vibrate)")]
298+
#[test_case("test_serveu_protocol.yaml" ; "ServeU")]
296299
#[tokio::test]
297300
async fn test_device_protocols_embedded_v2(test_file: &str) {
298301
util::device_test::client::client_v2::run_embedded_test_case(&load_test_case(test_file).await)
@@ -367,6 +370,7 @@ async fn test_device_protocols_embedded_v2(test_file: &str) {
367370
#[test_case("test_deepsire.yaml" ; "DeepSire Protocol")]
368371
#[test_case("test_xuanhuan_protocol.yaml" ; "Xuanhuan Protocol")]
369372
#[test_case("test_tcode_linear_and_vibrate.yaml" ; "TCode (Linear + Vibrate)")]
373+
#[test_case("test_serveu_protocol.yaml" ; "ServeU")]
370374
#[tokio::test]
371375
async fn test_device_protocols_json_v2(test_file: &str) {
372376
util::device_test::client::client_v2::run_json_test_case(&load_test_case(test_file).await).await;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
devices:
2+
- identifier:
3+
name: "ServeU"
4+
expected_name: "ServeU"
5+
device_commands:
6+
# Commands
7+
#
8+
# Test @ 30 u/s
9+
- !Messages
10+
device_index: 0
11+
messages:
12+
- !Linear
13+
- Index: 0
14+
Position: 0.30
15+
Duration: 1000
16+
- !Commands
17+
device_index: 0
18+
commands:
19+
- !Write
20+
endpoint: tx
21+
data: [0x01, 30, 15]
22+
write_with_response: false
23+
# Test @ 250 u/s
24+
- !Messages
25+
device_index: 0
26+
messages:
27+
- !Linear
28+
- Index: 0
29+
Position: 0.80
30+
Duration: 200
31+
- !Commands
32+
device_index: 0
33+
commands:
34+
- !Write
35+
endpoint: tx
36+
data: [0x01, 80, 75]
37+
write_with_response: false
38+
# Test @ 1000 u/s
39+
- !Messages
40+
device_index: 0
41+
messages:
42+
- !Linear
43+
- Index: 0
44+
Position: 1.0
45+
Duration: 10
46+
- !Commands
47+
device_index: 0
48+
commands:
49+
- !Write
50+
endpoint: tx
51+
data: [0x01, 100, 250]
52+
write_with_response: false
53+
# Test same position/stop
54+
- !Messages
55+
device_index: 0
56+
messages:
57+
- !Linear
58+
- Index: 0
59+
Position: 1.0
60+
Duration: 10
61+
- !Commands
62+
device_index: 0
63+
commands:
64+
- !Write
65+
endpoint: tx
66+
data: [0x01, 100, 0]
67+
write_with_response: false

0 commit comments

Comments
 (0)