Scheduling Syntax#

banner

Astra uses a scheduling system to automate observatory operations. Schedules are defined using JSONL files (JSON Lines format), where each JSON line represents a scheduled action with these fields:

  • device_name: Name of the camera device (the primary instrument that coordinates all operations)

  • action_type: Type of action to perform

  • action_value: Parameters for the action

  • start_time: Earliest time the action is valid to start (UTC ISO format: YYYY-MM-DD HH:MM:SS.sss)

  • end_time: Latest time the action is valid (UTC ISO format: YYYY-MM-DD HH:MM:SS.sss)

Instrument-Centric Design

All scheduled actions specify a camera as the device_name. The camera acts as the primary instrument that coordinates operations with its paired devices (telescope, dome, filter wheel, focuser, etc.). This design ensures all devices work together as a cohesive system.

Timing and Execution Flow

The start_time and end_time fields define a validity window, not a strict duration block.

  • Early Completion: If an action (e.g., observatory open) completes successfully before its end_time, Astra does not wait. It moves immediately to the next action (idling only if the next action’s start_time has not yet been reached).

  • Astra actions are completed sequentially, ordered by start times, so the next action will not start until the current one finishes, even if the next action’s start_time has already passed. This is only invalidated if execute_parallel variable is set true.

Example Schedule#

// open observatory
{
   "device_name":"camera_main",
   "action_type":"open",
   "action_value":{},
   "start_time":"2025-08-23 22:38:25.210",
   "end_time":"2025-08-24 10:49:15.363"
}
// dusk sky flats
{
   "device_name":"camera_main",
   "action_type":"flats",
   "action_value":{"filter":["r'", "g'"],"n":[20, 20]},
   "start_time":"2025-08-23 22:39:25.210",
   "end_time":"2025-08-23 23:16:00.018"
}
// science observations
{
   "device_name":"camera_main",
   "action_type":"object",
   "action_value":{"object":"Kepler-1","filter":"r'","ra":286.808542,"dec":49.316422,"exptime":8,"guiding":true,"pointing":true},
   "start_time":"2025-08-23 23:17:00.018",
   "end_time":"2025-08-24 04:43:40.018"
}
// dawn sky flats
{
   "device_name":"camera_main",
   "action_type":"flats",
   "action_value":{"filter":["r'", "g'"],"n":[20, 20]},
   "start_time":"2025-08-24 10:24:40.018",
   "end_time":"2025-08-24 10:49:15.363"
}
// close observatory
{
   "device_name":"camera_main",
   "action_type":"close",
   "action_value":{},
   "start_time":"2025-08-24 10:49:15.363",
   "end_time":"2025-08-24 11:49:15.363"
}
// calibration frames, biases and darks
{
   "device_name":"camera_main",
   "action_type":"calibration",
   "action_value":{"exptime":[0,10,15,30,38,60,120],"n":[10,10,10,10,10,10,10]},
   "start_time":"2025-08-24 10:55:15.363",
   "end_time":"2025-08-24 11:49:15.363"
}

JSONL Comments

Astra’s JSONL files support comments using lines that start with //:

Schedule File Location#

Place your schedule file in the observatory schedules directory with a .jsonl extension. For example:

  • ~/Documents/Astra/schedules/{observatory_name}.jsonl

Astra will automatically detect and load the JSONL schedule file, with the specified name pattern, if modified.

Supported Action Types#

Astra supports the following action types for observatory automation, organized by function:

  • open: Open observatory

  • close: Close observatory

  • cool_camera: Activate camera cooling

  • object: Capture light frames with optional pointing correction & autoguiding

  • calibration: Capture dark and bias frames

  • flats: Capture sky flat field frames

  • autofocus: Autofocus

  • calibrate_guiding: Calibrate guiding parameters

  • pointing_model: Help build a telescope pointing model

  • complete_headers: Complete FITS headers of all images captured

Note

The complete_headers action automatically runs at the end of every schedule execution to ensure complete metadata in all FITS files.

Note

All actions run cool_camera as a prerequisite to ensure the camera is at the correct operating temperature before any exposures are taken. Only open and close run cool_camera after their execution.

