Skip to content

Commit

Permalink
feat(Device): Add support for left/right touchpad events for the Oran…
Browse files Browse the repository at this point in the history
…gePi Neo.
  • Loading branch information
pastaq committed Jun 15, 2024
1 parent d54074f commit 832f870
Show file tree
Hide file tree
Showing 12 changed files with 538 additions and 42 deletions.
19 changes: 19 additions & 0 deletions rootfs/usr/share/inputplumber/devices/50-orangepi_neo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ matches:
# One or more source devices to combine into a single virtual device. The events
# from these devices will be watched and translated according to the key map.
source_devices:
#- group: mouse # Touch Devices
# unique: false
# hidraw:
# vendor_id: 0x0911
# product_id: 0x5288
#- group: mouse
# blocked: true
# evdev:
# name: OPI0001:00 0911:5288 Touchpad
# phys_path: i2c-OPI0001:00
#- group: mouse
# blocked: true
# evdev:
# name: OPI0002:00 0911:5288 Touchpad
# phys_path: i2c-OPI0002:00
- group: gamepad
evdev:
name: Microsoft X-Box 360 pad
Expand All @@ -34,6 +49,10 @@ source_devices:
- group: imu
iio:
name: i2c-BMI0260:00
mount_matrix:
x: [1, 0, 0]
y: [0, 0, -1]
z: [0, -1, 0]

