Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
33c1f3398e | |||
b02fe2bc2e | |||
b53a842273 | |||
2328447f48 | |||
e1b285782d | |||
c35990b29d | |||
ade1b8644d | |||
821037f600 | |||
938df9648f | |||
b4c4947ac6 | |||
6972b92fa5 |
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "chuniio-rs"
|
name = "chuniio-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@ -17,6 +17,8 @@ panic = "abort"
|
|||||||
chusan = []
|
chusan = []
|
||||||
tasoller_v1 = []
|
tasoller_v1 = []
|
||||||
tasoller_v2 = []
|
tasoller_v2 = []
|
||||||
|
laverita_v3 = []
|
||||||
|
redboard = []
|
||||||
|
|
||||||
[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,6 @@
|
|||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
|
pub mod laverita_v2;
|
||||||
|
pub mod redboard;
|
||||||
pub mod tasoller_v1;
|
pub mod tasoller_v1;
|
||||||
pub mod tasoller_v2;
|
pub mod tasoller_v2;
|
||||||
|
|
||||||
|
129
src/backends/redboard.rs
Normal file
129
src/backends/redboard.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
//! # RedBoard
|
||||||
|
//! USB device: 0F0D:0092
|
||||||
|
//! 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
|
||||||
|
//!
|
||||||
|
#[cfg(any(chuni, chusanapp))]
|
||||||
|
use anyhow::Result;
|
||||||
|
#[cfg(any(chuni, chusanapp))]
|
||||||
|
use rusb::{DeviceHandle, UsbContext};
|
||||||
|
|
||||||
|
use super::ReadType;
|
||||||
|
|
||||||
|
pub const DEVICE_VID: u16 = 0x0F0D;
|
||||||
|
pub const DEVICE_PID: u16 = 0x0092;
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 buttons = (input[1] as u16) << 8 | (input[2] as u16);
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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] {
|
||||||
|
[0u8; 32]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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]) {}
|
||||||
|
|
||||||
|
/// 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<()> {
|
||||||
|
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(())
|
||||||
|
}
|
@ -19,12 +19,12 @@
|
|||||||
//! - Bits 0..26: ???
|
//! - Bits 0..26: ???
|
||||||
//! - Bit 26: FN2 (1 = pressed)
|
//! - Bit 26: FN2 (1 = pressed)
|
||||||
//! - Bit 27: FN1 (1 = pressed)
|
//! - Bit 27: FN1 (1 = pressed)
|
||||||
//! - Bit 28: IR2
|
//! - Bit 28: IR1
|
||||||
//! - Bit 29: IR1
|
//! - Bit 29: IR2
|
||||||
//! - Bit 30: IR4
|
//! - Bit 30: IR3
|
||||||
//! - Bit 31: IR3
|
//! - Bit 31: IR4
|
||||||
//! - Bit 32: IR6
|
//! - Bit 32: IR5
|
||||||
//! - Bit 33: IR5
|
//! - Bit 33: IR6
|
||||||
//! - Bits 34..66: touch sensor 1-32 (1 = held, top -> bottom then left -> right)
|
//! - Bits 34..66: touch sensor 1-32 (1 = held, top -> bottom then left -> right)
|
||||||
//! - Bits 66..88: ???
|
//! - Bits 66..88: ???
|
||||||
//!
|
//!
|
||||||
@ -58,12 +58,8 @@ pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
|
|||||||
let opbtn = input[3] >> 3 & 1 | input[3] >> 1 & 2;
|
let opbtn = input[3] >> 3 & 1 | input[3] >> 1 & 2;
|
||||||
|
|
||||||
// IR1-6 in order
|
// IR1-6 in order
|
||||||
let beams = input[3] >> 5 & 1
|
let beams = input[3] >> 4
|
||||||
| input[3] >> 3 & 2
|
| (input[4] & 3) << 4;
|
||||||
| input[3] >> 5 & 4
|
|
||||||
| input[3] >> 3 & 8
|
|
||||||
| input[4] << 3 & 16
|
|
||||||
| input[4] << 5 & 32;
|
|
||||||
|
|
||||||
(opbtn, beams)
|
(opbtn, beams)
|
||||||
}
|
}
|
||||||
@ -135,13 +131,14 @@ pub fn set_led_colors<T: UsbContext>(
|
|||||||
let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
|
let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
|
||||||
let output_start = if board == 0 { 96 } else { 168 };
|
let output_start = if board == 0 { 96 } else { 168 };
|
||||||
|
|
||||||
for buf_chunk in output[output_start..output_start + 72]
|
for (buf_chunk, state_chunk) in output[output_start..output_start + 72]
|
||||||
.chunks_mut(3)
|
.chunks_mut(3)
|
||||||
.take(24)
|
.take(24)
|
||||||
|
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
|
||||||
{
|
{
|
||||||
buf_chunk[0] = rgb[air_rgb + 1];
|
buf_chunk[0] = state_chunk[1];
|
||||||
buf_chunk[1] = rgb[air_rgb];
|
buf_chunk[1] = state_chunk[0];
|
||||||
buf_chunk[2] = rgb[air_rgb + 2];
|
buf_chunk[2] = state_chunk[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
//! ### OUT Bulk (0x03)
|
//! ### OUT Bulk (0x03)
|
||||||
//! - Content length: 240 bytes
|
//! - Content length: 240 bytes
|
||||||
//! - Bytes 0..3: magic bytes [0x42, 0x4C, 0x00]
|
//! - Bytes 0..3: magic bytes [0x42, 0x4C, 0x00]
|
||||||
//! - Bytes 3..96: Slider LED (GRB order, right -> left)
|
//! - Bytes 3..96: Slider LED (BGR order, right -> left)
|
||||||
//! - Bytes 96..168: Left tower LEDs (GRB)
|
//! - Bytes 96..168: Left tower LEDs (GRB)
|
||||||
//! - Bytes 168..240: Right tower LEDs (GRB)
|
//! - Bytes 168..240: Right tower LEDs (GRB)
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ pub const OUTPUT_MEMORY_SIZE: usize = 240;
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
|
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
|
||||||
let opbtn = input[3] >> 6;
|
let opbtn = input[3] >> 6;
|
||||||
|
let opbtn = (opbtn & 1) << 1 | (opbtn & 2) >> 1;
|
||||||
let beams = input[3] & 63; // mask for last 6 bits
|
let beams = input[3] & 63; // mask for last 6 bits
|
||||||
|
|
||||||
(opbtn, beams)
|
(opbtn, beams)
|
||||||
@ -82,9 +83,9 @@ pub fn set_slider_leds<T: UsbContext>(
|
|||||||
rgb: &[u8],
|
rgb: &[u8],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (buf_chunk, state_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
|
for (buf_chunk, state_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
|
||||||
buf_chunk[0] = state_chunk[1];
|
buf_chunk[0] = state_chunk[2];
|
||||||
buf_chunk[1] = state_chunk[0];
|
buf_chunk[1] = state_chunk[1];
|
||||||
buf_chunk[2] = state_chunk[2];
|
buf_chunk[2] = state_chunk[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||||
@ -107,13 +108,14 @@ pub fn set_led_colors<T: UsbContext>(
|
|||||||
let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
|
let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
|
||||||
let output_start = if board == 0 { 96 } else { 168 };
|
let output_start = if board == 0 { 96 } else { 168 };
|
||||||
|
|
||||||
for buf_chunk in output[output_start..output_start + 72]
|
for (buf_chunk, state_chunk) in output[output_start..output_start + 72]
|
||||||
.chunks_mut(3)
|
.chunks_mut(3)
|
||||||
.take(24)
|
.take(24)
|
||||||
|
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
|
||||||
{
|
{
|
||||||
buf_chunk[0] = rgb[air_rgb + 1];
|
buf_chunk[0] = state_chunk[1];
|
||||||
buf_chunk[1] = rgb[air_rgb];
|
buf_chunk[1] = state_chunk[0];
|
||||||
buf_chunk[2] = rgb[air_rgb + 2];
|
buf_chunk[2] = state_chunk[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||||
|
117
src/lib.rs
117
src/lib.rs
@ -34,6 +34,10 @@ 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 if #[cfg(feature = "redboard")] {
|
||||||
|
use crate::backends::redboard as con_impl;
|
||||||
} else {
|
} else {
|
||||||
use crate::backends::dummy as con_impl;
|
use crate::backends::dummy as con_impl;
|
||||||
}
|
}
|
||||||
@ -43,17 +47,15 @@ cfg_if::cfg_if! {
|
|||||||
if #[cfg(any(chuni, chusanapp))] {
|
if #[cfg(any(chuni, chusanapp))] {
|
||||||
type SliderCallbackFn = unsafe extern "C" fn(data: *const u8);
|
type SliderCallbackFn = unsafe extern "C" fn(data: *const u8);
|
||||||
|
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread;
|
||||||
|
|
||||||
use ::log::info;
|
use ::log::info;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use rusb::{DeviceHandle, GlobalContext};
|
use rusb::{DeviceHandle, GlobalContext};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
use crate::backends::ReadType;
|
use crate::backends::ReadType;
|
||||||
|
|
||||||
static DEVICE: OnceCell<DeviceHandle<GlobalContext>> = OnceCell::new();
|
static DEVICE: RwLock<Option<DeviceHandle<GlobalContext>>> = RwLock::new(None);
|
||||||
static mut SLIDER_THREAD: OnceCell<JoinHandle<()>> = OnceCell::new();
|
|
||||||
static SLIDER_ACTIVE: AtomicBool = AtomicBool::new(false);
|
static SLIDER_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||||
static SLIDER_OUTPUT: RwLock<[u8; con_impl::OUTPUT_MEMORY_SIZE]> = RwLock::new([0u8; con_impl::OUTPUT_MEMORY_SIZE]);
|
static SLIDER_OUTPUT: RwLock<[u8; con_impl::OUTPUT_MEMORY_SIZE]> = RwLock::new([0u8; con_impl::OUTPUT_MEMORY_SIZE]);
|
||||||
}
|
}
|
||||||
@ -69,7 +71,7 @@ cfg_if::cfg_if! {
|
|||||||
use crate::configuration::Configuration;
|
use crate::configuration::Configuration;
|
||||||
|
|
||||||
static COIN_COUNT: AtomicU16 = AtomicU16::new(0);
|
static COIN_COUNT: AtomicU16 = AtomicU16::new(0);
|
||||||
static mut COIN_PRESSED: bool = false;
|
static COIN_PRESSED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CONFIGURATION: Configuration = {
|
static ref CONFIGURATION: Configuration = {
|
||||||
@ -107,6 +109,7 @@ cfg_if::cfg_if! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub static TIMEOUT: Duration = Duration::from_millis(20);
|
pub static TIMEOUT: Duration = Duration::from_millis(20);
|
||||||
|
pub static DEVICE_POLLING_INTERVAL: Duration = Duration::from_millis(100);
|
||||||
static mut INPUT_SHMEM: Option<Rc<Shmem>> = None;
|
static mut INPUT_SHMEM: Option<Rc<Shmem>> = None;
|
||||||
const INPUT_SHMEM_OS_ID: &str = "fcfe5b1100568d65af167d81acbed71d";
|
const INPUT_SHMEM_OS_ID: &str = "fcfe5b1100568d65af167d81acbed71d";
|
||||||
|
|
||||||
@ -131,10 +134,9 @@ pub extern "C" fn chuni_io_get_api_version() -> u16 {
|
|||||||
pub extern "C" fn chuni_io_jvs_init() -> HRESULT {
|
pub extern "C" fn chuni_io_jvs_init() -> HRESULT {
|
||||||
#[cfg(chuni)]
|
#[cfg(chuni)]
|
||||||
{
|
{
|
||||||
if let Err(e) = device_init() {
|
// We don't care if we can't find the device at this time, it'll
|
||||||
error!("Could not initialize controller: {e:#?}");
|
// probably come eventually.
|
||||||
return E_FAIL;
|
thread::spawn(device_init);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create_input_shared_memory()
|
create_input_shared_memory()
|
||||||
@ -182,12 +184,12 @@ pub unsafe extern "C" fn chuni_io_jvs_read_coin_counter(total: *mut u16) {
|
|||||||
if con_impl::is_coin_button_pressed(input_shmem.as_slice())
|
if con_impl::is_coin_button_pressed(input_shmem.as_slice())
|
||||||
|| GetAsyncKeyState(CONFIGURATION.coin_key as c_int) != 0
|
|| GetAsyncKeyState(CONFIGURATION.coin_key as c_int) != 0
|
||||||
{
|
{
|
||||||
if !COIN_PRESSED {
|
if !COIN_PRESSED.load(Ordering::Relaxed) {
|
||||||
COIN_PRESSED = true;
|
COIN_PRESSED.store(true, Ordering::Relaxed);
|
||||||
COIN_COUNT.fetch_add(1, Ordering::Relaxed);
|
COIN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
COIN_PRESSED = false;
|
COIN_PRESSED.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
*total = COIN_COUNT.load(Ordering::Relaxed);
|
*total = COIN_COUNT.load(Ordering::Relaxed);
|
||||||
@ -207,11 +209,9 @@ pub unsafe extern "C" fn chuni_io_slider_init() -> HRESULT {
|
|||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = device_init() {
|
// We don't care if we can't find the device at this time, it'll
|
||||||
error!("Could not initialize controller: {e:#?}");
|
// probably come eventually.
|
||||||
return E_FAIL;
|
thread::spawn(device_init);
|
||||||
}
|
|
||||||
|
|
||||||
create_input_shared_memory()
|
create_input_shared_memory()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,14 +228,15 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if SLIDER_THREAD.get().is_some() {
|
if SLIDER_ACTIVE.load(Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SLIDER_ACTIVE.store(true, Ordering::SeqCst);
|
SLIDER_ACTIVE.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
|
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
|
||||||
let thread = thread::spawn(move || {
|
|
||||||
|
thread::spawn(move || {
|
||||||
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
|
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -249,8 +250,6 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
|
|||||||
thread::sleep(Duration::from_nanos(1_000_000));
|
thread::sleep(Duration::from_nanos(1_000_000));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
SLIDER_THREAD.set(thread).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@ -260,13 +259,7 @@ pub extern "C" fn chuni_io_slider_start(_callback: *const c_void) {}
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[cfg(any(chuni, chusanapp))]
|
#[cfg(any(chuni, chusanapp))]
|
||||||
pub extern "C" fn chuni_io_slider_stop() {
|
pub extern "C" fn chuni_io_slider_stop() {
|
||||||
let Some(thread) = (unsafe { SLIDER_THREAD.take() }) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
SLIDER_ACTIVE.store(false, Ordering::SeqCst);
|
SLIDER_ACTIVE.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
thread.join().expect("Couldn't join slider input thread");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@ -280,7 +273,9 @@ pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(device) = DEVICE.get() else { return };
|
let device_rg = DEVICE.read();
|
||||||
|
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
|
||||||
|
|
||||||
let rgb = std::slice::from_raw_parts(rgb, 93);
|
let rgb = std::slice::from_raw_parts(rgb, 93);
|
||||||
let mut output = SLIDER_OUTPUT.write();
|
let mut output = SLIDER_OUTPUT.write();
|
||||||
|
|
||||||
@ -316,8 +311,9 @@ pub unsafe extern "C" fn chuni_io_led_set_colors(board: u8, rgb: *const u8) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(device) = DEVICE.get() else { return };
|
let device_rg = DEVICE.read();
|
||||||
let rgb = std::slice::from_raw_parts(rgb, if board == 0 { 153 } else { 183 });
|
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();
|
let mut output = SLIDER_OUTPUT.write();
|
||||||
|
|
||||||
if let Err(e) = con_impl::set_led_colors(device, output.as_mut_slice(), board, rgb) {
|
if let Err(e) = con_impl::set_led_colors(device, output.as_mut_slice(), board, rgb) {
|
||||||
@ -331,28 +327,38 @@ pub extern "C" fn chuni_io_led_set_colors(_rgb: *const u8) {}
|
|||||||
|
|
||||||
#[cfg(any(chuni, chusanapp))]
|
#[cfg(any(chuni, chusanapp))]
|
||||||
fn device_init() -> Result<()> {
|
fn device_init() -> Result<()> {
|
||||||
let Some(mut device) =
|
{
|
||||||
rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID)
|
let mut global_device = DEVICE.write();
|
||||||
else {
|
*global_device = None;
|
||||||
return Err(anyhow!("Device not found."));
|
}
|
||||||
};
|
|
||||||
|
|
||||||
device.set_active_configuration(1)?;
|
info!("Waiting for device...");
|
||||||
device.claim_interface(0)?;
|
|
||||||
DEVICE
|
|
||||||
.set(device)
|
|
||||||
.map_err(|_| anyhow!("Could not store device handle"))?;
|
|
||||||
|
|
||||||
thread::spawn(input_thread_proc);
|
loop {
|
||||||
|
let Some(mut device) =
|
||||||
|
rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID)
|
||||||
|
else {
|
||||||
|
thread::sleep(DEVICE_POLLING_INTERVAL);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
device.set_active_configuration(1)?;
|
||||||
|
device.claim_interface(0)?;
|
||||||
|
|
||||||
|
let mut global_device = DEVICE.write();
|
||||||
|
*global_device = Some(device);
|
||||||
|
|
||||||
|
thread::spawn(input_thread_proc);
|
||||||
|
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(chuni, chusanapp))]
|
#[cfg(any(chuni, chusanapp))]
|
||||||
fn input_thread_proc() {
|
fn input_thread_proc() {
|
||||||
info!("Input thread started");
|
info!("Input thread started");
|
||||||
|
|
||||||
let mut shmem = match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, true)
|
let mut shmem = match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false)
|
||||||
{
|
{
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -361,7 +367,8 @@ fn input_thread_proc() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let usb_in = unsafe { shmem.as_slice_mut() };
|
let usb_in = unsafe { shmem.as_slice_mut() };
|
||||||
let device = DEVICE.get().unwrap();
|
let device_rg = DEVICE.read();
|
||||||
|
let device = device_rg.as_ref().unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let result = match con_impl::READ_TYPE {
|
let result = match con_impl::READ_TYPE {
|
||||||
@ -370,15 +377,31 @@ fn input_thread_proc() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
error!("Failed to read data from controller: {e:#?}");
|
match e {
|
||||||
|
rusb::Error::NoDevice | rusb::Error::Io => {
|
||||||
|
error!("Controller disconnected.");
|
||||||
|
|
||||||
|
usb_in.iter_mut().for_each(|m| *m = 0);
|
||||||
|
|
||||||
|
// Spawn a thread polling for a connection again
|
||||||
|
thread::spawn(device_init);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => error!("Could not read data from controller: {e:#?}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_input_shared_memory() -> HRESULT {
|
fn create_input_shared_memory() -> HRESULT {
|
||||||
match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false) {
|
match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false) {
|
||||||
Ok(s) => {
|
Ok(mut s) => {
|
||||||
unsafe { INPUT_SHMEM = Some(Rc::new(s)) };
|
unsafe {
|
||||||
|
s.as_slice_mut().iter_mut().for_each(|m| *m = 0);
|
||||||
|
|
||||||
|
INPUT_SHMEM = Some(Rc::new(s))
|
||||||
|
};
|
||||||
S_OK
|
S_OK
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -23,7 +23,7 @@ pub fn init_logger() {
|
|||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
.filter_level(::log::LevelFilter::Error)
|
.filter_level(::log::LevelFilter::Error)
|
||||||
.filter_module(
|
.filter_module(
|
||||||
"chuniio-tasoller",
|
"chuniio-rs",
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
::log::LevelFilter::Debug
|
::log::LevelFilter::Debug
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user