Action Value Parameters#

Each action type requires specific parameters in the action_value field. The sections below are generated automatically from the action configuration dataclasses to ensure the documentation always matches the implementation.

object#

Capture a sequence of light frames.

Workflow:
  1. Pre-sequence setup (pointing, filters, focus, binning, sub-framing, headers)
    • Observatory opens if not already done by a prior action if coordinates specified

  2. Capture exposures in succession

  3. Perform pointing correction if pointing=true

  4. Start autoguiding if guiding=true

  5. Stop exposures, guiding, and tracking at completion

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "object",
    "action_value": {
        "object": "M42",
        "exptime": 60.0,
        "ra": 83.82208,
        "dec": -5.39111,
        "filter": "V",
        "n": 3,
        "guiding": true,
        "pointing": true
    },
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

Action values

object: str Required — Target name.
exptime: float Required — Exposure time per frame in seconds.
ra: float | None = None — Right Ascension to slew to
dec: float | None = None — Declination to slew to
alt: float | None = None — Altitude coordinate when issuing Alt/Az pointings.
az: float | None = None — Azimuth coordinate when issuing Alt/Az pointings.
lookup_name: str | None = None — Instead of specifying ra/dec or alt/az, use SIMBAD/Astropy to look up coordinates for celestial body to observe (e.g., 'mars', 'M31').
filter: str | None = None — Filter name to load before imaging.
focus_shift: float | None = None — Focus offset relative to the stored best focus.
focus_position: float | None = None — Absolute focus position override.
n: int | None = None — Number of exposures in the sequence. If not specified, defaults to infinite exposures until end_time.
guiding: bool = false — Start autoguiding with Donuts before imaging.
pointing: bool = false — Perform pointing correction with twirl before imaging.
bin: int = 1 — Camera binning factor.
dir: str | None = None — Base directory path for saving images.
execute_parallel: bool = false — Execute action in parallel mode when supported.
disable_telescope_movement: bool = false — Prevent any telescope motion during the sequence.
reset_guiding_reference: bool = true — Acquire a fresh guiding reference frame at the start.
subframe_width: int | None = None — Width of the requested subframe in binned pixels.
subframe_height: int | None = None — Height of the requested subframe in binned pixels.
subframe_center_x: float = 0.5 — Horizontal location of the subframe center (0=left, 1=right).
subframe_center_y: float = 0.5 — Vertical location of the subframe center (0=top, 1=bottom).

calibration#

Capture a sequence of calibration images (bias/dark).

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "calibration",
    "action_value": {
        "exptime": [0.0, 5.0, 30.0],
        "n": [10, 5, 3]
    },
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

Action values

exptime: List[float] Required — Exposure times (seconds) to iterate.
n: List[int] Required — Exposure counts aligned with each exposure time.
filter: str | None = None — Filter name to load before imaging.
dir: str | None = None — Base directory path for saving images.
bin: int = 1 — Camera binning factor.
execute_parallel: bool = false — Execute action in parallel mode when supported.
subframe_width: int | None = None — Width of the requested subframe in binned pixels.
subframe_height: int | None = None — Height of the requested subframe in binned pixels.
subframe_center_x: float = 0.5 — Horizontal subframe center (0=left, 1=right).
subframe_center_y: float = 0.5 — Vertical subframe center (0=top, 1=bottom).

flats#

Capture a sequence of sky flats as the sky brightness evolves.

Steps:
  1. Wait for Sun altitude between -1° and -12°

  2. Point to a near-uniform patch of sky opposite the Sun
    • Opens observatory if not already done by a prior action

  3. Capture exposures and re-position between frames

  4. Iterate through requested filters while auto-adjusting exposure times

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "flats",
    "action_value": {
        "filter": ["V", "R"],
        "n": [10, 10]
    },
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

Action values

