zwo_mount_control 0.2.1

Rust library for controlling ZWO AM5/AM3 telescope mounts with satellite tracking support
Documentation
//! Mount trait defining the interface for telescope mount control.
//!
//! This module provides the core `Mount` trait that defines all operations
//! supported by ZWO AM5/AM3 telescope mounts. Both real serial-connected
//! mounts and mock mounts for testing implement this trait.

use crate::coordinates::{EquatorialPosition, HorizontalPosition, TrackingRates};
use crate::error::MountResult;
use crate::protocol::{Direction, MountMode, SlewRate, TrackingRate};

/// Mount status information.
#[derive(Debug, Clone)]
pub struct MountStatus {
    /// Current tracking state
    pub is_tracking: bool,
    /// Current slewing state
    pub is_slewing: bool,
    /// Current mount mode (Alt-Az or Equatorial)
    pub mount_mode: MountMode,
    /// Current tracking rate
    pub tracking_rate: TrackingRate,
    /// Whether mount is parked
    pub is_parked: bool,
    /// Whether mount is connected
    pub is_connected: bool,
    /// Current slew rate setting
    pub slew_rate: SlewRate,
    /// Current guide rate (fraction of sidereal)
    pub guide_rate: f64,
    /// Pier side (for GEM mounts): "East", "West", or "Unknown"
    pub pier_side: String,
}

impl Default for MountStatus {
    fn default() -> Self {
        Self {
            is_tracking: false,
            is_slewing: false,
            mount_mode: MountMode::default(),
            tracking_rate: TrackingRate::default(),
            is_parked: true,
            is_connected: false,
            slew_rate: SlewRate::default(),
            guide_rate: 0.5,
            pier_side: "Unknown".to_string(),
        }
    }
}

/// Site location information.
#[derive(Debug, Clone, Copy)]
pub struct SiteLocation {
    /// Latitude in decimal degrees (positive = North)
    pub latitude: f64,
    /// Longitude in decimal degrees (positive = East)
    pub longitude: f64,
    /// Altitude in meters above sea level
    pub altitude: f64,
}

impl Default for SiteLocation {
    fn default() -> Self {
        Self {
            latitude: 0.0,
            longitude: 0.0,
            altitude: 0.0,
        }
    }
}

impl SiteLocation {
    /// Create a new site location.
    pub fn new(latitude: f64, longitude: f64, altitude: f64) -> Self {
        Self {
            latitude,
            longitude,
            altitude,
        }
    }
}

/// The core trait defining telescope mount operations.
///
/// This trait is implemented by both real serial-connected mounts and
/// mock mounts for testing. All methods return `MountResult` to handle
/// potential errors during communication or invalid operations.
///
/// # Example
///
/// ```rust,ignore
/// use zwo_mount_control::{Mount, MountResult, EquatorialPosition};
///
/// fn slew_to_star(mount: &mut dyn Mount) -> MountResult<()> {
///     // Vega's coordinates
///     let target = EquatorialPosition::from_hms_dms(18, 36, 56, 38, 47, 1);
///     mount.goto_equatorial(target)?;
///
///     // Wait for slew to complete
///     while mount.is_slewing()? {
///         std::thread::sleep(std::time::Duration::from_millis(500));
///     }
///
///     Ok(())
/// }
/// ```
pub trait Mount {
    // ============================================
    // Connection Management
    // ============================================

    /// Connect to the mount.
    fn connect(&mut self) -> MountResult<()>;

    /// Disconnect from the mount.
    fn disconnect(&mut self) -> MountResult<()>;

    /// Check if the mount is connected.
    fn is_connected(&self) -> bool;

    // ============================================
    // Position Queries
    // ============================================

    /// Get the current equatorial position (RA/Dec).
    fn get_position(&self) -> MountResult<EquatorialPosition>;

    /// Get the current horizontal position (Az/Alt).
    fn get_altaz(&self) -> MountResult<HorizontalPosition>;

    // ============================================
    // GoTo/Slewing Commands
    // ============================================

    /// Slew to an equatorial position (RA/Dec).
    ///
    /// This command is non-blocking. Use `is_slewing()` to check if the
    /// slew is complete, or `abort_slew()` to cancel.
    fn goto_equatorial(&mut self, position: EquatorialPosition) -> MountResult<()>;

    /// Slew to a horizontal position (Az/Alt).
    ///
    /// This requires the mount to be in Alt-Az mode.
    fn goto_altaz(&mut self, position: HorizontalPosition) -> MountResult<()>;

    /// Check if the mount is currently slewing.
    fn is_slewing(&self) -> MountResult<bool>;

    /// Abort the current slew operation.
    fn abort_slew(&mut self) -> MountResult<()>;

    // ============================================
    // Manual Motion Commands
    // ============================================

    /// Start moving in the specified direction.
    fn move_axis(&mut self, direction: Direction) -> MountResult<()>;

    /// Stop moving in the specified direction.
    fn stop_axis(&mut self, direction: Direction) -> MountResult<()>;

