ESW Shell - A Testing Environment for ESW and CSW

This project contains an interactive shell powered by Ammonite that provides access to all the major CSW and ESW services via a command-line interface (CLI). ESW-shell can be used to communicate with Assemblies, HCDs (Hardware Control Daemon), and Sequencers using TMT Common Software (CSW) APIs.

Additionally, there are ESW-provided Sequencer commands and ESW applications using TMT Executive Software (ESW) that can be used and communicated with and controlled using ESW-shell.

1. Install coursier and the TMT Apps Channel

The coursier application must be installed on your machine and the OCS Apps channel must be installed. The instructions for doing this are provided here.

2. Start Any Needed CSW Services or ESW Apps

If you need some CSW Services or Assemblies or HCDs, they should be started depending on your goals according to steps 3 and 4 of here.

3. Starting esw-shell

Once coursier and apps are installed, it can be used to start esw-shell, type:

cs launch esw-shell

If you need to start with a specific version, the following can be used:

cs launch esw-shell

Normally, you don’t need to start the app with a specific version.

4. Exiting esw-shell

At any point in time, if you want to exit the shell, type exit and press enter.

Using ESW Shell

The following sections provide examples of some built-in capabilities of ESW-shell. Note that ESW shell can be extended by the user by defining your own functions and saving them in files that can be loaded dynamically whenever needed.

Imports Available in esw-shell

The contents of the following imports are available in esw-shell when it is started. A user does not need to import them again.

Scala
source|import java.nio.file.Path
|import java.nio.file.Paths
|import akka.util.Timeout
|import akka.Done
|import scala.concurrent.duration.{Duration, DurationDouble}
|import scala.concurrent.{Await, Future}
|import csw.alarm.models.AlarmSeverity
|import csw.alarm.models.Key.AlarmKey
|import csw.params.core.generics.KeyType._
|import csw.params.core.generics._
|import csw.params.events._
|import csw.params.commands._
|import csw.params.commands.CommandResponse._
|import csw.params.core.models._
|import csw.logging.models.Level._
|import csw.prefix.models.Subsystem._
|import csw.prefix.models.Prefix
|import csw.time.core.models._
|import csw.params.core.states._
|import csw.location.api.models._
|import csw.location.api.models.ComponentType._
|import csw.location.api.models.ConnectionType._
|import csw.location.api.models.Connection._
|import csw.logging.models.LogMetadata
|import csw.command.api.{DemandMatcher, DemandMatcherAll, PresenceMatcher}
|import esw.ocs.api.models._
|import esw.ocs.api.protocol._
|import esw.sm.api.models.ProvisionConfig
|import esw.sm.api.protocol._
|import esw.agent.service.api.models._
|import esw.shell.utils._
|import esw.commons.extensions.FutureExt._
|import esw.shell.utils.Timeouts._
|import eswWiring._
|import eswWiring.factories._
|import eswWiring.cswWiring.cswContext._
|import csw.framework.scaladsl.DefaultComponentHandlers

Usage of Command Service to interact with HCDs, Assemblies and Sequencers

The following is built-in functionality for working with Command Service and Assemblies and HCDs.

Spawning a Simulated HCD/Assembly

esw-shell can be used to spawn a simulated HCD/Assembly using these Simulated Component Handlers.

SimulatedComponentHandlers supports two commands:

  • noop : This command immediately returns Completed response with runId
  • sleep : This command immediately returns Started response with runId and Completed response after some sleep time. This sleep time is specified in timeInMs parameter of the command.

The example commands below will spawn a simulated HCD/Assembly without having the need of a running Agent.

spawnSimulatedHCD("ESW.testHCD1") // "ESW.testHCD1" is the HCD prefix
spawnSimulatedAssembly("ESW.testAssembly") // "ESW.testAssembly" is the assembly prefix

Using predefined component handlers on Agent

The Agent Service can also be used to spawn simulated HCD/Assemblies on any machine that is running an Agent.

These example commands will spawn a simulated HCD/Assembly on ESW.machine1 Agent. It is assumed that ESW.machine1 Agent is already running. For running agent refer Agent App

spawnSimulatedHCD("ESW.testHCD", "ESW.machine1") // "ESW.testHCD" is the HCD prefix
spawnSimulatedAssembly("ESW.testAssembly", "ESW.machine1") // "ESW.testAssembly" is the assembly prefix

Using custom component handlers

esw-shell can be used to spawn a real HCD/Assembly which uses custom component handlers passed by the user.

The example below will spawn an Assembly that uses the provided component handlers which will be used by the created Assembly.

val componentHandlers = (ctx, cswCtx) =>
      new DefaultComponentHandlers(ctx, cswCtx) {
        override def onSubmit(runId: Id, controlCommand: ControlCommand): CommandResponse.SubmitResponse = {
          controlCommand.commandName.name match {
            case "sleep" =>
              // do something on receiving move command
              cswCtx.timeServiceScheduler.scheduleOnce(UTCTime(UTCTime.now().value.plusSeconds(5))) {
                cswCtx.commandResponseManager.updateCommand(CommandResponse.Completed(runId))
              }
              CommandResponse.Started(runId)
            case _ => CommandResponse.Completed(runId)
          }
        }
      }
spawnAssemblyWithHandler("ESW.testHCD", componentHandlers) // "ESW.testHCD" is the HCD prefix
spawnHCDWithHandler("ESW.testAssembly", componentHandlers) // "ESW.testAssembly" is the assembly prefix

This is a very nice feature! It can be used to create components that can simulate other components within tests, for instance.

Finding a required component

