Skip to main content

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}