From 3634c1760a4a441e14457b4daa1785f74b0a7f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20L=C3=BChrs?= Date: Thu, 26 Oct 2023 14:45:40 -0300 Subject: [PATCH] Implemented rotator tracking configuration. --- .../navigate/model/NavigateCommand.scala | 74 ++++---- .../navigate/server/NavigateEngine.scala | 22 ++- .../server/tcs/RotatorTrackConfig.scala | 12 ++ .../server/tcs/RotatorTrackingMode.scala | 17 ++ .../navigate/server/tcs/SlewConfig.scala | 3 +- .../server/tcs/TcsBaseController.scala | 5 + .../server/tcs/TcsBaseControllerEpics.scala | 160 ++++++++++-------- .../server/tcs/TcsBaseControllerSim.scala | 2 + .../navigate/server/tcs/TcsEpicsSystem.scala | 63 +------ .../tcs/TcsBaseControllerEpicsSpec.scala | 28 ++- .../server/src/main/resources/NewTCC.graphql | 12 ++ .../web/server/http4s/GrackleParsers.scala | 4 + .../web/server/http4s/NavigateMappings.scala | 53 +++++- .../server/http4s/NavigateMappingsTest.scala | 51 ++++++ 14 files changed, 318 insertions(+), 188 deletions(-) create mode 100644 modules/server/src/main/scala/navigate/server/tcs/RotatorTrackConfig.scala create mode 100644 modules/server/src/main/scala/navigate/server/tcs/RotatorTrackingMode.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/NavigateCommand.scala b/modules/model/shared/src/main/scala/navigate/model/NavigateCommand.scala index e84e1156..743f7a88 100644 --- a/modules/model/shared/src/main/scala/navigate/model/NavigateCommand.scala +++ b/modules/model/shared/src/main/scala/navigate/model/NavigateCommand.scala @@ -57,47 +57,49 @@ object NavigateCommand { case object InstSpecifics extends NavigateCommand case object OiwfsTarget extends NavigateCommand case object OiwfsProbeTracking extends NavigateCommand + case object RotatorTrackingConfig extends NavigateCommand given Eq[NavigateCommand] = Eq.fromUniversalEquals extension (self: NavigateCommand) { def name: String = self match { - case McsFollow(_) => "Mcs Follow" - case ScsFollow(_) => "Scs Follow" - case CrcsFollow(_) => "Crcs Follow" - case Pwfs1Follow(_) => "Pwfs1 Follow" - case Pwfs2Follow(_) => "Pwfs2 Follow" - case OiwfsFollow(_) => "Oiwfs Follow" - case AowfsFollow(_) => "Aowfs Follow" - case Cwfs1Follow(_) => "Cwfs1 Follow" - case Cwfs2Follow(_) => "Cwfs2 Follow" - case Cwfs3Follow(_) => "Cwfs3 Follow" - case Odgw1Follow(_) => "Odgw1 Follow" - case Odgw2Follow(_) => "Odgw2 Follow" - case Odgw3Follow(_) => "Odgw3 Follow" - case Odgw4Follow(_) => "Odgw4 Follow" - case McsPark => "Mcs Park" - case ScsPark => "Scs Park" - case CrcsPark => "Crcs Park" - case Pwfs1Park => "Pwfs1 Park" - case Pwfs2Park => "Pwfs2 Park" - case OiwfsPark => "Oiwfs Park" - case AowfsPark => "Aowfs Park" - case Cwfs1Park => "Cwfs1 Park" - case Cwfs2Park => "Cwfs2 Park" - case Cwfs3Park => "Cwfs3 Park" - case Odgw1Park => "Odgw1 Park" - case Odgw2Park => "Odgw2 Park" - case Odgw3Park => "Odgw3 Park" - case Odgw4Park => "Odgw4 Park" - case CrcsStop(_) => "Crcs Stop" - case CrcsMove(_) => "Crcs Move" - case _: EcsCarouselMode => "Ecs Carousel Mode" - case _: EcsVentGatesMove => "Ecs Vent Gates Move" - case Slew => "Slew" - case InstSpecifics => "Instrument Specifics" - case OiwfsTarget => "OIWFS" - case OiwfsProbeTracking => "OIWFS Probe Tracking" + case McsFollow(_) => "Mcs Follow" + case ScsFollow(_) => "Scs Follow" + case CrcsFollow(_) => "Crcs Follow" + case Pwfs1Follow(_) => "Pwfs1 Follow" + case Pwfs2Follow(_) => "Pwfs2 Follow" + case OiwfsFollow(_) => "Oiwfs Follow" + case AowfsFollow(_) => "Aowfs Follow" + case Cwfs1Follow(_) => "Cwfs1 Follow" + case Cwfs2Follow(_) => "Cwfs2 Follow" + case Cwfs3Follow(_) => "Cwfs3 Follow" + case Odgw1Follow(_) => "Odgw1 Follow" + case Odgw2Follow(_) => "Odgw2 Follow" + case Odgw3Follow(_) => "Odgw3 Follow" + case Odgw4Follow(_) => "Odgw4 Follow" + case McsPark => "Mcs Park" + case ScsPark => "Scs Park" + case CrcsPark => "Crcs Park" + case Pwfs1Park => "Pwfs1 Park" + case Pwfs2Park => "Pwfs2 Park" + case OiwfsPark => "Oiwfs Park" + case AowfsPark => "Aowfs Park" + case Cwfs1Park => "Cwfs1 Park" + case Cwfs2Park => "Cwfs2 Park" + case Cwfs3Park => "Cwfs3 Park" + case Odgw1Park => "Odgw1 Park" + case Odgw2Park => "Odgw2 Park" + case Odgw3Park => "Odgw3 Park" + case Odgw4Park => "Odgw4 Park" + case CrcsStop(_) => "Crcs Stop" + case CrcsMove(_) => "Crcs Move" + case _: EcsCarouselMode => "Ecs Carousel Mode" + case _: EcsVentGatesMove => "Ecs Vent Gates Move" + case Slew => "Slew" + case InstSpecifics => "Instrument Specifics" + case OiwfsTarget => "OIWFS" + case OiwfsProbeTracking => "OIWFS Probe Tracking" + case RotatorTrackingConfig => "CR Tracking Configuration" } } diff --git a/modules/server/src/main/scala/navigate/server/NavigateEngine.scala b/modules/server/src/main/scala/navigate/server/NavigateEngine.scala index 55732f54..139a57e2 100644 --- a/modules/server/src/main/scala/navigate/server/NavigateEngine.scala +++ b/modules/server/src/main/scala/navigate/server/NavigateEngine.scala @@ -8,12 +8,12 @@ import cats.effect.{Async, Concurrent, Ref, Temporal} import cats.effect.kernel.Sync import cats.syntax.all.* import org.typelevel.log4cats.Logger -import navigate.model.NavigateCommand.{CrcsFollow, CrcsMove, CrcsPark, CrcsStop, EcsCarouselMode, InstSpecifics, McsFollow, McsPark, OiwfsFollow, OiwfsPark, OiwfsProbeTracking, OiwfsTarget, Slew} +import navigate.model.NavigateCommand.* import navigate.model.{NavigateCommand, NavigateEvent} import navigate.model.NavigateEvent.{CommandFailure, CommandPaused, CommandStart, CommandSuccess} import navigate.model.config.NavigateEngineConfiguration import navigate.model.enums.{DomeMode, ShutterMode} -import navigate.server.tcs.{InstrumentSpecifics, SlewConfig, Target, TrackingConfig} +import navigate.server.tcs.{InstrumentSpecifics, RotatorTrackConfig, SlewConfig, Target, TrackingConfig} import navigate.stateengine.StateEngine import NavigateEvent.NullEvent import fs2.{Pipe, Stream} @@ -32,6 +32,7 @@ trait NavigateEngine[F[_]] { def rotPark: F[Unit] def rotFollow(enable: Boolean): F[Unit] def rotMove(angle: Angle): F[Unit] + def rotTrackingConfig(cfg: RotatorTrackConfig): F[Unit] def ecsCarouselMode( domeMode: DomeMode, shutterMode: ShutterMode, @@ -181,6 +182,13 @@ object NavigateEngine { systems.tcsSouth.oiwfsFollow(enable), Focus[State](_.oiwfsFollowInProgress) ) + + override def rotTrackingConfig(cfg: navigate.server.tcs.RotatorTrackConfig): F[Unit] = command( + engine, + RotatorTrackingConfig, + systems.tcsSouth.rotTrackingConfig(cfg), + Focus[State](_.rotTrackingConfigInProgress) + ) } def build[F[_]: Concurrent: Logger]( @@ -198,15 +206,15 @@ object NavigateEngine { rotParkInProgress: Boolean, rotFollowInProgress: Boolean, rotMoveInProgress: Boolean, + rotTrackingConfigInProgress: Boolean, ecsDomeModeInProgress: Boolean, ecsVentGateMoveInProgress: Boolean, slewInProgress: Boolean, oiwfsInProgress: Boolean, instrumentSpecificsInProgress: Boolean, - rotIaaInProgress: Boolean, oiwfsProbeTrackingInProgress: Boolean, oiwfsParkInProgress: Boolean, - oiwfsFollowInProgress: Boolean + oiwfsFollowInProgress: Boolean, ) { lazy val tcsActionInProgress: Boolean = mcsParkInProgress || @@ -215,12 +223,12 @@ object NavigateEngine { rotParkInProgress || rotFollowInProgress || rotMoveInProgress || + rotTrackingConfigInProgress || ecsDomeModeInProgress || ecsVentGateMoveInProgress || slewInProgress || oiwfsInProgress || - instrumentSpecificsInProgress || - rotIaaInProgress || + instrumentSpecificsInProgress oiwfsProbeTrackingInProgress || oiwfsParkInProgress || oiwfsFollowInProgress @@ -233,12 +241,12 @@ object NavigateEngine { rotParkInProgress = false, rotFollowInProgress = false, rotMoveInProgress = false, + rotTrackingConfigInProgress = false, ecsDomeModeInProgress = false, ecsVentGateMoveInProgress = false, slewInProgress = false, oiwfsInProgress = false, instrumentSpecificsInProgress = false, - rotIaaInProgress = false, oiwfsProbeTrackingInProgress = false, oiwfsParkInProgress = false, oiwfsFollowInProgress = false diff --git a/modules/server/src/main/scala/navigate/server/tcs/RotatorTrackConfig.scala b/modules/server/src/main/scala/navigate/server/tcs/RotatorTrackConfig.scala new file mode 100644 index 00000000..63beb378 --- /dev/null +++ b/modules/server/src/main/scala/navigate/server/tcs/RotatorTrackConfig.scala @@ -0,0 +1,12 @@ +// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) +// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause + +package navigate.server.tcs + +import cats.Eq +import lucuma.core.math.Angle + +case class RotatorTrackConfig ( + ipa: Angle, + mode: RotatorTrackingMode +) diff --git a/modules/server/src/main/scala/navigate/server/tcs/RotatorTrackingMode.scala b/modules/server/src/main/scala/navigate/server/tcs/RotatorTrackingMode.scala new file mode 100644 index 00000000..1dbcf9bf --- /dev/null +++ b/modules/server/src/main/scala/navigate/server/tcs/RotatorTrackingMode.scala @@ -0,0 +1,17 @@ +// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) +// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause + +package navigate.server.tcs + +import lucuma.core.util.Enumerated + +sealed abstract class RotatorTrackingMode(val tag: String) extends Product with Serializable + +object RotatorTrackingMode { + + case object Tracking extends RotatorTrackingMode("Tracking") + case object Fixed extends RotatorTrackingMode("Fixed") + + given Enumerated[RotatorTrackingMode] = Enumerated.from(Tracking, Fixed).withTag(_.tag) + +} \ No newline at end of file diff --git a/modules/server/src/main/scala/navigate/server/tcs/SlewConfig.scala b/modules/server/src/main/scala/navigate/server/tcs/SlewConfig.scala index 244e1b3d..a6a6257a 100644 --- a/modules/server/src/main/scala/navigate/server/tcs/SlewConfig.scala +++ b/modules/server/src/main/scala/navigate/server/tcs/SlewConfig.scala @@ -7,5 +7,6 @@ case class SlewConfig( slewOptions: SlewOptions, baseTarget: Target, instrumentSpecifics: InstrumentSpecifics, - oiwfs: Option[GuiderConfig] + oiwfs: Option[GuiderConfig], + rotatorTrackConfig: RotatorTrackConfig ) diff --git a/modules/server/src/main/scala/navigate/server/tcs/TcsBaseController.scala b/modules/server/src/main/scala/navigate/server/tcs/TcsBaseController.scala index 26e0d1e7..446cc2ed 100644 --- a/modules/server/src/main/scala/navigate/server/tcs/TcsBaseController.scala +++ b/modules/server/src/main/scala/navigate/server/tcs/TcsBaseController.scala @@ -28,6 +28,7 @@ trait TcsBaseController[F[_]] { def instrumentSpecifics(config: InstrumentSpecifics): F[ApplyCommandResult] def oiwfsTarget(target: Target): F[ApplyCommandResult] def rotIaa(angle: Angle): F[ApplyCommandResult] + def rotTrackingConfig(cfg: RotatorTrackConfig): F[ApplyCommandResult] def oiwfsProbeTracking(config: TrackingConfig): F[ApplyCommandResult] def oiwfsPark: F[ApplyCommandResult] def oiwfsFollow(enable: Boolean): F[ApplyCommandResult] @@ -38,5 +39,9 @@ object TcsBaseController { case class TcsConfig( sourceATarget: Target ) + + val SystemDefault: String = "FK5" + val EquinoxDefault: String = "J2000" + val FixedSystem: String = "Fixed" } diff --git a/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerEpics.scala b/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerEpics.scala index 788c5f9f..501e8b60 100644 --- a/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerEpics.scala +++ b/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerEpics.scala @@ -14,6 +14,7 @@ import navigate.server.tcs.Target.* import navigate.server.tcs.TcsEpicsSystem.{ProbeTrackingCommand, TargetCommand, TcsCommands} import lucuma.core.math.{Angle, Parallax, ProperMotion, RadialVelocity, Wavelength} import monocle.Getter +import TcsBaseController.{EquinoxDefault, FixedSystem, SystemDefault} /* This class implements the common TCS commands */ class TcsBaseControllerEpics[F[_]: Async: Parallel]( @@ -69,12 +70,12 @@ class TcsBaseControllerEpics[F[_]: Async: Parallel]( .verifiedRun(ConnectionTimeout) override def ecsCarouselMode( - domeMode: DomeMode, - shutterMode: ShutterMode, - slitHeight: Double, - domeEnable: Boolean, - shutterEnable: Boolean - ): F[ApplyCommandResult] = + domeMode: DomeMode, + shutterMode: ShutterMode, + slitHeight: Double, + domeEnable: Boolean, + shutterEnable: Boolean + ): F[ApplyCommandResult] = tcsEpics .startCommand(timeout) .ecsCarouselModeCmd @@ -103,61 +104,58 @@ class TcsBaseControllerEpics[F[_]: Async: Parallel]( val DefaultBrightness: Double = 10.0 protected def setTarget( - l: Getter[TcsCommands[F], TargetCommand[F, TcsCommands[F]]], - target: Target - ): TcsCommands[F] => TcsCommands[F] = target match { - case t: AzElTarget => - { (x: TcsCommands[F]) => l.get(x).objectName(t.objectName) } - .compose[TcsCommands[F]](l.get(_).coordSystem("AzEl")) - .compose[TcsCommands[F]](l.get(_).coord1(t.coordinates.azimuth.toAngle.toDoubleDegrees)) - .compose[TcsCommands[F]](l.get(_).coord2(t.coordinates.elevation.toAngle.toDoubleDegrees)) - .compose[TcsCommands[F]](l.get(_).brightness(DefaultBrightness)) - .compose[TcsCommands[F]](l.get(_).epoch(2000.0)) - .compose[TcsCommands[F]](l.get(_).equinox("")) - .compose[TcsCommands[F]](l.get(_).parallax(0.0)) - .compose[TcsCommands[F]](l.get(_).radialVelocity(0.0)) - .compose[TcsCommands[F]](l.get(_).properMotion1(0.0)) - .compose[TcsCommands[F]](l.get(_).properMotion2(0.0)) - .compose[TcsCommands[F]](l.get(_).ephemerisFile("")) - case t: SiderealTarget => - { (x: TcsCommands[F]) => l.get(x).objectName(t.objectName) } - .compose[TcsCommands[F]](l.get(_).coordSystem("FK5")) - .compose[TcsCommands[F]](l.get(_).coord1(t.coordinates.ra.toAngle.toDoubleDegrees / 15.0)) - .compose[TcsCommands[F]](l.get(_).coord2(t.coordinates.dec.toAngle.toSignedDoubleDegrees)) - .compose[TcsCommands[F]](l.get(_).brightness(DefaultBrightness)) - .compose[TcsCommands[F]](l.get(_).epoch(t.epoch.epochYear)) - .compose[TcsCommands[F]](l.get(_).equinox("J2000")) - .compose[TcsCommands[F]]( - l.get(_).parallax(t.parallax.getOrElse(Parallax.Zero).mas.value.toDouble) - ) - .compose[TcsCommands[F]]( - l.get(_) - .radialVelocity( - t.radialVelocity.getOrElse(RadialVelocity.Zero).toDoubleKilometersPerSecond - ) - ) - .compose[TcsCommands[F]]( - l.get(_) - .properMotion1(t.properMotion.getOrElse(ProperMotion.Zero).ra.masy.value.toDouble) - ) - .compose[TcsCommands[F]]( - l.get(_) - .properMotion2(t.properMotion.getOrElse(ProperMotion.Zero).dec.masy.value.toDouble) - ) - .compose[TcsCommands[F]](l.get(_).ephemerisFile("")) - case t: EphemerisTarget => - { (x: TcsCommands[F]) => l.get(x).objectName(t.objectName) } - .compose[TcsCommands[F]](l.get(_).coordSystem("")) - .compose[TcsCommands[F]](l.get(_).coord1(0.0)) - .compose[TcsCommands[F]](l.get(_).coord2(0.0)) - .compose[TcsCommands[F]](l.get(_).brightness(DefaultBrightness)) - .compose[TcsCommands[F]](l.get(_).epoch(2000.0)) - .compose[TcsCommands[F]](l.get(_).equinox("")) - .compose[TcsCommands[F]](l.get(_).parallax(0.0)) - .compose[TcsCommands[F]](l.get(_).radialVelocity(0.0)) - .compose[TcsCommands[F]](l.get(_).properMotion1(0.0)) - .compose[TcsCommands[F]](l.get(_).properMotion2(0.0)) - .compose[TcsCommands[F]](l.get(_).ephemerisFile(t.ephemerisFile)) + l: Getter[TcsCommands[F], TargetCommand[F, TcsCommands[F]]], + target: Target + ): TcsCommands[F] => TcsCommands[F] = target match { + case t: AzElTarget => { (x: TcsCommands[F]) => l.get(x).objectName(t.objectName) } + .compose[TcsCommands[F]](l.get(_).coordSystem("AzEl")) + .compose[TcsCommands[F]](l.get(_).coord1(t.coordinates.azimuth.toAngle.toDoubleDegrees)) + .compose[TcsCommands[F]](l.get(_).coord2(t.coordinates.elevation.toAngle.toDoubleDegrees)) + .compose[TcsCommands[F]](l.get(_).brightness(DefaultBrightness)) + .compose[TcsCommands[F]](l.get(_).epoch(2000.0)) + .compose[TcsCommands[F]](l.get(_).equinox("")) + .compose[TcsCommands[F]](l.get(_).parallax(0.0)) + .compose[TcsCommands[F]](l.get(_).radialVelocity(0.0)) + .compose[TcsCommands[F]](l.get(_).properMotion1(0.0)) + .compose[TcsCommands[F]](l.get(_).properMotion2(0.0)) + .compose[TcsCommands[F]](l.get(_).ephemerisFile("")) + case t: SiderealTarget => { (x: TcsCommands[F]) => l.get(x).objectName(t.objectName) } + .compose[TcsCommands[F]](l.get(_).coordSystem(SystemDefault)) + .compose[TcsCommands[F]](l.get(_).coord1(t.coordinates.ra.toAngle.toDoubleDegrees / 15.0)) + .compose[TcsCommands[F]](l.get(_).coord2(t.coordinates.dec.toAngle.toSignedDoubleDegrees)) + .compose[TcsCommands[F]](l.get(_).brightness(DefaultBrightness)) + .compose[TcsCommands[F]](l.get(_).epoch(t.epoch.epochYear)) + .compose[TcsCommands[F]](l.get(_).equinox(EquinoxDefault)) + .compose[TcsCommands[F]]( + l.get(_).parallax(t.parallax.getOrElse(Parallax.Zero).mas.value.toDouble) + ) + .compose[TcsCommands[F]]( + l.get(_) + .radialVelocity( + t.radialVelocity.getOrElse(RadialVelocity.Zero).toDoubleKilometersPerSecond + ) + ) + .compose[TcsCommands[F]]( + l.get(_) + .properMotion1(t.properMotion.getOrElse(ProperMotion.Zero).ra.masy.value.toDouble) + ) + .compose[TcsCommands[F]]( + l.get(_) + .properMotion2(t.properMotion.getOrElse(ProperMotion.Zero).dec.masy.value.toDouble) + ) + .compose[TcsCommands[F]](l.get(_).ephemerisFile("")) + case t: EphemerisTarget => { (x: TcsCommands[F]) => l.get(x).objectName(t.objectName) } + .compose[TcsCommands[F]](l.get(_).coordSystem("")) + .compose[TcsCommands[F]](l.get(_).coord1(0.0)) + .compose[TcsCommands[F]](l.get(_).coord2(0.0)) + .compose[TcsCommands[F]](l.get(_).brightness(DefaultBrightness)) + .compose[TcsCommands[F]](l.get(_).epoch(2000.0)) + .compose[TcsCommands[F]](l.get(_).equinox("")) + .compose[TcsCommands[F]](l.get(_).parallax(0.0)) + .compose[TcsCommands[F]](l.get(_).radialVelocity(0.0)) + .compose[TcsCommands[F]](l.get(_).properMotion1(0.0)) + .compose[TcsCommands[F]](l.get(_).properMotion2(0.0)) + .compose[TcsCommands[F]](l.get(_).ephemerisFile(t.ephemerisFile)) } protected def setSourceAWalength(w: Wavelength): TcsCommands[F] => TcsCommands[F] = @@ -210,15 +208,15 @@ class TcsBaseControllerEpics[F[_]: Async: Parallel]( override def applyTcsConfig(config: TcsBaseController.TcsConfig): F[ApplyCommandResult] = setTarget(Getter[TcsCommands[F], TargetCommand[F, TcsCommands[F]]](_.sourceACmd), - config.sourceATarget + config.sourceATarget ).compose(setSourceAWalength(config.sourceATarget.wavelength))( - tcsEpics.startCommand(timeout) - ).post + tcsEpics.startCommand(timeout) + ).post .verifiedRun(ConnectionTimeout) override def slew(config: SlewConfig): F[ApplyCommandResult] = setTarget(Getter[TcsCommands[F], TargetCommand[F, TcsCommands[F]]](_.sourceACmd), - config.baseTarget + config.baseTarget ) .compose(setSourceAWalength(config.baseTarget.wavelength)) .compose(setSlewOptions(config.slewOptions)) @@ -229,18 +227,18 @@ class TcsBaseControllerEpics[F[_]: Async: Parallel]( config.oiwfs .map(o => setTarget(Getter[TcsCommands[F], TargetCommand[F, TcsCommands[F]]](_.oiwfsTargetCmd), - o.target + o.target ) .compose( setProbeTracking(Getter[TcsCommands[F], ProbeTrackingCommand[F, TcsCommands[F]]]( - _.oiwfsProbeTrackingCommand - ), - o.tracking + _.oiwfsProbeTrackingCommand + ), + o.tracking ) ) ) .getOrElse(identity[TcsCommands[F]]) - )( + ).compose(setRotatorTrackingConfig(config.rotatorTrackConfig))( tcsEpics.startCommand(timeout) ) .post @@ -268,9 +266,9 @@ class TcsBaseControllerEpics[F[_]: Async: Parallel]( .verifiedRun(ConnectionTimeout) def setProbeTracking( - l: Getter[TcsCommands[F], ProbeTrackingCommand[F, TcsCommands[F]]], - config: TrackingConfig - ): TcsCommands[F] => TcsCommands[F] = { (x: TcsCommands[F]) => + l: Getter[TcsCommands[F], ProbeTrackingCommand[F, TcsCommands[F]]], + config: TrackingConfig + ): TcsCommands[F] => TcsCommands[F] = { (x: TcsCommands[F]) => l.get(x).nodAchopA(config.nodAchopA) } .compose[TcsCommands[F]](l.get(_).nodAchopB(config.nodAchopB)) @@ -297,4 +295,20 @@ class TcsBaseControllerEpics[F[_]: Async: Parallel]( .oiwfsProbeCommands.follow.setFollow(enable) .post .verifiedRun(ConnectionTimeout) + + def setRotatorTrackingConfig(cfg: RotatorTrackConfig): TcsCommands[F] => TcsCommands[F] = + (x: TcsCommands[F]) => cfg.mode match { + case RotatorTrackingMode.Fixed => x.rotMoveCommand.setAngle(cfg.ipa) + .rotatorCommand.ipa(cfg.ipa) + .rotatorCommand.system(FixedSystem) + case RotatorTrackingMode.Tracking => x.rotatorCommand.ipa(cfg.ipa) + .rotatorCommand.system(SystemDefault) + .rotatorCommand.equinox(EquinoxDefault) + } + + override def rotTrackingConfig(cfg: RotatorTrackConfig): F[ApplyCommandResult] = + setRotatorTrackingConfig(cfg)(tcsEpics.startCommand(timeout)) + .post + .verifiedRun(ConnectionTimeout) + } diff --git a/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerSim.scala b/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerSim.scala index c7871fb7..3204345c 100644 --- a/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerSim.scala +++ b/modules/server/src/main/scala/navigate/server/tcs/TcsBaseControllerSim.scala @@ -59,4 +59,6 @@ class TcsBaseControllerSim[F[_]: Applicative] extends TcsBaseController[F] { override def oiwfsPark: F[ApplyCommandResult] = Applicative[F].pure(ApplyCommandResult.Completed) override def oiwfsFollow(enable: Boolean): F[ApplyCommandResult] = Applicative[F].pure(ApplyCommandResult.Completed) + + override def rotTrackingConfig(cfg: RotatorTrackConfig): F[ApplyCommandResult] = Applicative[F].pure(ApplyCommandResult.Completed) } diff --git a/modules/server/src/main/scala/navigate/server/tcs/TcsEpicsSystem.scala b/modules/server/src/main/scala/navigate/server/tcs/TcsEpicsSystem.scala index 004a1fc3..07dac0e9 100644 --- a/modules/server/src/main/scala/navigate/server/tcs/TcsEpicsSystem.scala +++ b/modules/server/src/main/scala/navigate/server/tcs/TcsEpicsSystem.scala @@ -48,7 +48,7 @@ object TcsEpicsSystem { val oiwfsTargetCmd: TargetCommandChannels[F] val wavelSourceA: Command1Channels[F, Double] val slewCmd: SlewCommandChannels[F] - val rotatorCmd: Command4Channels[F, Double, String, String, Double] + val rotatorConfigCmd: Command4Channels[F, Double, String, String, Double] val originCmd: Command6Channels[F, Double, Double, Double, Double, Double, Double] val focusOffsetCmd: Command1Channels[F, Double] val oiwfsProbeTrackingCmd: ProbeTrackingCommandChannels[F] @@ -65,14 +65,11 @@ object TcsEpicsSystem { // val m2Beam: M2Beam[F] // val pwfs1ProbeGuideCmd: ProbeGuideCmd[F] // val pwfs2ProbeGuideCmd: ProbeGuideCmd[F] - // val oiwfsProbeGuideCmd: ProbeGuideCmd[F] // val pwfs1ProbeFollowCmd: ProbeFollowCmd[F] // val pwfs2ProbeFollowCmd: ProbeFollowCmd[F] - // val oiwfsProbeFollowCmd: ProbeFollowCmd[F] // val aoProbeFollowCmd: ProbeFollowCmd[F] // val pwfs1Park: EpicsCommand[F] // val pwfs2Park: EpicsCommand[F] - // val oiwfsPark: EpicsCommand[F] // val pwfs1StopObserveCmd: EpicsCommand[F] // val pwfs2StopObserveCmd: EpicsCommand[F] // val oiwfsStopObserveCmd: EpicsCommand[F] @@ -699,7 +696,7 @@ object TcsEpicsSystem { tcsEpics.ventGatesMoveCmd.setParam2(pos) ) } - override val sourceACmd: TargetCommand[F, TcsCommands[F]] = + override val sourceACmd: TargetCommand[F, TcsCommands[F]] = new TargetCommand[F, TcsCommands[F]] { override def objectName(v: String): TcsCommands[F] = addParam( tcsEpics.sourceACmd.objectName(v) @@ -871,22 +868,22 @@ object TcsEpicsSystem { tcsEpics.slewCmd.autoparkAowfs(enable.fold(BinaryOnOff.On, BinaryOnOff.Off)) ) } - override val rotatorCommand: RotatorCommand[F, TcsCommands[F]] = + override val rotatorCommand: RotatorCommand[F, TcsCommands[F]] = new RotatorCommand[F, TcsCommands[F]] { override def ipa(v: Angle): TcsCommands[F] = addParam( - tcsEpics.rotatorCmd.setParam1(v.toDoubleDegrees) + tcsEpics.rotatorConfigCmd.setParam1(v.toDoubleDegrees) ) override def system(v: String): TcsCommands[F] = addParam( - tcsEpics.rotatorCmd.setParam2(v) + tcsEpics.rotatorConfigCmd.setParam2(v) ) override def equinox(v: String): TcsCommands[F] = addParam( - tcsEpics.rotatorCmd.setParam3(v) + tcsEpics.rotatorConfigCmd.setParam3(v) ) override def iaa(v: Angle): TcsCommands[F] = addParam( - tcsEpics.rotatorCmd.setParam4(v.toDoubleDegrees) + tcsEpics.rotatorConfigCmd.setParam4(v.toDoubleDegrees) ) } @@ -1018,7 +1015,7 @@ object TcsEpicsSystem { override val slewCmd: SlewCommandChannels[F] = SlewCommandChannels(channels.telltale, channels.slew) - override val rotatorCmd: Command4Channels[F, Double, String, String, Double] = Command4Channels( + override val rotatorConfigCmd: Command4Channels[F, Double, String, String, Double] = Command4Channels( channels.telltale, channels.rotator.ipa, channels.rotator.system, @@ -1439,50 +1436,6 @@ object TcsEpicsSystem { override def setFollowState(v: String): F[Unit] = setParameter(follow, v) } - trait TargetWavelengthCmd[F[_]] extends EpicsCommand[F] { - def setWavel(v: Double): F[Unit] - } - - final class TargetWavelengthCmdImpl[F[_]: Async](csName: String, epicsService: CaService) - extends EpicsCommandBase[F](sysName) - with TargetWavelengthCmd[F] { - override val cs: Option[CaCommandSender] = Option(epicsService.getCommandSender(csName)) - - private val wavel = cs.map(_.getDouble("wavel")) - - override def setWavel(v: Double): F[Unit] = setParameter[F, java.lang.Double](wavel, v) - } - - trait Target[F[_]] { - def objectName: F[String] - def ra: F[Double] - def dec: F[Double] - def frame: F[String] - def equinox: F[String] - def epoch: F[String] - def properMotionRA: F[Double] - def properMotionDec: F[Double] - def centralWavelenght: F[Double] - def parallax: F[Double] - def radialVelocity: F[Double] - } - - extension (val tio: Target[IO]) { - def to[F[_]: LiftIO]: Target[F] = new Target[F] { - def objectName: F[String] = tio.objectName.to[F] - def ra: F[Double] = tio.ra.to[F] - def dec: F[Double] = tio.dec.to[F] - def frame: F[String] = tio.frame.to[F] - def equinox: F[String] = tio.equinox.to[F] - def epoch: F[String] = tio.epoch.to[F] - def properMotionRA: F[Double] = tio.properMotionRA.to[F] - def properMotionDec: F[Double] = tio.properMotionDec.to[F] - def centralWavelenght: F[Double] = tio.centralWavelenght.to[F] - def parallax: F[Double] = tio.parallax.to[F] - def radialVelocity: F[Double] = tio.radialVelocity.to[F] - } - } - sealed trait VirtualGemsTelescope extends Product with Serializable object VirtualGemsTelescope { diff --git a/modules/server/src/test/scala/navigate/server/tcs/TcsBaseControllerEpicsSpec.scala b/modules/server/src/test/scala/navigate/server/tcs/TcsBaseControllerEpicsSpec.scala index 49883e37..14d2d48e 100644 --- a/modules/server/src/test/scala/navigate/server/tcs/TcsBaseControllerEpicsSpec.scala +++ b/modules/server/src/test/scala/navigate/server/tcs/TcsBaseControllerEpicsSpec.scala @@ -11,8 +11,8 @@ import navigate.model.enums.{DomeMode, ShutterMode} import navigate.model.Distance import navigate.server.acm.CadDirective import navigate.server.epicsdata.{BinaryOnOff, BinaryYesNo} -import navigate.server.tcs.Target.SiderealTarget -import navigate.server.tcs.TcsBaseController.TcsConfig +import Target.SiderealTarget +import TcsBaseController.* import lucuma.core.math.{Angle, Coordinates, Epoch, Wavelength} import lucuma.core.util.Enumerated import munit.CatsEffectSuite @@ -169,10 +169,12 @@ class TcsBaseControllerEpicsSpec extends CatsEffectSuite { x <- createController (st, ctr) = x _ <- ctr.slew( - SlewConfig(slewOptions, - target, - instrumentSpecifics, - GuiderConfig(oiwfsTarget, oiwfsTracking).some + SlewConfig( + slewOptions, + target, + instrumentSpecifics, + GuiderConfig(oiwfsTarget, oiwfsTracking).some, + RotatorTrackConfig(Angle.Angle90, RotatorTrackConfig.Tracking) ) ) rs <- st.get @@ -206,7 +208,7 @@ class TcsBaseControllerEpicsSpec extends CatsEffectSuite { assert(rs.sourceA.epoch.value.exists(x => compareDouble(x.toDouble, target.epoch.epochYear))) assert(rs.sourceA.parallax.value.exists(x => compareDouble(x.toDouble, 0.0))) assert(rs.sourceA.radialVelocity.value.exists(x => compareDouble(x.toDouble, 0.0))) - assertEquals(rs.sourceA.coordSystem.value, "FK5".some) + assertEquals(rs.sourceA.coordSystem.value, SystemDefault.some) assertEquals(rs.sourceA.ephemerisFile.value, "".some) // OIWFS Target @@ -240,7 +242,7 @@ class TcsBaseControllerEpicsSpec extends CatsEffectSuite { ) assert(rs.oiwfs.parallax.value.exists(x => compareDouble(x.toDouble, 0.0))) assert(rs.oiwfs.radialVelocity.value.exists(x => compareDouble(x.toDouble, 0.0))) - assertEquals(rs.oiwfs.coordSystem.value, "FK5".some) + assertEquals(rs.oiwfs.coordSystem.value, SystemDefault.some) assertEquals(rs.oiwfs.ephemerisFile.value, "".some) // OIWFS probe tracking @@ -369,6 +371,14 @@ class TcsBaseControllerEpicsSpec extends CatsEffectSuite { compareDouble(x.toDouble, instrumentSpecifics.origin.y.toMillimeters.value.toDouble) ) ) + + //Rotator configuration + assert(rs.rotator.ipa.connected) + assert(rs.rotator.system.connected) + assert(rs.rotator.equinox.connected) + assert(rs.rotator.ipa.value.exists(x => compareDouble(x.toDouble, Angle.Angle90.toDoubleDegrees))) + assert(rs.rotator.system.value.exists(_ === SystemDefault)) + assert(rs.rotator.equinox.value.exists(_ === EquinoxDefault)) } } @@ -476,7 +486,7 @@ class TcsBaseControllerEpicsSpec extends CatsEffectSuite { ) assert(rs.oiwfs.parallax.value.exists(x => compareDouble(x.toDouble, 0.0))) assert(rs.oiwfs.radialVelocity.value.exists(x => compareDouble(x.toDouble, 0.0))) - assertEquals(rs.oiwfs.coordSystem.value, "FK5".some) + assertEquals(rs.oiwfs.coordSystem.value, SystemDefault.some) assertEquals(rs.oiwfs.ephemerisFile.value, "".some) } } diff --git a/modules/web/server/src/main/resources/NewTCC.graphql b/modules/web/server/src/main/resources/NewTCC.graphql index d16e67a7..dc855d54 100644 --- a/modules/web/server/src/main/resources/NewTCC.graphql +++ b/modules/web/server/src/main/resources/NewTCC.graphql @@ -87,6 +87,17 @@ input SlewInput { baseTarget: TargetPropertiesInput! instParams: InstrumentSpecificsInput! oiwfs: GuiderConfig + rotator: RotatorTrackingInput! +} + +enum RotatorTrackingMode { + TRACKING + FIXED +} + +input RotatorTrackingInput { + ipa: AngleInput! + mode: RotatorTrackingMode! } input ProbeTrackingInput { @@ -129,6 +140,7 @@ type Mutation { mountFollow(enable: Boolean!): OperationOutcome! rotatorPark: OperationOutcome! rotatorFollow(enable: Boolean!): OperationOutcome! + rotatorConfig(config: RotatorTrackingInput!): OperationOutcome! slew(slewParams: SlewInput!): OperationOutcome! instrumentSpecifics(instrumentSpecificsParams: InstrumentSpecificsInput!): OperationOutcome! oiwfsTarget(target: TargetPropertiesInput!): OperationOutcome! diff --git a/modules/web/server/src/main/scala/navigate/web/server/http4s/GrackleParsers.scala b/modules/web/server/src/main/scala/navigate/web/server/http4s/GrackleParsers.scala index dcdd033f..e2a0b4cc 100644 --- a/modules/web/server/src/main/scala/navigate/web/server/http4s/GrackleParsers.scala +++ b/modules/web/server/src/main/scala/navigate/web/server/http4s/GrackleParsers.scala @@ -29,6 +29,7 @@ import lucuma.core.math.skycalc.solver.HourAngleSolver import lucuma.core.math.units.CentimetersPerSecond import lucuma.core.math.units.MetersPerSecond import lucuma.core.model.NonNegDuration +import lucuma.core.util.Enumerated import navigate.model.Distance import java.time.Duration @@ -172,4 +173,7 @@ trait GrackleParsers { case _ => None } + def parseEnumerated[T: Enumerated](tag: String): Option[T] = + Enumerated.fromTag[T].getOption(tag.toLowerCase.capitalize) + } diff --git a/modules/web/server/src/main/scala/navigate/web/server/http4s/NavigateMappings.scala b/modules/web/server/src/main/scala/navigate/web/server/http4s/NavigateMappings.scala index a56a11ec..355bc4a9 100644 --- a/modules/web/server/src/main/scala/navigate/web/server/http4s/NavigateMappings.scala +++ b/modules/web/server/src/main/scala/navigate/web/server/http4s/NavigateMappings.scala @@ -29,6 +29,7 @@ import grackle.Schema import grackle.TypeRef import grackle.Value import grackle.Value.BooleanValue +import grackle.Value.EnumValue import grackle.Value.FloatValue import grackle.Value.IntValue import grackle.Value.ObjectValue @@ -47,6 +48,7 @@ import lucuma.core.math.ProperMotion import lucuma.core.math.RadialVelocity import lucuma.core.math.RightAscension import lucuma.core.math.Wavelength +import lucuma.core.util.Enumerated import navigate.model.Distance import navigate.server.NavigateEngine import navigate.server.tcs.AutoparkAowfs @@ -60,6 +62,8 @@ import navigate.server.tcs.InstrumentSpecifics import navigate.server.tcs.Origin import navigate.server.tcs.ParkStatus import navigate.server.tcs.ResetPointing +import navigate.server.tcs.RotatorTrackConfig +import navigate.server.tcs.RotatorTrackingMode import navigate.server.tcs.ShortcircuitMountFilter import navigate.server.tcs.ShortcircuitTargetFilter import navigate.server.tcs.SlewConfig @@ -134,6 +138,23 @@ class NavigateMappings[F[_]: Sync](server: NavigateEngine[F])(override val schem Result.failure("rotatorFollow parameter could not be parsed.").pure[F] ) + def rotatorConfig(p: Path, env: Env): F[Result[OperationOutcome]] = + env + .get[RotatorTrackConfig]("config") + .map { cfg => + server + .rotTrackingConfig(cfg) + .attempt + .map(x => + Result.success( + x.fold(e => OperationOutcome.failure(e.getMessage), _ => OperationOutcome.success) + ) + ) + } + .getOrElse( + Result.failure("rotatorConfig parameter could not be parsed.").pure[F] + ) + def instrumentSpecifics(p: Path, env: Env): F[Result[OperationOutcome]] = env .get[InstrumentSpecifics]("instrumentSpecificsParams")(classTag[InstrumentSpecifics]) @@ -229,17 +250,25 @@ class NavigateMappings[F[_]: Sync](server: NavigateEngine[F])(override val schem Result.failure[OperationOutcome]("oiwfsFollow parameter could not be parsed.").pure[F] ) - val MutationType: TypeRef = schema.ref("Mutation") - val ParkStatusType: TypeRef = schema.ref("ParkStatus") - val FollowStatusType: TypeRef = schema.ref("FollowStatus") - val OperationOutcomeType: TypeRef = schema.ref("OperationOutcome") - val OperationResultType: TypeRef = schema.ref("OperationResult") + val MutationType: TypeRef = schema.ref("Mutation") + val ParkStatusType: TypeRef = schema.ref("ParkStatus") + val FollowStatusType: TypeRef = schema.ref("FollowStatus") + val OperationOutcomeType: TypeRef = schema.ref("OperationOutcome") + val OperationResultType: TypeRef = schema.ref("OperationResult") + val RotatorTrackingModeType: TypeRef = schema.ref("RotatorTrackingMode") override val selectElaborator: SelectElaborator = SelectElaborator { case (MutationType, "mountFollow", List(Binding("enable", BooleanValue(en)))) => Elab.env("enable" -> en) case (MutationType, "rotatorFollow", List(Binding("enable", BooleanValue(en)))) => Elab.env("enable" -> en) + case (MutationType, "rotatorConfig", List(Binding("config", ObjectValue(fields)))) => + for { + x <- Elab.liftR( + parseRotatorConfig(fields).toResult("Could not parse rotatorConfig parameters.") + ) + _ <- Elab.env("config", x) + } yield () case (MutationType, "slew", List(Binding("slewParams", ObjectValue(fields)))) => for { x <- Elab.liftR(parseSlewConfigInput(fields).toResult("Could not parse Slew parameters.")) @@ -282,6 +311,7 @@ class NavigateMappings[F[_]: Sync](server: NavigateEngine[F])(override val schem RootEffect.computeEncodable("mountFollow")((p, env) => mountFollow(p, env)), RootEffect.computeEncodable("rotatorPark")((p, env) => rotatorPark(p, env)), RootEffect.computeEncodable("rotatorFollow")((p, env) => rotatorFollow(p, env)), + RootEffect.computeEncodable("rotatorConfig")((p, env) => rotatorConfig(p, env)), RootEffect.computeEncodable("slew")((p, env) => slew(p, env)), RootEffect.computeEncodable("instrumentSpecifics")((p, env) => instrumentSpecifics(p, env)), RootEffect.computeEncodable("oiwfsTarget")((p, env) => oiwfsTarget(p, env)), @@ -293,7 +323,8 @@ class NavigateMappings[F[_]: Sync](server: NavigateEngine[F])(override val schem LeafMapping[ParkStatus](ParkStatusType), LeafMapping[FollowStatus](FollowStatusType), LeafMapping[OperationOutcome](OperationOutcomeType), - LeafMapping[OperationResult](OperationResultType) + LeafMapping[OperationResult](OperationResultType), + LeafMapping[RotatorTrackingMode](RotatorTrackingModeType) ) } @@ -442,6 +473,13 @@ object NavigateMappings extends GrackleParsers { }.flatten } yield GuiderConfig(target, tracking) + def parseRotatorConfig(l: List[(String, Value)]): Option[RotatorTrackConfig] = for { + ipa <- l.collectFirst { case ("ipa", ObjectValue(v)) => parseAngle(v) }.flatten + mode <- l.collectFirst { case ("mode", EnumValue(v)) => + parseEnumerated[RotatorTrackingMode](v) + }.flatten + } yield RotatorTrackConfig(ipa, mode) + def parseSlewConfigInput(l: List[(String, Value)]): Option[SlewConfig] = for { sol <- l.collectFirst { case ("slewOptions", ObjectValue(v)) => v } so <- parseSlewOptionsInput(sol) @@ -451,6 +489,7 @@ object NavigateMappings extends GrackleParsers { in <- parseInstrumentSpecificsInput(inl) oi <- l.collectFirst { case ("oiwfs", ObjectValue(v)) => parseGuiderConfig(v) }.orElse(none.some) - } yield SlewConfig(so, t, in, oi) + rc <- l.collectFirst { case ("rotator", ObjectValue(v)) => parseRotatorConfig(v) }.flatten + } yield SlewConfig(so, t, in, oi, rc) } diff --git a/modules/web/server/src/test/scala/navigate/web/server/http4s/NavigateMappingsTest.scala b/modules/web/server/src/test/scala/navigate/web/server/http4s/NavigateMappingsTest.scala index 66c145d7..b05f415f 100644 --- a/modules/web/server/src/test/scala/navigate/web/server/http4s/NavigateMappingsTest.scala +++ b/modules/web/server/src/test/scala/navigate/web/server/http4s/NavigateMappingsTest.scala @@ -18,6 +18,7 @@ import navigate.server.NavigateEngine import navigate.server.OdbProxy import navigate.server.Systems import navigate.server.tcs.InstrumentSpecifics +import navigate.server.tcs.RotatorTrackConfig import navigate.server.tcs.SlewConfig import navigate.server.tcs.Target import navigate.server.tcs.TcsNorthControllerSim @@ -144,6 +145,12 @@ class NavigateMappingsTest extends CatsEffectSuite { | nodBchopB: true | } | } + | rotator: { + | ipa: { + | microarcseconds: 89.76 + | } + | mode: TRACKING + | } |}) { | result |} } @@ -262,6 +269,48 @@ class NavigateMappingsTest extends CatsEffectSuite { ) } + test("Process rotator follow command") { + for { + eng <- buildServer + mp <- NavigateMappings[IO](eng) + r <- mp.compileAndRun("mutation { rotatorFollow(enable: true) { result } }") + } yield assert( + extractResult[OperationOutcome](r, "rotatorFollow").exists(_ === OperationOutcome.success) + ) + } + + test("Process rotator park command") { + for { + eng <- buildServer + mp <- NavigateMappings[IO](eng) + r <- mp.compileAndRun("mutation { rotatorPark { result } }") + } yield assert( + extractResult[OperationOutcome](r, "rotatorPark").exists(_ === OperationOutcome.success) + ) + } + + test("Process rotator tracking configuration command") { + for { + eng <- buildServer + mp <- NavigateMappings[IO](eng) + r <- mp.compileAndRun( + """ + |mutation { rotatorConfig( config: { + | ipa: { + | microarcseconds: 89.76 + | } + | mode: TRACKING + | } + |) { + | result + |} } + |""".stripMargin + ) + } yield assert( + extractResult[OperationOutcome](r, "rotatorConfig").exists(_ === OperationOutcome.success) + ) + } + } object NavigateMappingsTest { @@ -308,6 +357,8 @@ object NavigateMappingsTest { override def oiwfsPark: IO[Unit] = IO.unit override def oiwfsFollow(enable: Boolean): IO[Unit] = IO.unit + + override def rotTrackingConfig(cfg: RotatorTrackConfig): IO[Unit] = IO.unit }.pure[IO] given Decoder[OperationOutcome] = Decoder.instance(h =>