zwo_mount_control 0.2.1

Rust library for controlling ZWO AM5/AM3 telescope mounts with satellite tracking support
Documentation
//! Basic mount control example
//!
//! This example demonstrates basic mount operations:
//! - Setting Alt-Az mode
//! - Clearing mount model
//! - Slewing to Az/Alt coordinates
//!
//! By default, it tries to connect to a real mount via serial port.
//! If no mount is found, it falls back to a mock mount for demonstration.
//!
//! Run with: cargo run --example basic_control
//!
//! To specify a serial port: cargo run --example basic_control -- /dev/ttyUSB0

use std::env;
use std::thread::sleep;
use std::time::Duration;

use zwo_mount_control::{
    HorizontalPosition, MockMount, Mount, SerialMount, SiteLocation, SlewRate,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();

    println!("ZWO Mount Control - Basic Example");
    println!("==================================\n");

    // Try to get serial port from command line args, or auto-detect
    let args: Vec<String> = env::args().collect();
    let specified_port = args.get(1).cloned();

    // Create either a real mount or fall back to mock
    let mut mount: Box<dyn Mount> = match try_connect_real_mount(specified_port) {
        Some(m) => {
            println!("Connected to real mount!\n");
            m
        }
        None => {
            println!("No real mount found, using mock mount for demonstration.\n");
            let mut mock = MockMount::with_position(0.0, 45.0);
            mock.connect()?;
            Box::new(mock)
        }
    };

    // Get mount information
    let model = mount.get_model().unwrap_or_else(|_| "Unknown".to_string());
    let firmware = mount
        .get_firmware_version()
        .unwrap_or_else(|_| "Unknown".to_string());
    println!("Mount Model: {}", model);
    println!("Firmware: {}\n", firmware);

    // Set site location (Los Angeles, CA) - may not be supported by all mounts
    println!("Setting observer location...");
    match mount.set_site_location(SiteLocation::new(34.0522, -118.2437, 71.0)) {
        Ok(_) => println!("  Location: 34.0522°N, 118.2437°W, 71m\n"),
        Err(e) => println!("  Warning: Could not set location ({}), continuing...\n", e),
    }

    // Unpark the mount if parked
    if mount.is_parked()? {
        println!("Unparking mount...");
        mount.unpark()?;
    }

    // Set Alt-Az mode for horizontal coordinate slewing
    println!("Setting Alt-Az mode...");
    match mount.set_altaz_mode() {
        Ok(_) => println!(
            "  Mode: {:?}\n",
            mount
                .get_mount_mode()
                .unwrap_or(zwo_mount_control::MountMode::AltAz)
        ),
        Err(e) => println!(
            "  Warning: Could not set Alt-Az mode ({}), continuing...\n",
            e
        ),
    }

    // Set maximum slew rate for fast movement
    println!("Setting maximum slew rate...");
    match mount.set_slew_rate(SlewRate::MAX) {
        Ok(_) => println!("  Slew rate set to maximum (9)\n"),
        Err(e) => println!("  Warning: Could not set slew rate ({})\n", e),
    }

    // Go to home position first
    println!("Going to home position...");
    match mount.go_home() {
        Ok(_) => {
            // Wait for slew to complete
            while mount.is_slewing().unwrap_or(false) {
                sleep(Duration::from_millis(100));
            }
            println!("  Home position reached\n");
        }
        Err(e) => println!("  Warning: Could not go home ({}), continuing...\n", e),
    }

    // Get current position
    match mount.get_altaz() {
        Ok(pos) => println!("Current position: {}\n", pos),
        Err(e) => println!("Could not get position: {}\n", e),
    }

    // Slew to a specific Az/Alt position
    // Note: goto_altaz() uses a proportional control loop and blocks until complete
    let target = HorizontalPosition::new(180.0, 45.0); // South, 45° elevation
    println!("Slewing to Az=180°, Alt=45°...");
    println!("  (Using proportional control loop - this may take a moment)");
    match mount.goto_altaz(target) {
        Ok(_) => {
            println!("  Slew complete!");

            // Get final position
            match mount.get_altaz() {
                Ok(final_pos) => println!("  Final position: {}\n", final_pos),
                Err(e) => println!("  Could not get final position: {}\n", e),
            }
        }
        Err(e) => println!("  Could not slew: {}\n", e),
    }

    // Park and disconnect
    println!("Parking mount...");
    if let Err(e) = mount.park() {
        println!("  Warning: Could not park ({})", e);
    }

    println!("Disconnecting...");
    if let Err(e) = mount.disconnect() {
        println!("  Warning: Could not disconnect ({})", e);
    }

    println!("\nDone!");

    Ok(())
}

/// Try to connect to a real mount, either on the specified port or by auto-detection.
fn try_connect_real_mount(specified_port: Option<String>) -> Option<Box<dyn Mount>> {
    use zwo_mount_control::serial_mount::SerialConfig;

    // If a port was specified, try only that one
    if let Some(port) = specified_port {
        println!("Trying specified port: {}", port);
        let mut mount = SerialMount::new(&port);
        if mount.connect().is_ok() {
            return Some(Box::new(mount));
        } else {
            println!("  Failed to connect to {}", port);
            return None;
        }
    }

    // Otherwise, try to auto-detect
    println!("Searching for ZWO mount...");
    let ports = SerialMount::list_ports();

    if ports.is_empty() {
        println!("  No serial ports found.");
        return None;
    }

    // Filter to likely USB serial ports (skip debug/bluetooth ports)
    let likely_ports: Vec<_> = ports
        .into_iter()
        .filter(|p| {
            let p_lower = p.to_lowercase();
            (p_lower.contains("usb") || p_lower.contains("acm") || p_lower.contains("serial"))
                && !p_lower.contains("debug")
                && !p_lower.contains("bluetooth")
        })
        .collect();

    if likely_ports.is_empty() {
        println!("  No likely mount ports found.");
        return None;
    }

    println!(
        "  Checking {} port(s): {:?}",
        likely_ports.len(),
        likely_ports
    );

    // Try each port with shorter timeout for detection
    for port in &likely_ports {
        println!("  Trying {}...", port);
        let config = SerialConfig::new(port)
            .with_timeout(1000) // 1 second timeout for detection
            .with_retry_count(1); // Only 1 retry during detection
        let mut mount = SerialMount::with_config(config);
        match mount.connect() {
            Ok(_) => {
                // Verify it's a ZWO mount by checking the model
                if let Ok(model) = mount.get_model() {
                    let model_upper = model.to_uppercase();
                    if model_upper.contains("ZWO")
                        || model_upper.contains("AM5")
                        || model_upper.contains("AM3")
                        || model_upper.contains("AM")
                    {
                        println!("  Found ZWO mount: {} on {}", model, port);
                        // Disconnect and reconnect with normal timeout
                        let _ = mount.disconnect();
                        let mut real_mount = SerialMount::new(port);
                        if real_mount.connect().is_ok() {
                            return Some(Box::new(real_mount));
                        }
                    }
                }
                // Not a ZWO mount, disconnect and try next
                let _ = mount.disconnect();
            }
            Err(_) => continue,
        }
    }

    println!("  No ZWO mount found on any port.");
    None
}