tcsd + sequencerd: let the TCS itself satisfy the "ontarget" wait (opt-in) - #483
tcsd + sequencerd: let the TCS itself satisfy the "ontarget" wait (opt-in)#483cfremling wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f56675fd0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
| if ( !tracking || !( sep_as < this->tcs_auto_ontarget_sep ) ) { consecutive=0; continue; } | ||
|
|
||
| if ( ++consecutive < 2 ) continue; // debounce: two reads in a row |
There was a problem hiding this comment.
Arm the watcher only after observing a non-settled state
When the new target is within tcs_auto_ontarget_sep of the previous field and the TCS does not drop ?ONTARGET or leave tracking, the previous settled state already satisfies every predicate here. The watcher therefore reaches two consecutive samples and calls ontarget() even though it has never observed the commanded move begin or settle—the exact small-nudge case that is supposed to remain operator-controlled. Require a post-command disqualifying transition before incrementing this counter, or otherwise exclude slews below the threshold.
Useful? React with 👍 / 👎.
| this->broadcast.notice( function, message.str() ); | ||
| this->ontarget(); |
There was a problem hiding this comment.
Recheck cancellation before sending the automatic acknowledgment
If an abort sets cancel_flag after the loop condition was checked—for example while the second ?ONTARGET request is in flight—this path still calls ontarget(), whose implementation clears cancel_flag. The waiting sequence can then observe is_ontarget rather than the abort and continue into acquisition or exposure. Recheck auto_ontarget_active and cancel_flag immediately before acknowledging, and ensure the automatic path cannot clear a concurrently issued cancellation.
Useful? React with 👍 / 👎.
| double tgt_ra_h = radec_to_decimal( this->target.ra_hms ); // decimal hours | ||
| double tgt_dec_d = radec_to_decimal( this->target.dec_dms ); // decimal degrees |
There was a problem hiding this comment.
Compare telemetry with the coordinates actually sent to the TCS
For targets using a focal-plane point mode such as SLIT or ACAM, these database coordinates are transformed by compute_offset(pointmode, "SCOPE", ...), and move_to_target() sends the resulting ra_out/dec_out to the TCS. REQPOS telemetry describes that scope position, so comparing it with the untransformed input can leave the measured separation larger than the default 5 arcsec and prevent auto-ontarget from ever succeeding. Capture and compare against the commanded scope coordinates instead.
Useful? React with 👍 / 👎.
| double dra_as = ( this->tcsinfo.ra_h - tgt_ra_h ) * 15.0 | ||
| * cos( tgt_dec_d * M_PI / 180.0 ) * 3600.0; |
There was a problem hiding this comment.
Normalize the right-ascension delta across 24 hours
For a target near 00:00 whose measured position falls just across the 24-hour boundary, subtracting the hour values directly produces a delta close to 24 hours rather than a sub-arcsecond delta. The separation test consequently never passes even when the telescope is on target, leaving these observations dependent on the manual acknowledgment. Wrap the RA difference into the shortest signed interval before converting it to arcseconds.
Useful? React with 👍 / 👎.
9a91093 to
0451b1f
Compare
|
All four review findings addressed in the amended commit:
🤖 Generated with Claude Code |
0451b1f to
9f73642
Compare
?ONTARGET was invisible through tcsd because send_command translates
every non-whitelisted reply as a status code: the flag IS the reply, so
0 collided with TCS_SUCCESS ('success') and 1 fell through to
'tcs_undefined' ERROR. Whitelist it for verbatim passthrough.
sequencerd: config TCS_AUTO_ONTARGET (default no) spawns a watcher for
exactly the lifetime of move_to_target's TCSOP wait. It calls
ontarget() -- the operator's own path -- when, on two consecutive 1 Hz
reads: ?ONTARGET==1 AND motion==tracking AND |telescope-target| <
TCS_AUTO_ONTARGET_SEP arcsec. All three are required: shadow data
(UT 2026-08-25, 64 slews) shows the flag is position-agnostic, the
separation cannot see sub-threshold hops, and motion says nothing
about settle. Operator ack trailed the flag by median +9.7 s / p90
+18 s and never once preceded it. The operator's press is unchanged;
whichever arrives first wins.
9f73642 to
4b2bfcf
Compare
Reference implementation from on-sky measurement — offered as the working example for the preferred sequencerd implementation; adapt freely.
What the operator's press costs, measured
Every science slew ends with sequencerd blocking on the TCS operator's
manual "ontarget" press (
move_to_target, SEQ_WAIT_TCSOP). A shadow rig ran?ONTARGETat 1 Hz through a whole night (UT 2026-08-25, 64 slews, 59 witha clean 0→1 transition) alongside the operator's presses:
median +9.7 s, p90 +18 s, max +31.5 s — and never once preceded it
(0 of 59);
coordinates to median 0.4″;
To get this flag through tcsd
?ONTARGET's reply IS the flag (0/1), butTCS::Interface::send_commandtranslates every reply that is not on its information-command whitelist
through
parse_reply_code:0collides withTCS_SUCCESSand renders"success";
1is no valid status code and renders "tcs_undefined" ERROR.The code here properly exposes the flag status.
The change (120 lines, all opt-in)
tcsd — add
?ONTARGETtosend_command's verbatim whitelist(tcs_interface.cpp, 1 line + comment).
tcs native '?ONTARGET'nowreturns
0/1.sequencerd — config
TCS_AUTO_ONTARGET(defaultno: deployedbehaviour is byte-identical until someone flips it). When enabled,
move_to_targetspawnsdothread_auto_ontargetfor exactly the lifetimeof the TCSOP wait (an
auto_ontarget_activeatomic brackets it). Thewatcher acknowledges through the same condition variable the operator's
press uses — whichever arrives first wins — when two consecutive 1 Hz
reads satisfy all three of:
A wrong acknowledgment is deliberately treated as self-limiting: ACAM
cannot solve a field that is not there, and the acquisition retry
machinery handles the failure — so these conditions exist to make wrong
acks rare, not impossible, and no operator press is expected in this
mode. The comparison is against the SCOPE-frame coordinates
move_to_targetactually commanded (the database coordinates differ bythe pointmode focal-plane offset), with the RA delta wrapped to the
shortest arc. The acknowledgment deliberately does not go through
ontarget(), which clearscancel_flag: a concurrent cancel alwayswins against the automatic path (re-checked at the last instant, and
never cleared by it).
All three are required because each alone is measured to fail:
previous field (observed at 25–57° separation);
still in progress (the offset-star expose race);
On nearby offset-star acquisitions (~2.7′) all three signals transition cleanly
(flag drops to 0 for ~10 s, motion reads "offsetting"); for nudges below
a few arcsec nothing transitions and the watcher simply never fires —
the operator (or the existing settle logic) covers that case unchanged.
The debounce covers two observed settle bounces (1→0→1) from the logged night.
Config template
sequencerd.cfg.in: the two keys, default off.What does NOT change
TCS_AUTO_ONTARGET=no(the default): nothing. The watcher is neverspawned; the whitelist line only affects the reply text of a query
nothing currently issues.
the same
ontarget()and either satisfies the wait.cancel_flag, onis_ontarget, andwhen the wait closes (
auto_ontarget_activefalse) — it cannot outlivethe wait or fire into a later phase.
Validation plan before enabling
The shadow rig keeps running nightly; each night appends ~60 more
slews of (flag, motion, separation, operator) tuples. Enable on-sky only
after N nights show zero would-have-fired-early cases against the operator
baseline. First enable with the operator still watching: the broadcast
notice ("TCS reports on-target … sending ontarget") makes every automatic
ack visible in the sequencer log and GUI.
🤖 Generated with Claude Code