Skip to content

Commit 59e7dd4

Browse files
committed
Add Utimi protocol handler (benben lightspot BLE SDK)
Adds support for Utimi BLE toys using the benben lightspot protocol. Command format: A0 03 [vib] [thrust] [m2] [m3] [m4] AA on service 0000ffa0 / characteristic 0000ffa1. Similar to JoyHub but uses 5 motor slots (8 bytes) instead of 4 (7 bytes). Protocol reverse-engineered from the Utimi Android APK (com.benben.lightspot.bluetooth SDK).
1 parent 8ecb259 commit 59e7dd4

3 files changed

Lines changed: 141 additions & 0 deletions

File tree

  • crates
    • buttplug_server_device_config/device-config-v4/protocols
    • buttplug_server/src/device/protocol_impl

crates/buttplug_server/src/device/protocol_impl/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ pub mod thehandy_v3;
112112
pub mod tryfun;
113113
pub mod tryfun_blackhole;
114114
pub mod tryfun_meta2;
115+
pub mod utimi;
115116
pub mod vibcrafter;
116117
pub mod vibratissimo;
117118
pub mod vorze_sa;
@@ -538,6 +539,7 @@ pub fn get_default_protocol_map() -> HashMap<String, Arc<dyn ProtocolIdentifierF
538539
&mut map,
539540
tcode_v03::setup::TCodeV03IdentifierFactory::default(),
540541
);
542+
add_to_protocol_map(&mut map, utimi::setup::UtimiIdentifierFactory::default());
541543
add_to_protocol_map(
542544
&mut map,
543545
vibcrafter::setup::VibCrafterIdentifierFactory::default(),
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2+
//
3+
// Copyright 2016-2026 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::device::hardware::{HardwareCommand, HardwareWriteCmd};
9+
use crate::device::{
10+
hardware::Hardware,
11+
protocol::{
12+
ProtocolHandler,
13+
ProtocolIdentifier,
14+
ProtocolInitializer,
15+
generic_protocol_initializer_setup,
16+
},
17+
};
18+
use async_trait::async_trait;
19+
use buttplug_core::errors::ButtplugDeviceError;
20+
use buttplug_server_device_config::{
21+
Endpoint,
22+
ProtocolCommunicationSpecifier,
23+
ServerDeviceDefinition,
24+
UserDeviceIdentifier,
25+
};
26+
use std::sync::Arc;
27+
use std::sync::atomic::{AtomicU8, Ordering};
28+
use uuid::{Uuid, uuid};
29+
30+
const UTIMI_PROTOCOL_UUID: Uuid = uuid!("d4a3e2b1-7c56-4f89-a012-3b4c5d6e7f80");
31+
32+
generic_protocol_initializer_setup!(Utimi, "utimi");
33+
34+
#[derive(Default)]
35+
pub struct UtimiInitializer {}
36+
37+
#[async_trait]
38+
impl ProtocolInitializer for UtimiInitializer {
39+
async fn initialize(
40+
&mut self,
41+
_hardware: Arc<Hardware>,
42+
_def: &ServerDeviceDefinition,
43+
) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
44+
Ok(Arc::new(Utimi::default()))
45+
}
46+
}
47+
48+
pub struct Utimi {
49+
// Benben lightspot protocol supports up to 5 motor slots.
50+
// Utimi devices use slot 0 (vibrate) and slot 1 (thrust/oscillate).
51+
last_cmds: [AtomicU8; 5],
52+
}
53+
54+
impl Default for Utimi {
55+
fn default() -> Self {
56+
Self {
57+
last_cmds: [const { AtomicU8::new(0) }; 5],
58+
}
59+
}
60+
}
61+
62+
impl Utimi {
63+
fn send_command(
64+
&self,
65+
index: u32,
66+
speed: u32,
67+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
68+
self.last_cmds[index as usize].store(speed as u8, Ordering::Relaxed);
69+
Ok(vec![
70+
HardwareWriteCmd::new(
71+
&[UTIMI_PROTOCOL_UUID],
72+
Endpoint::Tx,
73+
vec![
74+
0xa0,
75+
0x03,
76+
self.last_cmds[0].load(Ordering::Relaxed),
77+
self.last_cmds[1].load(Ordering::Relaxed),
78+
self.last_cmds[2].load(Ordering::Relaxed),
79+
self.last_cmds[3].load(Ordering::Relaxed),
80+
self.last_cmds[4].load(Ordering::Relaxed),
81+
0xaa,
82+
],
83+
false,
84+
)
85+
.into(),
86+
])
87+
}
88+
}
89+
90+
impl ProtocolHandler for Utimi {
91+
fn handle_output_vibrate_cmd(
92+
&self,
93+
feature_index: u32,
94+
_feature_id: Uuid,
95+
speed: u32,
96+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
97+
self.send_command(feature_index, speed)
98+
}
99+
100+
fn handle_output_oscillate_cmd(
101+
&self,
102+
feature_index: u32,
103+
_feature_id: Uuid,
104+
speed: u32,
105+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
106+
self.send_command(feature_index, speed)
107+
}
108+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
defaults:
3+
name: Utimi Device
4+
features:
5+
- id: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
6+
output:
7+
vibrate:
8+
value:
9+
- 0
10+
- 255
11+
index: 0
12+
- id: b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e
13+
output:
14+
oscillate:
15+
value:
16+
- 0
17+
- 255
18+
index: 1
19+
id: c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f
20+
configurations:
21+
- identifier:
22+
- Utimi
23+
name: Utimi Prostate Massager
24+
id: d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f80
25+
communication:
26+
- btle:
27+
names:
28+
- Utimi*
29+
services:
30+
0000ffa0-0000-1000-8000-00805f9b34fb:
31+
tx: 0000ffa1-0000-1000-8000-00805f9b34fb

0 commit comments

Comments
 (0)