Observation Plans Module

seestarpy.plan.create_mosaic_plan(plan_name, center_ra, center_dec, width, height, delta_ra, delta_dec, t_total, start_min, lp_filter=False, target_name_prefix='Mosaic')

Create an observation plan that tiles a rectangular sky region.

The Seestar S50 FOV is roughly 0.75 x 1.33 degrees — too small for many extended objects. This function generates a plan dictionary with individual pointings arranged in a grid, ready to be sent with set_view_plan(). No network I/O is performed.

Panels are traversed in boustrophedon (snake) order: even Dec rows go left-to-right in RA, odd rows go right-to-left. This minimises slew distance between consecutive panels.

Parameters:
  • plan_name (str) – Human-readable name for the plan.

  • center_ra (float) – Right ascension of the mosaic centre in decimal hours [0, 24).

  • center_dec (float) – Declination of the mosaic centre in decimal degrees [-90, 90].

  • width (float) – Angular extent in RA on the sky, in degrees. Use 0 for a single column of panels.

  • height (float) – Angular extent in Dec, in degrees. Use 0 for a single row.

  • delta_ra (float) – Panel spacing in the RA direction, in degrees (> 0).

  • delta_dec (float) – Panel spacing in the Dec direction, in degrees (> 0).

  • t_total (float) – Total observation time in minutes, divided equally among panels.

  • start_min (int) – Start time as minutes since local midnight (same convention as set_view_plan()).

  • lp_filter (bool, optional) – Enable the light-pollution filter for every panel (default False).

  • target_name_prefix (str, optional) – Prefix for panel names (default "Mosaic"). Panels are named "{prefix}_01", "{prefix}_02", etc.

Returns:

A plan dictionary suitable for set_view_plan().

Return type:

dict

Raises:

ValueError – If any parameter is out of range.

Examples

>>> from seestarpy import plan
>>> mosaic = plan.create_mosaic_plan(
...     plan_name="NGC 7000 Mosaic",
...     center_ra=20.99,       # ~20h 59m
...     center_dec=44.53,
...     width=3.0,
...     height=2.0,
...     delta_ra=1.0,
...     delta_dec=1.0,
...     t_total=180,
...     start_min=1320,        # 22:00
... )
>>> len(mosaic["list"])
6
>>> plan.set_view_plan(mosaic)
seestarpy.plan.create_named_plan(plan_name, targets, end_time, lp_filter=False)

Create an observation plan from a list of targets.

Targets can be specified by name (resolved automatically via the CDS Sesame service — SIMBAD/NED/VizieR) or by explicit RA/Dec coordinates. Each target observes from its start time until the next target begins; the last target observes until end_time.

Parameters:
  • plan_name (str) – Human-readable name for the plan.

  • targets (list of tuple) –

    Each element is (target, start_time) or (target, start_time, lp_filter), where:

    • target — either a string (object designation such as "M42", "NGC 884", "HD 126675", resolved via Sesame) or a (ra_hours, dec_deg) tuple/list giving explicit coordinates.

    • start_time (str) — local start time as "hh:mm" (24-hour format).

    • lp_filter (bool, optional) — per-target light-pollution filter override. If omitted, the function-level lp_filter default is used.

    Times that are earlier than the previous target’s start time are assumed to be after midnight (next calendar day).

  • end_time (str) – When the last target should stop observing, as "hh:mm". If earlier than the last target’s start, it is treated as post-midnight.

  • lp_filter (bool, optional) – Default light-pollution filter setting for targets that don’t specify one (default False).

Returns:

A plan dictionary suitable for set_view_plan().

Return type:

dict

Raises:
  • ValueError – If targets is empty or times are malformed.

  • LookupError – If a target name string cannot be resolved.

  • ConnectionError – If the CDS Sesame service is unreachable.

Examples

>>> from seestarpy import plan
>>> p = plan.create_named_plan(
...     plan_name="Evening Session",
...     targets=[
...         ("M42", "21:00"),                  # resolved via Sesame
...         ("NGC 884", "22:30", True),        # with LP filter
...         ((0.712, 41.27), "23:45"),          # explicit RA/Dec
...     ],
...     end_time="01:00",
... )
>>> plan.set_view_plan(p)
seestarpy.plan.create_polygon_plan(plan_name, corners, delta_ra, delta_dec, t_total, start_min, lp_filter=False, target_name_prefix='Poly')

