Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
33c1f3398e | |||
b02fe2bc2e | |||
b53a842273 | |||
2328447f48 | |||
e1b285782d | |||
c35990b29d | |||
ade1b8644d | |||
821037f600 | |||
938df9648f | |||
b4c4947ac6 | |||
6972b92fa5 |
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chuniio-rs"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@ -17,6 +17,8 @@ panic = "abort"
|
||||
chusan = []
|
||||
tasoller_v1 = []
|
||||
tasoller_v2 = []
|
||||
laverita_v3 = []
|
||||
redboard = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.76"
|
||||
|
@ -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
|
||||
|
@ -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
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 laverita_v2;
|
||||
pub mod redboard;
|
||||
pub mod tasoller_v1;
|
||||
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: ???
|
||||
//! - Bit 26: FN2 (1 = pressed)
|
||||
//! - Bit 27: FN1 (1 = pressed)
|
||||
//! - Bit 28: IR2
|
||||
//! - Bit 29: IR1
|
||||
//! - Bit 30: IR4
|
||||
//! - Bit 31: IR3
|
||||
//! - Bit 32: IR6
|
||||
//! - Bit 33: IR5
|
||||
//! - Bit 28: IR1
|
||||
//! - Bit 29: IR2
|
||||
//! - Bit 30: IR3
|
||||
//! - Bit 31: IR4
|
||||
//! - Bit 32: IR5
|
||||
//! - Bit 33: IR6
|
||||
//! - Bits 34..66: touch sensor 1-32 (1 = held, top -> bottom then left -> right)
|
||||
//! - 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;
|
||||
|
||||
// IR1-6 in order
|
||||
let beams = input[3] >> 5 & 1
|
||||
| input[3] >> 3 & 2
|
||||
| input[3] >> 5 & 4
|
||||
| input[3] >> 3 & 8
|
||||
| input[4] << 3 & 16
|
||||
| input[4] << 5 & 32;
|
||||
let beams = input[3] >> 4
|
||||
| (input[4] & 3) << 4;
|
||||
|
||||
(opbtn, beams)
|
||||
}
|
||||
@ -135,13 +131,14 @@ 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 in output[output_start..output_start + 72]
|
||||
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] = rgb[air_rgb + 1];
|
||||
buf_chunk[1] = rgb[air_rgb];
|
||||
buf_chunk[2] = rgb[air_rgb + 2];
|
||||
buf_chunk[0] = state_chunk[1];
|
||||
buf_chunk[1] = state_chunk[0];
|
||||
buf_chunk[2] = state_chunk[2];
|
||||
}
|
||||
|
||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||
|
@ -18,7 +18,7 @@
|
||||
//! ### OUT Bulk (0x03)
|
||||
//! - Content length: 240 bytes
|
||||
//! - 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 168..240: Right tower LEDs (GRB)
|
||||
|
||||
@ -42,6 +42,7 @@ pub const OUTPUT_MEMORY_SIZE: usize = 240;
|
||||
#[inline(always)]
|
||||
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
|
||||
let opbtn = input[3] >> 6;
|
||||
let opbtn = (opbtn & 1) << 1 | (opbtn & 2) >> 1;
|
||||
let beams = input[3] & 63; // mask for last 6 bits
|
||||
|
||||
(opbtn, beams)
|
||||
@ -82,9 +83,9 @@ pub fn set_slider_leds<T: UsbContext>(
|
||||
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[1];
|
||||
buf_chunk[1] = state_chunk[0];
|
||||
buf_chunk[2] = state_chunk[2];
|
||||
buf_chunk[0] = state_chunk[2];
|
||||
buf_chunk[1] = state_chunk[1];
|
||||
buf_chunk[2] = state_chunk[0];
|
||||
}
|
||||
|
||||
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 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)
|
||||
.take(24)
|
||||
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
|
||||
{
|
||||
buf_chunk[0] = rgb[air_rgb + 1];
|
||||
buf_chunk[1] = rgb[air_rgb];
|
||||
buf_chunk[2] = rgb[air_rgb + 2];
|
||||
buf_chunk[0] = state_chunk[1];
|
||||
buf_chunk[1] = state_chunk[0];
|
||||
buf_chunk[2] = state_chunk[2];
|
||||
}
|
||||
|
||||
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;
|
||||
} 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 if #[cfg(feature = "redboard")] {
|
||||
use crate::backends::redboard as con_impl;
|
||||
} else {
|
||||
use crate::backends::dummy as con_impl;
|
||||
}
|
||||
@ -43,17 +47,15 @@ cfg_if::cfg_if! {
|
||||
if #[cfg(any(chuni, chusanapp))] {
|
||||
type SliderCallbackFn = unsafe extern "C" fn(data: *const u8);
|
||||
|
||||
use std::thread::{self, JoinHandle};
|
||||
use std::thread;
|
||||
|
||||
use ::log::info;
|
||||
use once_cell::sync::OnceCell;
|
||||
use rusb::{DeviceHandle, GlobalContext};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use crate::backends::ReadType;
|
||||
|
||||
static DEVICE: OnceCell<DeviceHandle<GlobalContext>> = OnceCell::new();
|
||||
static mut SLIDER_THREAD: OnceCell<JoinHandle<()>> = OnceCell::new();
|
||||
static DEVICE: RwLock<Option<DeviceHandle<GlobalContext>>> = RwLock::new(None);
|
||||
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]);
|
||||
}
|
||||
@ -69,7 +71,7 @@ cfg_if::cfg_if! {
|
||||
use crate::configuration::Configuration;
|
||||
|
||||
static COIN_COUNT: AtomicU16 = AtomicU16::new(0);
|
||||
static mut COIN_PRESSED: bool = false;
|
||||
static COIN_PRESSED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
lazy_static! {
|
||||
static ref CONFIGURATION: Configuration = {
|
||||
@ -107,6 +109,7 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
|
||||
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;
|
||||
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 {
|
||||
#[cfg(chuni)]
|
||||
{
|
||||
if let Err(e) = device_init() {
|
||||
error!("Could not initialize controller: {e:#?}");
|
||||
return E_FAIL;
|
||||
}
|
||||
// We don't care if we can't find the device at this time, it'll
|
||||
// probably come eventually.
|
||||
thread::spawn(device_init);
|
||||
}
|
||||
|
||||
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())
|
||||
|| GetAsyncKeyState(CONFIGURATION.coin_key as c_int) != 0
|
||||
{
|
||||
if !COIN_PRESSED {
|
||||
COIN_PRESSED = true;
|
||||
if !COIN_PRESSED.load(Ordering::Relaxed) {
|
||||
COIN_PRESSED.store(true, Ordering::Relaxed);
|
||||
COIN_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
} else {
|
||||
COIN_PRESSED = false;
|
||||
COIN_PRESSED.store(false, 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;
|
||||
}
|
||||
|
||||
if let Err(e) = device_init() {
|
||||
error!("Could not initialize controller: {e:#?}");
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// We don't care if we can't find the device at this time, it'll
|
||||
// probably come eventually.
|
||||
thread::spawn(device_init);
|
||||
create_input_shared_memory()
|
||||
}
|
||||
|
||||
@ -228,14 +228,15 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
|
||||
return;
|
||||
}
|
||||
|
||||
if SLIDER_THREAD.get().is_some() {
|
||||
if SLIDER_ACTIVE.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
SLIDER_ACTIVE.store(true, Ordering::SeqCst);
|
||||
|
||||
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
|
||||
let thread = thread::spawn(move || {
|
||||
|
||||
thread::spawn(move || {
|
||||
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
SLIDER_THREAD.set(thread).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -260,13 +259,7 @@ pub extern "C" fn chuni_io_slider_start(_callback: *const c_void) {}
|
||||
#[no_mangle]
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
pub extern "C" fn chuni_io_slider_stop() {
|
||||
let Some(thread) = (unsafe { SLIDER_THREAD.take() }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
SLIDER_ACTIVE.store(false, Ordering::SeqCst);
|
||||
|
||||
thread.join().expect("Couldn't join slider input thread");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -280,7 +273,9 @@ pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
let Some(device) = DEVICE.get() else { return };
|
||||
let rgb = std::slice::from_raw_parts(rgb, if board == 0 { 153 } else { 183 });
|
||||
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, 256);
|
||||
let mut output = SLIDER_OUTPUT.write();
|
||||
|
||||
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))]
|
||||
fn device_init() -> Result<()> {
|
||||
let Some(mut device) =
|
||||
rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID)
|
||||
else {
|
||||
return Err(anyhow!("Device not found."));
|
||||
};
|
||||
{
|
||||
let mut global_device = DEVICE.write();
|
||||
*global_device = None;
|
||||
}
|
||||
|
||||
device.set_active_configuration(1)?;
|
||||
device.claim_interface(0)?;
|
||||
DEVICE
|
||||
.set(device)
|
||||
.map_err(|_| anyhow!("Could not store device handle"))?;
|
||||
info!("Waiting for device...");
|
||||
|
||||
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))]
|
||||
fn input_thread_proc() {
|
||||
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,
|
||||
Err(e) => {
|
||||
@ -361,7 +367,8 @@ fn input_thread_proc() {
|
||||
}
|
||||
};
|
||||
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 {
|
||||
let result = match con_impl::READ_TYPE {
|
||||
@ -370,15 +377,31 @@ fn input_thread_proc() {
|
||||
};
|
||||
|
||||
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 {
|
||||
match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false) {
|
||||
Ok(s) => {
|
||||
unsafe { INPUT_SHMEM = Some(Rc::new(s)) };
|
||||
Ok(mut s) => {
|
||||
unsafe {
|
||||
s.as_slice_mut().iter_mut().for_each(|m| *m = 0);
|
||||
|
||||
INPUT_SHMEM = Some(Rc::new(s))
|
||||
};
|
||||
S_OK
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -23,7 +23,7 @@ pub fn init_logger() {
|
||||
env_logger::builder()
|
||||
.filter_level(::log::LevelFilter::Error)
|
||||
.filter_module(
|
||||
"chuniio-tasoller",
|
||||
"chuniio-rs",
|
||||
if cfg!(debug_assertions) {
|
||||
::log::LevelFilter::Debug
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user