Events
Events are the most basic type of asynchronous notification in TMT when an activity occurs somewhere in the TMT system and other components need to be notified. Each type of event has a unique purpose and unique information, but they all share same structural features. All events have EventInfo and ParameterSet.
csw-messages
library offers out of the box support to serialize Events using Protobuf, so that events can be produced and consumed by JVM(Java virtual machine) as well as Non-JVM applications.
For more on this Protobuf support section below.
EventTime¶
It captures the instance of a time in UTC format. To create current instance of time use default constructor. For other utility functions, see below examples:
//default constructor will return current time in UTC
val now: EventTime = EventTime()
//using constructor
val anHourAgo: EventTime = EventTime(Instant.now().minusSeconds(3600))
//current event time using utility function
val currentTime: EventTime = EventTime()
//some past time using utility function
val aDayAgo = EventTime(Instant.now.minusSeconds(86400))
//apply returns current time in UTC
EventTime now = EventTime.apply();
//using constructor
EventTime anHourAgo = new EventTime(Instant.now().minusSeconds(3600));
//return current time in UTC
EventTime currentTime = EventTime.apply();
//some past time using utility function
EventTime aDayAgo = EventTime.apply(Instant.now().minusSeconds(86400));
System Event¶
SystemEvent is used to describe a demand or other algorithm input from one component to the other. It is also used to publish internal state or status values of a component that may be of interest to other components in the system.
//keys
val k1: Key[Int] = KeyType.IntKey.make("encoder")
val k2: Key[Int] = KeyType.IntKey.make("speed")
val k3: Key[String] = KeyType.StringKey.make("filter")
val k4: Key[Int] = KeyType.IntKey.make("notUsed")
//prefixes
val ck1 = Prefix("wfos.red.filter")
val name1 = EventName("filterWheel")
val ck3 = Prefix("iris.imager.filter")
val name3 = EventName("status")
//parameters
val p1: Parameter[Int] = k1.set(22)
val p2: Parameter[Int] = k2.set(44)
val p3: Parameter[String] = k3.set("A", "B", "C", "D")
//Create SystemEvent using madd
val se1: SystemEvent = SystemEvent(ck1, name1).madd(p1, p2)
//Create SystemEvent using apply
val se2: SystemEvent = SystemEvent(ck3, name3, Set(p1, p2))
//Create SystemEvent and use add
val se3: SystemEvent = SystemEvent(ck3, name3).add(p1).add(p2).add(p3)
//access keys
val k1Exists: Boolean = se1.exists(k1) //true
//access Parameters
val p4: Option[Parameter[Int]] = se1.get(k1)
//access values
val v1: Array[Int] = se1(k1).values
val v2: Array[Int] = se2.parameter(k2).values
//k4 is missing
val missingKeys: Set[String] = se3.missingKeys(k1, k2, k3, k4)
//remove keys
val se4: SystemEvent = se3.remove(k3)
//add more than one parameters, using madd
val se5: SystemEvent = se4.madd(k3.set("X", "Y", "Z").withUnits(Units.day), k4.set(99, 100))
val paramSize: Int = se5.size
//update existing key with set
val se6: SystemEvent = se5.add(k2.set(5, 6, 7, 8))
//keys
Key<Integer> k1 = JKeyType.IntKey().make("encoder");
Key<Integer> k2 = JKeyType.IntKey().make("speed");
Key<String> k3 = JKeyType.StringKey().make("filter");
Key<Integer> k4 = JKeyType.IntKey().make("notUsed");
//prefixes
Prefix prefix1 = new Prefix("wfos.red.filter");
EventName name1 = new EventName("filterWheel");
Prefix prefix2 = new Prefix("iris.imager.filter");
EventName name2 = new EventName("status");
//parameters
Parameter<Integer> p1 = k1.set(22);
Parameter<Integer> p2 = k2.set(44);
Parameter<String> p3 = k3.set("A", "B", "C", "D");
//Create SystemEvent using madd
SystemEvent se1 = new SystemEvent(prefix1, name1).madd(p1, p2);
//Create SystemEvent using add
SystemEvent se2 = new SystemEvent(prefix2, name2).add(p1).add(p2);
//Create SystemEvent and use add
SystemEvent se3 = new SystemEvent(prefix2, name2).add(p1).add(p2).add(p3);
//access keys
Boolean k1Exists = se1.exists(k1); //true
//access Parameters
Optional<Parameter<Integer>> p4 = se1.jGet(k1);
//access values
List<Integer> v1 = se1.jGet(k1).get().jValues();
List<Integer> v2 = se2.parameter(k2).jValues();
//k4 is missing
Set<String> missingKeys = se3.jMissingKeys(k1, k2, k3, k4);
//remove keys
SystemEvent se4 = se3.remove(k3);
Observe Event¶
ObserveEvent is used to describe an event within a standardized data acquisition process. Published only by Science Detector Assemblies, who emit ObserveEvents during their exposures to signal the occurrence of specific activities/actions during the acquisition of data. Observe Events are published by the detector system using the Event Service.
//keys
val k1: Key[Int] = KeyType.IntKey.make("readoutsCompleted")
val k2: Key[Int] = KeyType.IntKey.make("coaddsCompleted")
val k3: Key[String] = KeyType.StringKey.make("fileID")
val k4: Key[Int] = KeyType.IntKey.make("notUsed")
//prefixes
val ck1 = Prefix("iris.ifu.detectorAssembly")
val name1 = EventName("readoutEnd")
val ck3 = Prefix("wfos.red.detector")
val name3 = EventName("exposureStarted")
//parameters
val p1: Parameter[Int] = k1.set(4)
val p2: Parameter[Int] = k2.set(2)
val p3: Parameter[String] = k3.set("WFOS-RED-0001")
//Create ObserveEvent using madd
val se1: ObserveEvent = ObserveEvent(ck1, name1).madd(p1, p2)
//Create ObserveEvent using apply
val se2: ObserveEvent = ObserveEvent(ck3, name3, Set(p1, p2))
//Create ObserveEvent and use add
val se3: ObserveEvent = ObserveEvent(ck3, name3).add(p1).add(p2).add(p3)
//access keys
val k1Exists: Boolean = se1.exists(k1) //true
//access Parameters
val p4: Option[Parameter[Int]] = se1.get(k1)
//access values
val v1: Array[Int] = se1(k1).values
val v2: Array[Int] = se2.parameter(k2).values
//k4 is missing
val missingKeys: Set[String] = se3.missingKeys(k1, k2, k3, k4)
//remove keys
val se4: ObserveEvent = se3.remove(k3)
//keys
Key<Integer> k1 = JKeyType.IntKey().make("readoutsCompleted");
Key<Integer> k2 = JKeyType.IntKey().make("coaddsCompleted");
Key<String> k3 = JKeyType.StringKey().make("fileID");
Key<Integer> k4 = JKeyType.IntKey().make("notUsed");
//prefixes
Prefix prefix1 = new Prefix("iris.ifu.detectorAssembly");
EventName name1 = new EventName("readoutEnd");
Prefix prefix2 = new Prefix("wfos.red.detector");
EventName name2 = new EventName("exposureStarted");
//parameters
Parameter<Integer> p1 = k1.set(4);
Parameter<Integer> p2 = k2.set(2);
Parameter<String> p3 = k3.set("WFOS-RED-0001");
//Create ObserveEvent using madd
ObserveEvent oc1 = new ObserveEvent(prefix1, name1).madd(p1, p2);
//Create ObserveEvent using add
ObserveEvent oc2 = new ObserveEvent(prefix2, name2).add(p1).add(p2);
//Create ObserveEvent and use add
ObserveEvent oc3 = new ObserveEvent(prefix2, name2).add(p1).add(p2).add(p3);
//access keys
Boolean k1Exists = oc1.exists(k1); //true
//access Parameters
Optional<Parameter<Integer>> p4 = oc1.jGet(k1);
//access values
List<Integer> v1 = oc1.jGet(k1).get().jValues();
List<Integer> v2 = oc2.parameter(k2).jValues();
//k4 is missing
Set<String> missingKeys = oc3.jMissingKeys(k1, k2, k3, k4);
//remove keys
ObserveEvent oc4 = oc3.remove(k3);
JSON Serialization¶
Events can be serialized to JSON. The library has provided JsonSupport helper class and methods to serialize Status, Observe and System events.
import play.api.libs.json.{JsValue, Json}
//key
val k1: Key[MatrixData[Double]] = DoubleMatrixKey.make("myMatrix")
val name1 = EventName("correctionInfo")
val prefix = Prefix("aoesw.rpg")
//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)
)
//parameter
val i1: Parameter[MatrixData[Double]] = k1.set(m1)
//events
val observeEvent: ObserveEvent = ObserveEvent(prefix, name1).add(i1)
val systemEvent: SystemEvent = SystemEvent(prefix, name1).add(i1)
//json support - write
val observeJson: JsValue = JsonSupport.writeEvent(observeEvent)
val systemJson: JsValue = JsonSupport.writeEvent(systemEvent)
//optionally prettify
val str: String = Json.prettyPrint(systemJson)
//construct command from string
val systemEventFromPrettyStr: SystemEvent = JsonSupport.readEvent[SystemEvent](Json.parse(str))
//json support - read
val observeEvent1: ObserveEvent = JsonSupport.readEvent[ObserveEvent](observeJson)
val systemEvent1: SystemEvent = JsonSupport.readEvent[SystemEvent](systemJson)
//key
Key<MatrixData<Double>> k1 = JKeyType.DoubleMatrixKey().make("myMatrix");
//prefixes
Prefix prefix1 = new Prefix("aoesw.rpg");
EventName name1 = new EventName("correctionInfo");
//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.fromJavaArrays(Double.class, doubles);
//parameter
Parameter<MatrixData<Double>> i1 = k1.set(m1);
//events
ObserveEvent observeEvent = new ObserveEvent(prefix1, name1).add(i1);
SystemEvent systemEvent = new SystemEvent(prefix1, name1).add(i1);
//json support - write
JsValue observeJson = JavaJsonSupport.writeEvent(observeEvent);
JsValue systemJson = JavaJsonSupport.writeEvent(systemEvent);
//optionally prettify
String str = Json.prettyPrint(systemJson);
//construct DemandState from string
SystemEvent statusFromPrettyStr = JavaJsonSupport.readEvent(Json.parse(str));
//json support - read
ObserveEvent observeEvent1 = JavaJsonSupport.readEvent(observeJson);
SystemEvent systemEvent1 = JavaJsonSupport.readEvent(systemJson);
Unique Key Constraint¶
By choice, a ParameterSet in either ObserveEvent or SystemEvent event will be optimized to store only unique keys. When using add
or madd
methods on events 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 an event, 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:
//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")
//prefix
val prefix = Prefix("wfos.blue.filter")
val name1 = EventName("filterWheel")
//params
val encParam1 = encoderKey.set(1)
val encParam2 = encoderKey.set(2)
val encParam3 = encoderKey.set(3)
val filterParam1 = filterKey.set(1)
val filterParam2 = filterKey.set(2)
val filterParam3 = filterKey.set(3)
val miscParam1 = miscKey.set(100)
//StatusEvent with duplicate key via constructor
val systemEvent =
SystemEvent(prefix, name1, Set(encParam1, encParam2, encParam3, filterParam1, filterParam2, filterParam3))
//four duplicate keys are removed; now contains one Encoder and one Filter key
val uniqueKeys1 = systemEvent.paramSet.toList.map(_.keyName)
//try adding duplicate keys via add + madd
val changedStatusEvent = systemEvent
.add(encParam3)
.madd(
filterParam1,
filterParam2,
filterParam3
)
//duplicate keys will not be added. Should contain one Encoder and one Filter key
val uniqueKeys2 = changedStatusEvent.paramSet.toList.map(_.keyName)
//miscKey(unique) will be added; encoderKey(duplicate) will not be added
val finalStatusEvent = systemEvent.madd(Set(miscParam1, encParam1))
//now contains encoderKey, filterKey, miscKey
val uniqueKeys3 = finalStatusEvent.paramSet.toList.map(_.keyName)
//keys
Key<Integer> encoderKey = JKeyType.IntKey().make("encoder");
Key<Integer> filterKey = JKeyType.IntKey().make("filter");
Key<Integer> miscKey = JKeyType.IntKey().make("misc");
//prefix
Prefix prefix1 = new Prefix("wfos.blue.filter");
EventName name1 = new EventName("filterWheel");
//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);
//StatusEvent with duplicate key via madd
SystemEvent event = new SystemEvent(prefix1, name1).madd(
encParam1,
encParam2,
encParam3,
filterParam1,
filterParam2,
filterParam3);
//four duplicate keys are removed; now contains one Encoder and one Filter key
List<String> uniqueKeys1 = event.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toList());
//try adding duplicate keys via add + madd
SystemEvent changedEvent = event.add(encParam3).madd(filterParam1, filterParam2, filterParam3);
//duplicate keys will not be added. Should contain one Encoder and one Filter key
List<String> uniqueKeys2 = changedEvent.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toList());
//miscKey(unique) will be added; encoderKey(duplicate) will not be added
SystemEvent finalEvent = changedEvent.madd(miscParam1, encParam1);
//now contains encoderKey, filterKey, miscKey
List<String> uniqueKeys3 = finalEvent.jParamSet().stream().map(Parameter::keyName).collect(Collectors.toList());
Protobuf¶
Protobuf aka Protocol buffers, are a language-neutral, platform-neutral extensible mechanism for serializing structured data. For more, visit Protobuf home page
In TMT observatory, subsystems and components could be running on JVM(Java virtual machine) and Non-JVM platform. This leads to solving a non-trivial problem of a Non-JVM component wanting to consume an Event produced by a JVM component. Amongst the available options for data over the wire, Protobuf was chosen for its performance, data compression and official/unofficial support many mainstream languages.
csw-messages
library enhances the Protobuf support, by providing out of the box helper methods, to convert events from/to protobuf binary data.
The protobuf schema is defined in csw_protobuf directory. The contained .proto files can be fed to a protoc compiler in the language of your choice and it will do the required code generation.
Here are some examples:
//Key
val raDecKey = RaDecKey.make("raDecKey")
//values
val raDec1 = RaDec(10.20, 40.20)
val raDec2 = RaDec(11.20, 50.20)
//parameters
val param = raDecKey.set(raDec1, raDec2).withUnits(arcmin)
val prefix = Prefix("tcs.pk")
val name = EventName("targetCoords")
//events
val observeEvent: ObserveEvent = ObserveEvent(prefix, name).add(param)
val systemEvent1: SystemEvent = SystemEvent(prefix, name).add(param)
val systemEvent2: SystemEvent =
SystemEvent(prefix, name).add(param)
//convert events to protobuf bytestring
val byteArray2: PbEvent = PbConverter.toPbEvent(observeEvent)
val byteArray3: PbEvent = PbConverter.toPbEvent(systemEvent1)
val byteArray4: PbEvent = PbConverter.toPbEvent(systemEvent2)
//convert protobuf bytestring to events
val pbObserveEvent: ObserveEvent = PbConverter.fromPbEvent(byteArray2)
val pbSystemEvent1: SystemEvent = PbConverter.fromPbEvent(byteArray3)
val pbSystemEvent2: SystemEvent = PbConverter.fromPbEvent(byteArray4)
//prefixes
Prefix prefix1 = new Prefix("tcs.pk");
EventName name1 = new EventName("targetCoords");
Prefix prefix2 = new Prefix("tcs.cm");
EventName name2 = new EventName("guiderCoords");
//Key
Key<RaDec> raDecKey = JKeyType.RaDecKey().make("raDecKey");
//values
RaDec raDec1 = new RaDec(10.20, 40.20);
RaDec raDec2 = new RaDec(11.20, 50.20);
//parameters
Parameter<RaDec> param = raDecKey.set(raDec1, raDec2).withUnits(JUnits.arcmin);
//events
ObserveEvent observeEvent = new ObserveEvent(prefix1, name1).add(param);
SystemEvent systemEvent1 = new SystemEvent(prefix1, name1).add(param);
SystemEvent systemEvent2 = new SystemEvent(prefix2, name2).add(param);
//convert events to protobuf bytestring
PbEvent byteArray2 = PbConverter.toPbEvent(observeEvent);
PbEvent byteArray3 = PbConverter.toPbEvent(systemEvent1);
PbEvent byteArray4 = PbConverter.toPbEvent(systemEvent2);
//convert protobuf bytestring to events
ObserveEvent pbObserveEvent = PbConverter.fromPbEvent(byteArray2);
SystemEvent pbSystemEvent1 = PbConverter.fromPbEvent(byteArray3);
SystemEvent pbSystemEvent2 = PbConverter.fromPbEvent(byteArray4);