Add Tasoller+ support (untested)

This commit is contained in:
beerpsi
2025-03-19 13:43:18 +07:00
parent 33c1f3398e
commit a7c1a8edd9
9 changed files with 138 additions and 92 deletions
Generated
+2 -2
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "anyhow"
@@ -43,7 +43,7 @@ checksum = "77e53693616d3075149f4ead59bdeecd204ac6b8192d8969757601b74bddf00f"
[[package]]
name = "chuniio-rs"
version = "0.1.0"
version = "0.1.3"
dependencies = [
"anyhow",
"cfg-if",
+5 -2
View File
@@ -13,12 +13,15 @@ lto = true
codegen-units = 1
panic = "abort"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(chusanapp)', 'cfg(chuni)', 'cfg(amdaemon)'] }
[features]
chusan = []
tasoller_v1 = []
tasoller_v2 = []
laverita_v3 = []
redboard = []
laverita_v2 = []
tasoller_plus = []
[dependencies]
anyhow = "1.0.76"
+2 -2
View File
@@ -3,7 +3,7 @@
mkdir -p dist/chusan
mkdir -p dist/chuni
for backend in tasoller_v1 tasoller_v2 laverita_v3
for backend in tasoller_v1 tasoller_v2 laverita_v2 tasoller_plus
do
cargo build --target i686-pc-windows-msvc --release --features chusan,$backend
cargo build --target x86_64-pc-windows-msvc --release --features chusan,$backend
@@ -13,4 +13,4 @@ do
cargo build --target i686-pc-windows-msvc --release --features $backend
cp target/i686-pc-windows-msvc/release/chuniio_rs.dll dist/chuni/chuniio_${backend}.dll
done
done
+8 -8
View File
@@ -1,13 +1,13 @@
//! # 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,
//! 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
//! {
@@ -15,11 +15,11 @@
//! 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
+1 -1
View File
@@ -1,6 +1,6 @@
pub mod dummy;
pub mod laverita_v2;
pub mod redboard;
pub mod tasoller_plus;
pub mod tasoller_v1;
pub mod tasoller_v2;
@@ -1,46 +1,40 @@
//! # RedBoard
//! USB device: 0F0D:0092
//! # Tasoller Plus
//! USB device: 0E8F:1231
//!
//! USB interface: 0
//!
//! ## Protocol information
//! - Content length: 9 bytes (72 bits)
//! - Byte 1: Report ID
//! - Byte 2:
//! - 0x01: Service
//! - 0x02: Start (?)
//! - 0x10: Test
//! - Byte 3:
//! - 0x01: IR2
//! - 0x02: IR3
//! - 0x04: IR4
//! - 0x08: IR1
//! - 0x10: IR5
//! - 0x20: IR6
//! - Byte 4: HAT switch, whatever this is
//! - Byte
//!
//! ### IN Interrupt (0x84)
//! - Content length: 36 bytes
//! - Bytes 0..3: Magic bytes [0x44, 0x42, 0x54] ("DBT")
//! - Byte 3: the order of the bits from MSB to LSB are:
//! FN1 FN2 IR1 IR2 IR3 IR4 IR5 IR6
//! - Bytes 4..36: Touch sensor pressure (top right, top to bottom, right to left,
//! like segatools basically)
//!
//! ### OUT Bulk (0x03)
//! - Content length: 114 bytes
//! - Byte 0..3: Magic bytes [0x44, 0x4C, 0x02] ("DL\02")
//! - Byte 3..96: Slider LED (RGB order, right -> left)
//! - Byte 96..105: Left tower LED (RGB)
//! - Byte 105..114: Right tower LED (RGB)
#[cfg(any(chuni, chusanapp))]
use anyhow::Result;
#[cfg(any(chuni, chusanapp))]
use rusb::{DeviceHandle, UsbContext};
use super::ReadType;
#[cfg(any(chuni, chusanapp))]
use crate::TIMEOUT;
pub const DEVICE_VID: u16 = 0x0F0D;
pub const DEVICE_PID: u16 = 0x0092;
pub const DEVICE_VID: u16 = 0x0E8F;
pub const DEVICE_PID: u16 = 0x1231;
pub const READ_TYPE: ReadType = ReadType::Interrupt;
pub const READ_ENDPOINT: u8 = 0x01;
pub const INPUT_MEMORY_SIZE: usize = 9;
pub const OUTPUT_MEMORY_SIZE: usize = 98;
#[repr(C, packed)]
struct ControllerReport {
pub report_id: u8,
pub buttons: u16,
pub _hat: u8,
pub axis: u32,
pub vendor_spec: u8,
}
pub const READ_ENDPOINT: u8 = 0x84;
pub const INPUT_MEMORY_SIZE: usize = 36;
pub const OUTPUT_MEMORY_SIZE: usize = 114;
/// Poll JVS input.
///
@@ -54,14 +48,23 @@ struct ControllerReport {
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let buttons = (input[1] as u16) << 8 | (input[2] as u16);
(0, 0)
// The order of the bits from MSB to LSB is
// FN1 FN2 IR1 IR2 IR3 IR4 IR5 IR6
// so when reversed, it becomes
// IR6 IR5 IR4 IR3 IR2 IR1 FN2 FN1
// which is basically what segatools expect.
let reversed = input[3].reverse_bits();
let opbtn = reversed & 3;
let beams = reversed >> 2;
(opbtn, 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 {
// Tasoller PLUS still doesn't have a coin button
false
}
@@ -85,25 +88,41 @@ pub fn is_coin_button_pressed(_input: &[u8]) -> bool {
/// ```
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn read_pressure_data(_input: &[u8]) -> [u8; 32] {
[0u8; 32]
pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
let mut pressure = [0u8; 32];
pressure.copy_from_slice(&input[4..36]);
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]) {}
pub fn init_output_buffer(output: &mut [u8]) {
output[0] = 0x44;
output[1] = 0x4C;
output[2] = 0x02;
}
/// 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],
device: &DeviceHandle<T>,
output: &mut [u8],
rgb: &[u8],
) -> Result<()> {
for (output_chunk, rgb_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
output_chunk[0] = rgb_chunk[1]; // R
output_chunk[1] = rgb_chunk[2]; // G
output_chunk[2] = rgb_chunk[0]; // B
}
device.write_bulk(0x03, output, TIMEOUT)?;
Ok(())
}
@@ -120,10 +139,21 @@ pub fn set_slider_leds<T: UsbContext>(
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn set_led_colors<T: UsbContext>(
_device: &DeviceHandle<T>,
_output: &mut [u8],
_board: u8,
_rgb: &[u8],
device: &DeviceHandle<T>,
output: &mut [u8],
board: u8,
rgb: &[u8],
) -> Result<()> {
if board > 1 {
return Ok(())
}
let output_start = if board == 0 { 0x60 } else { 0x69 };
let air_rgb_start = if board == 0 { 0x96 } else { 0xB4 };
output[output_start..output_start + 9].copy_from_slice(&rgb[air_rgb_start..air_rgb_start + 9]);
device.write_bulk(0x03, output, TIMEOUT)?;
Ok(())
}
+1 -2
View File
@@ -58,8 +58,7 @@ pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let opbtn = input[3] >> 3 & 1 | input[3] >> 1 & 2;
// IR1-6 in order
let beams = input[3] >> 4
| (input[4] & 3) << 4;
let beams = input[3] >> 4 | (input[4] & 3) << 4;
(opbtn, beams)
}
+18 -14
View File
@@ -8,17 +8,15 @@
//! ### IN Interrupt (0x84)
//! - Content length: 36 bytes
//! - Bytes 0..3: Magic bytes [0x68, 0x66, 0x84]
//! - Byte 3
//! - Bit 0..6: ir 1-6 (1 = blocked)
//! - Bit 6: FN1 (1 = pressed)
//! - Bit 7: FN2 (1 = pressed)
//! - Byte 3: the order of the bits from MSB to LSB are:
//! FN1 FN2 IR6 IR5 IR4 IR3 IR2 IR1
//! - Bytes 4..36: Touch sensor pressure (starts from top left,
//! top to bottom then left to right)
//!
//! ### OUT Bulk (0x03)
//! - Content length: 240 bytes
//! - Bytes 0..3: magic bytes [0x42, 0x4C, 0x00]
//! - Bytes 3..96: Slider LED (BGR order, right -> left)
//! - Bytes 3..96: Slider LED (GRB order, right -> left)
//! - Bytes 96..168: Left tower LEDs (GRB)
//! - Bytes 168..240: Right tower LEDs (GRB)
@@ -41,9 +39,15 @@ pub const OUTPUT_MEMORY_SIZE: usize = 240;
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
// Drop the IR bits from the input...
let opbtn = input[3] >> 6;
// Swap bit position of FN1 and FN2 so that FN1 maps to Test (bit 0)
// and FN2 maps to service (bit 1)
let opbtn = (opbtn & 1) << 1 | (opbtn & 2) >> 1;
let beams = input[3] & 63; // mask for last 6 bits
// Mask for last 6 bits to get the IR beams
let beams = input[3] & 63;
(opbtn, beams)
}
@@ -82,10 +86,10 @@ pub fn set_slider_leds<T: UsbContext>(
output: &mut [u8],
rgb: &[u8],
) -> Result<()> {
for (buf_chunk, state_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
buf_chunk[0] = state_chunk[2];
buf_chunk[1] = state_chunk[1];
buf_chunk[2] = state_chunk[0];
for (output_chunk, rgb_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
output_chunk[0] = rgb_chunk[2]; // G
output_chunk[1] = rgb_chunk[1]; // R
output_chunk[2] = rgb_chunk[0]; // B
}
device.write_bulk(0x03, output, TIMEOUT)?;
@@ -107,15 +111,15 @@ pub fn set_led_colors<T: UsbContext>(
let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
let output_start = if board == 0 { 96 } else { 168 };
for (buf_chunk, state_chunk) in output[output_start..output_start + 72]
.chunks_mut(3)
.take(24)
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
{
buf_chunk[0] = state_chunk[1];
buf_chunk[1] = state_chunk[0];
buf_chunk[2] = state_chunk[2];
buf_chunk[0] = state_chunk[1]; // R
buf_chunk[1] = state_chunk[0]; // B
buf_chunk[2] = state_chunk[2]; // G
}
device.write_bulk(0x03, output, TIMEOUT)?;
+28 -18
View File
@@ -36,8 +36,8 @@ cfg_if::cfg_if! {
use crate::backends::tasoller_v2 as con_impl;
} else if #[cfg(feature = "laverita_v2")] {
use crate::backends::laverita_v2 as con_impl;
} else if #[cfg(feature = "redboard")] {
use crate::backends::redboard as con_impl;
} else if #[cfg(feature = "tasoller_plus")] {
use crate::backends::tasoller_plus as con_impl;
} else {
use crate::backends::dummy as con_impl;
}
@@ -155,12 +155,12 @@ pub unsafe extern "C" fn chuni_io_jvs_poll(opbtn: *mut u8, beams: *mut u8) {
return;
}
let Some(input_shmem) = &INPUT_SHMEM else {
let input_shmem_ptr = &raw const INPUT_SHMEM;
let Some(ref input_shmem) = *input_shmem_ptr else {
return;
};
(*opbtn, *beams) = con_impl::jvs_poll(input_shmem.as_slice());
if GetAsyncKeyState(CONFIGURATION.test_key as c_int) != 0 {
*opbtn |= 1;
}
@@ -177,7 +177,8 @@ pub extern "C" fn chuni_io_jvs_poll(_opbtn: *mut u8, _beams: *mut u8) {}
#[no_mangle]
#[cfg(any(chuni, amdaemon))]
pub unsafe extern "C" fn chuni_io_jvs_read_coin_counter(total: *mut u16) {
let Some(input_shmem) = &INPUT_SHMEM else {
let input_shmem_ptr = &raw const INPUT_SHMEM;
let Some(ref input_shmem) = *input_shmem_ptr else {
return;
};
@@ -237,7 +238,8 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
thread::spawn(move || {
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
let input_shmem_ptr = &raw const INPUT_SHMEM;
let Some(ref input_shmem) = *input_shmem_ptr else {
return;
};
let usb_in = input_shmem.as_slice();
@@ -274,7 +276,9 @@ pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
}
let device_rg = DEVICE.read();
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
let Some(device) = device_rg.as_ref().map(|z| z) else {
return;
};
let rgb = std::slice::from_raw_parts(rgb, 93);
let mut output = SLIDER_OUTPUT.write();
@@ -311,8 +315,14 @@ pub unsafe extern "C" fn chuni_io_led_set_colors(board: u8, rgb: *const u8) {
return;
}
if board > 2 {
return;
}
let device_rg = DEVICE.read();
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
let Some(device) = device_rg.as_ref().map(|z| z) else {
return;
};
let rgb = std::slice::from_raw_parts(rgb, 256);
let mut output = SLIDER_OUTPUT.write();
@@ -350,7 +360,7 @@ fn device_init() -> Result<()> {
thread::spawn(input_thread_proc);
return Ok(())
return Ok(());
}
}
@@ -358,14 +368,14 @@ fn device_init() -> Result<()> {
fn input_thread_proc() {
info!("Input thread started");
let mut shmem = match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false)
{
Ok(s) => s,
Err(e) => {
error!("Could not obtain shared memory for controller input: {e:#?}");
return;
}
};
let mut shmem =
match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false) {
Ok(s) => s,
Err(e) => {
error!("Could not obtain shared memory for controller input: {e:#?}");
return;
}
};
let usb_in = unsafe { shmem.as_slice_mut() };
let device_rg = DEVICE.read();
let device = device_rg.as_ref().unwrap();
@@ -400,7 +410,7 @@ fn create_input_shared_memory() -> HRESULT {
unsafe {
s.as_slice_mut().iter_mut().for_each(|m| *m = 0);
INPUT_SHMEM = Some(Rc::new(s))
INPUT_SHMEM = Some(Rc::new(s));
};
S_OK
}