Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/basic-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ jobs:

- name: Run regression tests
run: |
for t in dev/test_*.py; do
shopt -s nullglob
tests=(dev/test_*.py)
if [ "${#tests[@]}" -eq 0 ]; then
echo "No legacy dev/test_*.py regression tests found."
exit 0
fi
for t in "${tests[@]}"; do
echo "Running $t"
python "$t"
done
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ SeeVar is organized as a sovereign observing pipeline:
2. **Flight**
Executes one canonical target sequence per object using the `A1-A12` flight chain:
target lock, safety gate, session init, slew, verify, settle, exposure planning, acquire, quality gate, and commit.
Each target writes a proof ledger in `data/flight_runs/`; success requires
the full connect, prepare, solve, track, expose, accept, and complete chain.
Partial target success is disabled by default.

3. **Postflight**
Processes captured frames using the `P1-P8` science chain:
Expand All @@ -108,6 +111,9 @@ SeeVar is organized as a sovereign observing pipeline:
## Hardware Interface

SeeVar communicates with the Seestar through the official Alpaca REST interface exposed by the telescope firmware.
The default control path remains firmware Alpaca. A dormant `seestar_alp`
adapter can be enabled with `[seestar_alp].enabled = true` and
`mode = "controlled"`; otherwise it is diagnostics-only.

Confirmed device access includes:

Expand Down Expand Up @@ -300,9 +306,11 @@ plate_solve_timeout_sec = 90
plate_solve_cpulimit_sec = 75
```

The accountant tries the aligned stack first, then the newest calibrated single
frames. When the cap is reached, the target fails honestly and processing moves
on.
For multi-frame targets, the accountant now requires a stacked product by
default. Singles are not promoted to successful object previews unless
`require_stacked_products = false` or the target only has one calibrated frame.
Successful postflight should publish one accepted stacked FITS/JPEG/report per
object.

A7 in-flight pointing verification is separate from postflight. It is governed
by `[flight]` and should fail fast:
Expand All @@ -318,6 +326,8 @@ pointing_accept_target_in_frame = true
pointing_edge_margin_px = 250
verify_retention_sets = 40
frame_retry_limit = 0
allow_partial_target_success = false
strict_target_proof_chain = true
```

When `pointing_accept_target_in_frame` is enabled, A7 accepts a solved frame if
Expand Down
29 changes: 29 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ It is making the science chain defensible end-to-end.
- postflight doctrine frozen around `P1-P8`
- photometry doctrine rewritten around Bayer-aware measurement rather than naive debayering
- docs now reflect that flight and postflight have separate responsibilities
- per-target proof ledger started in `data/flight_runs/`
- partial target success disabled by default
- multi-frame postflight requires one stacked accepted product by default
- dormant `seestar_alp` controlled adapter added behind the pilot interface

#### 1.9.0 — Doctrine Freeze
- freeze sovereign `A1-A12`
Expand Down Expand Up @@ -107,6 +111,7 @@ Next:
- wire accepted postflight TG results into AAVSO report staging
- make report generation a true `P8` output
- stop treating the reporter as a side utility
- enforce no AAVSO upload without stack, WCS, photometry, and report proof

#### 1.9.5 — Astropy Review Pass
- replace custom code with `astropy` components where that improves correctness and maintainability
Expand All @@ -116,6 +121,30 @@ Next:
- custody/state workflow
- perform a helicopter-view audit of places where Astropy is the better answer

#### 1.9.6 — seestarpy Continuity Adapter
Future:
- test `seestarpy` as the preferred telescope execution adapter
- keep SeeVar responsible for target accounting, proof ledger, photometry, and AAVSO reporting
- map SeeVar proof steps onto `seestarpy` primitives:
- connect/open
- goto target
- plate-solve result
- tracking state
- start/stop stack
- stack info
- file download
- use `seestarpy` multi-Seestar support for Wilhelmina and Anna when stable
- handle firmware `7.18+` authentication explicitly before making this production
- prove one strict object chain before replacing the current Alpaca capture path

#### 1.9.7 — SeeVar Lite Reducer
Future:
- make Lite the preferred development path
- submit a generated seestarpy/seestar_alp plan instead of steering frames directly
- monitor plan state into `system_state_lite.json` and `flight_runs/lite_*.jsonl`
- download one accepted stack per target
- keep the old Alpaca loop as fallback until Lite proves one full science target

#### 1.9.x also includes
- rewrite `WORKFLOW.MD` to match current Alpaca-era reality
- remove stale TCP-era doctrine from remaining docs
Expand Down
14 changes: 14 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,19 @@ pointing_gross_max_retries = 1
pointing_accept_target_in_frame = true
pointing_edge_margin_px = 250
verify_retention_sets = 40
strict_tracking_required = true
strict_camera_idle_required = true
mount_idle_verify_timeout_sec = 5.0
tracking_verify_timeout_sec = 10.0
tracking_verify_interval_sec = 0.5
camera_idle_timeout_sec = 8.0
pointing_proof_max_age_sec = 900.0
pointing_model_enabled = true
pointing_model_max_age_hours = 12.0
pointing_reverify_interval_frames = 5
frame_retry_limit = 0
allow_partial_target_success = false
strict_target_proof_chain = true
prealign_before_flight = false
prealign_points = 3
prealign_exposure_sec = 5.0
Expand All @@ -157,6 +167,7 @@ prealign_wide_solve_radius_deg = 60.0
[seestar_alp]
enabled = false
base_url = "http://127.0.0.1:5555"
port = 5555
mode = "diagnostic" # diagnostic | controlled

# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -199,6 +210,9 @@ plate_solve_timeout_sec = 90
plate_solve_cpulimit_sec = 75
auto_stage_reports = true
report_mirror_dir = "/mnt/astronas/reports"
accepted_products_dir = ""
publish_single_frame_previews = false
require_stacked_products = true
auto_park = true
auto_shutdown_scope = false

Expand Down
10 changes: 9 additions & 1 deletion core/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,15 @@ def index():
loc.get('elevation', 0.0),
sun_limit,
)
return render_template('index.html', target_data=target_data, flight_window=fw_text)
co_lat = f"{float(loc.get('lat', 51.4769)):.2f}"
co_lon = f"{float(loc.get('lon', 0.0)):.2f}"
return render_template(
'index.html',
target_data=target_data,
flight_window=fw_text,
clearoutside_url=f"https://clearoutside.com/forecast/{co_lat}/{co_lon}",
clearoutside_image=f"https://clearoutside.com/forecast_image_small/{co_lat}/{co_lon}/forecast.png",
)

@app.route('/telemetry')
def get_telemetry():
Expand Down
38 changes: 38 additions & 0 deletions core/dashboard/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@
.grey { background-color: var(--led-grey); }
.blue-grey { background-color: var(--led-bluegrey); box-shadow: inset 0 2px 4px rgba(0,0,0,0.5); border: 1px solid #333; color: transparent; }
.weather-icon { background: none; box-shadow: none; font-size: 18px; color: transparent; }
.weather-forecast-card { margin: -2px 0 14px 0; padding: 8px 0 12px 0; border-bottom: 1px dashed #333; }
.weather-forecast-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 0.65em; letter-spacing: 1.5px; color: var(--text-muted); }
.weather-forecast-head a { color: #88ccff; text-decoration: none; }
.weather-forecast-img-wrap { display: block; max-width: 100%; height: 42px; overflow: hidden; border: 1px solid #252525; background: #050505; margin-bottom: 6px; }
.weather-forecast-img { width: 100%; max-width: 520px; transform: translateY(-18px); opacity: 0.82; filter: saturate(0.85) brightness(0.8); }
.weather-hours { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 3px; }
.weather-hour { border: 1px solid #252525; padding: 3px 2px; min-height: 30px; text-align: center; font-size: 0.62em; color: #777; overflow: hidden; }
.weather-hour.clear { color: var(--led-green); border-color: #1a3a0a; }
.weather-hour.cloudy { color: var(--led-orange); border-color: #3a2a00; }
.weather-hour.abort { color: var(--alert-red); border-color: #4f1d1d; }
.weather-hour-time { display: block; color: #555; }
.weather-hour-val { display: block; font-weight: bold; }

.center-content { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 80%; text-align: center; }
.icon-p { background: linear-gradient(135deg, #1e90ff, #005cbf); color: white; font-size: 3em; font-weight: bold; font-family: sans-serif; width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; border-radius: 16px; margin-bottom: 20px; box-shadow: 0 8px 16px rgba(0,0,0,0.5); transition: all 0.5s ease; }
Expand Down Expand Up @@ -146,6 +158,16 @@ <h2>SYSTEM VITALS</h2>
<div class="flight-window-row"><span class="label">DARK WINDOW</span> <span class="fw-val">{{ flight_window }}</span></div>
<div class="row"><span class="label">GPS (LOCK)</span> <span id="gps-val" class="val"></span> <div id="gps-led" class="led grey"></div></div>
<div class="row"><span class="label">WEATHER</span> <span id="weather-val" class="val">FETCHING</span> <div id="weather-icon-led" class="led weather-icon">⭐</div></div>
<div class="weather-forecast-card">
<div class="weather-forecast-head">
<span>NIGHT FORECAST</span>
<a href="{{ clearoutside_url }}" target="_blank" rel="noopener">CLEAROUTSIDE</a>
</div>
<a class="weather-forecast-img-wrap" href="{{ clearoutside_url }}" target="_blank" rel="noopener">
<img class="weather-forecast-img" src="{{ clearoutside_image }}" alt="ClearOutside forecast">
</a>
<div id="weather-hours" class="weather-hours"></div>
</div>
<div class="row"><span class="label">FOG (VIS)</span> <span class="val">DISCONNECTED</span> <div class="led blue-grey"></div></div>

<div class="row fleet-row"><span class="label">FLEET</span> <span id="bb-fleet" class="val stat-val val-wrap">--</span></div>
Expand Down Expand Up @@ -363,6 +385,22 @@ <h2>POSTFLIGHT AUDIT</h2>
if (stale && gpsLed) {
// no-op placeholder to keep weather handling grouped
}

const weatherHours = document.getElementById('weather-hours');
if (weatherHours) {
const hours = (data.weather.hourly_detail || []).slice(0, 6);
weatherHours.innerHTML = hours.map(h => {
const cls = h.abort ? 'abort' : (h.cloud_block ? 'cloudy' : 'clear');
const clouds = Math.max(
Number(h.om_clouds || 0),
Number(h.met_clouds || 0),
Number(h.co_low || 0),
Number(h.co_mid || 0),
Number(h.co_high || 0)
);
return `<div class="weather-hour ${cls}"><span class="weather-hour-time">${h.hour_utc || '--'}</span><span class="weather-hour-val">${Math.round(clouds)}%</span></div>`;
}).join('');
}
}

if(data.orchestrator) {
Expand Down
Loading