Create an observation plan filling an arbitrary polygon on the sky.

Unlike create_mosaic_plan() which tiles an axis-aligned rectangle, this function accepts 3 or more RA/Dec corner pairs defining a simple (non-self-intersecting) polygon that may be non-rectangular or tilted relative to the equatorial grid. A gnomonic tangent-plane projection is used internally, so cos(dec) correction and RA wraparound near 0 h/24 h are handled automatically.

Panels are traversed in boustrophedon (snake) order within the tangent-plane grid. No network I/O is performed.

Parameters:
  • plan_name (str) – Human-readable name for the plan.

  • corners (list of (float, float)) – Three or more (ra_hours, dec_deg) tuples defining the polygon boundary, ordered either clockwise or counter-clockwise. Edges must not cross each other (simple polygon).

  • delta_ra (float) – Grid spacing in the RA (xi) direction of the tangent plane, in degrees (> 0).

  • delta_dec (float) – Grid spacing in the Dec (eta) direction of the tangent plane, in degrees (> 0).

  • t_total (float) – Total observation time in minutes, divided equally among panels.

  • start_min (int) – Start time as minutes since local midnight (same convention as set_view_plan()).

  • lp_filter (bool, optional) – Enable the light-pollution filter for every panel (default False).

  • target_name_prefix (str, optional) – Prefix for panel names (default "Poly").

Returns:

A plan dictionary suitable for set_view_plan().

Return type:

dict

Raises:

ValueError – If corners has fewer than 3 elements, spacings are not positive, or the polygon is degenerate (zero area).

Examples

>>> from seestarpy import plan
>>> # Quadrilateral
>>> quad = plan.create_polygon_plan(
...     plan_name="Veil Nebula Quad",
...     corners=[
...         (20.75, 30.0),
...         (20.95, 30.0),
...         (20.95, 32.0),
...         (20.75, 32.0),
...     ],
...     delta_ra=0.5,
...     delta_dec=0.5,
...     t_total=120,
...     start_min=1320,
... )
>>> # Pentagon
>>> penta = plan.create_polygon_plan(
...     plan_name="Five-sided region",
...     corners=[
...         (12.0, 0.0), (12.1, -0.5), (12.2, 0.0),
...         (12.15, 0.5), (12.05, 0.5),
...     ],
...     delta_ra=0.3,
...     delta_dec=0.3,
...     t_total=90,
...     start_min=1320,
... )
>>> plan.set_view_plan(quad)
seestarpy.plan.create_quadrilateral_plan(plan_name, corners, delta_ra, delta_dec, t_total, start_min, lp_filter=False, target_name_prefix='Poly')

Alias — the original name before generalisation to arbitrary polygons.

seestarpy.plan.get_running_plan()

Return the currently running observation plan, or None if no plan is active.

This queries iscope_get_app_state and extracts the ViewPlan key, which is how the official Seestar app checks plan status.

Note

Confirmed via traffic capture from the official Seestar app v3.0.2 on 2026-02-24.

Returns:

The ViewPlan state dict when a plan is loaded (even if stopped/cancelled), or None if no plan data is present. The dict includes state ("working", "cancel", etc.) and plan with the full plan payload including per-target state, lapse_ms, and skip fields added by the firmware.

Return type:

dict or None

Examples

>>> from seestarpy import plan
>>> vp = plan.get_running_plan()
>>> if vp and vp["state"] == "working":
...     print(f"Running: {vp['plan']['plan_name']}")
seestarpy.plan.plot_mosaic_plan(plan, fov_width=0.75, fov_height=1.33, ax=None)

Plot panel borders of a mosaic plan on a Mollweide projection.

The view is zoomed to 150 % of the area covered by the panel footprints, with Mollweide grid lines (RA meridians and Dec parallels) drawn for reference. Each panel is drawn as a closed rectangle in RA/Dec space, assuming an equatorial mount (panels axis-aligned).

Because matplotlib’s built-in Mollweide axes do not support zooming, coordinates are projected manually and drawn on a standard rectilinear axes.

Parameters:
  • plan (dict) – A plan dictionary (from create_mosaic_plan() or hand-built) containing "plan_name" and "list" with target dicts that each have "target_ra_dec" and "target_name".

  • fov_width (float, optional) – Field-of-view width in degrees (RA direction on the sky). Default is 0.75 (Seestar S50).

  • fov_height (float, optional) – Field-of-view height in degrees (Dec direction). Default is 1.33 (Seestar S50).

  • ax (matplotlib.axes.Axes or None, optional) – A rectilinear axes to plot on. If None, a new figure and axes are created.

