8000 Rel 4159 by hstecher · Pull Request #2904 · gemini-hlsw/seqexec · GitHub
[go: up one dir, main page]

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

Filter by extension

Filter by extension

8000
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions modules/server/src/main/scala/seqexec/server/altair/Altair.scala
8000
Original file line numberDiff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import seqexec.server.gems.GemsController.GemsConfig
import seqexec.server.tcs.Gaos
import seqexec.server.tcs.Gaos.{ PauseConditionSet, ResumeConditionSet }
import seqexec.server.tcs.TcsController.FocalPlaneOffset
import seqexec.server.tcs.TcsController.ProbeTrackingConfig
import squants.Time

trait Altair[F[_]] extends Gaos[F] {
Expand All @@ -36,8 +37,18 @@ trait Altair[F[_]] extends Gaos[F] {

def isFollowing: F[Boolean]

// Are we using a NGS, either for NGS mode or LGS + NGS ?
def hasTarget(guide: AltairConfig): Boolean
/**
* AOWFS probe tracking demand for an Altair mode. `sequenceTracking` is what the step's
* guideWithAO asks for; a mode may override it.
*
* This replaces the old `hasTarget: Boolean`, which could only pick between the sequence demand
* and ProbeTrackingConfig.Off, and so had no way to express "keep following, don't nod/chop" --
* the state the LGS relay modes need. See REL-4159.
*/
def aoProbeTracking(
guide: AltairConfig,
sequenceTracking: ProbeTrackingConfig
): ProbeTrackingConfig

}

Expand Down Expand Up @@ -73,12 +84,34 @@ object Altair {

override def isFollowing: F[Boolean] = controller.isFollowing

override def hasTarget(guide: AltairConfig): Boolean = guide match {
case Lgs(st, sf, _) => st || sf
case LgsWithOi => false
case LgsWithP1 => false
case Ngs(_, _) => true
case AltairOff => false
// Ngs and plain Lgs: the AOWFS carries the NGS and tracks it normally, so the
// step's demand stands. In particular it stands regardless of whether
// STRAP/SFO happen to be closed right now -- conditioning this on the loop
// state wrote aoFollow=Off and froze the probe (fixed and verified on-sky
// 2026-07-17).
//
// LgsWithP1 / LgsWithOi are the relay modes: the tip-tilt reference lives on
// P1 or the OIWFS. The AOWFS nod/chop must be AllOff (the TCS rejects
// nod/chop on the AOWFS in P1+LGS mode), and the TCC does not put the
// AOWFS/FSM into follow in these modes (confirmed on-sky 2026-07-28;
// demanding follow On drove the probe on every offset and the TCS never
// reached in-position). So the demand is Off: FollowOff + AllOff, matching
// the state the TCC leaves it in. setAltairProbe never writes follow in
// these modes, so this cannot freeze a probe the TCC has following, and
// the AO loop is treated as guiding from the detector demand alone (see
// TcsNorthControllerEpicsAo.aoGuideActive).
//
// This mirrors tcl/tk seqexec, whose beamconfig drives the AOWFS nod/chop
// channel to all-Off when lgsp1 or lgsoi is set, and whose telconfig
// inherits AO follow from the TCC rather than deciding it (RealTcs.itcl).
override def aoProbeTracking(
guide: AltairConfig,
sequenceTracking: ProbeTrackingConfig
): ProbeTrackingConfig = guide match {
case Lgs(_, _, _) => sequenceTracking
case Ngs(_, _) => sequenceTracking
case LgsWithOi | LgsWithP1 => ProbeTrackingConfig.Off
case AltairOff => ProbeTrackingConfig.Off
}

}
Expand Down
8000
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import seqexec.model.enum.ApplyCommandResult
import seqexec.server.SeqexecFailure
import seqexec.server.altair.AltairController._
import seqexec.server.tcs.FOCAL_PLANE_SCALE
import seqexec.server.tcs.Gaos.PauseCondition.{ GaosGuideOff, OiOff, P1Off }
import seqexec.server.tcs.Gaos.ResumeCondition.{ GaosGuideOn, OiOn, P1On }
import seqexec.server.tcs.Gaos.PauseCondition.GaosGuideOff
import seqexec.server.tcs.Gaos.ResumeCondition.GaosGuideOn
import seqexec.server.tcs.Gaos._
import seqexec.server.tcs.TcsController.FocalPlaneOffset
import seqexec.server.tcs.TcsEpics
Expand Down Expand Up @@ -309,9 +309,9 @@ object AltairControllerEpics {
).whenA(currCfg.sfoLoop === LgsSfoControl.Enable)

private def ttgsOn(strap: Boolean, sfo: Boolean, currCfg: EpicsAltairConfig): F[Unit] =
checkStrapLoopState(currCfg).fold(ApplicativeError[F, Throwable].raiseError,
Sync[F].delay(_)
) *>
checkStrapLoopState(currCfg)
.fold(ApplicativeError[F, Throwable].raiseError, Sync[F].delay(_))
.whenA(strap) *>
(startStrapGate(currCfg) *> startStrapLoop(currCfg)).whenA(strap) *>
startSfoLoop(currCfg).whenA(sfo)

Expand Down Expand Up @@ -411,7 +411,7 @@ object AltairControllerEpics {
val alreadyThere =
(currentCfg.sfoLoop === LgsSfoControl.Enable && sfo) && (currentCfg.strapLoop && strap)

val action = if (!alreadyThere && guidedStep)
val action = if (!alreadyThere && guidedStep && (strap || sfo))
L.debug(
s"Resuming Altair LGS(strap = $strap, sfo = $sfo) guiding because guidedStep=$guidedStep"
) *>
Expand Down Expand Up @@ -467,8 +467,8 @@ object AltairControllerEpics {
(L.debug("Flatting Altair DM") *> dmFlattenAction).whenA(unguidedStep).void
)

retrieveConfig.map { currCfg =>
cfg match {
retrieveConfig.flatMap { currCfg =>
val result = cfg match {
case Ngs(_, starPos) =>
pauseResumeNgsMode(starPos, currCfg, currentOffset, instrument)(pauseReasons,
resumeReasons
Expand All @@ -477,42 +477,45 @@ object AltairControllerEpics {
pauseResumeLgsMode(str, sfo, pos, currCfg, currentOffset, instrument)(pauseReasons,
resumeReasons
)
case LgsWithP1 =>
AltairPauseResume(
guideOff(currCfg.aoLoop && pauseReasons.fixed.contains(P1Off)),
GuideCapabilities(!pauseReasons.fixed.contains(P1Off),
canGuideM1 = !pauseReasons.fixed.contains(P1Off)
),
pauseTargetFilter = false,
(resumeReasons.contains(P1On) && resumeReasons.contains(
GaosGuideOn
) && (!currCfg.aoLoop || pauseReasons
.contains(P1Off))).option(setCorrectionsOn.void),
GuideCapabilities(resumeReasons.fixed.contains(P1On),
canGuideM1 = resumeReasons.fixed.contains(P1On)
),
none,
forceFreeze = false
)
case LgsWithOi =>
// LGS relay modes: replicate tcl/tk seqexec (RealTcs.itcl), which ran
// these modes correctly.
// - The Altair loop is never opened for offsets or because the
// tip-tilt guider (P1 / OI) goes off: aoduringmove is set
// unconditionally ("the laser moves with the telescope"). It is
// opened only when the step's AOWFS guide is park/freeze
// (GaosGuideOff, RealTcs.itcl:1351/1355).
// - It is closed again on a step that guides with the AOWFS if it is
// currently open.
// - While moving, M1 is left alone; M2 is gated by the tip-tilt
// guider in TcsNorthControllerEpicsAo.calcGuideOff.
// - After the move, M1 and the GAOS M2 source follow the AOWFS guide
// state, as tcl/tk telconfig does (m1/m2 guide on if the AOWFS is
// on). The P1 / OI M2 source is restored by guideOn from the step's
// own P1 / OI demand.
// See REL-4159.
case LgsWithP1 | LgsWithOi =>
val aoGuided = resumeReasons.contains(GaosGuideOn)
AltairPauseResume(
guideOff(currCfg.aoLoop && pauseReasons.fixed.contains(OiOff)),
GuideCapabilities(!pauseReasons.fixed.contains(OiOff),
canGuideM1 = !pauseReasons.fixed.contains(OiOff)
),
guideOff(false),
GuideCapabilities(canGuideM2 = !unguidedStep, canGuideM1 = true),
pauseTargetFilter = false,
(resumeReasons.contains(OiOn) && resumeReasons.contains(
GaosGuideOn
) && (!currCfg.aoLoop || pauseReasons
.contains(OiOff))).option(setCorrectionsOn.void),
GuideCapabilities(resumeReasons.fixed.contains(OiOn),
canGuideM1 = resumeReasons.fixed.contains(OiOn)
(aoGuided && !currCfg.aoLoop).option(
L.debug("Closing Altair loop for LGS relay mode") *> setCorrectionsOn.void
),
GuideCapabilities(canGuideM2 = aoGuided, canGuideM1 = aoGuided),
none,
forceFreeze = false
)
case AltairOff => turnOff(currCfg)
}
L.debug(
s"REL-4159 pauseResume: cfg=$cfg, currCfg=$currCfg, " +
s"pauseReasons=$pauseReasons, resumeReasons=$resumeReasons, " +
s"forceFreeze=${result.forceFreeze}, " +
s"pauseTargetFilter=${result.pauseTargetFilter}, " +
s"guideWhilePaused=${result.guideWhilePaused}, " +
s"restoreOnResume=${result.restoreOnResume}"
).as(result)
}
}

Expand Down
25 changes: 14 additions & 11 deletions modules/server/src/main/scala/seqexec/server/tcs/TcsNorth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,15 @@ class TcsNorth[F[_]: Sync: Logger] private (

override def configure(config: CleanConfig): F[ConfigResult[F]] =
buildTcsConfig.flatMap { cfg =>
subsystems.traverse_(s =>
Log.debug(s"Applying TCS/$s configuration/config: ${subsystemConfig(cfg, s)}")
) *>
val aoDebug = cfg match {
case x: TcsNorthAoConfig =>
s"REL-4159 buildTcsAoConfig: aog=${x.gaos}, aoguide.tracking=${x.gds.aoguide.tracking}, aoguide.detector=${x.gds.aoguide.detector}"
case _ => "REL-4159 buildTcsConfig: non-AO step"
}
Log.debug(aoDebug) *>
subsystems.traverse_(s =>
Log.debug(s"Applying TCS/$s configuration/config: ${subsystemConfig(cfg, s)}")
) *>
tcsController.applyConfig(subsystems, gaos, cfg).as(ConfigResult(this))
}

Expand Down Expand Up @@ -107,14 +113,11 @@ class TcsNorth[F[_]: Sync: Logger] private (
private def buildTcsAoConfig(gc: GuideConfig, ao: Altair[F]): TcsNorthConfig = {
val aog = gc.gaosGuide.flatMap(_.swap.toOption).getOrElse(AltairOff)

val aoGuiderConfig = ao
.hasTarget(aog)
.fold(
calcGuiderConfig(config.guideWithAO),
GuiderConfig(ProbeTrackingConfig.Off,
config.guideWithAO.map(_.toGuideSensorOption).getOrElse(GuiderSensorOff)
)
)
// The detector demand always comes from the step; only the probe tracking can
// be overridden by the Altair mode (e.g. Frozen for the LGS relay modes).
val sequenceAoGuider = calcGuiderConfig(config.guideWithAO)
val aoGuiderConfig =
sequenceAoGuider.copy(tracking = ao.aoProbeTracking(aog, sequenceAoGuider.tracking))

AoTcsConfig[GuiderConfig @@ AoGuide, AltairConfig](
gc.tcsGuide,
Expand Down
Loading
Loading
0