use core::fmt::Display;
use core::sync::atomic::{AtomicBool, Ordering};
use getset::Getters;
use nalgebra::{Point3, UnitQuaternion, Vector3};
use crate::{
base::{
Float, Observer, Pose, SensorOutput, Source, pose::impl_pose_methods,
transform::impl_transform,
},
measurement::hall_effect::hall_latch_state,
};
#[derive(Debug, Getters)]
#[getset(get = "pub")]
pub struct HallLatch<T: Float = f64> {
pose: Pose<T>,
sensitive_axis: Vector3<T>,
b_op: T,
b_rp: T,
state: AtomicBool,
}
impl_transform!(HallLatch<T> where T: Float);
impl<T: Float> Clone for HallLatch<T> {
fn clone(&self) -> Self {
Self {
pose: self.pose,
sensitive_axis: self.sensitive_axis,
b_op: self.b_op,
b_rp: self.b_rp,
state: AtomicBool::new(self.state.load(Ordering::SeqCst)),
}
}
}
impl<T: Float> PartialEq for HallLatch<T> {
fn eq(&self, other: &Self) -> bool {
self.pose == other.pose
&& self.sensitive_axis == other.sensitive_axis
&& self.b_op == other.b_op
&& self.b_rp == other.b_rp
&& self.state.load(Ordering::SeqCst) == other.state.load(Ordering::SeqCst)
}
}
impl<T: Float> Eq for HallLatch<T> {}
impl<T: Float> HallLatch<T> {
pub fn new(
position: impl Into<Point3<T>>,
orientation: UnitQuaternion<T>,
sensitive_axis: impl Into<Vector3<T>>,
b_op: T,
b_rp: T,
) -> Self {
if b_op <= b_rp {
panic!("B_OP must be greater than B_RP.");
}
Self {
pose: Pose::new(position.into(), orientation),
sensitive_axis: sensitive_axis.into().normalize(),
b_op,
b_rp,
state: AtomicBool::new(false),
}
}
impl_pose_methods!();
#[inline]
pub fn read_state(&self, source: &dyn Source<T>) -> bool {
let b_field = source.compute_B(self.pose.position());
let global_sensitive_axis = self.pose.orientation() * self.sensitive_axis;
let current_state = self.state.load(Ordering::SeqCst);
let new_state = hall_latch_state(
b_field,
global_sensitive_axis,
self.b_op,
self.b_rp,
current_state,
);
if new_state != current_state {
self.state.store(new_state, Ordering::SeqCst);
}
new_state
}
#[inline]
pub fn set_sensitive_axis(&mut self, sensitive_axis: impl Into<Vector3<T>>) {
self.sensitive_axis = sensitive_axis.into().normalize();
}
#[inline]
pub fn set_b_op(&mut self, b_op: T) {
if b_op <= self.b_rp {
panic!("B_OP must be greater than B_RP.");
}
self.b_op = b_op;
}
#[inline]
pub fn set_b_rp(&mut self, b_rp: T) {
if self.b_op <= b_rp {
panic!("B_OP must be greater than B_RP.");
}
self.b_rp = b_rp;
}
#[inline]
pub fn with_sensitive_axis(mut self, sensitive_axis: impl Into<Vector3<T>>) -> Self {
self.set_sensitive_axis(sensitive_axis);
self
}
#[inline]
pub fn with_b_op(mut self, b_op: T) -> Self {
self.set_b_op(b_op);
self
}
#[inline]
pub fn with_b_rp(mut self, b_rp: T) -> Self {
self.set_b_rp(b_rp);
self
}
}
impl<T: Float> Default for HallLatch<T> {
fn default() -> Self {
Self {
pose: Default::default(),
sensitive_axis: Vector3::z(),
b_op: T::zero(),
b_rp: T::zero(),
state: AtomicBool::new(false),
}
}
}
impl<T: Float> Observer<T> for HallLatch<T> {
fn read(&self, source: &dyn Source<T>) -> SensorOutput<T> {
let state = self.read_state(source);
SensorOutput::Digital(if state { 1 } else { 0 })
}
fn format(&self, f: &mut core::fmt::Formatter<'_>, _: &str) -> core::fmt::Result {
write!(
f,
"HallLatch (sensitive_axis=[{:?}, {:?}, {:?}], b_op={:?}, b_rp={:?}) at {}",
self.sensitive_axis.x,
self.sensitive_axis.y,
self.sensitive_axis.z,
self.b_op,
self.b_rp,
self.pose
)
}
}
impl<T: Float> Display for HallLatch<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
<Self as Observer<T>>::format(self, f, "")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::magnets::StableFieldMagnet;
use nalgebra::{UnitQuaternion, Vector3};
#[test]
fn test_hall_latch() {
use crate::base::SensorOutput;
let mut sensor = HallLatch::default()
.with_b_op(0.010)
.with_b_rp(-0.010)
.with_sensitive_axis([0.0, 0.0, 1.0]);
let source_zero = StableFieldMagnet::new(Vector3::new(0.0, 0.0, 0.0));
assert_eq!(sensor.read_state(&source_zero), false);
let source_weak = StableFieldMagnet::new(Vector3::new(0.0, 0.0, 0.005));
assert_eq!(sensor.read_state(&source_weak), false);
let source_op = StableFieldMagnet::new(Vector3::new(0.0, 0.0, 0.015));
assert_eq!(sensor.read_state(&source_op), true);
assert_eq!(sensor.read_state(&source_weak), true);
assert_eq!(sensor.read(&source_weak), SensorOutput::Digital(1));
let source_rp = StableFieldMagnet::new(Vector3::new(0.0, 0.0, -0.015));
assert_eq!(sensor.read_state(&source_rp), false);
assert_eq!(sensor.read_state(&source_weak), false);
assert_eq!(sensor.read(&source_weak), SensorOutput::Digital(0));
let rot_quat = UnitQuaternion::from_axis_angle(&Vector3::x_axis(), core::f64::consts::PI);
sensor.set_orientation(rot_quat);
assert_eq!(sensor.read_state(&source_rp), true);
}
#[test]
#[should_panic]
fn test_input_validation() {
let _ = HallLatch::new(
[0.0; 3],
UnitQuaternion::identity(),
[0.0, 0.0, 1.0],
0.0,
0.0,
);
}
#[test]
#[should_panic]
fn test_set_b_op_validation() {
let mut sensor = HallLatch::default().with_b_op(0.010).with_b_rp(-0.010);
sensor.set_b_op(-0.020);
}
#[test]
#[should_panic]
fn test_set_b_rp_validation() {
let mut sensor = HallLatch::default().with_b_op(0.010).with_b_rp(-0.010);
sensor.set_b_rp(0.020);
}
}