1use hidapi::{HidApi, HidDevice, HidResult};
2
3mod battery_level;
4
5pub mod headset;
6pub use self::headset::Headset;
7
8pub mod buttons;
9pub use self::buttons::{Button, Buttons};
10
11pub mod analog_sticks;
12pub use self::analog_sticks::{AnalogStick, AnalogSticks};
13
14pub mod touchpad;
15pub use self::touchpad::{Touchpad, TouchpadTouch};
16
17pub mod motion;
18pub use self::motion::Motion;
19
20const DUALSHOCK4_VENDOR_ID: u16 = 0x54c;
21
22const DUALSHOCK4_PRODUCT_ID_NEW: u16 = 0x9cc;
24const DUALSHOCK4_PRODUCT_ID_OLD: u16 = 0x5c4;
25
26const DUALSHOCK4_USB_RAW_BUFFER_DATA_LENGTH: usize = 64;
27const DUALSHOCK4_BLUETOOTH_RAW_BUFFER_DATA_LENGTH: usize = 10;
28
29#[derive(PartialEq, Debug)]
30pub enum ConnectionType {
31 Usb,
32 Bluetooth,
33}
34
35#[derive(PartialEq, Debug)]
36pub struct Dualshock4Data {
37 pub connection_type: ConnectionType,
38 pub battery_level: u8,
39 pub headset: Headset,
40 pub analog_sticks: AnalogSticks,
41 pub buttons: Buttons,
42 pub touchpad: Touchpad,
43 pub motion: Motion,
44}
45
46#[derive(Debug)]
47pub enum Dualshock4Error {
48 HidError(hidapi::HidError),
49 UnexpectedDataLength,
50}
51pub type Dualshock4Result<T> = Result<T, Dualshock4Error>;
52
53pub fn get_device(api: &HidApi) -> HidResult<HidDevice> {
55 api.open(DUALSHOCK4_VENDOR_ID, DUALSHOCK4_PRODUCT_ID_NEW)
56}
57
58pub fn get_device_old(api: &HidApi) -> HidResult<HidDevice> {
60 api.open(DUALSHOCK4_VENDOR_ID, DUALSHOCK4_PRODUCT_ID_OLD)
61}
62
63pub fn read(controller: &HidDevice) -> Dualshock4Result<Dualshock4Data> {
65 let mut buf = [0; DUALSHOCK4_USB_RAW_BUFFER_DATA_LENGTH];
66
67 match controller.read(&mut buf[..]) {
68 Ok(DUALSHOCK4_USB_RAW_BUFFER_DATA_LENGTH) => decode_buf(ConnectionType::Usb, &buf),
69 Ok(DUALSHOCK4_BLUETOOTH_RAW_BUFFER_DATA_LENGTH) => {
70 decode_buf(ConnectionType::Bluetooth, &buf)
71 }
72 Ok(_) => Err(Dualshock4Error::UnexpectedDataLength),
73 Err(err) => Err(Dualshock4Error::HidError(err)),
74 }
75}
76
77fn decode_buf(connection_type: ConnectionType, buf: &[u8]) -> Dualshock4Result<Dualshock4Data> {
78 let battery_level = battery_level::decode(buf);
79 let headset = headset::decode(buf);
80 let buttons = buttons::decode(buf);
81 let analog_sticks = analog_sticks::decode(buf);
82 let touchpad = touchpad::decode(buf);
83 let motion = motion::decode(buf);
84
85 Ok(Dualshock4Data {
86 connection_type,
87 battery_level,
88 headset,
89 analog_sticks,
90 buttons,
91 touchpad,
92 motion,
93 })
94}
95
96#[cfg(test)]
97mod tests {
98 use crate::*; use criterion::{Criterion, criterion_group, criterion_main};
100 use dualshock4::*;
101 use rand::Rng;
102 use scroll::Pwrite;
103 use std::hint::black_box;
104
105 fn rng_u8(range: std::ops::Range<u8>) -> u8 {
109 let mut rng = rand::rng();
110 rng.random_range(range)
111 }
112
113 fn rng_i16() -> i16 {
114 let mut rng = rand::rng();
115 rng.random()
116 }
117
118 fn rng_bool() -> bool {
119 let mut rng = rand::rng();
120 rng.random()
121 }
122
123 fn generate_test_usb_data(buf: &mut [u8]) -> Dualshock4Data {
127 let battery_level = generate_battery_level_data(buf);
128 let headset = generate_headset_data(buf);
129 let analog_sticks = generate_analog_sticks_data(buf);
130 let buttons = generate_buttons_data(buf);
131 let touchpad = generate_touchpad_data(buf);
132 let motion = generate_motion_data(buf);
133
134 Dualshock4Data {
135 connection_type: ConnectionType::Usb,
136 battery_level,
137 headset,
138 analog_sticks,
139 buttons,
140 touchpad,
141 motion,
142 }
143 }
144
145 fn generate_battery_level_data(buf: &mut [u8]) -> u8 {
146 let value = rng_u8(0..22);
147 buf[battery_level::DATA_BLOCK_BATTERY_LEVEL] = value;
148 value
149 }
150
151 fn generate_headset_data(buf: &mut [u8]) -> Headset {
152 let value = rng_u8(0..3);
153 buf[headset::DATA_BLOCK_HEADSET] = match value {
154 0 => headset::HEADSET_MASK_NONE,
155 1 => headset::HEADSET_MASK_HEADPHONES,
156 2 => headset::HEADSET_MASK_HEADSET_WITH_MIC,
157 _ => 0,
158 };
159 match value {
160 0 => Headset::None,
161 1 => Headset::Headphones,
162 2 => Headset::HeadsetWithMic,
163 _ => Headset::Unknown,
164 }
165 }
166
167 fn generate_analog_sticks_data(buf: &mut [u8]) -> AnalogSticks {
168 let left = generate_analog_stick_data(&analog_sticks::CONFIG.left, buf);
169 let right = generate_analog_stick_data(&analog_sticks::CONFIG.right, buf);
170 AnalogSticks { left, right }
171 }
172
173 fn generate_analog_stick_data(
174 config: &analog_sticks::AnalogStickConfig,
175 buf: &mut [u8],
176 ) -> AnalogStick {
177 let x = rng_u8(0..255);
178 let y = rng_u8(0..255);
179 buf[config.block_x] = x;
180 buf[config.block_y] = y;
181 AnalogStick { x, y }
182 }
183
184 fn generate_buttons_data(buf: &mut [u8]) -> Buttons {
185 Buttons {
186 x: generate_button_data(buttons::CONFIG.x, buf),
187 square: generate_button_data(buttons::CONFIG.square, buf),
188 triangle: generate_button_data(buttons::CONFIG.triangle, buf),
189 circle: generate_button_data(buttons::CONFIG.circle, buf),
190 dpad_up: generate_button_data(buttons::CONFIG.dpad_up, buf),
191 dpad_up_right: generate_button_data(buttons::CONFIG.dpad_up_right, buf),
192 dpad_right: generate_button_data(buttons::CONFIG.dpad_right, buf),
193 dpad_down_right: generate_button_data(buttons::CONFIG.dpad_down_right, buf),
194 dpad_down: generate_button_data(buttons::CONFIG.dpad_down, buf),
195 dpad_down_left: generate_button_data(buttons::CONFIG.dpad_down_left, buf),
196 dpad_left: generate_button_data(buttons::CONFIG.dpad_left, buf),
197 dpad_up_left: generate_button_data(buttons::CONFIG.dpad_up_left, buf),
198 share: generate_button_data(buttons::CONFIG.share, buf),
199 options: generate_button_data(buttons::CONFIG.options, buf),
200 psx: generate_button_data(buttons::CONFIG.psx, buf),
201 touchpad: generate_button_data(buttons::CONFIG.touchpad, buf),
202 l1: generate_button_data(buttons::CONFIG.l1, buf),
203 r1: generate_button_data(buttons::CONFIG.r1, buf),
204 left_stick: generate_button_data(buttons::CONFIG.left_stick, buf),
205 right_stick: generate_button_data(buttons::CONFIG.right_stick, buf),
206 l2: generate_button_data(buttons::CONFIG.l2, buf),
207 r2: generate_button_data(buttons::CONFIG.r2, buf),
208 }
209 }
210
211 fn generate_button_data(config: buttons::ButtonConfig, buf: &mut [u8]) -> Button {
212 static mut IS_DPAD_PRESSED: bool = false;
213 let is_dpad_up_config = config.value == buttons::CONFIG.dpad_up.value;
214 let mut is_pressed = rng_bool();
215
216 if config.block == 0x05 && config.value < 0x08 {
217 unsafe {
218 if IS_DPAD_PRESSED {
219 is_pressed = false;
220 } else if is_pressed {
221 IS_DPAD_PRESSED = true;
222 }
223 }
224 }
225
226 if !is_pressed && is_dpad_up_config {
227 buf[config.block] += 0x08;
228 }
229
230 if is_pressed && !is_dpad_up_config {
231 buf[config.block] += config.value;
232 }
233
234 let analog_value = config.analog_block.map(|block| {
235 let val = rng_u8(0..255);
236 buf[block] += val;
237 val
238 });
239
240 Button {
241 pressed: is_pressed,
242 analog_value,
243 }
244 }
245
246 fn generate_touchpad_data(buf: &mut [u8]) -> Touchpad {
247 Touchpad {
248 touch_1: generate_touchpad_touch_data(touchpad::CONFIG.touch_1, buf),
249 touch_2: generate_touchpad_touch_data(touchpad::CONFIG.touch_2, buf),
250 }
251 }
252
253 fn generate_touchpad_touch_data(
254 config: touchpad::TouchpadTouchConfig,
255 buf: &mut [u8],
256 ) -> TouchpadTouch {
257 const TOUCHPAD_RESOLUTION_WIDTH: u16 = 943;
258 const TOUCHPAD_RESOLUTION_HEIGHT: u16 = 1920;
259
260 let active = rng_bool();
261 let mut x = None;
262 let mut y = None;
263
264 if active {
265 let temp_x = rng_u8(0..TOUCHPAD_RESOLUTION_WIDTH as u8) as u16;
266 let temp_y = rng_u8(0..TOUCHPAD_RESOLUTION_HEIGHT as u8) as u16;
267
268 buf[config.data_block_a] = (temp_x & 0xff) as u8;
269 buf[config.data_block_b] = (((temp_y | 0xf0) << 4) ^ ((temp_x | 0x0f) >> 8)) as u8;
270 buf[config.data_block_c] = (temp_y >> 4) as u8;
271
272 x = Some(temp_x);
273 y = Some(temp_y);
274 }
275
276 buf[config.active_block] = if active { 0x00 } else { 0xff };
277
278 TouchpadTouch { active, x, y }
279 }
280
281 fn generate_motion_data(buf: &mut [u8]) -> Motion {
282 let x = rng_i16();
283 let y = rng_i16();
284 let z = rng_i16();
285 let roll = rng_i16();
286 let yaw = rng_i16();
287 let pitch = rng_i16();
288
289 buf.pwrite_with::<i16>(x, motion::CONFIG.motion_x, scroll::BE)
290 .unwrap();
291 buf.pwrite_with::<i16>(y, motion::CONFIG.motion_y, scroll::BE)
292 .unwrap();
293 buf.pwrite_with::<i16>(z, motion::CONFIG.motion_z, scroll::BE)
294 .unwrap();
295 buf.pwrite_with::<i16>(roll, motion::CONFIG.gyro_x, scroll::BE)
296 .unwrap();
297 buf.pwrite_with::<i16>(yaw, motion::CONFIG.gyro_y, scroll::BE)
298 .unwrap();
299 buf.pwrite_with::<i16>(pitch, motion::CONFIG.gyro_z, scroll::BE)
300 .unwrap();
301
302 Motion {
303 x,
304 y,
305 z,
306 roll,
307 yaw,
308 pitch,
309 }
310 }
311
312 #[test]
316 fn test_decode_usb_buf_unit() {
317 let mut buf = [0u8; DUALSHOCK4_USB_RAW_BUFFER_DATA_LENGTH];
318 let data = generate_test_usb_data(&mut buf);
319 assert!(data.battery_level <= 21);
320 assert!(matches!(
321 data.headset,
322 Headset::None | Headset::Headphones | Headset::HeadsetWithMic | Headset::Unknown
323 ));
324 }
325
326 fn bench_decode_buf(c: &mut Criterion) {
330 c.bench_function("decode_usb_buf", |b| {
331 b.iter(|| {
332 for _ in 0..1000 {
333 let mut buf = [0u8; DUALSHOCK4_USB_RAW_BUFFER_DATA_LENGTH];
334 black_box(generate_test_usb_data(&mut buf));
335 }
336 })
337 });
338 }
339
340 criterion_group!(benches, bench_decode_buf);
341 criterion_main!(benches);
342}