README.md

# ZwoController

An Elixir library for controlling ZWO AM5 telescope mounts via serial communication.

## Features

- **GoTo/Slewing**: Command the mount to slew to any celestial coordinates
- **Manual Motion**: Control axis motion at various speeds (guide to max slew)
- **Tracking**: Enable/disable tracking with sidereal, lunar, or solar rates
- **Alt-Az & Equatorial Modes**: Switch between altitude-azimuth and equatorial tracking
- **Autoguiding**: Send guide pulses for autoguiding applications
- **Mock Mount**: Test your application without physical hardware

## Installation

Add `zwo_controller` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:zwo_controller, "~> 0.1.0"}
  ]
end
```

Or for the latest development version from GitHub:

```elixir
def deps do
  [
    {:zwo_controller, github: "jmcguigs/zwo_controller"}
  ]
end
```

Then fetch dependencies:

```bash
mix deps.get
```

## Quick Start

### Connect to a Real Mount

```elixir
# Connect via serial port (typical on Linux/Mac)
{:ok, mount} = ZwoController.start_mount(port: "/dev/ttyUSB0")

# On Windows
{:ok, mount} = ZwoController.start_mount(port: "COM3")
```

### Use the Mock for Testing

```elixir
{:ok, mount} = ZwoController.start_mock()
```

### Basic Operations

```elixir
# Get current position (RA in decimal hours, DEC in decimal degrees)
{:ok, pos} = ZwoController.position(mount)
IO.puts("RA: #{pos.ra}h, DEC: #{pos.dec}°")

# Slew to coordinates
ZwoController.goto(mount, 12.5, 45.0)

# Slew to Vega using HMS/DMS
ra = ZwoController.ra(18, 36, 56)   # 18h 36m 56s
dec = ZwoController.dec(38, 47, 1)  # +38° 47' 01"
ZwoController.goto(mount, ra, dec)

# Enable sidereal tracking
ZwoController.track(mount, :sidereal)

# Manual motion
ZwoController.set_rate(mount, 5)    # Set medium speed
ZwoController.move(mount, :north)   # Start moving north
Process.sleep(1000)
ZwoController.stop(mount)           # Emergency stop
```

### Autoguiding

```elixir
# Set guide rate to 0.5x sidereal
ZwoController.set_guide_rate(mount, 0.5)

# Send guide pulses (direction, duration in ms)
ZwoController.guide(mount, :north, 200)
ZwoController.guide(mount, :east, 150)
```

### Tracking Modes

```elixir
ZwoController.track(mount, :sidereal)  # For stars
ZwoController.track(mount, :lunar)     # For the Moon
ZwoController.track(mount, :solar)     # For the Sun

ZwoController.track_off(mount)         # Disable tracking
```

### Mount Mode (Alt-Az vs Equatorial)

The ZWO AM5 can operate in two modes depending on physical installation:

```elixir
# Alt-Az mode - for mount on tripod (no wedge)
# Use for azimuth/altitude tracking (satellites, terrestrial objects)
ZwoController.set_altaz_mode(mount)

# Equatorial mode - for mount on wedge (polar aligned)
# Use for tracking celestial objects that move with Earth's rotation
ZwoController.set_polar_mode(mount)

# Check current mount type
{:ok, status} = ZwoController.status(mount)
IO.inspect(status.mount_type)  # => :altaz or :equatorial
```

**Important:** The mount mode must match your physical setup:
- **Alt-Az mode**: Mount on tripod, no wedge. Tracks by moving in azimuth and altitude.
- **Equatorial mode**: Mount on equatorial wedge, polar aligned. Tracks by rotating around polar axis.

For satellite tracking or azimuth/altitude control, you **must** set Alt-Az mode first:

```elixir
ZwoController.set_altaz_mode(mount)
Process.sleep(1000)  # Wait for mode change
{:ok, pos} = ZwoController.altaz(mount)  # Now returns Az/Alt coordinates
```

### Home and Park

```elixir
ZwoController.home(mount)  # Go to home position
ZwoController.park(mount)  # Go to park position
```

## Coordinate System

The mount supports two coordinate systems depending on its mode:

### Equatorial Coordinates (RA/Dec)

Used when mount is in equatorial mode (on wedge, polar aligned):

- **Right Ascension (RA)**: Decimal hours (0-24)
- **Declination (DEC)**: Decimal degrees (-90 to +90)

```elixir
# Get current RA/Dec position
{:ok, pos} = ZwoController.position(mount)
IO.puts("RA: #{pos.ra}h, DEC: #{pos.dec}°")
```

### Horizontal Coordinates (Az/Alt)

Used when mount is in Alt-Az mode (on tripod, no wedge):

- **Azimuth (Az)**: Degrees from North (0-360°, clockwise)
- **Altitude (Alt)**: Degrees above horizon (0-90°)

```elixir
# Get current Az/Alt position
{:ok, pos} = ZwoController.altaz(mount)
IO.puts("Az: #{pos.az}°, Alt: #{pos.alt}°")
```

### Converting Coordinates

```elixir
alias ZwoController.Coordinates

# HMS to decimal hours
ra = Coordinates.hms_to_ra(12, 30, 45)  # 12h 30m 45s → 12.5125

# DMS to decimal degrees
dec = Coordinates.dms_to_dec(-23, 26, 21)  # -23° 26' 21" → -23.439...

# Decimal to HMS/DMS
Coordinates.ra_to_hms(12.5)   # → %{hours: 12, minutes: 30, seconds: 0.0}
Coordinates.dec_to_dms(-23.5) # → %{degrees: -23, minutes: 30, seconds: 0.0}
```

## Module Overview

| Module | Description |
|--------|-------------|
| `ZwoController` | High-level convenience API |
| `ZwoController.Mount` | GenServer-based mount controller |
| `ZwoController.Mock` | Simulated mount for testing |
| `ZwoController.Protocol` | Serial command definitions (includes `:AA#` for Alt-Az mode, `:AP#` for Polar mode) |
| `ZwoController.Coordinates` | Coordinate conversion utilities |
| `ZwoController.SatelliteTracker` | Satellite tracking with TLE propagation |

## Low-Level Access

For advanced usage, you can send raw commands:

```elixir
alias ZwoController.Protocol

# Send any command
{:ok, response} = ZwoController.raw(mount, Protocol.get_version())

# Or construct custom commands
{:ok, response} = ZwoController.raw(mount, ":GVP#")  # Get mount model
```

## Hardware Requirements

- ZWO AM5 mount (or compatible)
- USB-serial connection to the mount
- Serial port permissions (on Linux, add user to `dialout` group)

```bash
# Linux: Add user to dialout group for serial access
sudo usermod -a -G dialout $USER
# Log out and back in for changes to take effect
```

## License

MIT