feat: add Yuancon Laverita v2
This commit is contained in:
parent
b4c4947ac6
commit
938df9648f
@ -17,6 +17,7 @@ panic = "abort"
|
|||||||
chusan = []
|
chusan = []
|
||||||
tasoller_v1 = []
|
tasoller_v1 = []
|
||||||
tasoller_v2 = []
|
tasoller_v2 = []
|
||||||
|
laverita_v3 = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.76"
|
anyhow = "1.0.76"
|
||||||
|
@ -23,6 +23,7 @@ coin=0x33
|
|||||||
Currently supported backends are:
|
Currently supported backends are:
|
||||||
- `tasoller_v1`
|
- `tasoller_v1`
|
||||||
- `tasoller_v2`
|
- `tasoller_v2`
|
||||||
|
- `laverita_v2`
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
CONTROLLER="tasoller_v1" # replace with your preferred controller backend
|
CONTROLLER="tasoller_v1" # replace with your preferred controller backend
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
mkdir -p dist/chusan
|
mkdir -p dist/chusan
|
||||||
mkdir -p dist/chuni
|
mkdir -p dist/chuni
|
||||||
|
|
||||||
for backend in tasoller_v1 tasoller_v2
|
for backend in tasoller_v1 tasoller_v2 laverita_v3
|
||||||
do
|
do
|
||||||
cargo build --target i686-pc-windows-msvc --release --features chusan,$backend
|
cargo build --target i686-pc-windows-msvc --release --features chusan,$backend
|
||||||
cargo build --target x86_64-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
162
src/backends/laverita_v2.rs
Normal 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(())
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
|
pub mod laverita_v2;
|
||||||
pub mod tasoller_v1;
|
pub mod tasoller_v1;
|
||||||
pub mod tasoller_v2;
|
pub mod tasoller_v2;
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ cfg_if::cfg_if! {
|
|||||||
use crate::backends::tasoller_v1 as con_impl;
|
use crate::backends::tasoller_v1 as con_impl;
|
||||||
} else if #[cfg(feature = "tasoller_v2")] {
|
} else if #[cfg(feature = "tasoller_v2")] {
|
||||||
use crate::backends::tasoller_v2 as con_impl;
|
use crate::backends::tasoller_v2 as con_impl;
|
||||||
|
} else if #[cfg(feature = "laverita_v2")] {
|
||||||
|
use crate::backends::laverita_v2 as con_impl;
|
||||||
} else {
|
} else {
|
||||||
use crate::backends::dummy as con_impl;
|
use crate::backends::dummy as con_impl;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user