vcs_classic_hid/lib.rs
1//! VCS Classic Joystick HID library.
2//!
3//! This crate uses `hidapi`
4//! for finding and using Atari VCS classic joysticks.
5//!
6//! With this crate, an assortment of facilities are provided
7//! for reading the current state of the device and,
8//! more importantly, send force feedback and LED manipulation messages.
9//!
10//! ## Finding a device
11//!
12//! The functions [open], [open_serial], and [open_all]
13//! are helper functions for opening devices for access to a classic controller
14//! via [`hidapi`](hidapi).
15//! The result provides an [`HidDevice`](hidapi::HidDevice).
16//!
17//! ```no_run
18//! # fn main() -> Result<(), hidapi::HidError> {
19//! let device = vcs_classic_hid::open()?;
20//! # Ok(())
21//! # }
22//! ```
23//!
24//! ## Using the device
25//!
26//! Raw access to the HID device can still be done
27//! through the interface provided,
28//! but a set of facilities are provided through
29//! the implemented [`Device`] trait
30//! and additional functions and data types.
31//!
32//! ### Receiving input state
33//!
34//! A [State] value represents a possible input state
35//! of the classic controller,
36//! and can be created from a device report via
37//! [`State::from_report`](crate::State::from_report).
38//! However, this is not very convenient, and may return stale input
39//!
40//! The function [process_input] handles all input state events in queue
41//! and returns a `State` instance.
42//!
43//! ```no_run
44//! # fn main() -> Result<(), hidapi::HidError> {
45//! let mut device = vcs_classic_hid::open()?;
46//! match vcs_classic_hid::process_input(&mut device)? {
47//! Some(state) => {
48//! // use state
49//! let _fuji_button_down = state.button_fuji;
50//! }
51//! None => {
52//! // no user input
53//! }
54//! }
55//! # Ok(())
56//! # }
57//! ```
58//!
59//! ### Changing LED state
60//!
61//! Both the light on the Fuji button and the ring of 24 LEDs
62//! can be manipulated programmatically.
63//! To set an exact state of the LEDs,
64//! create an [`LedReport`] and send it to the device.
65//!
66//! For example, to light up all LEDs in the controller's ring to the maximum:
67//!
68//! ```no_run
69//! use vcs_classic_hid::Device;
70//!
71//! # fn main() -> Result<(), hidapi::HidError> {
72//! # let mut device = vcs_classic_hid::open()?;
73//! let led = vcs_classic_hid::LedReport::filled(0xFF);
74//! Device::write(&mut device, led)?;
75//! # Ok(())
76//! # }
77//! ```
78//!
79//! To cancel LED manipulation and let the controller itself manipulate them
80//! based on user input
81//! (which is the default behavior), use [`reset_leds`](Device::reset_leds):
82//!
83//! ```no_run
84//! use vcs_classic_hid::Device;
85//!
86//! # fn main() -> Result<(), hidapi::HidError> {
87//! # let mut device = vcs_classic_hid::open()?;
88//! device.reset_leds()?;
89//! # Ok(())
90//! # }
91//! ```
92//!
93use std::ffi::CStr;
94
95pub use hidapi;
96use hidapi::{HidApi, HidDevice};
97
98pub mod force_feedback;
99pub mod led;
100pub mod input;
101
102pub use force_feedback::FfReport;
103pub use led::LedReport;
104pub use input::{State, StickPosition, process_input};
105
106/// Generic interface for human interaction devices.
107pub trait Device {
108 /// The type used for errors
109 type Error;
110
111 /// Set or unset blocking mode
112 fn set_blocking(&mut self, blocking: bool) -> Result<(), Self::Error>;
113
114 /// Read a report into the given array,
115 /// returns the number of bytes read.
116 fn read(&mut self, out: &mut [u8]) -> Result<usize, Self::Error>;
117
118 /// Write a report to the device,
119 /// returns the number of bytes effectively written.
120 ///
121 /// **Safety:** the operation is not memory unsafe,
122 /// but can still cause catastrophic problems to the device
123 /// depending on the data passed.
124 fn write<T>(&mut self, data: T) -> Result<usize, Self::Error>
125 where
126 T: AsRef<[u8]>;
127
128 /// Write a report which disables LED manipulation
129 /// in the VCS classic controller.
130 fn reset_leds(&mut self) -> Result<(), Self::Error> {
131 self.write(&[2, 0, 0, 0]).map(|_| ())
132 }
133}
134
135impl<D> Device for &mut D
136where
137 D: Device
138{
139 type Error = D::Error;
140
141 fn set_blocking(&mut self, blocking: bool) -> Result<(), Self::Error> {
142 (**self).set_blocking(blocking)
143 }
144
145 fn read(&mut self, out: &mut [u8]) -> Result<usize, Self::Error> {
146 (**self).read(out)
147 }
148
149 fn write<T>(&mut self, data: T) -> Result<usize, Self::Error>
150 where
151 T: AsRef<[u8]>,
152 {
153 (**self).write(data)
154 }
155}
156
157impl Device for HidDevice {
158 type Error = hidapi::HidError;
159
160 fn set_blocking(&mut self, blocking: bool) -> Result<(), Self::Error> {
161 HidDevice::set_blocking_mode(self, blocking)
162 }
163
164 fn read(&mut self, out: &mut [u8]) -> Result<usize, Self::Error> {
165 HidDevice::read(self, out)
166 }
167
168 fn write<T>(&mut self, data: T) -> Result<usize, Self::Error>
169 where
170 T: AsRef<[u8]>,
171 {
172 let data = data.as_ref();
173 let k = HidDevice::write(self, data)?;
174 if k != data.len() {
175 eprintln!("Expected to write {} bytes, but wrote {}", data.len(), k);
176 }
177 Ok(k)
178 }
179}
180
181const VENDOR_ID: u16 = 0x3250;
182const PRODUCT_ID: u16 = 0x1001;
183
184/// Inspect the list of devices available
185/// and open the first VCS classic controller device found.
186pub fn open() -> Result<hidapi::HidDevice, hidapi::HidError> {
187 let api = HidApi::new()?;
188 api.open(VENDOR_ID, PRODUCT_ID)
189}
190
191/// Inspect the list of devices available
192/// and open a classic controller device by path.
193///
194/// **Safety:** The function does not check whether the device
195/// behind the given path is actually the classic controller.
196pub fn open_path(device_path: &CStr) -> Result<hidapi::HidDevice, hidapi::HidError> {
197 let api = HidApi::new()?;
198 api.open_path(device_path)
199}
200
201//// Inspect the list of devices available
202/// and open a classic controller device by path.
203///
204/// Open a classic controller device by serial number.
205pub fn open_serial(sn: &str) -> Result<hidapi::HidDevice, hidapi::HidError> {
206 let api = HidApi::new()?;
207 api.open_serial(VENDOR_ID, PRODUCT_ID, sn)
208}
209
210/// Find and open all classic controller devices available into a list.
211pub fn open_all() -> Result<Vec<hidapi::HidDevice>, hidapi::HidError> {
212 let api = HidApi::new()?;
213 api.device_list()
214 .filter(|d| d.vendor_id() == VENDOR_ID && d.product_id() == PRODUCT_ID)
215 .map(|d| d.open_device(&api))
216 .collect()
217}