feat: add Yuancon Laverita v2

This commit is contained in:
beerpsi 2024-01-03 22:44:12 +07:00
parent b4c4947ac6
commit 938df9648f
6 changed files with 168 additions and 1 deletions

View File

@ -17,6 +17,7 @@ panic = "abort"
chusan = []
tasoller_v1 = []
tasoller_v2 = []
laverita_v3 = []
[dependencies]
anyhow = "1.0.76"

View File

@ -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

View File

@ -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

162
src/backends/laverita_v2.rs Normal file
View File

@ -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<T: UsbContext>(
device: &DeviceHandle<T>,
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<T: UsbContext>(
_device: &DeviceHandle<T>,
_output: &mut [u8],
_board: u8,
_rgb: &[u8],
) -> Result<()> {
Ok(())
}

View File

@ -1,4 +1,5 @@
pub mod dummy;
pub mod laverita_v2;
pub mod tasoller_v1;
pub mod tasoller_v2;

View File

@ -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;
}