use crate::imu::Acceleration;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GravityDirection {
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Pose {
FaceUp,
FaceDown,
PortraitUp,
PortraitDown,
LandscapeLeft,
LandscapeRight,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MotionState {
Unknown,
Still,
Moving,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MotionContext {
pub state: MotionState,
pub pose: Pose,
pub gravity: GravityDirection,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MotionConfig {
pub still_threshold_mg: i32,
pub still_samples: u8,
}
impl Default for MotionConfig {
fn default() -> Self {
Self {
still_threshold_mg: 80,
still_samples: 4,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MotionDetector {
config: MotionConfig,
previous: Option<Acceleration>,
stable_count: u8,
}
impl MotionDetector {
#[must_use]
pub const fn new() -> Self {
Self::with_config(MotionConfig {
still_threshold_mg: 80,
still_samples: 4,
})
}
#[must_use]
pub const fn with_config(config: MotionConfig) -> Self {
Self {
config,
previous: None,
stable_count: 0,
}
}
#[must_use]
pub fn update(&mut self, acceleration: Acceleration) -> MotionContext {
let gravity = dominant_gravity(acceleration);
let pose = pose_from_gravity(gravity);
let state = self.motion_state(acceleration);
self.previous = Some(acceleration);
MotionContext {
state,
pose,
gravity,
}
}
fn motion_state(&mut self, acceleration: Acceleration) -> MotionState {
let Some(previous) = self.previous else {
self.stable_count = 0;
return MotionState::Unknown;
};
let delta = (i32::from(acceleration.x_mg) - i32::from(previous.x_mg)).abs()
+ (i32::from(acceleration.y_mg) - i32::from(previous.y_mg)).abs()
+ (i32::from(acceleration.z_mg) - i32::from(previous.z_mg)).abs();
if delta <= self.config.still_threshold_mg {
self.stable_count = self.stable_count.saturating_add(1);
if self.stable_count >= self.config.still_samples {
MotionState::Still
} else {
MotionState::Unknown
}
} else {
self.stable_count = 0;
MotionState::Moving
}
}
}
impl Default for MotionDetector {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn dominant_gravity(acceleration: Acceleration) -> GravityDirection {
let x = i32::from(acceleration.x_mg);
let y = i32::from(acceleration.y_mg);
let z = i32::from(acceleration.z_mg);
let abs_x = x.abs();
let abs_y = y.abs();
let abs_z = z.abs();
if abs_x >= abs_y && abs_x >= abs_z {
if x >= 0 {
GravityDirection::PositiveX
} else {
GravityDirection::NegativeX
}
} else if abs_y >= abs_z {
if y >= 0 {
GravityDirection::PositiveY
} else {
GravityDirection::NegativeY
}
} else if z >= 0 {
GravityDirection::PositiveZ
} else {
GravityDirection::NegativeZ
}
}
#[must_use]
pub const fn pose_from_gravity(direction: GravityDirection) -> Pose {
match direction {
GravityDirection::PositiveZ => Pose::FaceUp,
GravityDirection::NegativeZ => Pose::FaceDown,
GravityDirection::PositiveY => Pose::PortraitUp,
GravityDirection::NegativeY => Pose::PortraitDown,
GravityDirection::PositiveX => Pose::LandscapeRight,
GravityDirection::NegativeX => Pose::LandscapeLeft,
}
}