Field rotator

Starship can drive a connected field rotator to set the camera's framing angle — either to a raw mechanical angle or, more usefully, to a true sky position angle (PA) verified by plate-solving. Rotator support landed in v19.8; the per-target sequencer rotate step followed in v19.9, and mechanical travel limits on manual moves in v20.3. This page reflects the current build.

What it is

A field rotator turns the camera (and filter wheel/OAG) about the optical axis so you can frame a target at a chosen angle and keep that framing consistent night to night. Starship talks to the rotator over either the ASCOM or Alpaca transport — whichever has a device in the rotator slot connected. There is no simulated rotator; if no rotator is connected, the rotator commands fail cleanly with "no rotator connected".

Two operations are exposed:

Both run as background jobs (you get a job id and can watch progress / cancel), and both honour the same RotatorConfig knobs.

Where you drive it

The rotator lives on its own Rotator settings page (Settings → Rotator). That page holds the connection/transport picker, all the config knobs below, and a small control panel with three buttons and a live status line:

The same Move / Solve & rotate to PA / Halt controls also appear inline on the Camera block, greyed out unless a rotator is connected. On both surfaces the buttons are gated by the rotator-move capability; a viewer-only session can still see rotator status but not command a move.

Rotator status

rotator_status() reports what the driver exposes:

Other properties read from the driver but not surfaced in status include can_reverse, reverse, and step_size.

Absolute move — rotator_move

rotator_move(position_deg) sends MoveAbsolute to the rotator. The angle is wrapped into 0–360° first, so -10 becomes 350. The worker then:

  1. Commands the absolute move.
  2. Waits for the rotator to go idle by polling is_moving every 0.5 s, up to move_timeout_s.
  3. Sleeps settle_s to let vibration die down.
  4. Reports the final position.

This is the right command when you already know the mechanical angle you want (e.g. reproducing a saved framing on a rotator you trust to be sky-calibrated). It does not verify the result against the sky.

Solve-and-rotate — rotate_to_pa

rotate_to_pa(target_pa_deg) is the precise path: it rotates until a plate solve confirms the camera is at the requested sky PA, within tolerance. The loop, per iteration:

  1. Capture a frame (exposure_s, binning, object name rotate-to-pa, prefix rot). These are solve-only frames, so they also pick up the camera's plate-solve overrides: if you've set [camera] plate_solve_gain_override the solve frames use that gain, and if you've set [camera] plate_solve_binning_override it wins over the rotator's own binning knob. Leave those blank to fall through to the science defaults / the [rotator] binning value.
  2. Plate-solve it. The solver (ASTAP) returns rotation_deg, computed from the WCS CD matrix as atan2(cd2_1, cd1_1) — the actual sky PA of the frame.
  3. Compute the PA error toward target_pa_deg.
  4. If |error| <= tolerance_deg, done.
  5. Otherwise rotate the mechanical axis by gain error (absolute move to current_position + gainerror, wrapped 0–360°), wait for idle, settle, and loop.

It iterates up to max_iterations (clamped to 1–10 internally). If it never gets inside tolerance it fails with "did not converge in N iterations (last error …°)" and returns the full per-iteration history.

Direction self-calibration

The sign between a mechanical move and the resulting change in sky PA depends on the optical train — mirrors flip it, and the rotator's own direction convention varies. Rather than assume, rotate_to_pa measures the response: after the first move it computes gain = ΔPA / Δmove and snaps it to +1 or -1. So the first iteration may move the "wrong" way, but the loop corrects on the next one. This is why even a fresh, uncalibrated rotator converges without you setting a direction flag.

accept_180

For imaging, framing at PA and PA+180° is visually identical (the field is just rotated halfway around — same stars, same crop). When accept_180 is true (the default), the loop treats the two as equivalent and rotates to whichever is the shorter move. This avoids a needless near-180° swing when the rotator happens to sit on the far side. Set it false only if up/down orientation matters to you (e.g. matching a specific published frame or a guider/OAG geometry constraint).

Where it plugs in

Config knobs (RotatorConfig)

Set under [rotator] in the config. Defaults shown.

Safety and gotchas

Tying it together

For unattended runs, the intended flow is: precise slew to the target, then rotate_to_pa to its pa_deg, then expose. The rotate step verifies framing against the real sky, self-calibrates its own direction, and (by default) avoids needless 180° swings — so each target comes up framed the same way every night without a per-rotator direction setting. See Sequencer for plan-level framing and Plate solving / Pointing for the solve and slew steps it builds on.

Astroworx Starship v22.2.10 · this page is the in-app help of that buildDownload · HTTP API