# The target input device(s) that the virtual device profile can use
target_devices:
Expand Down
5 changes: 0 additions & 5 deletions rootfs/usr/share/inputplumber/schema/composite_device_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,6 @@
"type": "integer"
}
},
"required": [
"interface_num",
"product_id",
"vendor_id"
],
"title": "Hidraw"
},
"IIO": {
Expand Down
6 changes: 0 additions & 6 deletions src/drivers/lego/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ pub struct TouchAxisInput {
pub y: u16,
}

/// TouchAxisID tracks the sequential count of touch inputs
#[derive(Clone, Debug)]
pub struct TouchAxisID {
pub value: u32,
}

/// Axis input contain (x, y) coordinates
#[derive(Clone, Debug)]
pub struct MouseAxisInput {
Expand Down
1 change: 1 addition & 0 deletions src/drivers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod dualsense;
pub mod iio_imu;
pub mod lego;
pub mod opineo;
pub mod steam_deck;
165 changes: 165 additions & 0 deletions src/drivers/opineo/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use std::{
error::Error,
ffi::CString,
time::{Duration, Instant},
u8, vec,
};

use hidapi::HidDevice;
use packed_struct::{types::SizedInteger, PackedStruct};

use super::{
event::{Event, TouchAxisInput},
hid_report::TouchpadDataReport,
};

// Hardware ID's
pub const VID: u16 = 0x0911;
pub const PID: u16 = 0x5288;
// Report ID
pub const TOUCH_DATA: u8 = 0x04;
// Input report size
const TOUCHPAD_PACKET_SIZE: usize = 10;
// HID buffer read timeout
const HID_TIMEOUT: i32 = 10;
// Input report axis ranges
pub const PAD_X_MAX: f64 = 512.0;
pub const PAD_Y_MAX: f64 = 512.0;

pub struct Driver {
/// HIDRAW device instance
device: HidDevice,
/// Whether or not we are detecting a touch event currently.
is_touching: bool,
/// Timestamp of the last touch event.
last_touch: Instant,
/// State for the touchpad device
touchpad_state: Option<TouchpadDataReport>,
}

impl Driver {
pub fn new(path: String) -> Result<Self, Box<dyn Error + Send + Sync>> {
let fmtpath = path.clone();
let path = CString::new(path)?;
let api = hidapi::HidApi::new()?;
let device = api.open_path(&path)?;
let info = device.get_device_info()?;
if info.vendor_id() != VID || info.product_id() != PID {
return Err(format!("Device '{fmtpath}' is not a OrangePi NEO Controller").into());
}

Ok(Self {
device,
is_touching: false,
last_touch: Instant::now(),
touchpad_state: None,
})
}

/// Poll the device and read input reports
pub fn poll(&mut self) -> Result<Vec<Event>, Box<dyn Error + Send + Sync>> {
// Read data from the device into a buffer
let mut buf = [0; TOUCHPAD_PACKET_SIZE];
let bytes_read = self.device.read_timeout(&mut buf[..], HID_TIMEOUT)?;

let report_id = buf[0];
let slice = &buf[..bytes_read];
//log::trace!("Got Report ID: {report_id}");
//log::trace!("Got Report Size: {bytes_read}");

let mut events = match report_id {
TOUCH_DATA => {
log::trace!("Got touch data.");
if bytes_read != TOUCHPAD_PACKET_SIZE {
return Err("Invalid packet size for Keyboard or Touchpad Data.".into());
}
// Handle the incoming input report
let sized_buf = slice.try_into()?;

self.handle_touchinput_report(sized_buf)?
}
_ => {
//log::trace!("Invalid Report ID.");
let events = vec![];
events
}
};

if self.is_touching && (self.last_touch.elapsed() > Duration::from_millis(4)) {
let event: Event = self.release_touch();
events.push(event);
}

Ok(events)
}

/// Unpacks the buffer into a [TouchpadDataReport] structure and updates
/// the internal touchpad_state
fn handle_touchinput_report(
&mut self,
buf: [u8; TOUCHPAD_PACKET_SIZE],
) -> Result<Vec<Event>, Box<dyn Error + Send + Sync>> {
let input_report = TouchpadDataReport::unpack(&buf)?;

// Print input report for debugging
//log::trace!("--- Input report ---");
//log::trace!("{input_report}");
//log::trace!("---- End Report ----");

// Update the state
let old_dinput_state = self.update_touchpad_state(input_report);

// Translate the state into a stream of input events
let events = self.translate_touch(old_dinput_state);

Ok(events)
}

/// Update touchinput state
fn update_touchpad_state(
&mut self,
input_report: TouchpadDataReport,
) -> Option<TouchpadDataReport> {
let old_state = self.touchpad_state;
self.touchpad_state = Some(input_report);
old_state
}

/// Translate the state into individual events
fn translate_touch(&mut self, old_state: Option<TouchpadDataReport>) -> Vec<Event> {
let mut events = Vec::new();
let Some(state) = self.touchpad_state else {
return events;
};

// Translate state changes into events if they have changed
let Some(_) = old_state else {
return events;
};
//// Axis events
if !self.is_touching {
self.is_touching = true;
log::trace!("Started TOUCH event");
}
events.push(Event::TouchAxis(TouchAxisInput {
index: 0,
is_touching: true,
x: state.touch_x.to_primitive(),
y: state.touch_y.to_primitive(),
}));

self.last_touch = Instant::now();
events
}

fn release_touch(&mut self) -> Event {
log::trace!("Released TOUCH event.");
self.is_touching = false;
Event::TouchAxis(TouchAxisInput {
index: 0,
is_touching: false,
x: 0,
y: 0,
})
}
}
20 changes: 20 additions & 0 deletions src/drivers/opineo/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// Events that can be emitted by the Steam Deck controller
#[derive(Clone, Debug)]
pub enum Event {
TouchAxis(TouchAxisInput),
}

/// Axis input contain (x, y) coordinates
#[derive(Clone, Debug)]
pub struct TouchAxisInput {
pub index: u8,
pub is_touching: bool,
pub x: u16,
pub y: u16,
}

/// TouchAxisID tracks the sequential count of touch inputs
#[derive(Clone, Debug)]
pub struct TouchAxisID {
pub value: u32,
}
70 changes: 70 additions & 0 deletions src/drivers/opineo/hid_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Reference: https://gitlab.com/open-sd/opensd/-/blob/main/src/opensdd/drivers/gamepad/hid_reports.hpp
#![allow(warnings)]
use packed_struct::prelude::*;

/// Different report types
pub enum ReportType {
TouchpadData = 0x04,
}

impl ReportType {
pub fn to_u8(&self) -> u8 {
match self {
ReportType::TouchpadData => ReportType::TouchpadData as u8,
}
}
}

// TouchpadData
//
// # ReportID: 4 / Confidence: 1 | Tip Switch: 0 | Contact Id: 0 | # | X: 0 | Y: 0 | Scan Time: 40742 | Contact Count: 1 | Button: 0 0 0 | #
// E: 000000.000000 10 04 01 00 00 00 00 26 9f 01 00
//
// X Axis
// # ReportID: 4 / Confidence: 1 | Tip Switch: 0 | Contact Id: 0 | # | X: 512 | Y: 0 | Scan Time: 49251 | Contact Count: 1 | Button: 0 0 0 | #
// E: 000022.851877 10 04 01 00 02 00 00 63 c0 01 00
// # ReportID: 4 / Confidence: 1 | Tip Switch: 0 | Contact Id: 0 | # | X: 467 | Y: 0 | Scan Time: 2804 | Contact Count: 1 | Button: 0 0 0 | #
// E: 000455.355131 10 04 01 d3 01 00 00 f4 0a 01 00
//
// Y Axis
// # ReportID: 4 / Confidence: 1 | Tip Switch: 0 | Contact Id: 0 | # | X: 0 | Y: 512 | Scan Time: 11269 | Contact Count: 1 | Button: 0 0 0 | #
//E: 000054.609703 10 04 01 00 00 00 02 05 2c 01 00
#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)]
#[packed_struct(bit_numbering = "msb0", size_bytes = "10")]
pub struct TouchpadDataReport {
// BYTE 0
#[packed_field(bytes = "0")]
pub report_id: u8,
// BYTE 1
#[packed_field(bytes = "1")]
pub confidence: u8,
// BYTE 2-3
#[packed_field(bytes = "2..=3", endian = "lsb")]
pub touch_x: Integer<u16, packed_bits::Bits<16>>,
// BYTE 4
#[packed_field(bytes = "4")]
pub unk_4: u8,
// BYTE 5-6
#[packed_field(bytes = "5..=6", endian = "lsb")]
pub touch_y: Integer<u16, packed_bits::Bits<16>>,
// BYTE 7-8
#[packed_field(bytes = "7..=8", endian = "lsb")]
pub scan_time: Integer<u16, packed_bits::Bits<16>>,
// BYTE 9
#[packed_field(bytes = "9")]
pub unk_9: u8,
}

impl Default for TouchpadDataReport {
fn default() -> Self {
Self {
report_id: Default::default(),
confidence: Default::default(),
touch_x: Default::default(),
unk_4: Default::default(),
touch_y: Default::default(),
scan_time: Default::default(),
unk_9: Default::default(),
}
}
}
19 changes: 19 additions & 0 deletions src/drivers/opineo/hid_report_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::error::Error;

use packed_struct::types::{Integer, SizedInteger};

use crate::drivers::opineo::hid_report::TouchpadDataReport;

#[tokio::test]
async fn test_opi_hid() -> Result<(), Box<dyn Error>> {
let mut report = TouchpadDataReport::default();
println!("Before Report: {}", report);
report.touch_x = Integer::from_primitive(467);
report.touch_y = Integer::from_primitive(512);

println!("After Report: {}", report);
assert_eq!(report.touch_x.to_primitive(), 467);
assert_eq!(report.touch_y.to_primitive(), 512);

Ok(())
}
5 changes: 5 additions & 0 deletions src/drivers/opineo/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod driver;
pub mod event;
pub mod hid_report;
#[cfg(test)]
pub mod hid_report_test;
5 changes: 4 additions & 1 deletion src/input/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,9 @@ impl Manager {
}
}
SourceDeviceInfo::HIDRawDeviceInfo(info) => {
log::trace!("Checking if existing composite device is missing hidraw device");
log::trace!(
"Checking if existing composite device is missing hidraw device: {info:?}"
);
for source_device in source_devices {
if source_device.hidraw.is_none() {
continue;
Expand Down Expand Up @@ -986,6 +988,7 @@ impl Manager {
}
}
SourceDeviceInfo::HIDRawDeviceInfo(info) => {
log::trace!("Checking if hidraw device has a device profile: {info:?}");
for source_device in source_devices {
if source_device.hidraw.is_none() {
continue;
Expand Down
Loading

0 comments on commit 832f870

Please sign in to comment.