filter: List[str] Required — Filters to iterate while capturing flats.
n: List[int] Required — Number of flats to capture per filter.
dir: str | None = None — Base directory path for saving images.
bin: int = 1 — Camera binning factor.
execute_parallel: bool = false — Execute action in parallel mode when supported.
disable_telescope_movement: bool = false — Prevent telescope motion during the sequence.
subframe_width: int | None = None — Width of the requested subframe in binned pixels.
subframe_height: int | None = None — Height of the requested subframe in binned pixels.
subframe_center_x: float = 0.5 — Horizontal subframe center (0=left, 1=right).
subframe_center_y: float = 0.5 — Vertical subframe center (0=top, 1=bottom).

calibrate_guiding#

Calibrate guiding parameters using timed guide pulses.

Steps:
  1. Slews telescope to RA = LST - 1 hour, Dec = 0° at the start of sequence
    • Opens observatory if not already done by a prior action

  2. Issues a series of guide pulses in each cardinal direction with specified duration and settling time

  3. Captures exposures after each pulse and measures star shifts to determine pixel-to-time scales and camera orientation relative to mount axes

  4. Averages results over specified number of cycles

  5. Saves calibration parameters in the observatory configuration for use in guiding

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "calibrate_guiding",
    "action_value": {},
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

Action values

filter: str | None = None — Filter to use during calibration.
pulse_time: int = 5000 — Duration of guide pulses in milliseconds.
exptime: float = 1.0 — Exposure time for calibration images.
settle_time: float = 1.0 — Wait time after pulses before exposing.
number_of_cycles: int = 10 — How many calibration cycles to take average over.
focus_shift: float | None = None — Focus offset relative to best focus.
focus_position: float | None = None — Absolute focus position override.
bin: int = 1 — Camera binning factor.
subframe_width: int | None = None — Width of the requested subframe in binned pixels.
subframe_height: int | None = None — Height of the requested subframe in binned pixels.
subframe_center_x: float = 0.5 — Horizontal subframe center (0=left, 1=right).
subframe_center_y: float = 0.5 — Vertical subframe center (0=top, 1=bottom).

autofocus#

Perform an autofocus sweep to determine the optimal focus position.

Steps:
  1. Select a suitable autofocus field (or use provided coordinates)
    • Opens observatory if not already done by a prior action

  2. Move the telescope if needed

  3. Capture images at different focus positions

  4. Measure star sharpness in each image

  5. Fit a curve to determine optimal focus

  6. Save plots/results and save the best focus position in the observatory configuration

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "autofocus",
    "action_value": {
        "exptime": 1.0,
        "filter": "V",
        "search_range_is_relative": true,
        "search_range": 1000,
        "n_steps": [30, 20],
        "n_exposures": [1, 1]
    },
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

Action values

