🕌
hidapi クレートを使ってみる
はじめに
hidapi クレートの存在は知っていたけど、そういえばちゃんと使ったことがなかった。
ということで hidapi クレートを使ってネイティブ環境の HID からレポートを読んでみる。
デバイス列挙
最後に rust を触ってから日が経ってしまったので、 cargo の使い方も覚束ない。
以下のように始めた。
> rustup update
> cargo new try-hidapi
> cd try-hidapi
> cargo add hidapi
> code .
すると、 "Cargo.toml" は以下のようになっている。
[dependencies]
hidapi = "2.1.0"
"main.rs" を以下のようにしてみる。
main.rs
extern crate hidapi;
use hidapi::HidApi;
fn main() {
match HidApi::new() {
Ok(api) => {
let mut devs: Vec<_> = api.device_list().collect();
devs.sort_by_key(|d| d.product_id());
devs.sort_by_key(|d| d.vendor_id());
for device in devs {
println!("PID:{:04X}_VID:{:04X}&UP:{:04X}_U:{:04X}",
device.vendor_id(), device.product_id(),
device.usage_page(), device.usage());
if let Ok(hid) = device.open_device(&api) {
if let Ok(man) = hid.get_manufacturer_string() {
println!(" manufacturer: {}", man.unwrap());
} else {
println!(" failed to get manufacturer");
}
if let Ok(prd) = hid.get_product_string() {
println!(" product name: {}", prd.unwrap());
} else {
println!(" failed to get product name");
}
// try `let...else...` statement
let Ok(sn) = hid.get_serial_number_string() else {
println!(" failed to get serial number");
continue;
};
println!(" serial number: {}", sn.unwrap());
} else {
println!(" it cannot be opened");
continue;
}
}
},
Err(e) => {
eprintln!("Error: {}", e);
},
}
}
cargo run
すると
PID:0000_VID:0000&UP:000D_U:0001
manufacturer: Microsoft
product name: HID VHF Driver
serial number: 1.0
... (中略) ...
PID:046D_VID:0A4C&UP:000B_U:0001
manufacturer: Logitech
product name: Logi Z407
serial number: 00000000
PID:046D_VID:C52B&UP:FF00_U:0004
manufacturer: Logitech
product name: USB Receiver
serial number:
... (中略) ...
PID:054C_VID:09CC&UP:0001_U:0005
manufacturer: Sony Interactive Entertainment
product name: Wireless Controller
serial number: 401b5fc1d427
... (以下略) ...
VID と PID でソートされた状態で HID デバイスが列挙された。
PID:054C_VID:09CC&UP:0001_U:0005
は DualShock4 だ。
Input Report を読む
ということで毎度ながら DualShock4 の HID Input Report を読んでみる。
"main.rs" は以下のようにした。
main.rs
extern crate hidapi;
use hidapi::{HidApi, HidDevice};
/// from "https://bitbucket.org/unessa/dualshock4-rust/src/master/src/dualshock4/mod.rs"
const DUALSHOCK4_VENDOR_ID:u16 = 0x54c;
// Dualshock4 product ID changed after playstation update 5.50
const DUALSHOCK4_PRODUCT_ID_NEW:u16 = 0x9cc;
const DUALSHOCK4_PRODUCT_ID_OLD:u16 = 0x5c4;
fn start(hid: HidDevice) {
let mut buf = [0u8; 78];
let timeout = 1000;
loop {
match hid.read_timeout(&mut buf, timeout) {
Ok(sz) => {
println!("Read ({} bytes): {:?}", sz, &buf[..sz]);
},
Err(_) => ()
}
}
}
fn main() {
match HidApi::new() {
Ok(api) => {
for info in api.device_list() {
if info.vendor_id() == DUALSHOCK4_VENDOR_ID && (
info.product_id() == DUALSHOCK4_PRODUCT_ID_NEW ||
info.product_id() == DUALSHOCK4_PRODUCT_ID_OLD ) {
println!("DualShock4 was found");
if let Ok(hid) = info.open_device(&api) {
println!("Succeeded to open DualShock4");
start(hid);
break;
} else {
println!("Failed to open DualShock4");
continue;
}
}
}
},
Err(e) => {
eprintln!("Error: {}", e);
},
}
println!("DualShock4 was not found");
}
出力は以下のようになった。
DualShock4 was found
Succeeded to open DualShock4
Read (78 bytes): [17, 192, 0, 132, 124, 127, 124, 8, 0, 164, 0, 0, 149, 134, 10, 9, 0, 0, 0, 247, 255, 244, 255, 164, 30, 0, 2, 0, 0, 0, 0, 0, 27, 0, 0, 1, 204, 132, 163, 32, 13, 131, 172, 21, 40, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 141, 101, 244, 80]
Read (78 bytes): [17, 192, 0, 132, 124, 127, 124, 8, 0, 168, 0, 0, 132, 137, 10, 9, 0, 255, 255, 245, 255, 245, 255, 132, 30, 12, 2, 0, 0, 0, 0, 0, 27, 0, 0, 1, 204, 132, 163, 32, 13, 131, 172, 21, 40, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 76, 51, 207, 92]
Read (78 bytes): [17, 192, 0, 132, 124, 127, 124, 8, 0, 172, 0, 0, 114, 140, 10, 11, 0, 0, 0, 246, 255, 5, 0, 134, 30, 249, 1, 0, 0, 0, 0, 0, 27, 0, 0, 1, 204, 132, 163, 32, 13, 131, 172, 21, 40, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 228, 96, 6, 13]
...
(すごい勢いでコマンドラインが流れるので、Ctrl-C ですぐに止める)
せっかくなのでデコードする
このバイト列をデコードするために以下のようなモジュールを追加する。
ds4.rs
ds4.rs
/// refer to https://www.psdevwiki.com/ps4/DS4-BT#0x11
#[derive(Debug, Default, Clone)]
pub struct Ds4Buttons<T: Clone> {
pub north: T,
pub east: T,
pub south: T,
pub west: T,
pub square: T,
pub cross: T,
pub circle: T,
pub triangle: T,
pub l1: T,
pub r1: T,
pub l2: T,
pub r2: T,
pub pause: T,
pub option: T,
pub l3: T,
pub r3: T,
pub ps: T,
pub tpad: T,
}
fn gen_buttons(raw: [u8; 3]) -> Ds4Buttons<bool> {
let mut n = false;
let mut e = false;
let mut s = false;
let mut w = false;
match raw[0] & 0xF {
0 => { n = true; },
1 => { n = true; e = true; },
2 => { e = true; },
3 => { e = true; s = true; },
4 => { s = true; },
5 => { s = true; w = true; },
6 => { w = true; },
7 => { w = true; n = true; },
_ => (),
}
Ds4Buttons {
north: n,
east: e,
south: s,
west: w,
square: (raw[0] & 0x10) != 0,
cross: (raw[0] & 0x20) != 0,
circle: (raw[0] & 0x40) != 0,
triangle: (raw[0] & 0x80) != 0,
l1: (raw[1] & 0x01) != 0,
r1: (raw[1] & 0x02) != 0,
l2: (raw[1] & 0x04) != 0,
r2: (raw[1] & 0x08) != 0,
pause: (raw[1] & 0x10) != 0,
option: (raw[1] & 0x20) != 0,
l3: (raw[1] & 0x40) != 0,
r3: (raw[1] & 0x80) != 0,
ps: (raw[2] & 0x01) != 0,
tpad: (raw[2] & 0x02) != 0,
}
}
#[derive(Debug, Default, Clone)]
pub struct Ds4Finger {
pub down: bool,
pub track_num: u8,
pub coord: (u16, u16), // (x, y) in 12bit
}
fn gen_finger(raw: [u8;4]) -> Ds4Finger {
Ds4Finger {
down: (raw[0] & 0x80) != 0,
track_num: (raw[0] & 0x7f),
coord: (
(raw[1] as u16) | (((raw[2] & 0xF) as u16) << 8),
((raw[2] >> 4) as u16) | ((raw[3] as u16) << 4)
),
}
}
#[derive(Debug, Default, Clone)]
pub struct Ds4Input {
pub report_id: u8,
pub stick_left: (u8, u8), // 0(left, top)..+256(right, bottom)
pub stick_right: (u8, u8), // 0(left, top)..+256(right, bottom)
pub buttons: Ds4Buttons<bool>,
pub counter: u8,
pub trigger_left: u8, // 0..+256
pub trigger_right: u8, // 0..+256
pub gyro: (i16, i16, i16), // (x, y, z)
pub accel: (i16, i16, i16), // (x, y, z) positive: right, up, backward
pub finger1: Ds4Finger,
pub finger2: Ds4Finger,
}
pub fn input(rep: &Vec<u8>, ofs: usize) -> Ds4Input {
Ds4Input {
report_id: rep[ofs + 0],
stick_left: (rep[ofs + 2], rep[ofs + 3]),
stick_right: (rep[ofs + 4], rep[ofs + 5]),
buttons: gen_buttons([rep[ofs + 6], rep[ofs + 7], rep[ofs + 8]]),
counter: rep[ofs + 8] >> 2,
trigger_left: rep[ofs + 9],
trigger_right: rep[ofs + 10],
gyro: (
(rep[ofs + 14] as i16)|((rep[ofs + 15] as i16) << 8),
(rep[ofs + 16] as i16)|((rep[ofs + 17] as i16) << 8),
(rep[ofs + 18] as i16)|((rep[ofs + 19] as i16) << 8)),
accel: (
(rep[ofs + 20] as i16)|((rep[ofs + 21] as i16) << 8),
(rep[ofs + 22] as i16)|((rep[ofs + 23] as i16) << 8),
(rep[ofs + 24] as i16)|((rep[ofs + 25] as i16) << 8)),
finger1: gen_finger([rep[ofs + 36], rep[ofs + 37], rep[ofs + 39], rep[ofs + 40]]),
finger2: gen_finger([rep[ofs + 41], rep[ofs + 42], rep[ofs + 43], rep[ofs + 44]]),
}
}
main.rs の start()
は以下のように変更。
main.rs
mod ds4;
// ...
fn start(hid: HidDevice) {
let mut buf = [0u8; 78];
let timeout = 1000;
loop {
match hid.read_timeout(&mut buf, timeout) {
Ok(_) => {
let rep: Vec<u8> = buf.to_vec();
// offset 1 byte for HID report ID
let input = ds4::input(&rep, 1);
println!("{:?}", &input);
},
Err(_) => ()
}
}
}
これで、デコードされた DualShock4 の入力情報が Debug 用フォーマットで表示されるようになった。
DualShock4 was found
Succeeded to open DualShock4
Ds4Input { report_id: 192, stick_left: (133, 124), stick_right: (125, 123), buttons: Ds4Buttons { north: false, east: false, south: false, west: false, square: false, cross: false, circle: false, triangle: false, l1: false, r1: false, l2: false, r2: false, pause: false, option: false, l3: false, r3: false, ps: false, tpad: false }, counter: 56, trigger_left: 0, trigger_right: 0, gyro: (9, -1, -10), accel: (-8, 7824, 548), finger1: Ds4Finger { down: true, track_num: 16, coord: (3696, 2290) }, finger2: Ds4Finger { down: false, track_num: 40, coord: (3671, 1) } }
...
Discussion