use nom::{combinator::eof, sequence::tuple};
use crate::{
error::{JoyConDriverError, JoyConDriverResult},
joycon::{JoyConDevice, SUB_COMMAND_READ_HEADER_BYTES, USER_CALIBRATION_DATA_MAGIC},
parser::{parse_stick_data, parse_stick_data_var},
};
#[derive(Debug, Clone)]
pub struct StickData {
pub horizontal: u16,
pub vertical: u16,
}
impl TryFrom<&[u8]> for StickData {
type Error = JoyConDriverError;
fn try_from(buf: &[u8]) -> JoyConDriverResult<Self> {
let (_, ((horizontal, vertical), _)) = tuple((parse_stick_data_var, eof))(buf)
.map_err(|_| JoyConDriverError::InvalidResultBuffer)?;
Ok(Self {
horizontal,
vertical,
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum StickDirection {
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
TopLeft,
Neutral,
}
impl TryFrom<u8> for StickDirection {
type Error = JoyConDriverError;
fn try_from(data: u8) -> JoyConDriverResult<Self> {
Ok(match data {
0 => StickDirection::Top,
1 => StickDirection::TopRight,
2 => StickDirection::Right,
3 => StickDirection::BottomRight,
4 => StickDirection::Bottom,
5 => StickDirection::BottomLeft,
6 => StickDirection::Left,
7 => StickDirection::TopLeft,
8 => StickDirection::Neutral,
_ => return Err(JoyConDriverError::InvalidResultBuffer),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct StickAxisCalibration {
pub max: u16,
pub center: u16,
pub min: u16,
}
impl StickAxisCalibration {
fn from_data(center: u16, max_above_center: u16, min_below_center: u16) -> Self {
Self {
max: center.saturating_add(max_above_center),
center,
min: center.saturating_sub(min_below_center),
}
}
}
#[derive(Debug, Clone)]
pub struct StickCalibration {
pub x: StickAxisCalibration,
pub y: StickAxisCalibration,
}
impl StickCalibration {
const DATA_BYTES: usize = 9;
pub fn read_left_factory_data(device: &JoyConDevice) -> JoyConDriverResult<Option<Self>> {
let mut buf = [0u8; SUB_COMMAND_READ_HEADER_BYTES + Self::DATA_BYTES];
device.read(&mut buf, 0x603D)?;
Self::try_from_left(&buf[SUB_COMMAND_READ_HEADER_BYTES..])
}
pub fn read_left_user_data(device: &JoyConDevice) -> JoyConDriverResult<Option<Self>> {
let mut buf = [0u8; SUB_COMMAND_READ_HEADER_BYTES
+ USER_CALIBRATION_DATA_MAGIC.len()
+ Self::DATA_BYTES];
device.read(&mut buf, 0x8010)?;
const DATA_START: usize = SUB_COMMAND_READ_HEADER_BYTES + USER_CALIBRATION_DATA_MAGIC.len();
if buf[SUB_COMMAND_READ_HEADER_BYTES..DATA_START] == USER_CALIBRATION_DATA_MAGIC {
Self::try_from_left(&buf[DATA_START..])
} else {
Ok(None)
}
}
fn try_from_left(buf: &[u8]) -> JoyConDriverResult<Option<Self>> {
if buf.iter().all(|b| *b == 0xFF) {
return Ok(None);
}
let (_, (max_above_center, center, min_below_center, _)) =
tuple((parse_stick_data, parse_stick_data, parse_stick_data, eof))(buf)
.map_err(|_| JoyConDriverError::InvalidResultBuffer)?;
Ok(Some(Self {
x: StickAxisCalibration::from_data(center.0, max_above_center.0, min_below_center.0),
y: StickAxisCalibration::from_data(center.1, max_above_center.1, min_below_center.1),
}))
}
pub fn read_right_factory_data(device: &JoyConDevice) -> JoyConDriverResult<Option<Self>> {
let mut buf = [0u8; SUB_COMMAND_READ_HEADER_BYTES + Self::DATA_BYTES];
device.read(&mut buf, 0x6046)?;
Self::try_from_right(&buf[SUB_COMMAND_READ_HEADER_BYTES..])
}
pub fn read_right_user_data(device: &JoyConDevice) -> JoyConDriverResult<Option<Self>> {
let mut buf = [0u8; SUB_COMMAND_READ_HEADER_BYTES
+ USER_CALIBRATION_DATA_MAGIC.len()
+ Self::DATA_BYTES];
device.read(&mut buf, 0x801B)?;
const DATA_START: usize = SUB_COMMAND_READ_HEADER_BYTES + USER_CALIBRATION_DATA_MAGIC.len();
if buf[SUB_COMMAND_READ_HEADER_BYTES..DATA_START] == USER_CALIBRATION_DATA_MAGIC {
Self::try_from_left(&buf[DATA_START..])
} else {
Ok(None)
}
}
fn try_from_right(buf: &[u8]) -> JoyConDriverResult<Option<Self>> {
if buf.iter().all(|b| *b == 0xFF) {
return Ok(None);
}
let (_, (center, min_below_center, max_above_center, _)) =
tuple((parse_stick_data, parse_stick_data, parse_stick_data, eof))(buf)
.map_err(|_| JoyConDriverError::InvalidResultBuffer)?;
Ok(Some(Self {
x: StickAxisCalibration::from_data(center.0, max_above_center.0, min_below_center.0),
y: StickAxisCalibration::from_data(center.1, max_above_center.1, min_below_center.1),
}))
}
}