# 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