ESW Shell
This project contains an interactive REPL shell powered by Ammonite and allows its users to gain access to all the major CSW and ESW services via CLI which then can be used to communicate with a HCD (Hardware Control Daemon) and an Assembly using TMT Common Software (CSW) APIs and with a Sequencer using TMT Executive Software (ESW).
Build Instructions
The build is based on sbt and depends on libraries generated from the csw and esw project.
Prerequisites for running Components
The CSW services need to be running before starting the components. This is done by starting the csw-services.sh
script, which is installed as part of the csw build. If you are not building csw from the sources, you can run csw-services
as follows:
- Install
coursier
using steps described here and add TMT channel. - Run
cs install csw-services:<CSW version | SHA>
. This will create an executable file namedcsw-services
in the default installation directory. - Run
csw-services --help
to get more information. - Run
csw-services start -c
to start the location service and config server.
Running the esw-shell using sbt
After making sure that all the pre-requisites are satisfied, we can directly run the esw-shell via sbt from the root directory of the project
- Run
sbt esw-shell/run
Running esw-shell using Coursier
- Add TMT apps channel to your local Coursier installation using below command
cs channel --add https://raw.githubusercontent.com/tmtsoftware/osw-apps/master/apps.json
- After adding TMT apps channel you can simply launch esw-shell by executing
cs launch esw-shell:<version | SHA>
Exiting esw-shell
At any point in time, if you want to exit the shell, type exit
and press enter.
Imports available in esw-shell
These imports are available in shell, user does not need to import 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
Spawning simulated HCD/Assembly
esw-shell
can be used to spawn simulated HCD/Assembly which uses the handlers specified in Simulated Component Handlers
SimulatedComponentHandlers
supports two commands.
noop
: This command immediately returnsCompleted
response withrunId
sleep
: This command immediately returnsStarted
response withrunId
andCompleted
response after some sleep time. This sleep time is specified intimeInMs
parameter of command itself.
Using predefined component handlers
Below example commands will spawn a simulated HCD/Assembly without having the need of 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
Below example commands will spawn a simulated HCD/Assembly on ESW.machine1
agent. It is assumed that ESW.machine1
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 real HCD/Assembly which uses the custom component handlers passed by the user.
Below example commands will spawn a real Assembly by the provided component handlers which will be used by component actor.
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
Finding the required component
Get handle to the command service for a particular HCD/Assembly/Sequencer using following commands within esw-shell repl
For HCDs
val hcdComponent = hcdCommandService("iris.hcd_name")
For Assemblies
val assemblyComponent = assemblyCommandService("iris.assembly_name")
For Sequencers
val sequencer = sequencerCommandService(IRIS, "darknight")
iris.hcd_name and iris.assembly_name are the prefix by which both HCD and Assembly components were registered with location service respectively.
IRIS and darknight are the subsystem and the observing mode for the sequencer respectively.
Note - The above calls internally uses location service to resolve the required HCD/Assembly/Sequencer.
Creating the commands to submit to HCD/Assembly
Create a setup command object using similar command to what is shown below
val longKey = LongKey.make("timeInMs")
val paramSet: Set[Parameter[_]] = Set(longKey.set(1000))
val setup = Setup(Prefix("iris.filter.wheel"), CommandName("sleep"), Some(ObsId("2020A-001-123")), paramSet)
Creating the sequence to submit to Sequencer
val byteKey = ByteKey.make("byteKey")
val paramSet: Set[Parameter[_]] = Set(byteKey.set(100, 100))
val setup = Setup(Prefix("iris.filter.wheel"), CommandName("move"), Some(ObsId("2020A-001-123")), paramSet)
val sequence = Sequence(setup)
Other than command service handles, following pre-defined handles or factories are available in 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 ComponentId
val componentId = ComponentId(Prefix(ESW, "test1"), Assembly)
Creating Event
val byteKey = ByteKey.make("byteKey")
val paramSet: Set[Parameter[_]] = Set(byteKey.set(100, 100 ))
val prefix = Prefix("tcs.assembly")
val event = SystemEvent(prefix, EventName("event_1"), paramSet)
Creating AlarmKey
val alarmKey = AlarmKey(Prefix(NFIRAOS, "trombone"), "tromboneAxisHighLimitAlarm")
Submitting the commands to components
Submit the setup command object created in a previous step using command service for the HCD/Assembly
Submit returns a response wrapped in Future, you can use get
to extract response out of any Future response, it has default wait timeout of 10.seconds
for 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 custom timeout
val hcdResponse = hcdComponent.submit(setup).await(20.seconds)
Submit the sequence object created in a previous step using command service for the Sequencer
val sequencerResponse = sequencer.submit(sequence).get
Submitting the commands to 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 EventService
val publishResponse = eventService.publish(event).get
To AlarmService
val response = alarmService.setSeverity(alarmKey, AlarmSeverity.Major).get
Interacting with Sequence Manager
Handler to running Sequence Manager can be obtained using:
val sm = sequenceManager()
All Sequence Manager APIs can be called upon the handle. For example:
val configureResponse = sequenceManager.configure(ObsMode("darknight")).get
val shutdownSequencerResponse = sequenceManager.shutdownSequencer(ESW, ObsMode("darknight")).get
val resources = sm.getResources.get
Interacting with Sequence Component
Handler to running Sequence Component can be obtained using:
val sc = sequenceComponentService("ESW.ESW_1")
All Sequence Component APIs can be called upon the handle. For example:
val loadScriptResponse = sc.loadScript(ESW,ObsMode("darknight")).get
val unloadScriptResponse = sc.unloadScript().get
val restartScriptResponse = sc.restartScript().get
Provisioning sequence components
In order to provision sequence components that use some custom version of sequencer scripts, we have provided 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 out in dev environment. Sequencer scripts version for these new scripts can be provided in this method. It will take care of updating this version in the config service before setting up the sequence components.