From 93240ef4afa0183d0672d8ec6771574adc6fd71a Mon Sep 17 00:00:00 2001 From: 4yn Date: Wed, 2 Feb 2022 15:14:56 +0800 Subject: [PATCH] voltex hid controller --- src-tauri/Cargo.lock | 102 +++++++++++++++++- src-tauri/Cargo.toml | 2 + src-tauri/src/bin/test_serial.rs | 11 ++ src-tauri/src/bin/test_usb.rs | 37 +++++-- src-tauri/src/slider_io/config.rs | 18 +++- src-tauri/src/slider_io/device.rs | 45 ++++---- src-tauri/src/slider_io/gamepad.rs | 101 ++++++++++++++++++ src-tauri/src/slider_io/keyboard.rs | 41 +++++--- src-tauri/src/slider_io/led.rs | 158 +++++++++++++++++++++------- src-tauri/src/slider_io/manager.rs | 11 +- src-tauri/src/slider_io/mod.rs | 7 +- src-tauri/src/slider_io/output.rs | 36 +++++-- src-tauri/src/slider_io/utils.rs | 34 ++++++ src-tauri/src/slider_io/voltex.rs | 39 +++++++ src-tauri/src/slider_io/worker.rs | 10 +- src/App.svelte | 37 +++++-- 16 files changed, 566 insertions(+), 123 deletions(-) create mode 100644 src-tauri/src/bin/test_serial.rs create mode 100644 src-tauri/src/slider_io/gamepad.rs create mode 100644 src-tauri/src/slider_io/utils.rs create mode 100644 src-tauri/src/slider_io/voltex.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3b56bf9..3db3b9a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2,6 +2,27 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "CoreFoundation-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" +dependencies = [ + "libc", + "mach 0.1.2", +] + +[[package]] +name = "IOKit-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a" +dependencies = [ + "CoreFoundation-sys", + "libc", + "mach 0.1.2", +] + [[package]] name = "adler" version = "1.0.2" @@ -1524,6 +1545,26 @@ version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50" +[[package]] +name = "libudev" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "libusb1-sys" version = "0.6.0" @@ -1587,6 +1628,24 @@ dependencies = [ "objc-foundation", ] +[[package]] +name = "mach" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" +dependencies = [ + "libc", +] + +[[package]] +name = "mach" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1745,6 +1804,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nix" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + [[package]] name = "nix" version = "0.17.0" @@ -2748,6 +2820,23 @@ dependencies = [ "url", ] +[[package]] +name = "serialport" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cd7c0f22290ee2c01457009fa6fc1cae4153d5608a924e5dc423babc2c655" +dependencies = [ + "CoreFoundation-sys", + "IOKit-sys", + "bitflags", + "cfg-if 0.1.10", + "libudev", + "mach 0.2.3", + "nix 0.16.1", + "regex", + "winapi", +] + [[package]] name = "servo_arc" version = "0.1.1" @@ -2800,8 +2889,10 @@ dependencies = [ "rusb", "serde", "serde_json", + "serialport", "tauri", "tauri-build", + "vigem-client", "winapi", ] @@ -3484,6 +3575,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vigem-client" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965e349c8ec4eb36c06878b99952f35b9f459e6912419837ecb85fb5502a6de3" +dependencies = [ + "winapi", +] + [[package]] name = "void" version = "1.0.2" @@ -3841,7 +3941,7 @@ dependencies = [ "fastrand", "futures", "nb-connect", - "nix", + "nix 0.17.0", "once_cell", "polling", "scoped-tls", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index efc213f..9ed1b4d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,6 +19,8 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.0.0-beta.8", features = ["api-all", "system-tray"] } rusb = "0.9.0" +serialport = "4.0.1" +vigem-client = "0.1.1" palette = "0.6.0" winapi = "0.3.9" directories = "4.0.1" diff --git a/src-tauri/src/bin/test_serial.rs b/src-tauri/src/bin/test_serial.rs new file mode 100644 index 0000000..e328361 --- /dev/null +++ b/src-tauri/src/bin/test_serial.rs @@ -0,0 +1,11 @@ +// extern crate slidershim; + +use serialport::available_ports; +use std::io; + +fn main() { + let res = available_ports(); + println!("{:?}", res); + let mut input = String::new(); + let string = io::stdin().read_line(&mut input).unwrap(); +} diff --git a/src-tauri/src/bin/test_usb.rs b/src-tauri/src/bin/test_usb.rs index abc291f..2bbea90 100644 --- a/src-tauri/src/bin/test_usb.rs +++ b/src-tauri/src/bin/test_usb.rs @@ -9,6 +9,31 @@ fn main() { .filter_level(log::LevelFilter::Debug) .init(); + // voltex? + let config = Config::from_str( + r#"{ + "deviceMode": "yuancon", + "outputMode": "gamepad-voltex", + "keyboardSensitivity": 50, + "ledMode": "reactive-voltex", + "ledSensitivity": 50 + }"#, + ) + .unwrap(); + + // serial? + // let config = Config::from_str( + // r#"{ + // "deviceMode": "yuancon", + // "outputMode": "kb-32-tasoller", + // "keyboardSensitivity": 50, + // "ledMode": "serial", + // "ledSerialPort": "COM5" + // }"#, + // ) + // .unwrap(); + + // basic // let config = Config::from_str( // r#"{ // "deviceMode": "yuancon", @@ -20,17 +45,7 @@ fn main() { // ) // .unwrap(); - let config = Config::from_str( - r#"{ - "deviceMode": "yuancon", - "outputMode": "kb-voltex", - "keyboardSensitivity": 50, - "ledMode": "reactive-voltex", - "ledSensitivity": 50 - }"#, - ) - .unwrap(); - + // tasoller/ // let config = Config::from_str( // r#"{ // "deviceMode": "tasoller-two", diff --git a/src-tauri/src/slider_io/config.rs b/src-tauri/src/slider_io/config.rs index 9918e01..baaf0e4 100644 --- a/src-tauri/src/slider_io/config.rs +++ b/src-tauri/src/slider_io/config.rs @@ -20,6 +20,7 @@ pub enum KeyboardLayout { Yuancon, Deemo, Voltex, + GamepadVoltex, } #[derive(Debug, Clone)] @@ -52,6 +53,9 @@ pub enum LedMode { Websocket { url: String, }, + Serial { + port: String, + }, } #[derive(Debug, Clone)] @@ -95,8 +99,12 @@ impl Config { layout: KeyboardLayout::Voltex, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, }, + "gamepad-voltex" => OutputMode::Keyboard { + layout: KeyboardLayout::GamepadVoltex, + sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + }, "websocket" => OutputMode::Websocket { - url: v["outputWebsocketUrl"].to_string(), + url: v["outputWebsocketUrl"].as_str()?.to_string(), }, _ => panic!("Invalid output mode"), }, @@ -121,7 +129,10 @@ impl Config { "attract" => LedMode::Attract, "test" => LedMode::Test, "websocket" => LedMode::Websocket { - url: v["ledWebsocketUrl"].to_string(), + url: v["ledWebsocketUrl"].as_str()?.to_string(), + }, + "serial" => LedMode::Serial { + port: v["ledSerialPort"].as_str()?.to_string(), }, _ => panic!("Invalid led mode"), }, @@ -137,7 +148,8 @@ impl Config { "keyboardSensitivity": 20, "outputWebsocketUrl": "localhost:3000", "ledSensitivity": 20, - "ledWebsocketUrl": "localhost:3001" + "ledWebsocketUrl": "localhost:3001", + "ledSerialPort": "COM5" }"#, ) .unwrap() diff --git a/src-tauri/src/slider_io/device.rs b/src-tauri/src/slider_io/device.rs index 3d8a363..fc1b11d 100644 --- a/src-tauri/src/slider_io/device.rs +++ b/src-tauri/src/slider_io/device.rs @@ -1,5 +1,5 @@ use std::{ - error, + error::Error, ops::{Deref, DerefMut}, thread, time::Duration, @@ -12,27 +12,10 @@ use rusb::{self, DeviceHandle, GlobalContext}; use crate::slider_io::{ config::DeviceMode, controller_state::{ControllerState, FullState, LedState}, + utils::{Buffer, ShimError}, worker::Job, }; -pub struct Buffer { - pub data: [u8; 256], - pub len: usize, -} - -impl Buffer { - pub fn new() -> Self { - Buffer { - data: [0; 256], - len: 0, - } - } - - fn slice(&self) -> &[u8] { - &self.data[0..self.len] - } -} - type HidReadCallback = fn(&Buffer, &mut ControllerState) -> (); type HidLedCallback = fn(&mut Buffer, &LedState) -> (); @@ -204,11 +187,12 @@ impl HidDeviceJob { } } - fn setup_impl(&mut self) -> Result<(), Box> { + fn setup_impl(&mut self) -> Result<(), Box> { info!("Device finding vid {} pid {}", self.vid, self.pid); let handle = rusb::open_device_with_vid_pid(self.vid, self.pid); if handle.is_none() { - error!("Could not find device"); + error!("Device not found"); + return Err(Box::new(ShimError)); } let mut handle = handle.unwrap(); info!("Device found {:?}", handle); @@ -229,8 +213,17 @@ impl HidDeviceJob { const TIMEOUT: Duration = Duration::from_millis(20); impl Job for HidDeviceJob { - fn setup(&mut self) { - self.setup_impl().unwrap(); + fn setup(&mut self) -> bool { + match self.setup_impl() { + Ok(r) => { + info!("Device OK"); + true + } + Err(e) => { + error!("Device setup failed, exiting thread early"); + false + } + } } fn tick(&mut self) { @@ -276,7 +269,9 @@ impl Job for HidDeviceJob { } fn teardown(&mut self) { - let handle = self.handle.as_mut().unwrap(); - handle.release_interface(0).ok(); + if self.handle.is_some() { + let handle = self.handle.as_mut().unwrap(); + handle.release_interface(0).ok(); + } } } diff --git a/src-tauri/src/slider_io/gamepad.rs b/src-tauri/src/slider_io/gamepad.rs new file mode 100644 index 0000000..8d4f562 --- /dev/null +++ b/src-tauri/src/slider_io/gamepad.rs @@ -0,0 +1,101 @@ +use std::sync::{Arc, Mutex}; + +use vigem_client::{Client, TargetId, XButtons, XGamepad, Xbox360Wired}; + +use crate::slider_io::{output::OutputHandler, voltex::VoltexState}; + +pub struct GamepadOutput { + target: Xbox360Wired, + gamepad: XGamepad, +} + +impl GamepadOutput { + pub fn new() -> Self { + let client = Client::connect().unwrap(); + let mut target = Xbox360Wired::new(client, TargetId::XBOX360_WIRED); + target.plugin().unwrap(); + target.wait_ready().unwrap(); + Self { + target, + gamepad: XGamepad::default(), + } + } +} + +impl OutputHandler for GamepadOutput { + fn tick(&mut self, flat_controller_state: &Vec) { + let voltex_state = VoltexState::from_flat(flat_controller_state); + + let buttons = voltex_state + .bt + .iter() + .chain(voltex_state.fx.iter()) + .chain(voltex_state.extra.iter()) + .zip([ + XButtons::A, + XButtons::B, + XButtons::X, + XButtons::Y, + XButtons::LB, + XButtons::RB, + XButtons::START, + XButtons::BACK, + XButtons::GUIDE, + ]) + .fold(0, |buttons, (state, code)| { + buttons + | match state { + true => code, + false => 0, + } + }); + + let lx = (match voltex_state.laser[0] { + true => -30000, + false => 0, + } + match voltex_state.laser[1] { + true => 30000, + false => 0, + }); + + let rx = (match voltex_state.laser[2] { + true => -30000, + false => 0, + } + match voltex_state.laser[3] { + true => 30000, + false => 0, + }); + + let mut dirty = false; + if self.gamepad.buttons.raw != buttons { + self.gamepad.buttons.raw = buttons; + dirty = true; + } + if self.gamepad.thumb_lx != lx { + self.gamepad.thumb_lx = lx; + dirty = true; + } + if self.gamepad.thumb_rx != rx { + self.gamepad.thumb_rx = rx; + dirty = true; + } + + if dirty { + self.target.update(&self.gamepad).unwrap(); + } + } + + fn reset(&mut self) { + self.gamepad = XGamepad::default(); + self.target.update(&self.gamepad).unwrap(); + } +} + +impl Drop for GamepadOutput { + fn drop(&mut self) { + self.target.unplug().unwrap(); + } +} + +// dammit vigem_client::Event +unsafe impl Send for GamepadOutput {} diff --git a/src-tauri/src/slider_io/keyboard.rs b/src-tauri/src/slider_io/keyboard.rs index d9eb7e4..69df596 100644 --- a/src-tauri/src/slider_io/keyboard.rs +++ b/src-tauri/src/slider_io/keyboard.rs @@ -5,7 +5,7 @@ use winapi::{ um::winuser::{SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP}, }; -use crate::slider_io::config::KeyboardLayout; +use crate::slider_io::{config::KeyboardLayout, output::OutputHandler}; #[rustfmt::skip] const TASOLLER_KB_MAP: [usize; 41] = [ @@ -80,6 +80,7 @@ impl KeyboardOutput { KeyboardLayout::Yuancon => &YUANCON_KB_MAP, KeyboardLayout::Deemo => &DEEMO_KB_MAP, KeyboardLayout::Voltex => &VOLTEX_KB_MAP, + _ => panic!("Not implemented"), }; let mut ground_to_idx = [0 as usize; 41]; @@ -121,21 +122,6 @@ impl KeyboardOutput { } } - pub fn tick(&mut self, flat_controller_state: &Vec) { - self.next_keys.fill(false); - for (idx, x) in flat_controller_state.iter().enumerate() { - if *x { - self.next_keys[self.ground_to_idx[idx]] = true; - } - } - self.send(); - } - - pub fn reset(&mut self) { - self.next_keys.fill(false); - self.send(); - } - fn send(&mut self) { self.n_kb_buf = 0; @@ -178,3 +164,26 @@ impl KeyboardOutput { } } } + +impl OutputHandler for KeyboardOutput { + fn tick(&mut self, flat_controller_state: &Vec) { + self.next_keys.fill(false); + for (idx, x) in flat_controller_state.iter().enumerate() { + if *x { + self.next_keys[self.ground_to_idx[idx]] = true; + } + } + self.send(); + } + + fn reset(&mut self) { + self.next_keys.fill(false); + self.send(); + } +} + +impl Drop for KeyboardOutput { + fn drop(&mut self) { + self.reset(); + } +} diff --git a/src-tauri/src/slider_io/led.rs b/src-tauri/src/slider_io/led.rs index 63557b3..0c38ad7 100644 --- a/src-tauri/src/slider_io/led.rs +++ b/src-tauri/src/slider_io/led.rs @@ -4,17 +4,23 @@ use std::{ time::{Duration, Instant}, }; +use log::{error, info}; + use palette::{FromColor, Hsv, Srgb}; +use serialport::{ClearBuffer, SerialPort, StopBits}; use crate::slider_io::{ config::{LedMode, ReactiveLayout}, controller_state::{FullState, LedState}, + utils::Buffer, + voltex::VoltexState, worker::Job, }; pub struct LedJob { state: FullState, mode: LedMode, + serial_port: Option>, } impl LedJob { @@ -22,10 +28,16 @@ impl LedJob { Self { state: state.clone(), mode: mode.clone(), + serial_port: None, } } - fn calc_lights(&self, flat_controller_state: Option<&Vec>, led_state: &mut LedState) { + fn calc_lights( + &self, + flat_controller_state: Option<&Vec>, + serial_buffer: Option<&Buffer>, + led_state: &mut LedState, + ) { match self.mode { LedMode::Reactive { layout, .. } => { let flat_controller_state = flat_controller_state.unwrap(); @@ -63,47 +75,40 @@ impl LedJob { } led_state.paint(27, &[64, 0, 0]); - // Left laser left - if flat_controller_state[0..4].contains(&true) { - for idx in 0..3 { - led_state.paint(idx, &[0, 0, 255]); - } - }; + let voltex_state = VoltexState::from_flat(flat_controller_state); - // Left laser right - if flat_controller_state[4..8].contains(&true) { - for idx in 4..7 { - led_state.paint(idx, &[0, 0, 255]); + // Left laser + for (idx, state) in voltex_state.laser[0..2].iter().enumerate() { + if *state { + led_state.paint(0 + idx * 4, &[0, 0, 255]); + led_state.paint(1 + idx * 4, &[0, 0, 255]); + led_state.paint(2 + idx * 4, &[0, 0, 255]); } - }; + } - // Right laser left - if flat_controller_state[24..28].contains(&true) { - for idx in 24..27 { - led_state.paint(idx, &[255, 0, 0]); + // Right laser + for (idx, state) in voltex_state.laser[2..4].iter().enumerate() { + if *state { + led_state.paint(24 + idx * 4, &[255, 0, 0]); + led_state.paint(25 + idx * 4, &[255, 0, 0]); + led_state.paint(26 + idx * 4, &[255, 0, 0]); } - }; - // Right laser right - if flat_controller_state[28..32].contains(&true) { - for idx in 28..31 { - led_state.paint(idx, &[255, 0, 0]); - } - }; + } // Buttons - for (btn_idx, btn_banks) in flat_controller_state[8..24].chunks(4).enumerate() { - if btn_banks.iter().skip(1).step_by(2).any(|x| *x) { - led_state.paint(8 + btn_idx * 4, &[255, 255, 255]); - led_state.paint(10 + btn_idx * 4, &[255, 255, 255]); + for (idx, state) in voltex_state.bt.iter().enumerate() { + if *state { + led_state.paint(8 + idx * 4, &[255, 255, 255]); + led_state.paint(10 + idx * 4, &[255, 255, 255]); } } // Fx - for (fx_idx, fx_banks) in flat_controller_state[8..24].chunks(8).enumerate() { - if fx_banks.iter().step_by(2).any(|x| *x) { - led_state.paint(9 + fx_idx * 8, &[255, 0, 0]); - led_state.paint(11 + fx_idx * 8, &[255, 0, 0]); - led_state.paint(13 + fx_idx * 8, &[255, 0, 0]); + for (idx, state) in voltex_state.fx.iter().enumerate() { + if *state { + led_state.paint(9 + idx * 8, &[255, 0, 0]); + led_state.paint(11 + idx * 8, &[255, 0, 0]); + led_state.paint(13 + idx * 8, &[255, 0, 0]); } } } @@ -118,6 +123,26 @@ impl LedJob { led_state.paint(idx, &[color.red, color.green, color.blue]); } } + LedMode::Serial { .. } => { + // https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialProcessor.h + // https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialProcessor.cpp + // https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialLeds.h + // https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialLeds.cpp + if let Some(serial_buffer) = serial_buffer { + // println!("buffer {:?}", serial_buffer.data); + if serial_buffer.data[0] == 0xaa && serial_buffer.data[1] == 0xaa { + for (idx, buf_chunk) in serial_buffer.data[2..95] + .chunks(3) + .take(31) + .rev() + .enumerate() + { + led_state.paint(idx, &[(*buf_chunk)[1], (*buf_chunk)[2], (*buf_chunk)[0]]); + } + println!("leds {:?}", led_state.led_state); + } + } + } _ => panic!("Not implemented"), } @@ -126,22 +151,77 @@ impl LedJob { } impl Job for LedJob { - fn setup(&mut self) {} + fn setup(&mut self) -> bool { + match &self.mode { + LedMode::Serial { port } => { + info!( + "Serial port for led opening at {} {:?}", + port.as_str(), + 115200 + ); + self.serial_port = match serialport::new(port, 115200).open() { + Ok(s) => { + info!("Serial port opened"); + Some(s) + } + Err(e) => { + error!("Serial port could not open, exiting thread early"); + None + } + }; + + self.serial_port.is_some() + } + _ => true, + } + } fn tick(&mut self) { - let flat_controller_state: Option> = match self.mode { + let mut flat_controller_state: Option> = None; + let mut serial_buffer: Option = None; + + // Do the IO here + match self.mode { LedMode::Reactive { sensitivity, .. } => { let controller_state_handle = self.state.controller_state.lock().unwrap(); - Some(controller_state_handle.flat(&sensitivity)) + flat_controller_state = Some(controller_state_handle.flat(&sensitivity)); } - _ => None, - }; + LedMode::Serial { .. } => { + if let Some(serial_port) = self.serial_port.as_mut() { + let mut serial_data_avail = serial_port.bytes_to_read().unwrap_or(0); + if serial_data_avail < 100 { + return; + } + if serial_data_avail % 100 == 0 { + let mut serial_buffer_working = Buffer::new(); + serial_port + .as_mut() + .read_exact(&mut serial_buffer_working.data[..100]) + .ok() + .unwrap(); + serial_data_avail -= 100; + serial_buffer = Some(serial_buffer_working); + } + + if serial_data_avail > 0 { + serial_port.clear(ClearBuffer::All); + } + } + } + _ => {} + } + + // Then calculate and transfer { let mut led_state_handle = self.state.led_state.lock().unwrap(); - self.calc_lights(flat_controller_state.as_ref(), led_state_handle.deref_mut()); + self.calc_lights( + flat_controller_state.as_ref(), + serial_buffer.as_ref(), + led_state_handle.deref_mut(), + ); } - thread::sleep(Duration::from_millis(33)); + thread::sleep(Duration::from_millis(30)); } fn teardown(&mut self) {} diff --git a/src-tauri/src/slider_io/manager.rs b/src-tauri/src/slider_io/manager.rs index 7f28061..bed9fa6 100644 --- a/src-tauri/src/slider_io/manager.rs +++ b/src-tauri/src/slider_io/manager.rs @@ -2,7 +2,7 @@ use log::info; use crate::slider_io::{ config::Config, controller_state::FullState, device::HidDeviceJob, led::LedJob, - output::KeyboardOutputJob, worker::Worker, + output::OutputJob, worker::Worker, }; pub struct Manager { @@ -15,13 +15,16 @@ pub struct Manager { impl Manager { pub fn new(config: Config) -> Self { + info!("Starting manager"); + info!("Device config {:?}", config.device_mode); + info!("Output config {:?}", config.output_mode); + info!("LED config {:?}", config.led_mode); + let state = FullState::new(); let device_worker = Worker::new(HidDeviceJob::from_config(&state, &config.device_mode)); - let output_worker = Worker::new(KeyboardOutputJob::new(&state, &config.output_mode)); + let output_worker = Worker::new(OutputJob::new(&state, &config.output_mode)); let led_worker = Worker::new(LedJob::new(&state, &config.led_mode)); - info!("Starting manager with config: {:?}", config); - Self { state, config, diff --git a/src-tauri/src/slider_io/mod.rs b/src-tauri/src/slider_io/mod.rs index 3a27273..343bb9f 100644 --- a/src-tauri/src/slider_io/mod.rs +++ b/src-tauri/src/slider_io/mod.rs @@ -1,8 +1,11 @@ mod config; - -mod controller_state; +mod utils; mod worker; +mod controller_state; +mod voltex; + +mod gamepad; mod keyboard; mod device; diff --git a/src-tauri/src/slider_io/output.rs b/src-tauri/src/slider_io/output.rs index 6532652..95574d2 100644 --- a/src-tauri/src/slider_io/output.rs +++ b/src-tauri/src/slider_io/output.rs @@ -1,16 +1,25 @@ use std::{thread, time::Duration}; use crate::slider_io::{ - config::OutputMode, controller_state::FullState, keyboard::KeyboardOutput, worker::Job, + config::{KeyboardLayout, OutputMode}, + controller_state::FullState, + gamepad::GamepadOutput, + keyboard::KeyboardOutput, + worker::Job, }; -pub struct KeyboardOutputJob { - state: FullState, - sensitivity: u8, - keyboard_output: KeyboardOutput, +pub trait OutputHandler: Send + Drop { + fn tick(&mut self, flat_controller_state: &Vec); + fn reset(&mut self); } -impl KeyboardOutputJob { +pub struct OutputJob { + state: FullState, + sensitivity: u8, + handler: Box, +} + +impl OutputJob { pub fn new(state: &FullState, mode: &OutputMode) -> Self { match mode { OutputMode::Keyboard { @@ -19,15 +28,20 @@ impl KeyboardOutputJob { } => Self { state: state.clone(), sensitivity: *sensitivity, - keyboard_output: KeyboardOutput::new(layout.clone()), + handler: match layout { + KeyboardLayout::GamepadVoltex => Box::new(GamepadOutput::new()), + layout => Box::new(KeyboardOutput::new(layout.clone())), + }, }, _ => panic!("Not implemented"), } } } -impl Job for KeyboardOutputJob { - fn setup(&mut self) {} +impl Job for OutputJob { + fn setup(&mut self) -> bool { + true + } fn tick(&mut self) { let flat_controller_state: Vec; @@ -36,11 +50,11 @@ impl Job for KeyboardOutputJob { flat_controller_state = controller_state_handle.flat(&self.sensitivity); } - self.keyboard_output.tick(&flat_controller_state); + self.handler.tick(&flat_controller_state); thread::sleep(Duration::from_millis(10)); } fn teardown(&mut self) { - self.keyboard_output.reset(); + self.handler.reset(); } } diff --git a/src-tauri/src/slider_io/utils.rs b/src-tauri/src/slider_io/utils.rs new file mode 100644 index 0000000..972bf78 --- /dev/null +++ b/src-tauri/src/slider_io/utils.rs @@ -0,0 +1,34 @@ +use std::{error::Error, fmt}; + +pub struct Buffer { + pub data: [u8; 256], + pub len: usize, +} + +impl Buffer { + pub fn new() -> Self { + Buffer { + data: [0; 256], + len: 0, + } + } + + fn slice(&self) -> &[u8] { + &self.data[0..self.len] + } +} + +#[derive(Debug)] +pub struct ShimError; + +impl<'a> fmt::Display for ShimError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ShimError") + } +} + +impl Error for ShimError { + fn description(&self) -> &str { + "shimError" + } +} diff --git a/src-tauri/src/slider_io/voltex.rs b/src-tauri/src/slider_io/voltex.rs new file mode 100644 index 0000000..600a8b4 --- /dev/null +++ b/src-tauri/src/slider_io/voltex.rs @@ -0,0 +1,39 @@ +pub struct VoltexState { + pub laser: [bool; 4], + pub bt: [bool; 4], + pub fx: [bool; 2], + pub extra: [bool; 3], +} + +impl VoltexState { + pub fn from_flat(flat_controller_state: &Vec) -> Self { + let mut voltex_state = Self { + laser: [false; 4], + bt: [false; 4], + fx: [false; 2], + extra: [false; 3], + }; + + voltex_state.laser[0] = flat_controller_state[0..4].contains(&true); + voltex_state.laser[1] = flat_controller_state[4..8].contains(&true); + voltex_state.laser[2] = flat_controller_state[24..28].contains(&true); + voltex_state.laser[3] = flat_controller_state[28..32].contains(&true); + + for i in 0..4 { + voltex_state.bt[i] = flat_controller_state[9 + i * 4] || flat_controller_state[11 + i * 4]; + } + + for i in 0..2 { + voltex_state.fx[i] = flat_controller_state[8 + i * 8] + || flat_controller_state[10 + i * 8] + || flat_controller_state[12 + i * 8] + || flat_controller_state[14 + i * 8]; + } + + for i in 0..3 { + voltex_state.extra[i] = flat_controller_state[38 + i]; + } + + voltex_state + } +} diff --git a/src-tauri/src/slider_io/worker.rs b/src-tauri/src/slider_io/worker.rs index 8ce017f..2b893bd 100644 --- a/src-tauri/src/slider_io/worker.rs +++ b/src-tauri/src/slider_io/worker.rs @@ -7,7 +7,7 @@ use std::{ }; pub trait Job: Send { - fn setup(&mut self); + fn setup(&mut self) -> bool; fn tick(&mut self); fn teardown(&mut self); } @@ -24,12 +24,14 @@ impl Worker { let stop_signal_clone = Arc::clone(&stop_signal); Self { thread: Some(thread::spawn(move || { - job.setup(); + let setup_res = job.setup(); + stop_signal_clone.store(!setup_res, Ordering::SeqCst); + loop { - job.tick(); if stop_signal_clone.load(Ordering::SeqCst) { break; } + job.tick(); } job.teardown(); })), @@ -40,7 +42,7 @@ impl Worker { impl Drop for Worker { fn drop(&mut self) { - self.stop_signal.swap(true, Ordering::SeqCst); + self.stop_signal.store(true, Ordering::SeqCst); if self.thread.is_some() { self.thread.take().unwrap().join().ok(); } diff --git a/src/App.svelte b/src/App.svelte index 485294f..8281afa 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -12,6 +12,7 @@ let outputWebsocketUrl = "http://localhost:3000"; let ledSensitivity = 20; let ledWebsocketUrl = "http://localhost:3001"; + let ledSerialPort = "COM5"; let debugstr = ""; @@ -21,13 +22,15 @@ console.log("heartbeat", event); debugstr = event.payload; const payload: any = JSON.parse(event.payload as any); - deviceMode = payload.deviceMode; - outputMode = payload.outputMode; - ledMode = payload.ledMode; - keyboardSensitivity = payload.keyboardSensitivity; - outputWebsocketUrl = payload.outputWebsocketUrl; - ledSensitivity = payload.ledSensitivity; - ledWebsocketUrl = payload.ledWebsocketUrl; + deviceMode = payload.deviceMode || "none"; + outputMode = payload.outputMode || "none"; + ledMode = payload.ledMode || "none"; + keyboardSensitivity = payload.keyboardSensitivity || 20; + outputWebsocketUrl = + payload.outputWebsocketUrl || "http://localhost:3000/"; + ledSensitivity = payload.ledSensitivity || 20; + ledWebsocketUrl = payload.ledWebsocketUrl || "http://localhost:3001"; + ledSerialPort = payload.ledSerialPort || "COM5"; }); await emit("heartbeat", ""); }); @@ -44,6 +47,7 @@ outputWebsocketUrl, ledSensitivity, ledWebsocketUrl, + ledSerialPort, }) ); console.log("Done"); @@ -144,6 +148,7 @@ + @@ -181,6 +186,24 @@ {/if} + {#if ledMode === "serial"} +
+
LED Serial Port
+
+ +
+
+ {/if}