Commands
Commands are parameter sets called Setup, Observe, and Wait. A command is created with the source of the command, given by a prefix, the name of the command, and an optional ObsId. Parameters are added to the command as needed. As the ESW design is developed, these command structures may evolve.
ObsId
An ObsID
, or observation ID, indicates the observation the command is associated with. It can be constructed by creating an instance of ObsId
.
- Scala
-
source
val obsId: ObsId = ObsId("2020A-001-123")
- Java
-
source
final ObsId obsId = ObsId.apply("2020A-001-123");
Source
The source of the command is given by the prefix, which should be the full name of the component sending the command. A prefix can be constructed with a string, but must start with a valid subsystem as in Subsystem. A component developer should supply a valid prefix string and the subsystem will be automatically parsed from it. An example of a valid string prefix is “nfiraos.ncc.trombone”.
See below examples:
- Scala
-
source
// using constructor, supplying subsystem and prefix both val source1: Prefix = Prefix("nfiraos.ncc.trombone") // just by supplying prefix val source2: Prefix = Prefix("tcs.mobie.blue.filter") // invalid prefix string which does not contain valid subsystem in the beginning will throw an exception, // val badSource: Prefix = Prefix("abcdefgh") // use implicit conversion to convert from String to Prefix val source3: Prefix = Prefix("wfos.prog.cloudcover")
- Java
-
source
//using constructor, supplying subsystem and prefix both Prefix source1 = Prefix.apply(JSubsystem.NFIRAOS, "ncc.trombone"); //just by supplying prefix Prefix source2 = Prefix.apply(JSubsystem.TCS, "mobie.blue.filter"); //invalid prefix string which does not contain valid subsystem in the beginning will throw an exception, // Prefix badSource = Prefix.apply("abcdefgh");
CommandName
Each command has a name given as a string. The CommandName
object wraps the string name. The string should be continuous with no spaces.
Setup Command
This command is used to describe a goal that a system should match. The component developer is required to supply following arguments to create a Setup
command.
- Source: The source of the command is given by the prefix, which should be the full name of the component sending the command.
- CommandName: a simple string name for the command (no spaces)
- ObsId: an optional observation Id.
- paramSet: Optional Set of Parameters. Default is empty.
- Scala
-
source
// keys val k1: Key[Int] = KeyType.IntKey.make("encoder") val k2: Key[String] = KeyType.StringKey.make("stringThing") val k2bad: Key[Int] = KeyType.IntKey.make("missingKey") val k3: Key[Int] = KeyType.IntKey.make("filter") val k4: Key[Float] = KeyType.FloatKey.make("correction") // Source of the command is given by the prefix // Source should be full name of the component sending the command. val source: Prefix = Prefix("wfos.red.detector") // parameters val i1: Parameter[Int] = k1.set(22) val i2: Parameter[String] = k2.set("A") // create Setup, add sequentially using add val sc1: Setup = Setup(source, CommandName("move"), Some(obsId)).add(i1).add(i2) // access keys val k1Exists: Boolean = sc1.exists(k1) // true // access parameters val tryParam1: Try[Parameter[Int]] = Try(sc1(k1)) // success val tryk2Bad: Try[Parameter[Int]] = Try(sc1(k2bad)) // failure // add more than one parameters, using madd val sc2: Setup = sc1.madd(k3.set(1, 2, 3, 4).withUnits(Units.day), k4.set(1.0f, 2.0f)) val paramSize: Int = sc2.size // add binary payload val byteKey1: Key[Byte] = ByteKey.make("byteKey1") val byteKey2: Key[Byte] = ByteKey.make("byteKey2") val bytes1: Array[Byte] = Array[Byte](10, 20) val bytes2: Array[Byte] = Array[Byte](30, 40) val b1: Parameter[Byte] = byteKey1.setAll(bytes1) val b2: Parameter[Byte] = byteKey2.setAll(bytes2) val sc3: Setup = Setup(source, CommandName("move"), Some(obsId), Set(b1, b2)) // remove a key val sc4: Setup = sc3.remove(b1) // list all keys val allKeys: Set[String] = sc4.paramSet.map(_.keyName)
- Java
-
source
//keys Key<Integer> k1 = JKeyType.IntKey().make("encoder", JUnits.encoder); Key<String> k2 = JKeyType.StringKey().make("stringThing"); Key<Integer> k2bad = JKeyType.IntKey().make("missingKey"); Key<Integer> k3 = JKeyType.IntKey().make("filter"); Key<Float> k4 = JKeyType.FloatKey().make("correction"); Prefix source = Prefix.apply(JSubsystem.WFOS, "red.detector"); //parameters Parameter<Integer> i1 = k1.set(22); Parameter<String> i2 = k2.set("A"); //create setup, add sequentially using add Setup sc1 = new Setup(source, new CommandName("move"), Optional.of(obsId)).add(i1).add(i2); //access keys boolean k1Exists = sc1.exists(k1); //true //access parameters Optional<Parameter<Integer>> optParam1 = sc1.jGet(k1); //present Optional<Parameter<Integer>> optK2Bad = sc1.jGet(k2bad); //absent //add more than one parameters, using madd Setup sc2 = sc1.madd(k3.set(1, 2, 3, 4).withUnits(JUnits.day), k4.set(1.0f, 2.0f)); int paramSize = sc2.size(); //add binary payload Key<Byte> byteKey1 = JKeyType.ByteKey().make("byteKey1"); Key<Byte> byteKey2 = JKeyType.ByteKey().make("byteKey2"); Byte[] bytes1 = {10, 20}; Byte[] bytes2 = {30, 40}; Parameter<Byte> b1 = byteKey1.setAll(bytes1); Parameter<Byte> b2 = byteKey2.setAll(bytes2); Setup sc3 = new Setup(source, new CommandName("move"), Optional.of(obsId)).add(b1).add(b2); //remove a key Setup sc4 = sc3.remove(b1); //list all keys java.util.List<String> allKeys = sc4.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toList());
Observe Command
This command describes a science observation. It is intended to only be sent to Science Detector Assemblies and Sequencers.
- Scala
-
source
// keys val k1: Key[Boolean] = KeyType.BooleanKey.make("repeat") val k2: Key[Int] = KeyType.IntKey.make("expTime") val k2bad: Key[Int] = KeyType.IntKey.make("missingKey") val k3: Key[Int] = KeyType.IntKey.make("filter") val k4: Key[UTCTime] = KeyType.UTCTimeKey.make("creation-time") // Source of the command is given by the prefix // Source should be full name of the component sending the command. val source: Prefix = Prefix("wfos.red.detector") // parameters val i1: Parameter[Boolean] = k1.set(true, false, true, false) val i2: Parameter[Int] = k2.set(1, 2, 3, 4) // create Observe, add sequentially using add val oc1: Observe = Observe(source, CommandName("move"), Some(obsId)).add(i1).add(i2) // access parameters using apply method val k1Param: Parameter[Boolean] = oc1.get(k1).get // true val values: Array[Boolean] = k1Param.values // access parameters val tryParam1: Try[Parameter[Boolean]] = Try(oc1(k1)) // success val tryk2Bad: Try[Parameter[Int]] = Try(oc1(k2bad)) // failure // add more than one parameters, using madd val oc2: Observe = oc1.madd(k3.set(1, 2, 3, 4).withUnits(Units.day), k4.set(UTCTime.now())) val paramSize: Int = oc2.size // update existing key with set val oc3: Observe = oc1.add(k2.set(5, 6, 7, 8)) // remove a key val oc4: Observe = oc2.remove(k4)
- Java
-
source
//keys Key<Boolean> k1 = JKeyType.BooleanKey().make("repeat"); Key<Integer> k2 = JKeyType.IntKey().make("expTime", JUnits.second); Key<Integer> k2bad = JKeyType.IntKey().make("missingKey"); Key<Integer> k3 = JKeyType.IntKey().make("filter"); Key<UTCTime> k4 = JKeyType.UTCTimeKey().make("creation-time"); //Source of the command is given by the prefix //Source should be full name of the component sending the command. Prefix source = Prefix.apply(JSubsystem.WFOS, "red.detector"); //parameters Boolean[] boolArray = {true, false, true, false}; Parameter<Boolean> i1 = k1.setAll(boolArray); Parameter<Integer> i2 = k2.set(1, 2, 3, 4); //create Observe, add sequentially using add Observe oc1 = new Observe(source, new CommandName("move"), Optional.of(obsId)).add(i1).add(i2); //access parameters Optional<Parameter<Boolean>> k1Param = oc1.jGet(k1); //present java.util.List<Boolean> values = k1Param.orElseThrow().jValues(); //access parameters Optional<Parameter<ArrayData<Float>>> k2BadParam = oc1.jGet(k2bad.keyName(), JKeyType.FloatArrayKey()); //add more than one parameters, using madd Observe oc2 = oc1.madd(k3.set(1, 2, 3, 4).withUnits(JUnits.day), k4.set(UTCTime.now())); int paramSize = oc2.size(); //update existing key with set Integer[] intArray = {5, 6, 7, 8}; Observe oc3 = oc1.add(k2.setAll(intArray)); //remove a key Observe oc4 = oc2.remove(k4); //list all keys java.util.List<String> allKeys = oc4.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toList());
Wait Command
This command causes a Sequencer to wait until notified. It can only be sent to Sequencers.
- Scala
-
source
// keys val k1: Key[Boolean] = KeyType.BooleanKey.make("repeat") val k2: Key[Int] = KeyType.IntKey.make("expTime") val k2bad: Key[Int] = KeyType.IntKey.make("missingKey") val k3: Key[Int] = KeyType.IntKey.make("filter") val k4: Key[UTCTime] = KeyType.UTCTimeKey.make("creation-time") // parameters val i1: Parameter[Boolean] = k1.set(true, false, true, false) val i2: Parameter[Int] = k2.set(1, 2, 3, 4) // Source of the command is given by the prefix // Source should be full name of the component sending the command. val source: Prefix = Prefix("wfos.red.detector") // create wait, add sequentially using add val wc1: Wait = Wait(source, CommandName("move"), Some(obsId)).add(i1).add(i2) // access params using get method val k1Param: Option[Parameter[Boolean]] = wc1.get(k1) val values: Array[Boolean] = k1Param.map(_.values).getOrElse(Array.empty[Boolean]) // access parameters val tryParam1: Try[Parameter[Boolean]] = Try(wc1(k1)) // success val tryk2Bad: Try[Parameter[Int]] = Try(wc1(k2bad)) // failure // add more than one parameters, using madd val wc2: Wait = wc1.madd(k3.set(1, 2, 3, 4).withUnits(Units.day), k4.set(UTCTime.now())) val paramSize: Int = wc2.size // update existing key with set val wc3: Wait = wc1.add(k2.set(5, 6, 7, 8)) // remove a key val wc4: Wait = wc2.remove(k4)
- Java
-
source
//keys Key<Boolean> k1 = JKeyType.BooleanKey().make("repeat"); Key<Integer> k2 = JKeyType.IntKey().make("expTime", JUnits.second); Key<Integer> k2bad = JKeyType.IntKey().make("missingKey"); Key<Integer> k3 = JKeyType.IntKey().make("filter"); Key<UTCTime> k4 = JKeyType.UTCTimeKey().make("creation-time"); //Source of the command is given by the prefix //Source should be full name of the component sending the command. Prefix source = Prefix.apply(JSubsystem.WFOS, "red.detector"); //parameters Boolean[] boolArray = {true, false, true, false}; Parameter<Boolean> i1 = k1.setAll(boolArray); Parameter<Integer> i2 = k2.set(1, 2, 3, 4); //create Wait, add sequentially using add Wait wc1 = new Wait(source, new CommandName("move"), Optional.of(obsId)).add(i1).add(i2); //access parameters using jGet Optional<Parameter<Boolean>> k1Param = wc1.jGet(k1); //present java.util.List<Boolean> values = k1Param.orElseThrow().jValues(); //access parameters Optional<Parameter<ArrayData<Float>>> k2BadParam = wc1.jGet("absentKeyHere", JKeyType.FloatArrayKey()); //add more than one parameters, using madd Wait wc2 = wc1.madd(k3.set(1, 2, 3, 4).withUnits(JUnits.day), k4.set(UTCTime.now())); int paramSize = wc2.size(); //update existing key with set Integer[] intArray = {5, 6, 7, 8}; Wait wc3 = wc1.add(k2.setAll(intArray)); //remove a key Wait wc4 = wc2.remove(k4); //list all keys java.util.List<String> allKeys = wc4.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toList());
JSON serialization
Commands can be serialized to JSON. The library has provided JsonSupport helper class and methods to serialize Setup, Observe and Wait commands.
- Scala
-
source
import play.api.libs.json.{JsValue, Json} // key val k1: Key[MatrixData[Double]] = DoubleMatrixKey.make("myMatrix") // values val m1: MatrixData[Double] = MatrixData.fromArrays( Array(1.0, 2.0, 3.0), Array(4.1, 5.1, 6.1), Array(7.2, 8.2, 9.2) ) // Source of the command is given by the prefix // Source should be full name of the component sending the command. val source: Prefix = Prefix("wfos.red.detector") // parameter val i1: Parameter[MatrixData[Double]] = k1.set(m1) // commands val sc: Setup = Setup(source, CommandName("move"), Some(obsId)).add(i1) val oc: Observe = Observe(source, CommandName("move"), Some(obsId)).add(i1) val wc: Wait = Wait(source, CommandName("move"), Some(obsId)).add(i1) // json support - write val scJson: JsValue = JsonSupport.writeSequenceCommand(sc) val ocJson: JsValue = JsonSupport.writeSequenceCommand(oc) val wcJson: JsValue = JsonSupport.writeSequenceCommand(wc) // optionally prettify val str: String = Json.prettyPrint(scJson) // construct command from string val scFromPrettyStr = JsonSupport.readSequenceCommand[Setup](Json.parse(str)) // json support - read val sc1: Setup = JsonSupport.readSequenceCommand[Setup](scJson) val oc1: Observe = JsonSupport.readSequenceCommand[Observe](ocJson) val wc1: Wait = JsonSupport.readSequenceCommand[Wait](wcJson)
- Java
-
source
//key Key<MatrixData<Double>> k1 = JKeyType.DoubleMatrixKey().make("myMatrix"); //values Double[][] doubles = {{1.0, 2.0, 3.0}, {4.1, 5.1, 6.1}, {7.2, 8.2, 9.2}}; MatrixData<Double> m1 = MatrixData.fromArrays(doubles); //parameter Parameter<MatrixData<Double>> i1 = k1.set(m1); //Source of the command is given by the prefix //Source should be full name of the component sending the command. Prefix source = Prefix.apply(JSubsystem.WFOS, "blue.filter"); //commands Setup sc = new Setup(source, new CommandName("move"), Optional.of(obsId)).add(i1); Observe oc = new Observe(source, new CommandName("move"), Optional.of(obsId)).add(i1); Wait wc = new Wait(source, new CommandName("move"), Optional.of(obsId)).add(i1); //json support - write JsValue scJson = JavaJsonSupport.writeSequenceCommand(sc); JsValue ocJson = JavaJsonSupport.writeSequenceCommand(oc); JsValue wcJson = JavaJsonSupport.writeSequenceCommand(wc); //optionally prettify String str = Json.prettyPrint(scJson); //construct command from string Setup sc1 = JavaJsonSupport.readSequenceCommand(Json.parse(str)); Observe oc1 = JavaJsonSupport.readSequenceCommand(ocJson); Wait wc1 = JavaJsonSupport.readSequenceCommand(wcJson);
Unique Key constraint
By design, a ParameterSet in a Setup, Observe, or Wait command is optimized to store only unique keys. When using add
or madd
methods on commands to add new parameters, if the parameter being added has a key which is already present in the paramSet
, the already stored parameter will be replaced by the given parameter.
If the Set
is created by component developers and given directly while creating a command, then it will be the responsibility of component developers to maintain uniqueness with parameters based on key.
Here are some examples that illustrate this point:
- Scala
-
source
// keys val encoderKey: Key[Int] = KeyType.IntKey.make("encoder") val filterKey: Key[Int] = KeyType.IntKey.make("filter") val miscKey: Key[Int] = KeyType.IntKey.make("misc.") // Source of the command is given by the prefix // Source should be full name of the component sending the command. val source: Prefix = Prefix("wfos.red.detector") // params val encParam1: Parameter[Int] = encoderKey.set(1) val encParam2: Parameter[Int] = encoderKey.set(2) val encParam3: Parameter[Int] = encoderKey.set(3) val filterParam1: Parameter[Int] = filterKey.set(1) val filterParam2: Parameter[Int] = filterKey.set(2) val filterParam3: Parameter[Int] = filterKey.set(3) val miscParam1 = miscKey.set(100) // Setup command with duplicate key via constructor val setup: Setup = Setup( source, CommandName("move"), Some(obsId), Set(encParam1, encParam2, encParam3, filterParam1, filterParam2, filterParam3) ) // four duplicate keys are removed; now contains one Encoder and one Filter key val uniqueKeys1 = setup.paramSet.toList.map(_.keyName) // try adding duplicate keys via add + madd val changedSetup: Setup = setup .add(encParam3) .madd( filterParam1, filterParam2, filterParam3 ) // duplicate keys will not be added. Should contain one Encoder and one Filter key val uniqueKeys2: List[String] = changedSetup.paramSet.toList.map(_.keyName) // miscKey(unique) will be added; encoderKey(duplicate) will not be added val finalSetUp: Setup = setup.madd(Set(miscParam1, encParam1)) // now contains encoderKey, filterKey, miscKey val uniqueKeys3: List[String] = finalSetUp.paramSet.toList.map(_.keyName)
- Java
-
source
//keys Key<Integer> encoderKey = JKeyType.IntKey().make("encoder", JUnits.encoder); Key<Integer> filterKey = JKeyType.IntKey().make("filter"); Key<Integer> miscKey = JKeyType.IntKey().make("misc."); //Source of the command is given by the prefix, //Source should be full name of the component sending the command. Prefix source = Prefix.apply(JSubsystem.WFOS, "blue.filter"); //params Parameter<Integer> encParam1 = encoderKey.set(1); Parameter<Integer> encParam2 = encoderKey.set(2); Parameter<Integer> encParam3 = encoderKey.set(3); Parameter<Integer> filterParam1 = filterKey.set(1); Parameter<Integer> filterParam2 = filterKey.set(2); Parameter<Integer> filterParam3 = filterKey.set(3); Parameter<Integer> miscParam1 = miscKey.set(100); //Setup command with duplicate key via madd Setup setup = new Setup(source, new CommandName("move"), Optional.of(obsId)).madd( encParam1, encParam2, encParam3, filterParam1, filterParam2, filterParam3); //four duplicate keys are removed; now contains one Encoder and one Filter key Set<String> uniqueKeys1 = setup.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toUnmodifiableSet()); //try adding duplicate keys via add + madd Setup changedSetup = setup.add(encParam3).madd(filterParam1, filterParam2, filterParam3); //duplicate keys will not be added. Should contain one Encoder and one Filter key Set<String> uniqueKeys2 = changedSetup.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toUnmodifiableSet()); //miscKey(unique) will be added; encoderKey(duplicate) will not be added Setup finalSetUp = setup.madd(miscParam1, encParam1); //now contains encoderKey, filterKey, miscKey Set<String> uniqueKeys3 = finalSetUp.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toUnmodifiableSet());
Cloning a Command
In order to track the completion of a command, every command that is sent must have a unique RunId
. If you wish to resubmit a previously sent Setup, the cloneCommand
method must be used prior to submission to create a new command from existing parameters, but with a new RunId
.
- Scala
-
source
val setup = Setup(source, commandName, Some(obsId)).madd(i1) val setup2 = setup.cloneCommand val observe = Observe(source, commandName, Some(obsId)).madd(i1) val observe2 = observe.cloneCommand val wait = Wait(source, commandName, Some(obsId)).madd(i1) val wait2 = wait.cloneCommand
- Java
-
source
Setup setup = new Setup(source, commandName, Optional.of(obsId)).add(encoderParam); Setup setup2 = setup.cloneCommand(); Observe observe = new Observe(source, commandName, Optional.empty()).add(encoderParam); Observe observe2 = observe.cloneCommand(); Wait wait = new Wait(source, commandName, Optional.of(obsId)).add(encoderParam); Wait wait2 = wait.cloneCommand();