diff --git a/Cargo.toml b/Cargo.toml index 5901a6a..80d685f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ panic = "abort" chusan = [] tasoller_v1 = [] tasoller_v2 = [] +laverita_v3 = [] [dependencies] anyhow = "1.0.76" diff --git a/README.md b/README.md index dd1d052..93bec48 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ coin=0x33 Currently supported backends are: - `tasoller_v1` - `tasoller_v2` +- `laverita_v2` ```shell CONTROLLER="tasoller_v1" # replace with your preferred controller backend diff --git a/build-all.sh b/build-all.sh index cab53c1..34244c5 100644 --- a/build-all.sh +++ b/build-all.sh @@ -3,7 +3,7 @@ mkdir -p dist/chusan mkdir -p dist/chuni -for backend in tasoller_v1 tasoller_v2 +for backend in tasoller_v1 tasoller_v2 laverita_v3 do cargo build --target i686-pc-windows-msvc --release --features chusan,$backend cargo build --target x86_64-pc-windows-msvc --release --features chusan,$backend diff --git a/src/backends/laverita_v2.rs b/src/backends/laverita_v2.rs new file mode 100644 index 0000000..7594460 --- /dev/null +++ b/src/backends/laverita_v2.rs @@ -0,0 +1,162 @@ +//! # Yuancon Laverita v2 +//! (does anyone still use this?) +//! +//! Notes: I'm getting conflicting information on the protocol. +//! CrazyRedMachine is claiming that there is [a leading byte][1], while 4yn's +//! [slidershim][2] as well as their earlier [Yuancon HID test script][3] is +//! claiming the opposite. This implementation is based on CrazyRedMachine, +//! since the chuniio open-sourced by ZhouSensor himself seems to agree. +//! +//! +//! ```c +//! struct outputdata_gaosan +//! { +//! unsigned char Address; +//! unsigned char Touch[62]; +//! }OutputDataGaosan; +//! ``` +//! +//! [1]: https://github.com/CrazyRedMachine/yuancon_laverita_v2-docs/blob/main/pcb_protocol/hid_reports.txt +//! [2]: https://github.com/4yn/slidershim/blob/main/src-slider_io/src/device/hid.rs#L174-L207 +//! [3]: https://gist.github.com/4yn/53a6b6b681e40fd5658bf5b7f9fe9c15 +//! +//! USB device: 1973:2001 +//! +//! USB interface: 0 +//! +//! ## Protocol information +//! +//! ### IN Interrupt (0x81) +//! - Content length: 35 bytes +//! - Byte 0: ReportID 0x00 +//! - Byte 1: Air sensors, from lowest to highest bit {2, 1, 4, 3, 6, 5} +//! - Byte 2: +//! - Bit 0: Test +//! - Bit 1: Service +//! - Bit 2: Coin +//! - Bytes 3..35: Slider pressure (bottom -> top, then left -> right) +//! +//! ### OUT Interrupt (0x02) +//! - Content length: 63 bytes +//! - Byte 0: ReportID 0x00 +//! - Byte 1..63: Slider LED data (right -> left) +//! - Each LED is controlled by 2 bytes: GGGG GRRR (second byte) | RRRB BBBB (first byte) + +#[cfg(any(chuni, chusanapp))] +use anyhow::Result; +#[cfg(any(chuni, chusanapp))] +use rusb::{DeviceHandle, UsbContext}; + +use super::ReadType; + +pub const DEVICE_VID: u16 = 0x1973; +pub const DEVICE_PID: u16 = 0x2001; +pub const READ_TYPE: ReadType = ReadType::Interrupt; +pub const READ_ENDPOINT: u8 = 0x81; +pub const INPUT_MEMORY_SIZE: usize = 35; +pub const OUTPUT_MEMORY_SIZE: usize = 63; + +/// Poll JVS input. +/// +/// The return value is a tuple: +/// - The first value returns the cabinet test/service state, where bit 0 is Test +/// and bit 1 is Service. +/// - The second value returns the IR beams that are currently broken, where bit 0 +/// is the lowest IR beam and bit 5 is the highest IR beam, for a total of 6 beams. +/// +/// Both bit masks are active-high. +#[cfg(any(chuni, amdaemon))] +#[inline(always)] +pub fn jvs_poll(input: &[u8]) -> (u8, u8) { + let mixed_beams = input[1] & 0x3F; + let beams = ((mixed_beams & 0xAA) >> 1) | ((mixed_beams & 0x55) << 1); + + (input[2] & 3, beams) +} + +/// Checks the current state of the coin button (if there is one). +#[cfg(any(chuni, amdaemon))] +#[inline(always)] +pub fn is_coin_button_pressed(input: &[u8]) -> bool { + (input[2] & 4) != 0 +} + +/// Reads slider pressure information from USB input. +/// +/// There are a total of 32 regions on the touch slider. Each region can return +/// an 8-bit pressure value. The operator menu allows the operator to adjust the +/// pressure level at which a region is considered to be pressed; the factory +/// default value for this setting is 20. +/// +/// You should return an array of 32 unsigned 8-bit integers, starting from top right +/// and going from top to bottom, right to left. +/// +/// ```ignore +/// ^^ Towards screen ^^ +/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ +/// 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 | 0 | +/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ +/// 31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 | +/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ +/// ``` +#[cfg(any(chuni, chusanapp))] +#[inline(always)] +pub fn read_pressure_data(input: &[u8]) -> [u8; 32] { + let mut pressure = [0u8; 32]; + + for i in 0..32 { + pressure[i] = input[3 + 31 - i]; + } + + pressure +} + +/// Do some one-time initialization to the USB out buffer +/// (e.g. set magic bytes). +#[cfg(any(chuni, chusanapp))] +#[inline(always)] +pub fn init_output_buffer(output: &mut [u8]) { + output[0] = 0; +} + +/// Update the RGB lighting on the slider. A slice `rgb` is provided, alternating +/// between 16 touch pad pixels and 15 divider pixels, going from right to left. +#[cfg(any(chuni, chusanapp))] +#[inline(always)] +pub fn set_slider_leds( + device: &DeviceHandle, + output: &mut [u8], + rgb: &[u8], +) -> Result<()> { + use crate::TIMEOUT; + + for (buf_chunk, state_chunk) in output[1..].chunks_mut(2).take(31).zip(rgb.chunks(3)) { + buf_chunk[0] = (state_chunk[0] << 3 & 0xE0) | (state_chunk[2] >> 3); + buf_chunk[1] = (state_chunk[1] & 0xF8) | (state_chunk[0] >> 5); + } + + device.write_interrupt(0x02, output, TIMEOUT)?; + + Ok(()) +} + +/// Update the RGB LEDs. +/// +/// Board 0 corresponds to the left LEDs, with 5 * 10 * 3 RGB values for the +/// billboard, followed by 3 RGB values for the air tower. +/// +/// Board 1 corresponds to the right LEDs, with 6 * 10 * 3 RGB values for the +/// billboard, followed by 3 RGB values for the air tower. +/// +/// Note that billboard strips have alternating direction (bottom to top, top +/// to bottom...) +#[cfg(any(chuni, chusanapp))] +#[inline(always)] +pub fn set_led_colors( + _device: &DeviceHandle, + _output: &mut [u8], + _board: u8, + _rgb: &[u8], +) -> Result<()> { + Ok(()) +} diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 9dd5b3f..c600532 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -1,4 +1,5 @@ pub mod dummy; +pub mod laverita_v2; pub mod tasoller_v1; pub mod tasoller_v2; diff --git a/src/lib.rs b/src/lib.rs index 5ff3e97..a437116 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,8 @@ cfg_if::cfg_if! { use crate::backends::tasoller_v1 as con_impl; } else if #[cfg(feature = "tasoller_v2")] { use crate::backends::tasoller_v2 as con_impl; + } else if #[cfg(feature = "laverita_v2")] { + use crate::backends::laverita_v2 as con_impl; } else { use crate::backends::dummy as con_impl; }