To get a handle to a Command Service for a particular HCD/Assembly/Sequencer, use the following esw-shell short-cut commands:

To create a CommandService for an HCD:

  • val hcdComponent = hcdCommandService("IRIS.hcd_name")

For Assemblies:

  • val assemblyComponent = assemblyCommandService("IRIS.assembly_name")

This can also be done for a Sequencer in order to send a Sequence to a Sequencer. (See also: Sequencers)

  • val sequencer = sequencerCommandService("IRIS.darknight")

IRIS.hcd_name and IRIS.assembly_name are the Prefix by which the HCD and Assembly respectively, are registered with Location Service.

IRIS and darknight are the Subsystem and the observing mode for the Sequencer.

Note

The above calls internally use Location Service to resolve the required HCD/Assembly/Sequencer.

Creating a Setup to submit to HCD/Assembly using Command Service

To send a Setup or Observe command to a component, create the Parameters and add them to the Setup or Observe.

val longKey  = LongKey.make("timeInMs")
val paramSet = Set(longKey.set(1000))

val setup = Setup(Prefix("iris.filter.wheel"), CommandName("sleep"), Some(ObsId("2020A-001-123"))).madd(paramSet)

Creating a Sequence to submit to a Sequencer using Command Service

val byteKey  = ByteKey.make("byteKey")
val paramSet = Set(byteKey.set(100, 100))

val setup = Setup(Prefix("iris.filter.wheel"), CommandName("move"), Some(ObsId("2020A-001-123"))).madd(paramSet)
val sequence = Sequence(setup)

Note that in the last step, the Setup is wrapped in a Sequence to make a Sequence with one Step.

Other than Command Service handles, the following pre-defined handles or factories are available in esw-shell to interact with different services:

  • For Sequence Manager, use pre-imported sequenceManager() handle
  • For Agent, create new handle using agentClient("iris.machine_1")
  • For SequenceComponent, create new handle using sequenceComponentService("ESW.ESW_1")
  • For AdminApi, use pre-imported adminApi handle
  • For EventService, use pre-imported eventService handle
  • For AlarmService, use pre-imported alarmService handle

Creating a CSW ComponentId

val componentId = ComponentId(Prefix(ESW, "test1"), Assembly)

Creating an Event

Events work much like Setups.

val byteKey  = ByteKey.make("byteKey")
val paramSet = Set(byteKey.set(100, 100 ))
val prefix   = Prefix("tcs.assembly")
val event    = SystemEvent(prefix, EventName("event_1")).madd(paramSet)

Creating an AlarmKey

val alarmKey = AlarmKey(Prefix(NFIRAOS, "trombone"), "tromboneAxisHighLimitAlarm")

Submitting a Setup to a component

Use the Command Service submit call with the Setup created in a previous step to send the Setup to the HCD or Assembly.

The esw-shell is just providing a Scala programming environment. Therefore, submit returns a response wrapped in a Future, you can use get to extract the response out of any Future response once it completes. The Future has a default wait timeout of 10.seconds for the future to complete:

  • val hcdResponse = hcdComponent.submit(setup).get
  • val assemblyResponse = assemblyComponent.submit(setup).get

If you have a long-running command, you can use await method with your own custom timeout:

  • val hcdResponse = hcdComponent.submit(setup).await(20.seconds)

Submit the Sequence created in a previous step using the Command Service for the Sequencer:

  • val sequencerResponse = sequencer.submit(sequence).get

The other Command Service calls are also present on a Command Service handle.

Submitting commands to an ESW service

To Agent:

  • val agent = agentClient("iris.machine_1")
  • val spawnResponse = agent.spawnSequenceComponent("ESW_1", Some("1.0.0")).get

To AdminApi:

  • val logMetadata = adminApi.getLogMetadata(componentId).get

To Event Service:

  • val publishResponse = eventService.publish(event).get

To AlarmService:

  • val response = alarmService.setSeverity(alarmKey, AlarmSeverity.Major).get

Interacting with Sequence Manager

You can also use the Sequence Manager to send commands to the ESW Sequence Manager. Sequence Manager is a service, it is not an Assembly or HCD. It has its own specialized Sequence Manager API.

A handle to a running Sequence Manager can be obtained using:

val sm = sequenceManager()
Note

Unlike Assemblies, and HCDs, there is only one Sequence Manager service at a time.

All Sequence Manager APIs can be called upon the handle. For example:

val configureResponse = sm.configure(ObsMode("darknight")).get
val shutdownSequencerResponse = sm.shutdownSequencer(ESW, ObsMode("darknight")).get
val resources = sm.getResources.get

Interacting with Sequence Component

Sequence Components are used to create Sequencers. They, too, can be accessed with esw-shell.

Get a handle to a running Sequence Component using:

val sc = sequenceComponentService("ESW.ESW_1")

Note that the Sequence Component is registered with a Subsystem.name.

All Sequence Component APIs can be called upon the handle. For example:

val loadScriptResponse =  sc.loadScript(Prefix(ESW,"darknight")).get
val unloadScriptResponse =  sc.unloadScript().get
val restartScriptResponse =  sc.restartScript().get

Provisioning Sequence Components

In order to provision Sequence Components using Sequence Manager, use some custom version of Sequencer scripts, we have provided a special method in esw-shell:

provision(ProvisionConfig((Prefix(ESW, "primary") -> 3)), <sequencer scripts version | SHA>)

This is useful in case new scripts are to be tested in a dev environment. The Sequencer scripts version for these new scripts can be provided in this method. It will take care of updating this version in the Configuration Service before setting up the Sequence Components.