use crate::Error;
pub const SERVO_YAW: u8 = 1;
pub const SERVO_PITCH: u8 = 2;
pub const YAW_ZERO_DEFAULT: u16 = 460;
pub const PITCH_ZERO_DEFAULT: u16 = 620;
const NVS_NS: &[u8] = b"servo\0";
const NVS_KEY_YAW: &[u8] = b"zero_pos_1\0";
const NVS_KEY_PITCH: &[u8] = b"zero_pos_2\0";
fn angle_to_raw(angle_deg: i32, zero_raw: u16) -> u16 {
let raw = zero_raw as i32 + (angle_deg as f32 * 16.0 / 5.0) as i32;
raw.clamp(0, 1023) as u16
}
fn raw_to_angle(raw: i32, zero_raw: u16) -> f32 {
(raw - zero_raw as i32) as f32 * 5.0 / 16.0
}
fn nvs_read_zero(key: &[u8], default: u16) -> u16 {
let mut val: i32 = 0;
let ok = unsafe {
m5unified_sys::m5u_nvs_read_i32(
NVS_NS.as_ptr() as *const core::ffi::c_char,
key.as_ptr() as *const core::ffi::c_char,
&mut val,
)
};
if ok && (0..=1023).contains(&val) {
val as u16
} else {
default
}
}
fn nvs_write_zero(key: &[u8], val: u16) -> bool {
unsafe {
m5unified_sys::m5u_nvs_write_i32(
NVS_NS.as_ptr() as *const core::ffi::c_char,
key.as_ptr() as *const core::ffi::c_char,
val as i32,
)
}
}
pub struct StackChanServos {
yaw_zero: u16,
pitch_zero: u16,
}
impl StackChanServos {
pub fn new() -> Result<Self, Error> {
Self::new_with_pins(-1, -1)
}
pub fn new_with_pins(tx_pin: i32, rx_pin: i32) -> Result<Self, Error> {
let ok = unsafe { m5unified_sys::m5u_servo_init(tx_pin, rx_pin, 0) };
if !ok {
return Err(Error::BeginFailed);
}
let yaw_zero = nvs_read_zero(NVS_KEY_YAW, YAW_ZERO_DEFAULT);
let pitch_zero = nvs_read_zero(NVS_KEY_PITCH, PITCH_ZERO_DEFAULT);
Ok(Self {
yaw_zero,
pitch_zero,
})
}
pub fn yaw_zero(&self) -> u16 {
self.yaw_zero
}
pub fn pitch_zero(&self) -> u16 {
self.pitch_zero
}
pub fn move_yaw(&mut self, angle_deg: i32, time_ms: u16, speed: u16) -> bool {
let raw = angle_to_raw(angle_deg, self.yaw_zero);
unsafe { m5unified_sys::m5u_servo_write_raw_pos(SERVO_YAW, raw, time_ms, speed) }
}
pub fn move_pitch(&mut self, angle_deg: i32, time_ms: u16, speed: u16) -> bool {
let raw = angle_to_raw(angle_deg, self.pitch_zero);
unsafe { m5unified_sys::m5u_servo_write_raw_pos(SERVO_PITCH, raw, time_ms, speed) }
}
pub fn move_both(&mut self, yaw_deg: i32, pitch_deg: i32, time_ms: u16, speed: u16) -> bool {
self.move_yaw(yaw_deg, time_ms, speed) & self.move_pitch(pitch_deg, time_ms, speed)
}
pub fn set_torque(&mut self, enable: bool) {
unsafe {
m5unified_sys::m5u_servo_enable_torque(SERVO_YAW, enable);
m5unified_sys::m5u_servo_enable_torque(SERVO_PITCH, enable);
}
}
pub fn set_torque_single(&mut self, id: u8, enable: bool) {
unsafe {
m5unified_sys::m5u_servo_enable_torque(id, enable);
}
}
pub fn read_raw_pos(&mut self, id: u8) -> Option<i32> {
let v = unsafe { m5unified_sys::m5u_servo_read_raw_pos(id) };
if v < 0 {
None
} else {
Some(v)
}
}
pub fn read_angle(&mut self, id: u8) -> Option<f32> {
let raw = self.read_raw_pos(id)?;
let zero = if id == SERVO_YAW {
self.yaw_zero
} else {
self.pitch_zero
};
Some(raw_to_angle(raw, zero))
}
pub fn calibrate_from_current_pos(&mut self, id: u8) -> Option<u16> {
let raw = self.read_raw_pos(id)?;
let raw = raw.clamp(0, 1023) as u16;
if id == SERVO_YAW {
self.yaw_zero = raw;
} else {
self.pitch_zero = raw;
}
Some(raw)
}
pub fn save_calibration(&self) -> bool {
nvs_write_zero(NVS_KEY_YAW, self.yaw_zero) & nvs_write_zero(NVS_KEY_PITCH, self.pitch_zero)
}
pub fn reset_calibration(&mut self) -> bool {
self.yaw_zero = YAW_ZERO_DEFAULT;
self.pitch_zero = PITCH_ZERO_DEFAULT;
self.save_calibration()
}
}
impl Drop for StackChanServos {
fn drop(&mut self) {
unsafe { m5unified_sys::m5u_servo_deinit() }
}
}