use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeadzoneShape {
Circular,
Square,
}
impl Default for DeadzoneShape {
fn default() -> Self {
Self::Circular
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SensitivityCurve {
Linear,
Quadratic,
Exponential { exponent: f32 },
}
impl Default for SensitivityCurve {
fn default() -> Self {
Self::Linear
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct AnalogCalibration {
#[serde(default = "default_deadzone")]
pub deadzone: f32,
#[serde(default)]
pub deadzone_shape: DeadzoneShape,
#[serde(default)]
pub sensitivity: SensitivityCurve,
#[serde(default = "default_sensitivity_multiplier")]
pub sensitivity_multiplier: f32,
#[serde(default = "default_range_min")]
pub range_min: i32,
#[serde(default = "default_range_max")]
pub range_max: i32,
#[serde(default)]
pub invert_x: bool,
#[serde(default)]
pub invert_y: bool,
}
fn default_deadzone() -> f32 {
0.15 }
fn default_sensitivity_multiplier() -> f32 {
1.0 }
fn default_range_min() -> i32 {
-32768 }
fn default_range_max() -> i32 {
32767 }
impl Default for AnalogCalibration {
fn default() -> Self {
Self {
deadzone: default_deadzone(),
deadzone_shape: DeadzoneShape::default(),
sensitivity: SensitivityCurve::default(),
sensitivity_multiplier: default_sensitivity_multiplier(),
range_min: default_range_min(),
range_max: default_range_max(),
invert_x: false,
invert_y: false,
}
}
}
impl AnalogCalibration {
pub fn new() -> Self {
Self::default()
}
pub fn with_deadzone(deadzone: f32) -> Self {
Self {
deadzone,
..Default::default()
}
}
pub fn with_sensitivity_curve(curve: SensitivityCurve) -> Self {
Self {
sensitivity: curve,
..Default::default()
}
}
pub fn validate(&self) -> Result<(), String> {
if self.deadzone < 0.0 || self.deadzone > 1.0 {
return Err(format!(
"Deadzone must be between 0.0 and 1.0, got {}",
self.deadzone
));
}
if self.sensitivity_multiplier < 0.1 || self.sensitivity_multiplier > 5.0 {
return Err(format!(
"Sensitivity multiplier must be between 0.1 and 5.0, got {}",
self.sensitivity_multiplier
));
}
if self.range_min >= self.range_max {
return Err(format!(
"Range min ({}) must be less than range max ({})",
self.range_min, self.range_max
));
}
if let SensitivityCurve::Exponential { exponent } = self.sensitivity {
if exponent < 0.1 || exponent > 10.0 {
return Err(format!(
"Exponential curve exponent must be between 0.1 and 10.0, got {}",
exponent
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_calibration() {
let calib = AnalogCalibration::default();
assert_eq!(calib.deadzone, 0.15);
assert_eq!(calib.deadzone_shape, DeadzoneShape::Circular);
assert_eq!(calib.sensitivity, SensitivityCurve::Linear);
assert_eq!(calib.sensitivity_multiplier, 1.0);
assert_eq!(calib.range_min, -32768);
assert_eq!(calib.range_max, 32767);
assert_eq!(calib.invert_x, false);
assert_eq!(calib.invert_y, false);
}
#[test]
fn test_new_calibration() {
let calib = AnalogCalibration::new();
assert_eq!(calib.deadzone, 0.15);
assert_eq!(calib.sensitivity, SensitivityCurve::Linear);
}
#[test]
fn test_with_deadzone() {
let calib = AnalogCalibration::with_deadzone(0.25);
assert_eq!(calib.deadzone, 0.25);
assert_eq!(calib.sensitivity, SensitivityCurve::Linear);
assert_eq!(calib.deadzone_shape, DeadzoneShape::Circular);
}
#[test]
fn test_with_sensitivity_curve() {
let calib = AnalogCalibration::with_sensitivity_curve(SensitivityCurve::Quadratic);
assert_eq!(calib.sensitivity, SensitivityCurve::Quadratic);
assert_eq!(calib.deadzone, 0.15);
}
#[test]
fn test_deadzone_shape_serialization() {
let circular = DeadzoneShape::Circular;
let serialized = serde_yaml::to_string(&circular).unwrap();
assert!(serialized.contains("circular"));
let square = DeadzoneShape::Square;
let serialized = serde_yaml::to_string(&square).unwrap();
assert!(serialized.contains("square"));
}
#[test]
fn test_sensitivity_curve_serialization() {
let linear = SensitivityCurve::Linear;
let serialized = serde_yaml::to_string(&linear).unwrap();
assert!(serialized.contains("linear"));
let quad = SensitivityCurve::Quadratic;
let serialized = serde_yaml::to_string(&quad).unwrap();
assert!(serialized.contains("quadratic"));
let exp = SensitivityCurve::Exponential { exponent: 2.5 };
let serialized = serde_yaml::to_string(&exp).unwrap();
assert!(serialized.contains("exponential"));
}
#[test]
fn test_calibration_yaml_roundtrip() {
let original = AnalogCalibration {
deadzone: 0.20,
deadzone_shape: DeadzoneShape::Square,
sensitivity: SensitivityCurve::Quadratic,
sensitivity_multiplier: 1.5,
range_min: -16384,
range_max: 16383,
invert_x: true,
invert_y: false,
};
let yaml = serde_yaml::to_string(&original).unwrap();
let deserialized: AnalogCalibration = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(deserialized.deadzone, original.deadzone);
assert_eq!(deserialized.deadzone_shape, original.deadzone_shape);
assert_eq!(deserialized.sensitivity, original.sensitivity);
assert_eq!(deserialized.sensitivity_multiplier, original.sensitivity_multiplier);
assert_eq!(deserialized.range_min, original.range_min);
assert_eq!(deserialized.range_max, original.range_max);
assert_eq!(deserialized.invert_x, original.invert_x);
assert_eq!(deserialized.invert_y, original.invert_y);
}
#[test]
fn test_calibration_yaml_with_defaults() {
let calib = AnalogCalibration::default();
let yaml = serde_yaml::to_string(&calib).unwrap();
let deserialized: AnalogCalibration = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(deserialized, calib);
}
#[test]
fn test_deadzone_bounds_valid() {
let calib = AnalogCalibration::default();
assert!(calib.validate().is_ok());
}
#[test]
fn test_deadzone_bounds_invalid_low() {
let mut calib = AnalogCalibration::default();
calib.deadzone = -0.1;
assert!(calib.validate().is_err());
}
#[test]
fn test_deadzone_bounds_invalid_high() {
let mut calib = AnalogCalibration::default();
calib.deadzone = 1.5;
assert!(calib.validate().is_err());
}
#[test]
fn test_sensitivity_multiplier_bounds_valid() {
let mut calib = AnalogCalibration::default();
calib.sensitivity_multiplier = 2.5;
assert!(calib.validate().is_ok());
}
#[test]
fn test_sensitivity_multiplier_bounds_invalid_low() {
let mut calib = AnalogCalibration::default();
calib.sensitivity_multiplier = 0.05;
assert!(calib.validate().is_err());
}
#[test]
fn test_sensitivity_multiplier_bounds_invalid_high() {
let mut calib = AnalogCalibration::default();
calib.sensitivity_multiplier = 10.0;
assert!(calib.validate().is_err());
}
#[test]
fn test_inversion_defaults() {
let calib = AnalogCalibration::default();
assert_eq!(calib.invert_x, false);
assert_eq!(calib.invert_y, false);
}
#[test]
fn test_exponential_curve_validation() {
let mut calib = AnalogCalibration::default();
calib.sensitivity = SensitivityCurve::Exponential { exponent: 2.0 };
assert!(calib.validate().is_ok());
calib.sensitivity = SensitivityCurve::Exponential { exponent: 0.05 };
assert!(calib.validate().is_err());
calib.sensitivity = SensitivityCurve::Exponential { exponent: 15.0 };
assert!(calib.validate().is_err());
}
#[test]
fn test_range_validation() {
let mut calib = AnalogCalibration::default();
assert!(calib.validate().is_ok());
calib.range_min = 32767;
calib.range_max = 32767;
assert!(calib.validate().is_err());
calib.range_min = 40000;
calib.range_max = 30000;
assert!(calib.validate().is_err());
}
#[test]
fn test_partial_yaml_deserialization() {
let yaml = r#"
deadzone: 0.25
sensitivity_multiplier: 2.0
"#;
let calib: AnalogCalibration = serde_yaml::from_str(yaml).unwrap();
assert_eq!(calib.deadzone, 0.25);
assert_eq!(calib.sensitivity_multiplier, 2.0);
assert_eq!(calib.deadzone_shape, DeadzoneShape::Circular);
assert_eq!(calib.sensitivity, SensitivityCurve::Linear);
assert_eq!(calib.range_min, -32768);
assert_eq!(calib.range_max, 32767);
assert_eq!(calib.invert_x, false);
assert_eq!(calib.invert_y, false);
}
#[test]
fn test_exponential_with_default_exponent() {
let original = AnalogCalibration {
sensitivity: SensitivityCurve::Exponential { exponent: 3.0 },
..Default::default()
};
let yaml = serde_yaml::to_string(&original).unwrap();
assert!(yaml.contains("!exponential"));
assert!(yaml.contains("exponent: 3"));
let calib: AnalogCalibration = serde_yaml::from_str(&yaml).unwrap();
match calib.sensitivity {
SensitivityCurve::Exponential { exponent } => {
assert_eq!(exponent, 3.0);
}
_ => panic!("Expected Exponential curve"),
}
}
}