Accessing telemetry data
Clients maintain an internal view of the most recent values for each subscribed topic. You can access these data directly, at any time, without blocking, via Python dot notation.
az = SRT.mount.azimuth.currentPosition
Every node of the inner structure held by the client, is a
DISCOSNamespace object
instance. These namespace nodes are organized as a live, nested tree
that mirrors the structure of each DISCOS telemetry topic: branches represent
JSON objects, leaves represent primitive values (numbers, booleans, strings,
lists). The entire tree updates in place as new messages are received.
Since the whole tree is constantly updated, we provide several ways of accessing members, to cover different scenarios.
Accessing the value of a leaf DISCOSNamespace object
The method shown above provides access to a
DISCOSNamespace node of the status tree.
This class acts as a wrapper for the inner value, allowing it to be part of comparisons
and operations just like you were working with a pure string, integer, floating point
number or boolean value. In case you are accessing a leaf node of the tree, you may want
to access the internal value directly instead of using this wrapper functionality.
In order to retrieve the internal value, the
DISCOSNamespace class offers a method called
get_value().
projectCode = SRT.scheduler.projectCode
# projectCode is a DISCOSNamespace object
projectCode = SRT.scheduler.projectCode.get_value()
# projectCode is now a pure str object
Most of the times you won’t need to access inner values, but for more complex
operations, sometimes your Python distribution might raise some exceptions when
using a DISCOSNamespace object
as indexer for a list or a dictionary. In case you bump into some weird behavior,
try using get_value(). You will
also benefit in case you need to work with a fixed value and avoid the continuous
updating of a DISCOSNamespace node.
Immutable snapshots with copy()
Some application might want to access several values together, all of them
contained in a single node or received via the same message. Since the structure
is constantly updated, accessing different values directly as shown above might
lead to a scenario where property 1 is read at time t0 and property 2 is read
at time t1. In order to avoid this, a copy of the parent node can be retrieved
with the copy() method.
mount = SRT.mount.copy()
az, el = mount.azimuth.currentPosition, mount.elevation.currentPosition
Waiting for updates with wait()
Sometimes you only need to check the value of a property for changes, for
example, when a new subscan is being performed, or to check if the antenna was
stowed due to high winds. The wait()
method comes to your help.
elevationMode = SRT.mount.elevation.currentMode.wait()
The code above will block until the inner value of the desired node is changed.
Calling wait() without any argument
will block indefinitely, but an optional argument timeout can be provided.
elevationMode = SRT.mount.elevation.currentMode.wait(timeout=5)
In this example, the software waits at most for 5 seconds before returning a value. If the timeout expires, the current value of the node is returned, without it being updated.
React to updates with bind() and unbind()
The wait() method shown above
blocks and waits for an update of the desired node before going forward with
code execution. If you need execution to go through, a different approach is
needed. The bind() method lets
you register a callback function that will be executed when the desired value
changes.
def printValue(value):
print(value)
SRT.scheduler.tracking.bind(printValue)
...
True
The example above will call the simple printValue function, which will print
the value of scheduler.tracking as soon as it changes. The three dots … represent
some other code that the application will continue to execute in the main thread.
A callback registered with bind()
might not be needed anymore at some point in time. The
unbind() method is used to
detach the callback from the given node.
def printValue(value):
print(value)
SRT.scheduler.tracking.bind(printValue)
...
# callback not needed anymore, unregister it
SRT.scheduler.tracking.unbind(printValue)
The examples show a very minimal approach to react to an updated value. By
implementing a more complete callback logic you can handle more complex
scenarios.
The unbind() method can also
be called without any argument. By doing so, the given
DISCOSNamespace node will unregister all
callbacks that were previously registered to it.
Exploiting predicates for wait() and bind()
The wait() and
bind() methods also allow a
user to provide a predicate as an argument. This functionality allow more complex
filtering for the new value, and can be useful for several situations. Below is
an example of a code that is executed only whenever the elevation axis of the
antenna gets stowed, using the wait()
method.
SRT.mount.elevation.currentMode.wait(predicate=lambda value: value == "STOW")
...
# send an alarm to someone that is waiting for the antenna to be stowed
The same principle can be applied to the
bind() method. The following
example shows how to bind a callback that would send an alarm via e-mail
whenever the temperature of the cool head of the SRTKBandMFReceiver is greater
or equal to 30K.
def sendAlarm(value):
...
# generic implementation of a function that sends an alarm via e-mail
# bind the sendAlarm function
SRT.receivers.SRTKBandMFReceiver.cryoTemperatureCoolHead.bind(
sendAlarm,
predicate=lambda value: value >= 30
)
...
# execution of other code, the callback will be called by the client
# updating thread whenever a new value satisfying the predicate is
# received
...
# unbind the sendAlarm function
SRT.receivers.SRTKBandMFReceiver.cryoTemperatureCoolHead.unbind(sendAlarm)
In both bind() examples, both with
and without a predicate, the given callback function is supposed to only receive
one argument, shown as value. The received object will always be the same
DISCOSNamespace node on which
the bind() method was called,
meaning that inside the callback we can also perform the
unbind() operation, making
the callback a one-time called function.
def sendAlarm(value):
...
# generic implementation of a function that sends an alarm via e-mail
...
# now unregister the callback so that the alarm will not be sent again
value.unbind(sendAlarm)
# bind the sendAlarm function
SRT.receivers.SRTKBandMFReceiver.cryoTemperatureCoolHead.bind(
sendAlarm,
predicate=lambda value: value >= 30
)
In the last example, the sendAlarm callback is called once as soon as the
newly received value for cryoTemperatureCoolHead of the SRTKBandMFReceiver is
greater or equal to 30K. As soon as the alarm logic is executed, the callback
can be unregistered, preventing the application to send another unwanted alarm.
The callback must be registered again later if you would like to receive
another alarm if the temperature rises again.
Tips and best practices
Use
copy()orget_value()before doing long processing so that the value stays the sameAlways unregister callbacks you no longer need with
unbind()so that code is not executed again and againRefer to the
DISCOSNamespaceclass for more details.