    /// Stop all movement immediately.
    fn stop_all(&mut self) -> MountResult<()>;

    /// Set the manual slew rate (1-9 scale).
    fn set_slew_rate(&mut self, rate: SlewRate) -> MountResult<()>;

    // ============================================
    // Tracking Commands
    // ============================================

    /// Set the tracking rate mode.
    fn set_tracking(&mut self, rate: TrackingRate) -> MountResult<()>;

    /// Enable tracking (at current rate).
    fn tracking_on(&mut self) -> MountResult<()>;

    /// Disable tracking.
    fn tracking_off(&mut self) -> MountResult<()>;

    /// Check if tracking is enabled.
    fn is_tracking(&self) -> MountResult<bool>;

    /// Set custom tracking rates (for satellite tracking).
    ///
    /// # Arguments
    /// * `rates` - RA and Dec tracking rates in arcseconds per second
    fn set_custom_tracking_rates(&mut self, rates: TrackingRates) -> MountResult<()>;

    // ============================================
    // Autoguiding Commands
    // ============================================

    /// Set the guide rate as a fraction of sidereal (0.1 to 0.9).
    fn set_guide_rate(&mut self, rate: f64) -> MountResult<()>;

    /// Get the current guide rate.
    fn get_guide_rate(&self) -> MountResult<f64>;

    /// Send a guide pulse in the specified direction.
    ///
    /// # Arguments
    /// * `direction` - Direction to guide
    /// * `duration_ms` - Duration of pulse in milliseconds
    fn guide_pulse(&mut self, direction: Direction, duration_ms: u32) -> MountResult<()>;

    // ============================================
    // Mount Mode Commands
    // ============================================

    /// Set the mount to Alt-Az mode (for tripod without wedge).
    fn set_altaz_mode(&mut self) -> MountResult<()>;

    /// Set the mount to Equatorial/Polar mode (for wedge mount).
    fn set_polar_mode(&mut self) -> MountResult<()>;

    /// Get the current mount mode.
    fn get_mount_mode(&self) -> MountResult<MountMode>;

    // ============================================
    // Home and Park Commands
    // ============================================

    /// Slew to the home position.
    fn go_home(&mut self) -> MountResult<()>;

    /// Park the mount.
    fn park(&mut self) -> MountResult<()>;

    /// Unpark the mount.
    fn unpark(&mut self) -> MountResult<()>;

    /// Check if the mount is parked.
    fn is_parked(&self) -> MountResult<bool>;

    // ============================================
    // Sync Commands
    // ============================================

    /// Sync the mount to an equatorial position.
    ///
    /// This tells the mount that its current pointing position
    /// corresponds to the given coordinates.
    fn sync(&mut self, position: EquatorialPosition) -> MountResult<()>;

    // ============================================
    // Site/Location Commands
    // ============================================

    /// Set the observation site location.
    fn set_site_location(&mut self, location: SiteLocation) -> MountResult<()>;

    /// Get the observation site location.
    fn get_site_location(&self) -> MountResult<SiteLocation>;

    // ============================================
    // Status and Information
    // ============================================

    /// Get comprehensive mount status.
    fn get_status(&self) -> MountResult<MountStatus>;

    /// Get the mount firmware version.
    fn get_firmware_version(&self) -> MountResult<String>;

    /// Get the mount model name.
    fn get_model(&self) -> MountResult<String>;

    // ============================================
    // Raw Command Access
    // ============================================

    /// Send a raw command to the mount and return the response.
    ///
    /// This is for advanced usage when you need to send commands
    /// not covered by the high-level API.
    fn send_raw_command(&mut self, command: &str) -> MountResult<String>;
}

/// Trait for mounts that support time simulation (primarily for testing).
pub trait SimulatedMount: Mount {
    /// Advance the simulated time by the given duration.
    fn advance_time(&mut self, duration: std::time::Duration);

    /// Set the simulation speed multiplier (1.0 = real-time).
    fn set_time_multiplier(&mut self, multiplier: f64);

    /// Get the current simulation time.
    fn get_simulation_time(&self) -> chrono::DateTime<chrono::Utc>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mount_status_default() {
        let status = MountStatus::default();
        assert!(!status.is_tracking);
        assert!(!status.is_slewing);
        assert!(status.is_parked);
        assert!(!status.is_connected);
        assert_eq!(status.mount_mode, MountMode::Equatorial);
    }

    #[test]
    fn test_site_location() {
        let loc = SiteLocation::new(34.0522, -118.2437, 71.0);
        assert!((loc.latitude - 34.0522).abs() < 0.0001);
        assert!((loc.longitude - (-118.2437)).abs() < 0.0001);
        assert!((loc.altitude - 71.0).abs() < 0.0001);
    }

    #[test]
    fn test_site_location_default() {
        let loc = SiteLocation::default();
        assert_eq!(loc.latitude, 0.0);
        assert_eq!(loc.longitude, 0.0);
        assert_eq!(loc.altitude, 0.0);
    }
}