exptime: float | int = 3.0 — Exposure time for focus frames in seconds.
filter: str | None = None — Filter to use during autofocus procedure.
bin: int = 1 — Camera binning factor.
reduce_exposure_time: bool = false — Automatically shorten exposures to prevent saturation.
search_range: List[int] | int | None = None — Range of focus positions to search. Accepts a single width or explicit bounds.
search_range_is_relative: bool = false — Interpret search_range relative to the current focus position.
n_steps: List[int] = [30, 20] — Number of steps for each sweep.
n_exposures: List[int] | int = [1, 1] — Number of exposures at each focus position or an array specifying exposures for each sweep. If an integer is given, the same number of exposures is used for each sweep. If an array is given, the length of the array must match the number of sweeps. 
decrease_search_range: bool = true — Reduce the search range after each sweep.
star_find_threshold: float | int = 5.0 — DAOStarFinder threshold for star detection.
fwhm: int = 8 — DAOStarFinder FWHM of the Gaussian kernel in pixels.
percent_to_cut: int = 60 — Percentage of worst-performing focus samples to drop when shrinking the range.
focus_measure_operator: str = 'HFR' — Focus metric to optimize (e.g., hfr, gauss, tenengrad, fft, normalized_variance).
save: bool = true — Persist the optimal focus position back into observatory configuration.
extremum_estimator: str = 'LOWESS' — Curve-fitting method used to determine the minimum (LOWESS, medianfilter, spline, rbf).
extremum_estimator_kwargs: Dict[str, Any] = {} — Additional keyword overrides for the extremum estimator.
secondary_focus_measure_operators: List[str] = ['fft', 'normalized_variance', 'tenengrad'] — Additional focus metrics to compute for diagnostics.
maximal_zenith_angle: float | int | Angle | None = None — Maximum zenith angle allowed when selecting autofocus fields.
airmass_threshold: float = 1.01 — Highest acceptable airmass for autofocus candidates.
g_mag_range: List[float | int] = [0, 10] — Inclusive Gaia G magnitude range to consider.
j_mag_range: List[float | int] = [0, 10] — Inclusive 2MASS J magnitude range to consider.
fov_height: float | int = 0 — Height of the field of view in degrees.
fov_width: float | int = 0 — Width of the field of view in degrees.
selection_method: SelectionMethod | str = 'single' — Strategy for selecting stars (single, maximal, any).
use_gaia: bool = true — Whether to rely on Gaia catalog sources.
observation_time: Time | None = None — Observation time used when evaluating constraints.
maximal_number_of_stars: int = 100000 — Maximum number of stars to query or consider.
ra: float | int | None = None — Fixed Right Ascension used to bypass automatic selection.
dec: float | int | None = None — Fixed Declination used to bypass automatic selection.
save_path: Path | None = None — Directory override for saving autofocus results.
subframe_width: int | None = None — Width of the requested subframe in binned pixels.
subframe_height: int | None = None — Height of the requested subframe in binned pixels.
subframe_center_x: float = 0.5 — Horizontal subframe center (0=left, 1=right).
subframe_center_y: float = 0.5 — Vertical subframe center (0=top, 1=bottom).

pointing_model#

Aid building a telescope pointing model. Astra itself does not build or maintain a pointing model.

Captures a spiral of points from zenith down to 30° altitude while avoiding positions within 20° of the Moon.

Plate solves each pointing and sends SyncToCoordinates commands to the mount. The receipt of these commands can be used to build a pointing model in the mount control software. The action can be configured to use the local star catalog for plate solving to speed up the process if the online Gaia catalog is unavailable or slow.

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "pointing_model",
    "action_value": {},
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

Action values

n: int = 50 — Number of points to include in the model.
exptime: float = 3.0 — Exposure time for each pointing image.
dark_subtraction: bool = false — Enable dark subtraction using previously taken calibration frames of same exposure time in the same date folder.
object: str = 'Pointing Model' — Descriptive label for the pointing run.
use_local_db: bool = false — Use local star catalog database for plate solving (faster).
filter: str | None = None — Filter to use for exposures.
focus_shift: float | None = None — Focus offset relative to best focus.
focus_position: float | None = None — Absolute focus position override.
bin: int = 1 — Camera binning factor.
dir: str | None = None — Directory path for saving images.
subframe_width: int | None = None — Width of the requested subframe in binned pixels.
subframe_height: int | None = None — Height of the requested subframe in binned pixels.
subframe_center_x: float = 0.5 — Horizontal subframe center (0=left, 1=right).
subframe_center_y: float = 0.5 — Vertical subframe center (0=top, 1=bottom).

open#

Open the observatory for observations.

Steps:
  1. Opens dome shutter

  2. Unparks telescope

  3. Cools camera

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "open",
    "action_value": {},
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

close#

Close the observatory safely.

Steps:
  1. Stop any active guiding operations

  2. Stop telescope slewing and tracking

  3. Park the telescope

  4. Park the dome and close shutter

  5. Cools camera

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "close",
    "action_value": {},
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

cool_camera#

Configuration for the cool_camera schedule action.

Activates the camera cooler and sets the target temperature with specified tolerance and timeout from observatory configuration.

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "cool_camera",
    "action_value": {},
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}

complete_headers#

Complete FITS headers after exposures finish.

Uses paired device polled data to fill in FITS header fields that were unavailable at exposure time. Automatically executed at the end of every schedule.

Minimal schedule example

{
    "device_name": "camera_name",
    "action_type": "complete_headers",
    "action_value": {},
    "start_time": "2025-01-01 00:00:00.000",
    "end_time": "2025-02-01 00:00:00.000"
}