Returns:

The axes with panel outlines, labels, and grid lines plotted.

Return type:

matplotlib.axes.Axes

Examples

>>> from seestarpy import plan
>>> mosaic = plan.create_mosaic_plan(
...     "NGC 7000", 20.99, 44.53, 3.0, 2.0, 1.0, 1.0, 180, 1320,
... )
>>> ax = plan.plot_mosaic_plan(mosaic)
seestarpy.plan.resolve_name(name)

Resolve an astronomical object name to RA/Dec using the CDS Sesame name resolver.

Sesame queries SIMBAD, NED, and VizieR in sequence, so most common designations work (Messier, NGC, IC, HD, HIP, Bayer, etc.).

Parameters:

name (str) – Object designation, e.g. "M42", "NGC 884", "HD 126675".

Returns:

(ra_hours, dec_deg) in the same convention used by set_view_plan() — RA as decimal hours [0, 24), Dec as decimal degrees [-90, 90].

Return type:

(float, float)

Raises:
  • LookupError – If the name cannot be resolved (not found in any catalogue).

  • ConnectionError – If the Sesame service is unreachable.

Examples

>>> from seestarpy.plan import resolve_name
>>> ra, dec = resolve_name("M42")
>>> print(f"RA={ra:.4f}h  Dec={dec:.2f}°")
RA=5.5881h  Dec=-5.39°
seestarpy.plan.set_view_plan(plan)

Send an observation plan to the Seestar and start executing it.

Parameters:

plan (dict) –

A plan dictionary with keys plan_name (str), update_time_seestar (str, format "yyyy.MM.dd"), and list (list of target dicts). Each target dict has:

  • target_id (int): 9-digit unique identifier.

  • target_name (str): Display name (e.g. "M 31").

  • alias_name (str): Alias or empty string "".

  • target_ra_dec (list[float, float]): [RA_hours, Dec_degrees].

  • lp_filter (bool): Enable the light-pollution filter for this target.

  • start_min (int): Start time as minutes since local midnight.

  • duration_min (int): Observation duration in minutes.

Return type:

dict

Notes

Note

Payload format confirmed via traffic capture from the official Seestar app v3.0.2 on 2026-02-24.

RA convention: target_ra_dec uses the same convention as iscope_start_view() — RA in decimal hour-angle [0, 24) and Dec in decimal degrees [-90, 90]. The decompiled SetViewPlanCmd.java does not divide RA by 15 because the app already stores RA in hour-angle.

start_min: This is not a relative offset from when the plan starts. It is the absolute minutes since local midnight, using the Seestar’s internal clock (which is synced to the phone/host local time via pi_set_time()). For example, 22:30 = 1350, 23:00 = 1380. For targets after midnight, use values above 1440 (e.g. 01:30 AM = 1530).

duration_min: Observation duration in minutes. The official app displays plans on a chart with 10-minute resolution, but the firmware receives the raw integer – sub-10-minute durations are valid at the protocol level.

Examples

>>> from seestarpy import plan
>>> # M42 at 22:30 for 30 min, then M31 at 23:00 for 45 min
>>> my_plan = {
...     "plan_name": "Evening Session",
...     "update_time_seestar": "2026.02.23",
...     "list": [
...         {
...             "target_id": 123456789,
...             "target_name": "M42",
...             "alias_name": "Orion Nebula",
...             "target_ra_dec": [5.588, -5.39],
...             "lp_filter": True,
...             "start_min": 1350,
...             "duration_min": 30,
...         },
...         {
...             "target_id": 123456790,
...             "target_name": "M31",
...             "alias_name": "Andromeda Galaxy",
...             "target_ra_dec": [0.712, 41.27],
...             "lp_filter": False,
...             "start_min": 1380,
...             "duration_min": 45,
...         },
...     ],
... }
>>> plan.set_view_plan(my_plan)
seestarpy.plan.stop_view_plan()

Stop the currently executing observation plan.

This is the method the official Seestar app uses to stop a running plan. It sends stop_func with {"name": "ViewPlan"}.

Note

Confirmed via traffic capture from the official Seestar app v3.0.2 on 2026-02-24.

Return type:

dict

Examples

>>> from seestarpy import plan
>>> plan.stop_view_plan()